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.
- package/CHANGELOG.md +126 -0
- package/README.md +98 -1
- package/dist/cjs/compilers/mutation.compiler.d.ts +94 -31
- package/dist/cjs/compilers/mutation.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/mutation.compiler.js +381 -343
- package/dist/cjs/compilers/mutation.compiler.js.map +1 -1
- package/dist/cjs/compilers/selection.compiler.d.ts +1 -1
- package/dist/cjs/compilers/selection.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/selection.compiler.js +33 -39
- package/dist/cjs/compilers/selection.compiler.js.map +1 -1
- package/dist/cjs/compilers/where.compiler.d.ts +206 -6
- package/dist/cjs/compilers/where.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/where.compiler.js +525 -176
- package/dist/cjs/compilers/where.compiler.js.map +1 -1
- package/dist/cjs/execution/executor.d.ts +11 -1
- package/dist/cjs/execution/executor.d.ts.map +1 -1
- package/dist/cjs/execution/executor.js +6 -3
- package/dist/cjs/execution/executor.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/interface-model.d.ts +20 -10
- package/dist/cjs/interface-model.d.ts.map +1 -1
- package/dist/cjs/interface-model.js +47 -104
- package/dist/cjs/interface-model.js.map +1 -1
- package/dist/cjs/model.d.ts +42 -30
- package/dist/cjs/model.d.ts.map +1 -1
- package/dist/cjs/model.js +134 -99
- package/dist/cjs/model.js.map +1 -1
- package/dist/cjs/ogm.d.ts.map +1 -1
- package/dist/cjs/ogm.js +1 -0
- package/dist/cjs/ogm.js.map +1 -1
- package/dist/cjs/policy/explain.d.ts +59 -0
- package/dist/cjs/policy/explain.d.ts.map +1 -0
- package/dist/cjs/policy/explain.js +175 -0
- package/dist/cjs/policy/explain.js.map +1 -0
- package/dist/cjs/policy/nested-writes.d.ts +35 -0
- package/dist/cjs/policy/nested-writes.d.ts.map +1 -0
- package/dist/cjs/policy/nested-writes.js +199 -0
- package/dist/cjs/policy/nested-writes.js.map +1 -0
- package/dist/cjs/policy/resolver.d.ts +26 -2
- package/dist/cjs/policy/resolver.d.ts.map +1 -1
- package/dist/cjs/policy/resolver.js +103 -33
- package/dist/cjs/policy/resolver.js.map +1 -1
- package/dist/cjs/policy/types.d.ts +105 -0
- package/dist/cjs/policy/types.d.ts.map +1 -1
- package/dist/cjs/policy/types.js +24 -0
- package/dist/cjs/policy/types.js.map +1 -1
- package/dist/esm/compilers/mutation.compiler.d.ts +94 -31
- package/dist/esm/compilers/mutation.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/mutation.compiler.js +381 -344
- package/dist/esm/compilers/mutation.compiler.js.map +1 -1
- package/dist/esm/compilers/selection.compiler.d.ts +1 -1
- package/dist/esm/compilers/selection.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/selection.compiler.js +33 -39
- package/dist/esm/compilers/selection.compiler.js.map +1 -1
- package/dist/esm/compilers/where.compiler.d.ts +206 -6
- package/dist/esm/compilers/where.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/where.compiler.js +524 -178
- package/dist/esm/compilers/where.compiler.js.map +1 -1
- package/dist/esm/execution/executor.d.ts +11 -1
- package/dist/esm/execution/executor.d.ts.map +1 -1
- package/dist/esm/execution/executor.js +6 -3
- package/dist/esm/execution/executor.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/interface-model.d.ts +20 -10
- package/dist/esm/interface-model.d.ts.map +1 -1
- package/dist/esm/interface-model.js +47 -104
- package/dist/esm/interface-model.js.map +1 -1
- package/dist/esm/model.d.ts +42 -30
- package/dist/esm/model.d.ts.map +1 -1
- package/dist/esm/model.js +136 -101
- package/dist/esm/model.js.map +1 -1
- package/dist/esm/ogm.d.ts.map +1 -1
- package/dist/esm/ogm.js +1 -0
- package/dist/esm/ogm.js.map +1 -1
- package/dist/esm/policy/explain.d.ts +59 -0
- package/dist/esm/policy/explain.d.ts.map +1 -0
- package/dist/esm/policy/explain.js +169 -0
- package/dist/esm/policy/explain.js.map +1 -0
- package/dist/esm/policy/nested-writes.d.ts +35 -0
- package/dist/esm/policy/nested-writes.d.ts.map +1 -0
- package/dist/esm/policy/nested-writes.js +193 -0
- package/dist/esm/policy/nested-writes.js.map +1 -0
- package/dist/esm/policy/resolver.d.ts +26 -2
- package/dist/esm/policy/resolver.d.ts.map +1 -1
- package/dist/esm/policy/resolver.js +102 -33
- package/dist/esm/policy/resolver.js.map +1 -1
- package/dist/esm/policy/types.d.ts +105 -0
- package/dist/esm/policy/types.d.ts.map +1 -1
- package/dist/esm/policy/types.js +23 -0
- package/dist/esm/policy/types.js.map +1 -1
- 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
|
|
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[]
|
|
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
|
|
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":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"mutation.compiler.d.ts","sourceRoot":"","sources":["../../../src/compilers/mutation.compiler.ts"],"names":[],"mappings":"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"}
|