grafeo-ogm 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +98 -1
  3. package/dist/cjs/compilers/mutation.compiler.d.ts +94 -31
  4. package/dist/cjs/compilers/mutation.compiler.d.ts.map +1 -1
  5. package/dist/cjs/compilers/mutation.compiler.js +381 -343
  6. package/dist/cjs/compilers/mutation.compiler.js.map +1 -1
  7. package/dist/cjs/compilers/selection.compiler.d.ts +1 -1
  8. package/dist/cjs/compilers/selection.compiler.d.ts.map +1 -1
  9. package/dist/cjs/compilers/selection.compiler.js +33 -39
  10. package/dist/cjs/compilers/selection.compiler.js.map +1 -1
  11. package/dist/cjs/compilers/where.compiler.d.ts +206 -6
  12. package/dist/cjs/compilers/where.compiler.d.ts.map +1 -1
  13. package/dist/cjs/compilers/where.compiler.js +525 -176
  14. package/dist/cjs/compilers/where.compiler.js.map +1 -1
  15. package/dist/cjs/execution/executor.d.ts +11 -1
  16. package/dist/cjs/execution/executor.d.ts.map +1 -1
  17. package/dist/cjs/execution/executor.js +6 -3
  18. package/dist/cjs/execution/executor.js.map +1 -1
  19. package/dist/cjs/index.d.ts +2 -2
  20. package/dist/cjs/index.d.ts.map +1 -1
  21. package/dist/cjs/index.js.map +1 -1
  22. package/dist/cjs/interface-model.d.ts +20 -10
  23. package/dist/cjs/interface-model.d.ts.map +1 -1
  24. package/dist/cjs/interface-model.js +47 -104
  25. package/dist/cjs/interface-model.js.map +1 -1
  26. package/dist/cjs/model.d.ts +42 -30
  27. package/dist/cjs/model.d.ts.map +1 -1
  28. package/dist/cjs/model.js +134 -99
  29. package/dist/cjs/model.js.map +1 -1
  30. package/dist/cjs/ogm.d.ts.map +1 -1
  31. package/dist/cjs/ogm.js +1 -0
  32. package/dist/cjs/ogm.js.map +1 -1
  33. package/dist/cjs/policy/explain.d.ts +59 -0
  34. package/dist/cjs/policy/explain.d.ts.map +1 -0
  35. package/dist/cjs/policy/explain.js +175 -0
  36. package/dist/cjs/policy/explain.js.map +1 -0
  37. package/dist/cjs/policy/nested-writes.d.ts +35 -0
  38. package/dist/cjs/policy/nested-writes.d.ts.map +1 -0
  39. package/dist/cjs/policy/nested-writes.js +199 -0
  40. package/dist/cjs/policy/nested-writes.js.map +1 -0
  41. package/dist/cjs/policy/resolver.d.ts +26 -2
  42. package/dist/cjs/policy/resolver.d.ts.map +1 -1
  43. package/dist/cjs/policy/resolver.js +103 -33
  44. package/dist/cjs/policy/resolver.js.map +1 -1
  45. package/dist/cjs/policy/types.d.ts +105 -0
  46. package/dist/cjs/policy/types.d.ts.map +1 -1
  47. package/dist/cjs/policy/types.js +24 -0
  48. package/dist/cjs/policy/types.js.map +1 -1
  49. package/dist/esm/compilers/mutation.compiler.d.ts +94 -31
  50. package/dist/esm/compilers/mutation.compiler.d.ts.map +1 -1
  51. package/dist/esm/compilers/mutation.compiler.js +381 -344
  52. package/dist/esm/compilers/mutation.compiler.js.map +1 -1
  53. package/dist/esm/compilers/selection.compiler.d.ts +1 -1
  54. package/dist/esm/compilers/selection.compiler.d.ts.map +1 -1
  55. package/dist/esm/compilers/selection.compiler.js +33 -39
  56. package/dist/esm/compilers/selection.compiler.js.map +1 -1
  57. package/dist/esm/compilers/where.compiler.d.ts +206 -6
  58. package/dist/esm/compilers/where.compiler.d.ts.map +1 -1
  59. package/dist/esm/compilers/where.compiler.js +524 -178
  60. package/dist/esm/compilers/where.compiler.js.map +1 -1
  61. package/dist/esm/execution/executor.d.ts +11 -1
  62. package/dist/esm/execution/executor.d.ts.map +1 -1
  63. package/dist/esm/execution/executor.js +6 -3
  64. package/dist/esm/execution/executor.js.map +1 -1
  65. package/dist/esm/index.d.ts +2 -2
  66. package/dist/esm/index.d.ts.map +1 -1
  67. package/dist/esm/index.js.map +1 -1
  68. package/dist/esm/interface-model.d.ts +20 -10
  69. package/dist/esm/interface-model.d.ts.map +1 -1
  70. package/dist/esm/interface-model.js +47 -104
  71. package/dist/esm/interface-model.js.map +1 -1
  72. package/dist/esm/model.d.ts +42 -30
  73. package/dist/esm/model.d.ts.map +1 -1
  74. package/dist/esm/model.js +136 -101
  75. package/dist/esm/model.js.map +1 -1
  76. package/dist/esm/ogm.d.ts.map +1 -1
  77. package/dist/esm/ogm.js +1 -0
  78. package/dist/esm/ogm.js.map +1 -1
  79. package/dist/esm/policy/explain.d.ts +59 -0
  80. package/dist/esm/policy/explain.d.ts.map +1 -0
  81. package/dist/esm/policy/explain.js +169 -0
  82. package/dist/esm/policy/explain.js.map +1 -0
  83. package/dist/esm/policy/nested-writes.d.ts +35 -0
  84. package/dist/esm/policy/nested-writes.d.ts.map +1 -0
  85. package/dist/esm/policy/nested-writes.js +193 -0
  86. package/dist/esm/policy/nested-writes.js.map +1 -0
  87. package/dist/esm/policy/resolver.d.ts +26 -2
  88. package/dist/esm/policy/resolver.d.ts.map +1 -1
  89. package/dist/esm/policy/resolver.js +102 -33
  90. package/dist/esm/policy/resolver.js.map +1 -1
  91. package/dist/esm/policy/types.d.ts +105 -0
  92. package/dist/esm/policy/types.d.ts.map +1 -1
  93. package/dist/esm/policy/types.js +23 -0
  94. package/dist/esm/policy/types.js.map +1 -1
  95. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,131 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.0 (2026-09-23) — 🛡️ Security release: NLS enforcement through relationships, nested writes and cascade delete
4
+
5
+ > **Security release. Upgrade if you use `policies`.** Node-level-security (NLS) enforcement of the **root** type was sound, but enforcement of **other types reached through relationships** had several gaps. Together they let a bound `withContext(ctx)` read, link, update, create and delete nodes that the target type's policies forbid. One of them (cascade delete) deleted unrelated data with no policy involved at all. Every gap was reproduced before the fix and is pinned by a red-first regression test, both in the mock suite and in a new opt-in live-Neo4j suite.
6
+ >
7
+ > **Reads on OGMs without `policies` emit byte-identical Cypher.** Three changes apply to everyone: the cascade-delete fix, strict `null` filters, and nested-write `where` now being compiled by `WhereCompiler` (see *Behavior changes*).
8
+
9
+ ### 🛡️ Types without policies no longer bypass their neighbours' policies (critical)
10
+
11
+ When the root type of a query had no policies of its own, the policy context was dropped entirely. Every related type's policies were then skipped in nested selection, in relationship filters (`_SOME`/`_NONE`/`_ALL`/`_SINGLE`/`…Connection`), on `connect`/`disconnect`, and in `explainPolicies`. A policy-free type (a tag, a join node) was a gateway around its neighbours. The same happened one level down: a policy-free type in the **middle** of a traversal (`a_SOME: { b_SOME: { c_SOME } }`) switched enforcement off for everything beyond it.
12
+
13
+ A policy-bound model now always carries its policy context. A type without policies contributes no clause of its own, and every type it reaches still enforces its own. `count`/`aggregate` keep their fallback from `aggregate` to `read` policies.
14
+
15
+ ### 🛡️ Cascade delete honours its `where` and the target's `delete` policy (critical)
16
+
17
+ `Model.delete({ delete: { rel: [{ where }] } })` ignored the per-relationship `where`: the documented README example deleted **every** related node, including nodes shared with other records, and never checked the target type's policies. Each cascade item now deletes only the related nodes matching its `where` (`{}` still means all), and each cascaded node must pass its own type's `delete` policy. A `delete` nested inside a cascade item (multi-level cascade, previously ignored) and unknown item keys now throw `OGMError` before anything runs.
18
+
19
+ ### 🛡️ Nested writes enforce the target type's policies
20
+
21
+ A nested write now enforces exactly what the equivalent direct write on the target type would. Before, most nested writes enforced no target-type policy at all:
22
+
23
+ | Nested operation | Now enforces on the target type |
24
+ |---|---|
25
+ | `connect` / `disconnect`, including `connect` inside `create` (previously unchecked) | `read` |
26
+ | nested `update` | `update` row filter, plus its write restrictives (`WITH CHECK`) on the nested input |
27
+ | nested `create`, in `create` or `update` | `create`: default deny, plus its write restrictives on the nested input |
28
+ | `delete` inside `update` | `delete` row filter |
29
+
30
+ Write restrictives and create default-deny are checked, at any depth, before the query runs. A rejected nested write throws `PolicyDeniedError` and nothing is written. The mutation compiler's separate `where` builder, which had drifted from `WhereCompiler` (it threaded no policy context into traversals and misparsed `null`), is removed. Nested-write `where` is now compiled by `WhereCompiler`, so traversals inside it enforce the traversed types' `read` policies too.
31
+
32
+ ### 🛡️ Interface and union relationship targets apply each member's policy (critical)
33
+
34
+ When a relationship pointed at an **interface** or **union**, enforcement looked up the abstract type's name. That name carries only interface-level policies (none for a union), so the concrete members' own policies were never applied through the relationship: not in nested selection, not in relationship filters, not in nested writes. Each related node is now checked against its concrete type's policy: `CASE WHEN n:Chart THEN <Chart policy> WHEN n:Drug THEN <Drug policy> … ELSE false END`. One shared helper builds this clause for every consumer, including `InterfaceModel` (below).
35
+
36
+ ### 🛡️ Relationship filters no longer leak hidden nodes
37
+
38
+ The target type's policy was compiled **inside** the caller's filter, which is only correct for `_SOME`. Wherever the quantifier negates the filter, hidden nodes leaked into the result:
39
+
40
+ - `rel_ALL: { … }` was falsified by hidden related nodes.
41
+ - `rel_ALL: {}` held only when no related node existed at all, which revealed whether hidden nodes exist.
42
+ - A connection `node_NOT` filter was satisfied by hidden nodes.
43
+ - An edge-only connection filter (`relConnection: { edge: { … } }`) applied no target policy at all.
44
+
45
+ The filter and the policy are now composed separately, with the policy outside every negation. Quantifiers range over the related nodes the caller may see: a hidden node neither satisfies nor falsifies them. `_SOME` emits the same Cypher as before.
46
+
47
+ ### 🛡️ `null` filters no longer fail open (high)
48
+
49
+ `{ field_NOT: null }` compiled to `` n.`field_NOT` IS NULL ``, which is always true, so a restrictive policy such as `{ approvedAt_NOT: null }` matched every row. `null` is now interpreted after the operator suffix is split off:
50
+
51
+ - `field_NOT: null` → `IS NOT NULL`
52
+ - `rel_NOT: null` → `EXISTS`
53
+ - any other operator with `null`, or an unknown field with `null` (even without `strictWhere`), throws `OGMError`
54
+
55
+ This applies to every `where`, including policy partials and nested-write `where`.
56
+
57
+ ### 🛡️ `InterfaceModel` branches match each implementer's own model (medium)
58
+
59
+ An implementer whose `read` permissives were all gated off by `appliesWhen` got `THEN true` in `InterfaceModel`'s per-label `CASE`, while `Model(<Impl>).find` denies it. Each branch is now exactly the clause that the implementer's own model enforces, which also removes the interface predicates that previously appeared twice per branch. `aggregate`/`count` branches now use each implementer's own `aggregate` policies (falling back to `read`), as `Model.aggregate` does. The `onDeny: 'throw'` pre-check no longer throws when an implementer has no policies (it is visible under its own model).
60
+
61
+ ### ⚠️ Behavior changes
62
+
63
+ - **Cascade delete honours `where`.** If you relied on the old delete-everything behavior while passing a `where`, pass `{}` instead. Multi-level cascade and unknown cascade keys throw.
64
+ - **Nested writes into protected types can now be filtered or denied.** Add the matching policy for that operation on the target type, or perform the write directly on its model.
65
+ - **Relationship filters over protected types** now range over visible related nodes only (`_ALL` in particular).
66
+ - **Strict `null`:** `field_NOT: null` flips from always-true to `IS NOT NULL`; unsupported or unknown `null` filters throw.
67
+ - **Emitted Cypher changes** for: reads that reach protected types through policy-free types; every nested write with a `where` or a protected target (parameters now come from the shared `paramN` counter); `InterfaceModel` reads with policies; and cascade deletes (one `CALL { … }` per item).
68
+ - Two edge cases now throw `OGMError` instead of emitting Cypher that fails at runtime: `@cypher`-projecting policies on more than one member of an interface/union target, and `@cypher` fields in both an abstract target's filter and a member policy.
69
+
70
+ ### 🔧 Internal
71
+
72
+ - New `NO_ROOT_POLICY` resolution ("bound, but no root clause"). New `src/policy/nested-writes.ts`, where the root and nested create/update checks share one implementation.
73
+ - `WhereCompiler.compileTargetPolicyClause()` (concrete or abstract, optional operation fallback) and `compileTargetBody()` are the single construction behind every target-policy consumer. `compile()` accepts a caller-owned `@cypher` scope.
74
+ - `MutationCompiler.compileCreate` / `compileDelete` gain optional trailing `policyContext` / `paramCounter` parameters. The bulk-connect `UNWIND` fast path is kept for scalar, non-null, non-logical items; everything else compiles through `WhereCompiler`.
75
+
76
+ ### Tests
77
+
78
+ - Mock red/green tests for every gap (`tests/policy/enforcement-gaps.spec.ts`).
79
+ - A 25-case invariant suite: each `InterfaceModel` branch equals its implementer's own `find`/`aggregate`/`count` clause, in text and parameter values.
80
+ - Against v2.2.0, 56 of these 75 new tests fail. The other 19 are byte-identity guards, positive controls, and invariant cases where the old construction already agreed.
81
+ - An opt-in live-Neo4j suite (`tests/integration/`, skipped unless `NEO4J_URI` is set) asserting database outcomes per gap.
82
+ - The 17-case policy golden suite is unchanged except for the deliberately re-pinned `InterfaceModel` case (deduplicated predicates).
83
+
84
+ ---
85
+
86
+ ## 2.2.0 (2026-09-23) — 🔎 Explain policy decisions
87
+
88
+ > **Policy-bound reads can now explain themselves.** When a policy hides a node, `find` could only show *that* it disappeared, never *which* named clause removed it, because every clause is compiled into a single `WHERE` predicate. The new `Model.explainPolicies()` evaluates each `read` policy separately for every candidate node, covering both the declarative `when:` form and the raw `cypher:` form, and returns the hidden nodes too ([#6](https://github.com/neomodular/grafeo-ogm/issues/6)).
89
+ >
90
+ > **Additive minor release.** No existing signature changes, and every existing query emits byte-identical Cypher (pinned by a new 17-case golden suite). The one observable change is a correction to `policiesEvaluated` in the audit metadata (see below).
91
+
92
+ ### ✨ `Model.explainPolicies()`
93
+
94
+ ```typescript
95
+ const report = await ogm
96
+ .withContext(ctx)
97
+ .model('Chart')
98
+ .explainPolicies({ where: { id_IN: candidateIds }, select: { id: true } });
99
+ // → [{ node, visible, overriddenBy, permissiveGranted, failedRestrictives,
100
+ // policies: [{ name, named, kind, source, applied, outcome }] }]
101
+ ```
102
+
103
+ - **Matches enforcement exactly.** Explain reuses the fragments `find` compiles, and `visible` is computed from the same composed predicate `find` puts in its `WHERE`. The verdict recomputed from the per-policy outcomes is checked against it, and a mismatch throws rather than returning a wrong explanation.
104
+ - **Outcomes:** `pass`, `fail`, `null` (NULL is reported separately from `false`), `abstain` (the policy emitted no predicate), `not-applied` (`appliesWhen` returned false), `skipped` (an override fired).
105
+ - **Candidates** are selected by `where` / `labels` exactly as in `find`. Full `select` / `selectionSet` is supported, and nested relationships are still filtered by their own policies.
106
+ - **Read-only:** when grafeo opens the session itself, it uses `READ` access mode. Candidates default to 100 per call; more than 1,000 is rejected.
107
+ - **An explicit bypass:** every call logs a `warn`, is tagged `explain: true` in the transaction metadata (even with `auditMetadata: false`), and throws on models without a policy binding and on policy-bypassed OGMs. `onDeny: 'throw'` is reported as `visible: false` instead of being thrown.
108
+ - Scope: `Model` and the `read` operation only. `InterfaceModel`, `count`, `aggregate`, and `delete` are not supported yet.
109
+ - New exported types: `PolicyExplanation`, `PolicyClauseExplanation`, `PolicyClauseOutcome`, and `ExecuteOptions`.
110
+
111
+ ### 🐛 `policiesEvaluated` lists only policies that applied
112
+
113
+ The audit metadata field is documented as "names of policies that fired", but it also listed restrictives whose `appliesWhen(ctx)` returned `false`, even though those contributed nothing to the query. They are no longer listed. Emitted Cypher is unchanged.
114
+
115
+ To produce this list, the resolver now evaluates restrictive `appliesWhen` itself, and the compiler (or `evaluateWriteRestrictives`) still evaluates it too. As a result, **restrictive `appliesWhen` callbacks run twice per query**. They are documented as pure compile-time gates, so this only matters if yours have side effects (counters, logging).
116
+
117
+ ### 🔧 Internal
118
+
119
+ - `WhereCompiler`'s policy clause is now built in two steps: `compilePolicyFragments()` (the per-policy parts) and `composePolicyClause()` (the combination). Output is byte-identical to 2.1.1. The new `compileForExplain()` returns the fragments without folding them into the `WHERE`.
120
+ - `PolicyResolver.resolveDetailed()` returns every operation-matching policy with its `applied` / `skipped` state, source, and registration index. `resolve()` is now a projection of it via `projectResolution()`.
121
+ - `Executor.execute()` accepts an optional 4th argument, `{ accessMode }`. It only affects sessions the executor opens itself; calls without it behave as before.
122
+
123
+ ### Tests
124
+
125
+ - `tests/policy/explain-refactor-golden.spec.ts`: 17 goldens captured from the 2.1.1 compiler, covering every fragment shape, interface inheritance, `@cypher` preludes, traversal, nested selection, count, aggregate, delete, override, and `InterfaceModel`.
126
+ - `tests/policy/explain.spec.ts`: 27 tests covering guards, clause forms, no short-circuiting, applicability, the outcome vocabulary, fidelity with `find` (including a forced mismatch), identity, selection and prelude scoping, and security signalling.
127
+ - 10 new resolver tests, 2 new audit-metadata tests, and `tests/executor-access-mode.spec.ts` (5 tests).
128
+
3
129
  ## 2.1.1 (2026-08-13) — 📍 Point fields round-trip
4
130
 
5
131
  > **The last member of the 2.1.0 type-faithfulness family.** Reads flatten a native Point into a plain `{ x, y[, z], srid }` object — but writing that object back bound it as a map property value, which Neo4j rejects at runtime ("Property values can only be of primitive types or arrays thereof"). A `Point` read through the OGM could not be written back through the OGM.
package/README.md CHANGED
@@ -580,6 +580,8 @@ const book = await Book.findUniqueOrThrow({
580
580
  | `_ALL` | Every related node matches | `{ categories_ALL: { name: 'Tech' } }` |
581
581
  | `_SINGLE` | Exactly one related node matches | `{ categories_SINGLE: { name: 'Tech' } }` |
582
582
 
583
+ With [Node-Level Security](#node-level-security), quantifiers range over the related nodes the caller may **see**: a related node hidden by its type's `read` policy neither satisfies nor falsifies `_SOME`, `_NONE`, `_ALL` or `_SINGLE`.
584
+
583
585
  **Logical operators:**
584
586
 
585
587
  ```typescript
@@ -599,11 +601,24 @@ await Book.find({
599
601
  ```typescript
600
602
  // Scalar null check
601
603
  await Book.find({ where: { isbn: null } }); // WHERE n.isbn IS NULL
604
+ await Book.find({ where: { isbn_NOT: null } }); // WHERE n.isbn IS NOT NULL
602
605
 
603
606
  // Relationship existence
604
607
  await Book.find({ where: { author: null } }); // NOT EXISTS pattern
608
+ await Book.find({ where: { author_NOT: null } }); // EXISTS pattern
605
609
  ```
606
610
 
611
+ | Input | Compiles to |
612
+ |---|---|
613
+ | `field: null` | `field IS NULL` |
614
+ | `field_NOT: null` | `field IS NOT NULL` |
615
+ | `relationship: null` | `NOT EXISTS { MATCH … }` |
616
+ | `relationship_NOT: null` | `EXISTS { MATCH … }` |
617
+ | any other operator with `null` (`_IN`, `_GT`, `_CONTAINS`, `_SOME`, …) | throws `OGMError` |
618
+ | an unknown field with `null` | throws `OGMError`, even without `strictWhere` |
619
+
620
+ The same rules apply everywhere a `where` is accepted: policy `when` partials, nested `select` filters, and nested-write `where`. Before v2.3.0, `{ field_NOT: null }` compiled to the always-true `` n.`field_NOT` IS NULL ``, so a filter such as a policy restrictive `{ approvedAt_NOT: null }` silently matched every row.
621
+
607
622
  **Connection filters (with edge properties):**
608
623
 
609
624
  ```typescript
@@ -840,6 +855,15 @@ await Book.delete({
840
855
  });
841
856
  ```
842
857
 
858
+ Cascade semantics:
859
+
860
+ - Each item deletes only the related nodes matching its `where`. An empty item (`{}`) deletes every related node of that relationship.
861
+ - An item may be a single object or an array of items.
862
+ - With [Node-Level Security](#node-level-security), each cascaded node must also pass its own type's `delete` policy. Nodes the policy rejects are kept.
863
+ - A `delete` nested inside a cascade item (multi-level cascade) throws `OGMError`, as do unknown keys in a cascade item.
864
+
865
+ > **Behavior change in v2.3.0:** before v2.3.0 the per-item `where` was ignored and the cascade deleted **every** related node, including nodes shared with other records. If you relied on that, pass `{}` explicitly.
866
+
843
867
  ### Delete Many
844
868
 
845
869
  ```typescript
@@ -1269,6 +1293,28 @@ policies: {
1269
1293
  }
1270
1294
  ```
1271
1295
 
1296
+ #### Relationships and nested writes
1297
+
1298
+ Policies protect a **type**, wherever it is reached from. When a bound model reaches another type through a relationship, that type's own policies apply, whether or not the root type has any.
1299
+
1300
+ Reads:
1301
+ - **Nested selection**: related nodes are filtered by their type's `read` policy.
1302
+ - **Relationship filters** (`_SOME`, `_NONE`, `_ALL`, `_SINGLE`, `…Connection`): these range over the related nodes the caller may see. At any depth, a hidden node neither satisfies nor falsifies the quantifier.
1303
+ - **Interface and union targets**: each related node is checked against the policy of its concrete type (`CASE WHEN n:Chart THEN <Chart policy> WHEN n:Drug THEN <Drug policy> … ELSE false END`).
1304
+
1305
+ Writes follow one principle: **a nested write enforces exactly what the equivalent direct write on the target type would.**
1306
+
1307
+ | Nested operation | Target-type policy applied |
1308
+ |---|---|
1309
+ | `connect` / `disconnect` (in `create` or `update`) | `read`: only visible nodes can be linked or unlinked |
1310
+ | nested `update` | `update` row filter, plus its write restrictives (`WITH CHECK`) on the nested input |
1311
+ | nested `create` (in `create` or `update`) | `create`: at least one permissive must apply, plus its write restrictives on the nested input |
1312
+ | `delete` inside `update`, cascade `delete` | `delete` row filter |
1313
+
1314
+ Write restrictives and create default-deny are checked before the query runs, so a rejected nested write throws `PolicyDeniedError` and nothing is written.
1315
+
1316
+ > **A type without policies is open, but not a gateway.** Before v2.3.0, reading or writing through a type with no policies of its own skipped the policies of the types beyond it, and nested writes enforced no target-type policy at all. If a nested write into a protected type now gets filtered or denied, add the matching policy for that operation on the target type, or perform the write directly on its model.
1317
+
1272
1318
  #### Enforcement boundary
1273
1319
 
1274
1320
  > **The OGM enforces policies at the compiler.** Policies do NOT apply to `ogm.$queryRaw`, `ogm.$executeRaw`, or to `@cypher` directive bodies that traverse from a stored field. If you need raw-Cypher enforcement, write the predicate into your raw Cypher manually or layer a Neo4j role/RBAC at the database.
@@ -1302,12 +1348,62 @@ When `policies` is configured, every OGM-emitted query attaches transaction meta
1302
1348
 
1303
1349
  Disable via `policyDefaults: { auditMetadata: false }`. The fingerprint is intentionally key-only — no ctx values are leaked.
1304
1350
 
1351
+ `policiesEvaluated` lists only the policies that **applied** to the query. A restrictive whose `appliesWhen(ctx)` returned `false` is omitted (before v2.2.0 it was listed even though it contributed nothing). `explainPolicies` calls add `explain: true` and are always recorded, even with `auditMetadata: false`.
1352
+
1353
+ #### Explaining policy decisions
1354
+
1355
+ When a policy-bound read hides a node, `find` can only show *that* the node disappeared. `explainPolicies` shows *which* named clause decided it. It evaluates every `read` policy for the type separately, for each candidate node, without short-circuiting. Both `when:` and `cypher:` clauses are covered, and nodes the policies reject are returned too.
1356
+
1357
+ ```typescript
1358
+ const report = await ogm
1359
+ .withContext(ctx)
1360
+ .model('Chart')
1361
+ .explainPolicies({ where: { id_IN: candidateIds }, select: { id: true } });
1362
+
1363
+ // [{
1364
+ // node: { id: 'chart-8617' },
1365
+ // visible: false, // exactly what `find` would enforce
1366
+ // overriddenBy: null,
1367
+ // permissiveGranted: true,
1368
+ // failedRestrictives: ['chart.dose-translation-coverage'],
1369
+ // policies: [
1370
+ // { name: 'chart.read-grant', kind: 'permissive', source: 'Chart', applied: true, outcome: 'pass' },
1371
+ // { name: 'chart.weight-zone', kind: 'restrictive', source: 'Chart', applied: true, outcome: 'pass' },
1372
+ // { name: 'chart.text-field-scope', kind: 'restrictive', source: 'Chart', applied: false, outcome: 'not-applied' },
1373
+ // { name: 'chart.dose-translation-coverage', kind: 'restrictive', source: 'Chart', applied: true, outcome: 'fail' },
1374
+ // ],
1375
+ // }]
1376
+ ```
1377
+
1378
+ | `outcome` | Meaning |
1379
+ |---|---|
1380
+ | `pass` | Applied, and its predicate evaluated to `true`. |
1381
+ | `fail` | Applied, and it evaluated to `false`. This includes a restrictive hard deny (`when: () => false`). |
1382
+ | `null` | Applied, and it evaluated to NULL (for example, a missing property). Enforcement treats NULL as not-true. It is reported separately from `fail` because the fix is different. |
1383
+ | `abstain` | Applied, but it emitted no predicate. A permissive abstain grants nothing; a restrictive abstain restricts nothing. |
1384
+ | `not-applied` | `appliesWhen(ctx)` returned `false` (for an override: `when(ctx)` returned `false`). The policy was not evaluated. |
1385
+ | `skipped` | Not evaluated, because an earlier override fired. |
1386
+
1387
+ How it works:
1388
+
1389
+ - **Same predicates as `find`.** The explain query projects the exact compiled fragments `find` would enforce. `visible` is computed from the same composed predicate `find` puts in its `WHERE`. The verdict recomputed from the per-policy outcomes must agree with it; if it ever doesn't, the call throws instead of returning a wrong explanation.
1390
+ - **Candidate selection.** `where` and `labels` select candidates exactly as in `find`, including target-type policies on relationship filters. The root type's policy clause is reported, not used as a filter. Candidates that don't match `where` are simply absent from the result.
1391
+ - **Selection.** `select` / `selectionSet` accept everything `find` accepts. Relationships in the selection are still filtered by their own target-type policies. Only the root type's policies are explained.
1392
+ - **Read-only.** When grafeo opens the session itself, it opens it in `READ` access mode, so the server rejects any write. If you pass your own `transaction` or `session`, it is used as-is, and you control its access mode.
1393
+ - **Bounded.** `options.limit` defaults to 100, and values above 1,000 are rejected. Each candidate evaluates every policy, and the enforcement predicate is evaluated a second time for the cross-check, so explain costs more than a normal read. `sort` and `offset` are supported.
1394
+ - **Unnamed policies** are reported as `<source>.<kind>[<index>]` with `named: false`. Give every policy a `name` if you want stable, readable output.
1395
+ - **Default deny is reported, not thrown.** With `onDeny: 'throw'`, `explainPolicies` still returns rows with `visible: false` and `permissiveGranted: false`.
1396
+
1397
+ > **Security: `explainPolicies` bypasses policy filtering by design.** It returns nodes the bound context cannot see, together with their selected fields. Expose it only on admin-only diagnostic paths, and never let request input decide whether it runs. Every call logs a `warn` and is tagged `explain: true` in the transaction metadata. It is only available on policy-bound models (`ogm.withContext(ctx).model(...)`) and throws on a model with no policy binding or on a policy-bypassed OGM. In this release it covers `Model` and the `read` operation only: `InterfaceModel`, `count`, `aggregate`, and `delete` are not supported yet.
1398
+
1305
1399
  #### Limitations
1306
1400
 
1307
1401
  - **`@cypher` scalar fields inside a policy `where`-partial throw when the policy is injected into nested-selection enforcement.** Refactor the policy to use stored properties or a relationship traversal.
1308
1402
  - **`upsert`** evaluates create- and update-side policies at the application layer (MERGE has no WHERE). Documented limit; full MERGE-aware enforcement is not yet implemented.
1309
1403
  - **Restrictives are split into read-side and write-side flavors.** A restrictive's `operations` array determines which `when` signature applies. Read-side ops (`read|delete|aggregate|count`) → `when(ctx)` returns a where-partial or boolean. Write-side ops (`create|update`) → `when(ctx, input)` returns a boolean only. Mixed arrays (e.g. `['read', 'create']`) are rejected at construction time — split into two restrictives. Each flavor is invoked exactly once per query (read-side at compile, write-side at the application layer). Use `isReadRestrictive` / `isWriteRestrictive` if you need to inspect a policy at runtime.
1310
- - **InterfaceModel CASE-per-label fallback.** Implementers without a registered policy fall back to interface-level enforcement on their branch. The OGM emits a `logger.warn` at construction time when an interface has policies and one of its implementers does not — silence the warning by registering an explicit policy on each implementer.
1404
+ - **InterfaceModel branches.** Each implementer's `CASE` branch is exactly the policy clause that implementer's own model enforces, including the interface-level policies it inherits. An implementer with no policies of its own therefore gets only the interface-level ones. The OGM emits a `logger.warn` at construction time when an interface has policies and one of its implementers does not. Silence it by registering an explicit policy on each implementer.
1405
+ - **`edge` filters in nested-write `where`** (connect, disconnect, nested update and delete) are not supported and throw.
1406
+ - **`@cypher` fields in the policies of more than one member of an interface or union** cannot be combined in one query and throw. Refactor all but one to stored properties.
1311
1407
  - **No AsyncLocalStorage integration.** Context is supplied via explicit `withContext()` only — create one wrapper per request, discard after.
1312
1408
 
1313
1409
  ### Raw Cypher
@@ -1879,6 +1975,7 @@ Open an issue on [GitHub](https://github.com/neomodular/grafeo-ogm/issues). For
1879
1975
  | `count(options?)` | `number` | Count matching nodes |
1880
1976
  | `aggregate(options)` | `AggregateResult` | Aggregate values (min, max, avg, count) |
1881
1977
  | `setLabels(options)` | `void` | Add or remove labels on matching nodes |
1978
+ | `explainPolicies(options?)` | `PolicyExplanation<T>[]` | Per-clause `read` policy outcomes per candidate, including hidden nodes (policy-bound models only; see [Explaining policy decisions](#explaining-policy-decisions)) |
1882
1979
 
1883
1980
  ### InterfaceModel
1884
1981
 
@@ -55,6 +55,34 @@ export declare class MutationCompiler {
55
55
  */
56
56
  private buildTargetPolicyPredicate;
57
57
  private getPolicyWhereCompiler;
58
+ /**
59
+ * Compile a nested-write `where` (connect, disconnect, nested update,
60
+ * nested/cascade delete) against `targetVar` through `WhereCompiler` —
61
+ * the single where implementation (v2.3.0: the mutation compiler's
62
+ * parallel builder was removed after repeatedly drifting from it).
63
+ * Operators, null semantics, abstract relationship targets and —
64
+ * crucially — traversal-policy enforcement are identical to a direct
65
+ * `where`: a relationship filter inside it AND-stitches the traversed
66
+ * type's `read` policy. The target's OWN operation policy is added
67
+ * separately by `buildTargetPolicyPredicate`, so the root here is bound
68
+ * as `NO_ROOT_POLICY`.
69
+ *
70
+ * Returns `''` for an absent or empty where.
71
+ */
72
+ private compileTargetWhere;
73
+ /**
74
+ * The bulk-connect UNWIND fast path compiles each where key against the
75
+ * ROW's own value (`connItem.where.node.<key>`), which `WhereCompiler`
76
+ * cannot express. It is therefore restricted to the one shape where
77
+ * that is exact: every item's where is `{ node: { … } }` or bare
78
+ * properties, holding only declared, stored scalar properties of the
79
+ * target (registered operators allowed) with NON-NULL values — no
80
+ * `AND`/`OR`/`NOT`/`node_NOT`, no relationship traversal, no `@cypher`
81
+ * field. Every predicate it emits is then a scalar comparison against a
82
+ * concrete row value, so it can only narrow matches, never fail open.
83
+ * Anything else takes the per-item path through `WhereCompiler`.
84
+ */
85
+ private isConnectFastPathEligible;
58
86
  /**
59
87
  * Surface a logical operator that contributed zero effective conditions.
60
88
  * Mirrors WhereCompiler.warnEmptyLogical — fires only on the anomalous
@@ -68,7 +96,20 @@ export declare class MutationCompiler {
68
96
  * Generate CREATE Cypher for one or more nodes.
69
97
  * Handles scalar properties, nested relationship creates, and connects.
70
98
  */
71
- compileCreate(inputs: Record<string, unknown>[], nodeDef: NodeDefinition, labels?: string[]): MutationResult;
99
+ compileCreate(inputs: Record<string, unknown>[], nodeDef: NodeDefinition, labels?: string[],
100
+ /**
101
+ * The caller's `create` bundle (v2.3.0). Its `resolveForType` gates
102
+ * every connect target in the nested input by the TARGET type's `read`
103
+ * policy — pre-2.3.0 the create path threaded no policy at all.
104
+ */
105
+ policyContext?: PolicyContextBundle,
106
+ /**
107
+ * Shared `param<N>` counter; continue it into the RETURN selection so
108
+ * nested-filter params never collide with the selection's.
109
+ */
110
+ paramCounter?: {
111
+ count: number;
112
+ }): MutationResult;
72
113
  /**
73
114
  * Generate UPDATE Cypher (SET properties + connect/disconnect).
74
115
  */
@@ -95,12 +136,45 @@ export declare class MutationCompiler {
95
136
  }): MutationResult;
96
137
  /**
97
138
  * Generate DELETE Cypher with optional cascade.
139
+ *
140
+ * v2.3.0 — each cascaded relationship honours its per-item `where` and
141
+ * is gated by the TARGET type's `delete` policy (`buildNestedDelete`).
142
+ * Pre-2.3.0 the cascade iterated only the relationship KEYS: the
143
+ * documented `{ where }` was ignored, so every related node — shared
144
+ * ones included — was deleted, with no policy check.
98
145
  */
99
146
  compileDelete(nodeDef: NodeDefinition, whereResult: {
100
147
  cypher: string;
101
148
  params: Record<string, unknown>;
102
149
  preludes?: string[];
103
- }, deleteInput?: Record<string, unknown>): MutationResult;
150
+ }, deleteInput?: Record<string, unknown>,
151
+ /** The caller's `delete` bundle; gates each cascade target. */
152
+ policyContext?: PolicyContextBundle,
153
+ /** Shared `param<N>` counter, continued from the root WHERE. */
154
+ paramCounter?: {
155
+ count: number;
156
+ }): MutationResult;
157
+ /**
158
+ * Nested / cascade delete of ONE relationship's targets (v2.3.0), shared
159
+ * by `compileDelete` (`Model.delete`'s `delete` input) and delete-inside-
160
+ * `update`. One subquery per spec item:
161
+ *
162
+ * <withClause>
163
+ * CALL {
164
+ * <withClause>
165
+ * MATCH (src)-[:REL]->(t)
166
+ * WHERE <item.where via WhereCompiler> AND <target `delete` policy>
167
+ * DETACH DELETE t
168
+ * RETURN count(*) AS _del_<tag>_<i>
169
+ * }
170
+ *
171
+ * `spec` is one item or an array (singular vs list relationships);
172
+ * `null` means "no cascade" (GraphQL InputMaybe). `{}` / no `where`
173
+ * deletes every related node the target's `delete` policy permits.
174
+ * A multi-level cascade (`delete` inside an item) and unknown item keys
175
+ * are rejected instead of silently ignored.
176
+ */
177
+ private buildNestedDelete;
104
178
  /**
105
179
  * Generate MERGE (upsert) Cypher with ON CREATE SET / ON MATCH SET.
106
180
  * Scalar properties only — nested relationship ops are not supported.
@@ -157,37 +231,26 @@ export declare class MutationCompiler {
157
231
  * Returns the base property name and the Cypher expression template.
158
232
  */
159
233
  private parseOperatorSuffix;
160
- /**
161
- * Build WHERE conditions for connect/disconnect operations.
162
- * Handles operator suffixes (_IN, _NOT, _NOT_IN, _CONTAINS, etc.),
163
- * NOT blocks, and relationship filters (_SOME, _NONE, _ALL, _SINGLE).
164
- *
165
- * @param targetNodeDef - Optional node definition for the target node.
166
- * Required to resolve relationship filters like `configurations_SOME`.
167
- * When omitted, relationship filters are silently skipped.
168
- */
169
- private buildNodeWhereConditions;
170
- /**
171
- * Build WHERE conditions from a connection-level WHERE spec.
172
- * Handles `node`, `NOT`, `AND`, `OR` at the connection level.
173
- *
174
- * Connection WHERE format: `{ node: {...}, NOT: { node: {...} }, AND: [...], OR: [...] }`
175
- */
176
- private buildConnectionWhereConditions;
177
- /** Counter for unique relationship filter variables within a single WHERE clause. */
178
- private relFilterCounter;
179
- /**
180
- * Try to compile a relationship filter condition (_SOME, _NONE, _ALL, _SINGLE).
181
- * Returns the Cypher expression string or null if the key is not a relationship filter.
182
- */
183
- private tryBuildRelationshipFilter;
184
234
  private buildGeneratedIdClause;
185
- /**
186
- * Check if any item in an array connect spec has relationship filters in its WHERE clause.
187
- * Relationship filters require EXISTS subqueries which can't use UNWIND param references.
188
- */
189
- private arrayConnectHasRelationshipFilters;
190
235
  private extractConnectWhereConditions;
191
236
  private extractEdgeProperties;
192
237
  }
238
+ /**
239
+ * Map a nested-mutation connection where onto an equivalent NODE where on
240
+ * the target, for `WhereCompiler` (v2.3.0). Mutation `where` never supports
241
+ * edge filters, so the two shapes are interchangeable:
242
+ *
243
+ * { node: A } → A
244
+ * { node_NOT: A } → { NOT: A }
245
+ * { NOT: C } → { NOT: map(C) }
246
+ * { AND: [C…] } → { AND: [map(C)…] } (likewise OR; `OR: []`
247
+ * still matches nothing)
248
+ * bare properties → themselves (legacy `{ id: 'x' }` shape)
249
+ *
250
+ * `edge`/`edge_NOT` throw (unsupported in mutations). In a connection-
251
+ * shaped where, any other key throws too — the removed parallel builder
252
+ * silently IGNORED such keys (`{ id: 'x', NOT: {…} }` dropped `id`),
253
+ * widening the match.
254
+ */
255
+ export declare function connectionWhereToNodeWhere(spec: Record<string, unknown>): Record<string, unknown>;
193
256
  //# sourceMappingURL=mutation.compiler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mutation.compiler.d.ts","sourceRoot":"","sources":["../../../src/compilers/mutation.compiler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mutation.compiler.d.ts","sourceRoot":"","sources":["../../../src/compilers/mutation.compiler.ts"],"names":[],"mappings":"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"}