@aventara/core 1.0.0-rc.1 → 1.0.0-rc.10
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 +943 -16
- package/ERRORS.md +142 -0
- package/README.md +274 -16
- package/dist/adapters/adapter-discovery-diagnostic.guidance.d.ts +6 -0
- package/dist/adapters/adapter-discovery-diagnostic.guidance.js +7 -0
- package/dist/adapters/adapter-field.d.ts +22 -0
- package/dist/adapters/adapter-identifier.d.ts +9 -0
- package/dist/adapters/adapter-identifier.js +6 -1
- package/dist/adapters/adapter-model.d.ts +9 -0
- package/dist/adapters/adapter-operation.d.ts +18 -3
- package/dist/adapters/adapter-resource.d.ts +5 -0
- package/dist/adapters/adapter.interface.d.ts +10 -0
- package/dist/avz/avz.d.ts +1 -1
- package/dist/avz/json-shape.registry.d.ts +2 -1
- package/dist/compiler/contract-compiler-diagnostic.guidance.d.ts +6 -0
- package/dist/compiler/contract-compiler-diagnostic.guidance.js +97 -0
- package/dist/compiler/contract.compiler.d.ts +6 -0
- package/dist/compiler/contract.compiler.js +227 -24
- package/dist/compiler/managed-elsewhere.projector.d.ts +52 -0
- package/dist/compiler/managed-elsewhere.projector.js +179 -0
- package/dist/compiler/managed-elsewhere.validator.d.ts +9 -0
- package/dist/compiler/managed-elsewhere.validator.js +74 -0
- package/dist/config/computed-fields.d.ts +62 -0
- package/dist/config/computed-fields.js +73 -0
- package/dist/config/config-diagnostic.guidance.d.ts +6 -0
- package/dist/config/config-diagnostic.guidance.js +32 -0
- package/dist/config/config.resolver.d.ts +24 -0
- package/dist/config/config.resolver.js +65 -0
- package/dist/config/config.validator.js +222 -8
- package/dist/config/define-resource.d.ts +1 -5
- package/dist/config/entrypoint.normalizer.d.ts +3 -2
- package/dist/config/exact-framework-config.d.ts +67 -22
- package/dist/config/field-compute.resolver.d.ts +2 -0
- package/dist/config/field-compute.resolver.js +8 -0
- package/dist/config/framework-config.d.ts +53 -15
- package/dist/config/framework-resources-config.d.ts +25 -14
- package/dist/config/pipeline-config.d.ts +4 -4
- package/dist/config/pipeline-tree.resolver.d.ts +2 -2
- package/dist/contracts/access-route.rules.d.ts +10 -0
- package/dist/contracts/access-route.rules.js +45 -0
- package/dist/contracts/capability.d.ts +26 -1
- package/dist/contracts/capability.js +20 -0
- package/dist/contracts/client-contract-structure.validator.d.ts +301 -89
- package/dist/contracts/client-contract-structure.validator.js +37 -2
- package/dist/contracts/contract-access.d.ts +29 -0
- package/dist/contracts/contract-access.js +1 -0
- package/dist/contracts/contract-canonicalizer.d.ts +9 -1
- package/dist/contracts/contract-canonicalizer.js +30 -4
- package/dist/contracts/contract-hash.d.ts +0 -1
- package/dist/contracts/contract-hash.js +1 -1
- package/dist/contracts/contract.d.ts +27 -1
- package/dist/contracts/field.d.ts +58 -1
- package/dist/contracts/json-schema.projector.d.ts +11 -0
- package/dist/contracts/json-schema.projector.js +78 -0
- package/dist/contracts/scalar-value-type.d.ts +3 -1
- package/dist/framework/access-facade.d.ts +116 -0
- package/dist/framework/access-facade.js +120 -0
- package/dist/framework/contract-view.d.ts +120 -16
- package/dist/framework/framework-construction.error.d.ts +14 -4
- package/dist/framework/framework-construction.error.js +18 -2
- package/dist/framework/framework.d.ts +26 -5
- package/dist/framework/framework.factory.d.ts +1 -1
- package/dist/framework/framework.factory.js +115 -23
- package/dist/framework/operation-facade.d.ts +80 -13
- package/dist/framework/operation-facade.js +2 -1
- package/dist/index.d.ts +17 -5
- package/dist/index.js +1 -0
- package/dist/internal.d.ts +7 -2
- package/dist/internal.js +6 -0
- package/dist/operations/database-refusal.d.ts +51 -0
- package/dist/operations/database-refusal.js +45 -0
- package/dist/operations/operation-arguments.d.ts +148 -60
- package/dist/operations/operation-call.d.ts +9 -5
- package/dist/operations/operation-result.d.ts +6 -4
- package/dist/operations/operation-validator-core.js +159 -36
- package/dist/operations/operation-validator.d.ts +1 -1
- package/dist/operations/structural-limits.walker.d.ts +1 -1
- package/dist/operations/validation-code.guidance.d.ts +6 -0
- package/dist/operations/validation-code.guidance.js +90 -0
- package/dist/operations/write-scope.resolver.d.ts +4 -0
- package/dist/operations/write-scope.resolver.js +2 -0
- package/dist/policy/access-provider.interface.d.ts +91 -0
- package/dist/policy/access-provider.interface.js +1 -0
- package/dist/policy/access-provider.registry.d.ts +17 -0
- package/dist/policy/access-provider.registry.js +15 -0
- package/dist/policy/access-question.d.ts +20 -0
- package/dist/policy/access-question.js +1 -0
- package/dist/policy/access-question.registry.d.ts +5 -0
- package/dist/policy/access-question.registry.js +19 -0
- package/dist/policy/access-question.runtime.d.ts +36 -0
- package/dist/policy/access-question.runtime.js +297 -0
- package/dist/policy/access-verb.table.d.ts +56 -0
- package/dist/policy/access-verb.table.js +21 -0
- package/dist/policy/access-verbs.d.ts +46 -0
- package/dist/policy/access-verbs.js +1 -0
- package/dist/policy/policy-admission.d.ts +20 -0
- package/dist/policy/policy-admission.js +409 -0
- package/dist/policy/policy-after-update.runtime.d.ts +79 -0
- package/dist/policy/policy-after-update.runtime.js +208 -0
- package/dist/policy/policy-condition.d.ts +15 -0
- package/dist/policy/policy-condition.js +36 -0
- package/dist/policy/policy-engine.d.ts +205 -0
- package/dist/policy/policy-engine.js +1 -0
- package/dist/policy/policy-named-rows.d.ts +18 -0
- package/dist/policy/policy-named-rows.js +51 -0
- package/dist/policy/policy-nested-write.runtime.d.ts +44 -0
- package/dist/policy/policy-nested-write.runtime.js +545 -0
- package/dist/policy/policy-read.runtime.d.ts +81 -0
- package/dist/policy/policy-read.runtime.js +503 -0
- package/dist/policy/policy-reference-write.runtime.d.ts +51 -0
- package/dist/policy/policy-reference-write.runtime.js +345 -0
- package/dist/policy/policy-request.d.ts +41 -0
- package/dist/policy/policy-request.js +63 -0
- package/dist/policy/policy-session.d.ts +106 -0
- package/dist/policy/policy-session.js +295 -0
- package/dist/policy/policy-startup.validator.d.ts +10 -0
- package/dist/policy/policy-startup.validator.js +112 -0
- package/dist/policy/policy-write.runtime.d.ts +39 -0
- package/dist/policy/policy-write.runtime.js +532 -0
- package/dist/prebuilt/core-version.generated.d.ts +2 -0
- package/dist/prebuilt/core-version.generated.js +1 -0
- package/dist/prebuilt/prebuilt-changes.d.ts +29 -0
- package/dist/prebuilt/prebuilt-changes.js +65 -0
- package/dist/prebuilt/prebuilt-contracts.d.ts +40 -0
- package/dist/prebuilt/prebuilt-contracts.emitter.d.ts +16 -0
- package/dist/prebuilt/prebuilt-contracts.emitter.js +50 -0
- package/dist/prebuilt/prebuilt-contracts.js +1 -0
- package/dist/prebuilt/prebuilt-contracts.loader.d.ts +19 -0
- package/dist/prebuilt/prebuilt-contracts.loader.js +77 -0
- package/dist/prebuilt/prebuilt-inputs.fingerprint.d.ts +16 -0
- package/dist/prebuilt/prebuilt-inputs.fingerprint.js +229 -0
- package/dist/protocol/absent-route.responder.d.ts +9 -27
- package/dist/protocol/absent-route.responder.js +36 -3
- package/dist/protocol/contract-document.responder.d.ts +2 -1
- package/dist/protocol/http-request.decoder.d.ts +19 -3
- package/dist/protocol/http-request.decoder.js +24 -1
- package/dist/protocol/http-request.reader.d.ts +6 -3
- package/dist/protocol/http-request.reader.js +1 -0
- package/dist/protocol/http-response.encoder.js +2 -2
- package/dist/protocol/protocol-headers.d.ts +3 -3
- package/dist/protocol/protocol-surface.d.ts +6 -1
- package/dist/protocol/protocol-surface.js +7 -0
- package/dist/protocol/protocol.binder.d.ts +16 -1
- package/dist/protocol/protocol.binder.js +4 -1
- package/dist/protocol/protocol.handler.d.ts +12 -1
- package/dist/protocol/protocol.handler.js +86 -17
- package/dist/runtime/caller-data.copier.d.ts +18 -3
- package/dist/runtime/caller-data.copier.js +21 -8
- package/dist/runtime/caller-data.door.d.ts +13 -6
- package/dist/runtime/computed-read-runtime.d.ts +6 -1
- package/dist/runtime/computed-read-runtime.js +232 -45
- package/dist/runtime/computed-value.error.d.ts +3 -2
- package/dist/runtime/computed-value.error.js +3 -2
- package/dist/runtime/context-executor.runtime.d.ts +4 -3
- package/dist/runtime/context-executor.runtime.js +4 -2
- package/dist/runtime/core-class.brand.d.ts +10 -0
- package/dist/runtime/core-class.brand.js +21 -0
- package/dist/runtime/create-requirement.d.ts +31 -5
- package/dist/runtime/create-requirement.js +129 -21
- package/dist/runtime/decimal.d.ts +14 -0
- package/dist/runtime/decimal.js +21 -0
- package/dist/runtime/diagnostics-reporter.d.ts +44 -9
- package/dist/runtime/diagnostics-reporter.js +13 -1
- package/dist/runtime/error-boundary.d.ts +9 -2
- package/dist/runtime/error-boundary.js +12 -0
- package/dist/runtime/error-guidance.d.ts +12 -0
- package/dist/runtime/error-guidance.js +1 -0
- package/dist/runtime/error.catalog.d.ts +49 -0
- package/dist/runtime/error.catalog.js +49 -0
- package/dist/runtime/execution-options.d.ts +8 -0
- package/dist/runtime/field-check.evaluator.d.ts +0 -2
- package/dist/runtime/field-check.evaluator.js +1 -1
- package/dist/runtime/field-compute.d.ts +11 -10
- package/dist/runtime/field-stage.runtime.d.ts +3 -1
- package/dist/runtime/field-stage.runtime.js +8 -4
- package/dist/runtime/field-validator.runtime.d.ts +19 -1
- package/dist/runtime/field-validator.runtime.js +47 -8
- package/dist/runtime/framework-error.js +9 -0
- package/dist/runtime/framework-warning.emitter.d.ts +6 -0
- package/dist/runtime/framework-warning.emitter.js +8 -0
- package/dist/runtime/json-value.d.ts +10 -5
- package/dist/runtime/level-lifecycle.runtime.d.ts +4 -3
- package/dist/runtime/level-lifecycle.runtime.js +3 -5
- package/dist/runtime/mutation-planner.d.ts +1 -6
- package/dist/runtime/mutation-planner.js +3 -14
- package/dist/runtime/operation-code.guidance.d.ts +6 -0
- package/dist/runtime/operation-code.guidance.js +167 -0
- package/dist/runtime/operation-request.d.ts +16 -9
- package/dist/runtime/operation-runtime.d.ts +35 -17
- package/dist/runtime/operation-runtime.js +220 -49
- package/dist/runtime/outcome-mapper.d.ts +2 -1
- package/dist/runtime/output-validator.d.ts +1 -1
- package/dist/runtime/owned-execution.registry.d.ts +13 -3
- package/dist/runtime/pipe-output.validator.d.ts +10 -0
- package/dist/runtime/pipe-output.validator.js +4 -2
- package/dist/runtime/pipeline.d.ts +47 -22
- package/dist/runtime/readonly-value.d.ts +20 -5
- package/dist/runtime/readonly-value.js +74 -26
- package/dist/runtime/referential-action.d.ts +1 -1
- package/dist/runtime/scalar-runtime.d.ts +4 -11
- package/dist/runtime/scalar-runtime.js +91 -22
- package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
- package/dist/runtime/unsent-value.normalizer.js +128 -0
- package/dist/transactions/operation-arguments-encoder.js +8 -3
- package/dist/transactions/operation-fingerprint.js +2 -2
- package/dist/transactions/sql-statement.d.ts +10 -2
- package/dist/transactions/sql-statement.validator.d.ts +2 -2
- package/dist/transactions/sql-statement.validator.js +38 -5
- package/dist/transactions/transaction-envelope.d.ts +5 -4
- package/dist/transactions/transaction-integrity-error.js +2 -0
- package/dist/transactions/transaction-plan-assembler.d.ts +12 -5
- package/dist/transactions/transaction-plan-assembler.js +48 -10
- package/dist/transactions/transaction-plan-validator.d.ts +23 -7
- package/dist/transactions/transaction-plan-validator.js +27 -1
- package/dist/transactions/transaction-reference-resolver.d.ts +3 -2
- package/dist/transactions/transaction-reference-validator.d.ts +43 -3
- package/dist/transactions/transaction-reference-validator.js +83 -57
- package/dist/transactions/transaction-reference.d.ts +1 -1
- package/dist/transactions/transaction-reference.js +5 -0
- package/dist/transactions/transaction-request.d.ts +8 -8
- package/dist/transactions/transaction-runtime.d.ts +5 -2
- package/dist/transactions/transaction-runtime.js +79 -27
- package/dist/transactions/transaction-settlement.runtime.d.ts +4 -3
- package/dist/transactions/transaction-slot-path-error.js +2 -0
- package/dist/transactions/transaction-stage.runtime.d.ts +5 -3
- package/dist/transactions/transaction-stage.runtime.js +7 -4
- package/package.json +6 -5
- package/src/runtime/field-check.evaluator.ts +1 -1
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
|
|
2
|
+
import { toOneRelinkWithRewrite } from "../contracts/capability.js";
|
|
3
|
+
import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
|
|
4
|
+
import { isRelationMutationDirective } from "../runtime/mutation-planner.js";
|
|
5
|
+
import { frozenCopy } from "../runtime/readonly-value.js";
|
|
6
|
+
import { policyRefusal } from "./policy-admission.js";
|
|
7
|
+
import { afterUpdate, beforeFields, checkChanged, coveredBefore, rowFieldsOf, unchangedSince, } from "./policy-after-update.runtime.js";
|
|
8
|
+
import { allOf, noneOf, someOf, whereOf, } from "./policy-condition.js";
|
|
9
|
+
import { NamedRows } from "./policy-named-rows.js";
|
|
10
|
+
import { identifiedRows } from "./policy-read.runtime.js";
|
|
11
|
+
import { isHeldTarget } from "./policy-reference-write.runtime.js";
|
|
12
|
+
import { fieldAccess } from "./policy-session.js";
|
|
13
|
+
function isRecord(value) {
|
|
14
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
function isPlainObject(value) {
|
|
17
|
+
if (!isRecord(value)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const prototype = Object.getPrototypeOf(value);
|
|
21
|
+
return prototype === Object.prototype || prototype === null;
|
|
22
|
+
}
|
|
23
|
+
function namesReference(selector, references) {
|
|
24
|
+
if (references === undefined) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return (Object.keys(selector).length === references.length &&
|
|
28
|
+
references.every((reference) => {
|
|
29
|
+
const value = Object.hasOwn(selector, reference)
|
|
30
|
+
? selector[reference]
|
|
31
|
+
: undefined;
|
|
32
|
+
return (value !== undefined &&
|
|
33
|
+
value !== null &&
|
|
34
|
+
!Array.isArray(value) &&
|
|
35
|
+
!isPlainObject(value));
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
const DROPPED = Symbol("dropped");
|
|
39
|
+
function rewritten(value, replaced) {
|
|
40
|
+
if (value !== null && typeof value === "object" && replaced.has(value)) {
|
|
41
|
+
return replaced.get(value);
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
const entries = value.map((entry) => rewritten(entry, replaced));
|
|
45
|
+
return entries.every((entry, index) => entry === value[index])
|
|
46
|
+
? value
|
|
47
|
+
: entries.filter((entry) => entry !== DROPPED);
|
|
48
|
+
}
|
|
49
|
+
if (!isPlainObject(value)) {
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
let changed = false;
|
|
53
|
+
const copy = {};
|
|
54
|
+
for (const [key, member] of Object.entries(value)) {
|
|
55
|
+
const next = rewritten(member, replaced);
|
|
56
|
+
changed ||= next !== member;
|
|
57
|
+
copy[key] = next === DROPPED ? [] : next;
|
|
58
|
+
}
|
|
59
|
+
return changed ? copy : value;
|
|
60
|
+
}
|
|
61
|
+
function conditionOf(where) {
|
|
62
|
+
return isRecord(where) && Object.keys(where).length > 0 ? where : true;
|
|
63
|
+
}
|
|
64
|
+
function relationOf(core, resource, name) {
|
|
65
|
+
const field = lookupField(lookupResource(core, resource), name);
|
|
66
|
+
return field?.kind === "relation"
|
|
67
|
+
? field
|
|
68
|
+
: undefined;
|
|
69
|
+
}
|
|
70
|
+
export function writesNested(core, resource, record) {
|
|
71
|
+
return (isRecord(record) &&
|
|
72
|
+
Object.entries(record).some(([key, value]) => isRecord(value) &&
|
|
73
|
+
relationOf(core, resource, key) !== undefined &&
|
|
74
|
+
Object.keys(value).some(isRelationMutationDirective)));
|
|
75
|
+
}
|
|
76
|
+
class NestedRefusal extends Error {
|
|
77
|
+
name = "NestedRefusal";
|
|
78
|
+
}
|
|
79
|
+
const QUESTION_MESSAGES = {
|
|
80
|
+
link: (resource) => `The policy does not allow this nested write to link or unlink a row of Resource "${resource}" outside what the caller may change or read.`,
|
|
81
|
+
update: (resource) => `The policy does not allow this nested write to change a row of Resource "${resource}" outside what the caller may change.`,
|
|
82
|
+
delete: (resource) => `The policy does not allow this nested write to delete a row of Resource "${resource}" outside what the caller may delete.`,
|
|
83
|
+
fields: (resource) => `The policy does not allow this nested write to write a field of Resource "${resource}" on every row it reaches.`,
|
|
84
|
+
};
|
|
85
|
+
function linkedTo(inverse, parents) {
|
|
86
|
+
if (parents === false) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const reach = whereOf(parents);
|
|
90
|
+
return inverse.cardinality === "one"
|
|
91
|
+
? { [inverse.name]: reach }
|
|
92
|
+
: { [inverse.name]: { some: reach } };
|
|
93
|
+
}
|
|
94
|
+
class NestedWalk {
|
|
95
|
+
session;
|
|
96
|
+
core;
|
|
97
|
+
model;
|
|
98
|
+
questions = new Map();
|
|
99
|
+
named = new Map();
|
|
100
|
+
replaced = new Map();
|
|
101
|
+
held = [];
|
|
102
|
+
constructor(session, core, model) {
|
|
103
|
+
this.session = session;
|
|
104
|
+
this.core = core;
|
|
105
|
+
this.model = model;
|
|
106
|
+
}
|
|
107
|
+
ask(resource, kind, condition) {
|
|
108
|
+
if (condition === false) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const key = `${resource}\u0000${kind}`;
|
|
112
|
+
let question = this.questions.get(key);
|
|
113
|
+
if (question === undefined) {
|
|
114
|
+
question = { resource, kind, conditions: [] };
|
|
115
|
+
this.questions.set(key, question);
|
|
116
|
+
}
|
|
117
|
+
question.conditions.push(condition);
|
|
118
|
+
}
|
|
119
|
+
name(resource, selector, scope) {
|
|
120
|
+
let rows = this.named.get(resource);
|
|
121
|
+
if (rows === undefined) {
|
|
122
|
+
rows = new NamedRows();
|
|
123
|
+
this.named.set(resource, rows);
|
|
124
|
+
}
|
|
125
|
+
rows.add(selector, scope);
|
|
126
|
+
}
|
|
127
|
+
async answer() {
|
|
128
|
+
for (const [resource, rows] of this.named) {
|
|
129
|
+
if (rows.size > 0 && !(await rows.within(this.session, resource))) {
|
|
130
|
+
return policyRefusal(QUESTION_MESSAGES.link(resource));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const { resource, kind, conditions } of this.questions.values()) {
|
|
134
|
+
const reach = someOf(conditions);
|
|
135
|
+
if (reach !== false &&
|
|
136
|
+
(await this.session.exists(resource, whereOf(reach)))) {
|
|
137
|
+
return policyRefusal(QUESTION_MESSAGES[kind](resource));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
identifier(resource) {
|
|
143
|
+
const identifier = adapterPrimaryIdentifier(this.model, resource);
|
|
144
|
+
if (identifier.length === 0) {
|
|
145
|
+
throw new NestedRefusal(`The policy checks the rows a nested write reaches in Resource "${resource}" by identifier, and the Resource has none.`);
|
|
146
|
+
}
|
|
147
|
+
return identifier;
|
|
148
|
+
}
|
|
149
|
+
async grantsOf(resource, records, action) {
|
|
150
|
+
const fields = await this.session.fields(resource, {
|
|
151
|
+
access: "write",
|
|
152
|
+
action,
|
|
153
|
+
});
|
|
154
|
+
const grants = new Map();
|
|
155
|
+
for (const record of records) {
|
|
156
|
+
for (const [name, value] of Object.entries(record)) {
|
|
157
|
+
if (value === undefined) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const relation = relationOf(this.core, resource, name);
|
|
161
|
+
for (const member of [name, ...(relation?.referenceFields ?? [])]) {
|
|
162
|
+
const access = fieldAccess(this.core, resource, fields, member);
|
|
163
|
+
if (access === "never") {
|
|
164
|
+
throw new NestedRefusal(`The policy does not allow writing field "${member}" of Resource "${resource}".`);
|
|
165
|
+
}
|
|
166
|
+
if (access !== "always") {
|
|
167
|
+
grants.set(member, access);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return [...grants.values()];
|
|
173
|
+
}
|
|
174
|
+
async level(resource, parent, records) {
|
|
175
|
+
const relations = new Map();
|
|
176
|
+
for (const record of records) {
|
|
177
|
+
for (const [key, value] of Object.entries(record)) {
|
|
178
|
+
const relation = isRecord(value)
|
|
179
|
+
? relationOf(this.core, resource, key)
|
|
180
|
+
: undefined;
|
|
181
|
+
if (!isRecord(value) || relation === undefined) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const paired = relation.cardinality === "one"
|
|
185
|
+
? toOneRelinkWithRewrite(Object.fromEntries(Object.entries(value).filter(([, operand]) => operand !== undefined)))
|
|
186
|
+
: undefined;
|
|
187
|
+
if (paired !== undefined) {
|
|
188
|
+
throw new NestedRefusal(`Relation "${key}" of Resource "${resource}" pairs ${paired.rewrite.join("/")} with ${paired.relink.join("/")}: the row it would update is the newly linked one, which the policy cannot check before the write.`);
|
|
189
|
+
}
|
|
190
|
+
for (const [directive, operand] of Object.entries(value)) {
|
|
191
|
+
if (isRelationMutationDirective(directive) && operand !== undefined) {
|
|
192
|
+
let operands = relations.get(key);
|
|
193
|
+
if (operands === undefined) {
|
|
194
|
+
operands = [];
|
|
195
|
+
relations.set(key, operands);
|
|
196
|
+
}
|
|
197
|
+
operands.push([directive, operand]);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const checks = [];
|
|
203
|
+
for (const [name, operands] of relations) {
|
|
204
|
+
checks.push(...(await this.relation(resource, parent, name, operands)));
|
|
205
|
+
}
|
|
206
|
+
return checks;
|
|
207
|
+
}
|
|
208
|
+
async relation(resource, parent, name, operands) {
|
|
209
|
+
const core = this.core;
|
|
210
|
+
const relation = relationOf(core, resource, name);
|
|
211
|
+
const target = relation.target;
|
|
212
|
+
const back = relation.inverse === undefined
|
|
213
|
+
? undefined
|
|
214
|
+
: relationOf(core, target, relation.inverse);
|
|
215
|
+
const inverse = back === undefined || relation.inverse === undefined
|
|
216
|
+
? undefined
|
|
217
|
+
: { name: relation.inverse, cardinality: back.cardinality };
|
|
218
|
+
const throughInverse = () => {
|
|
219
|
+
if (inverse === undefined) {
|
|
220
|
+
throw new NestedRefusal(`The policy checks the rows relation "${name}" of Resource "${resource}" reaches through its other end, and the relation states none.`);
|
|
221
|
+
}
|
|
222
|
+
return inverse;
|
|
223
|
+
};
|
|
224
|
+
let reached;
|
|
225
|
+
const linked = () => {
|
|
226
|
+
reached ??=
|
|
227
|
+
parent.kind === "new"
|
|
228
|
+
? false
|
|
229
|
+
: linkedTo(throughInverse(), parent.reach);
|
|
230
|
+
return reached;
|
|
231
|
+
};
|
|
232
|
+
const read = await this.session.scope(target, "read");
|
|
233
|
+
const change = allOf(await this.session.scope(target, "update"), read);
|
|
234
|
+
const remove = allOf(await this.session.scope(target, "delete"), read);
|
|
235
|
+
const targetHolds = relation.referenceFields === undefined;
|
|
236
|
+
const unheld = !targetHolds && !isHeldTarget(core, this.session.unheld, target);
|
|
237
|
+
const linkScope = targetHolds ? change : unheld ? true : read;
|
|
238
|
+
const selectorScope = (selector) => unheld && !namesReference(selector, relation.references)
|
|
239
|
+
? read
|
|
240
|
+
: linkScope;
|
|
241
|
+
const many = relation.cardinality === "many";
|
|
242
|
+
const selectors = [];
|
|
243
|
+
const created = [];
|
|
244
|
+
const checks = [];
|
|
245
|
+
const changing = [];
|
|
246
|
+
const link = (selector, named = true) => {
|
|
247
|
+
if (isRecord(selector)) {
|
|
248
|
+
selectors.push(selector);
|
|
249
|
+
const scope = selectorScope(selector);
|
|
250
|
+
if (named) {
|
|
251
|
+
this.name(target, selector, scope);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
this.ask(target, "link", allOf(selector, noneOf(scope)));
|
|
255
|
+
}
|
|
256
|
+
if (targetHolds) {
|
|
257
|
+
changing.push(selector);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
const unlinkLinked = (except) => {
|
|
262
|
+
const unlinked = allOf(linked(), noneOf(except));
|
|
263
|
+
this.ask(target, "link", allOf(unlinked, noneOf(linkScope)));
|
|
264
|
+
if (targetHolds) {
|
|
265
|
+
changing.push(unlinked);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const entriesOf = (operand) => many && Array.isArray(operand) ? operand : [operand];
|
|
269
|
+
for (const [directive, operand] of operands) {
|
|
270
|
+
if (!many &&
|
|
271
|
+
targetHolds &&
|
|
272
|
+
parent.kind === "existing" &&
|
|
273
|
+
(directive === "$create" ||
|
|
274
|
+
directive === "$connect" ||
|
|
275
|
+
directive === "$connectOrCreate" ||
|
|
276
|
+
directive === "$set")) {
|
|
277
|
+
unlinkLinked(false);
|
|
278
|
+
}
|
|
279
|
+
switch (directive) {
|
|
280
|
+
case "$create":
|
|
281
|
+
created.push(...entriesOf(operand).filter(isRecord));
|
|
282
|
+
break;
|
|
283
|
+
case "$connect":
|
|
284
|
+
entriesOf(operand).forEach((one) => {
|
|
285
|
+
link(one);
|
|
286
|
+
});
|
|
287
|
+
break;
|
|
288
|
+
case "$connectOrCreate":
|
|
289
|
+
for (const entry of entriesOf(operand)) {
|
|
290
|
+
if (isRecord(entry)) {
|
|
291
|
+
link(entry.where, false);
|
|
292
|
+
if (isRecord(entry.create)) {
|
|
293
|
+
created.push(entry.create);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
break;
|
|
298
|
+
case "$disconnect":
|
|
299
|
+
if (many) {
|
|
300
|
+
entriesOf(operand).forEach((one) => {
|
|
301
|
+
link(one);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
unlinkLinked(false);
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
case "$set":
|
|
309
|
+
if (!many) {
|
|
310
|
+
if (operand === null) {
|
|
311
|
+
unlinkLinked(false);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
link(operand);
|
|
315
|
+
}
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
{
|
|
319
|
+
const kept = (Array.isArray(operand) ? operand : []).filter(isRecord);
|
|
320
|
+
kept.forEach((one) => {
|
|
321
|
+
link(one);
|
|
322
|
+
});
|
|
323
|
+
unlinkLinked(someOf(kept));
|
|
324
|
+
}
|
|
325
|
+
break;
|
|
326
|
+
case "$update":
|
|
327
|
+
for (const entry of entriesOf(operand)) {
|
|
328
|
+
if (isRecord(entry) && isRecord(entry.data)) {
|
|
329
|
+
checks.push(await this.changed(target, allOf(linked(), conditionOf(entry.where)), change, entry.data, many ? { kind: "narrow", entry } : { kind: "hold", entry }));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
break;
|
|
333
|
+
case "$upsert":
|
|
334
|
+
for (const entry of entriesOf(operand)) {
|
|
335
|
+
if (!isRecord(entry)) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const reach = allOf(linked(), conditionOf(entry.where));
|
|
339
|
+
if (reach !== false &&
|
|
340
|
+
isRecord(entry.update) &&
|
|
341
|
+
(await this.session.exists(target, whereOf(reach)))) {
|
|
342
|
+
checks.push(await this.changed(target, reach, change, entry.update));
|
|
343
|
+
}
|
|
344
|
+
else if (isRecord(entry.create)) {
|
|
345
|
+
created.push(entry.create);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
case "$delete":
|
|
350
|
+
for (const entry of entriesOf(operand)) {
|
|
351
|
+
const reach = allOf(linked(), conditionOf(isRecord(entry) ? entry.where : undefined));
|
|
352
|
+
if (many && isRecord(entry)) {
|
|
353
|
+
await this.narrow(entry, target, reach, remove);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
this.ask(target, "delete", allOf(reach, noneOf(remove)));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (changing.length > 0) {
|
|
363
|
+
checks.push(await this.relinked(target, inverse, read, changing));
|
|
364
|
+
}
|
|
365
|
+
if (created.length > 0) {
|
|
366
|
+
checks.push(await this.creates(resource, target, throughInverse(), linked(), selectors, created));
|
|
367
|
+
}
|
|
368
|
+
return checks;
|
|
369
|
+
}
|
|
370
|
+
async narrow(entry, resource, reach, scope, fields = this.identifier(resource), held = false) {
|
|
371
|
+
const identifier = this.identifier(resource);
|
|
372
|
+
const within = allOf(reach, scope);
|
|
373
|
+
const rows = within === false
|
|
374
|
+
? []
|
|
375
|
+
: await this.session.rows(resource, whereOf(within), fields);
|
|
376
|
+
const where = held
|
|
377
|
+
? unchangedSince(this.core, this.model, resource, rows)
|
|
378
|
+
: identifiedRows(this.core, identifier, resource, rows);
|
|
379
|
+
this.replaced.set(entry, where === false ? DROPPED : { ...entry, where: whereOf(where) });
|
|
380
|
+
return rows;
|
|
381
|
+
}
|
|
382
|
+
hold(entry, resource, rows) {
|
|
383
|
+
const unchanged = unchangedSince(this.core, this.model, resource, rows);
|
|
384
|
+
const where = typeof unchanged === "boolean"
|
|
385
|
+
? false
|
|
386
|
+
: allOf(conditionOf(entry.where), unchanged);
|
|
387
|
+
if (where === false) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
this.replaced.set(entry, { ...entry, where: whereOf(where) });
|
|
391
|
+
this.held.push({ resource, rows });
|
|
392
|
+
}
|
|
393
|
+
async failed() {
|
|
394
|
+
const fresh = this.session.renewed();
|
|
395
|
+
for (const { resource, rows } of this.held) {
|
|
396
|
+
const changed = allOf(identifiedRows(this.core, this.identifier(resource), resource, rows), noneOf(unchangedSince(this.core, this.model, resource, rows)));
|
|
397
|
+
if (changed !== false &&
|
|
398
|
+
(await fresh.exists(resource, whereOf(changed)))) {
|
|
399
|
+
return policyRefusal(`The policy checks this nested update of Resource "${resource}" against the row as it was before it, and a concurrent write changed that row before it ran, so it was not applied.`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
async changed(resource, reach, scope, data, directive) {
|
|
405
|
+
const identifier = this.identifier(resource);
|
|
406
|
+
const after = await afterUpdate(this.session, this.core, this.model, resource, data, true);
|
|
407
|
+
const fields = beforeFields(this.core, this.model, resource, after);
|
|
408
|
+
let rows;
|
|
409
|
+
if (directive?.kind !== "narrow") {
|
|
410
|
+
this.ask(resource, "update", allOf(reach, noneOf(scope)));
|
|
411
|
+
rows =
|
|
412
|
+
reach === false
|
|
413
|
+
? []
|
|
414
|
+
: await this.session.rows(resource, whereOf(reach), fields);
|
|
415
|
+
if (directive !== undefined && after?.before === true) {
|
|
416
|
+
this.hold(directive.entry, resource, rows);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
rows = await this.narrow(directive.entry, resource, reach, scope, fields, after?.before === true);
|
|
421
|
+
reach =
|
|
422
|
+
rows.length === 0
|
|
423
|
+
? false
|
|
424
|
+
: identifiedRows(this.core, identifier, resource, rows);
|
|
425
|
+
}
|
|
426
|
+
const grants = await this.grantsOf(resource, [data], "update");
|
|
427
|
+
if (grants.length > 0) {
|
|
428
|
+
this.ask(resource, "fields", allOf(reach, someOf(grants.map(noneOf))));
|
|
429
|
+
}
|
|
430
|
+
const before = await coveredBefore(this.session, this.core, this.model, resource, after, rows);
|
|
431
|
+
const inner = await this.level(resource, { kind: "existing", reach }, [
|
|
432
|
+
data,
|
|
433
|
+
]);
|
|
434
|
+
return async () => {
|
|
435
|
+
if (after !== undefined) {
|
|
436
|
+
const refused = await checkChanged(this.session, this.core, this.model, resource, after, rows, false, before);
|
|
437
|
+
if (refused !== undefined) {
|
|
438
|
+
return refused;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return runChecks(inner, rows);
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
async relinked(resource, inverse, read, changing) {
|
|
445
|
+
this.identifier(resource);
|
|
446
|
+
const reach = someOf(changing);
|
|
447
|
+
const after = (inverse === undefined
|
|
448
|
+
? undefined
|
|
449
|
+
: await afterUpdate(this.session, this.core, this.model, resource, { [inverse.name]: {} }, true)) ?? {
|
|
450
|
+
moveOut: read === true ? undefined : read,
|
|
451
|
+
after: true,
|
|
452
|
+
before: this.session.checksAfter(resource, "update"),
|
|
453
|
+
};
|
|
454
|
+
const rows = reach === false
|
|
455
|
+
? []
|
|
456
|
+
: await this.session.rows(resource, whereOf(reach), beforeFields(this.core, this.model, resource, after));
|
|
457
|
+
const before = await coveredBefore(this.session, this.core, this.model, resource, after, rows);
|
|
458
|
+
return () => checkChanged(this.session, this.core, this.model, resource, after, rows, false, before);
|
|
459
|
+
}
|
|
460
|
+
async creates(parentResource, resource, inverse, linked, selectors, records) {
|
|
461
|
+
const identifier = this.identifier(resource);
|
|
462
|
+
const parentIdentifier = this.identifier(parentResource);
|
|
463
|
+
const there = someOf([linked, ...selectors]);
|
|
464
|
+
const before = there === false
|
|
465
|
+
? []
|
|
466
|
+
: await this.session.rows(resource, whereOf(there), identifier);
|
|
467
|
+
const grants = await this.grantsOf(resource, records, "create");
|
|
468
|
+
const inner = await this.level(resource, { kind: "new" }, records);
|
|
469
|
+
return async (parents) => {
|
|
470
|
+
if (parents.length === 0) {
|
|
471
|
+
return undefined;
|
|
472
|
+
}
|
|
473
|
+
const fresh = this.session.renewed();
|
|
474
|
+
const reach = allOf(linkedTo(inverse, identifiedRows(this.core, parentIdentifier, parentResource, parents)), before.length === 0
|
|
475
|
+
? true
|
|
476
|
+
: noneOf(identifiedRows(this.core, identifier, resource, before)));
|
|
477
|
+
const rows = reach === false
|
|
478
|
+
? []
|
|
479
|
+
: await fresh.rows(resource, whereOf(reach), [
|
|
480
|
+
...new Set([...identifier, ...rowFieldsOf(this.core, resource)]),
|
|
481
|
+
]);
|
|
482
|
+
const refused = await checkCreatedRows(fresh, this.core, identifier, resource, grants, rows);
|
|
483
|
+
return refused ?? runChecks(inner, rows);
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
async function runChecks(checks, rows) {
|
|
488
|
+
for (const check of checks) {
|
|
489
|
+
const refused = await check(rows);
|
|
490
|
+
if (refused !== undefined) {
|
|
491
|
+
return refused;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
496
|
+
async function checkCreatedRows(session, core, identifier, resource, grants, rows) {
|
|
497
|
+
if (rows.length === 0) {
|
|
498
|
+
return undefined;
|
|
499
|
+
}
|
|
500
|
+
const answers = await Promise.all(rows.map((row) => session.can(resource, "create", frozenCopy(row))));
|
|
501
|
+
if (answers.includes(false)) {
|
|
502
|
+
return policyRefusal(`The policy does not allow creating this row of Resource "${resource}".`);
|
|
503
|
+
}
|
|
504
|
+
const missed = allOf(identifiedRows(core, identifier, resource, rows), someOf(grants.map(noneOf)));
|
|
505
|
+
if (missed !== false && (await session.exists(resource, whereOf(missed)))) {
|
|
506
|
+
return policyRefusal(`The policy does not allow writing a field of Resource "${resource}" on the row this request creates.`);
|
|
507
|
+
}
|
|
508
|
+
return undefined;
|
|
509
|
+
}
|
|
510
|
+
export async function planNestedWrites(session, core, model, resource, record, parent) {
|
|
511
|
+
const walk = new NestedWalk(session, core, model);
|
|
512
|
+
try {
|
|
513
|
+
const identifier = adapterPrimaryIdentifier(model, resource);
|
|
514
|
+
if (identifier.length === 0) {
|
|
515
|
+
throw new NestedRefusal(`The policy checks the rows a nested write reaches by identifier, and Resource "${resource}" has none.`);
|
|
516
|
+
}
|
|
517
|
+
const checks = await walk.level(resource, parent, [record]);
|
|
518
|
+
const refused = await walk.answer();
|
|
519
|
+
if (refused !== undefined) {
|
|
520
|
+
return { kind: "refused", response: refused };
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
kind: "planned",
|
|
524
|
+
record: rewritten(record, walk.replaced),
|
|
525
|
+
failed: () => walk.failed(),
|
|
526
|
+
after: async (rows) => {
|
|
527
|
+
if (parent.kind === "new" && rows.length > 0) {
|
|
528
|
+
const fresh = session.renewed();
|
|
529
|
+
const stored = await fresh.rows(resource, whereOf(identifiedRows(core, identifier, resource, rows)), [...new Set([...identifier, ...rowFieldsOf(core, resource)])]);
|
|
530
|
+
const refusal = await checkCreatedRows(fresh, core, identifier, resource, [], stored);
|
|
531
|
+
if (refusal !== undefined) {
|
|
532
|
+
return refusal;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return runChecks(checks, rows);
|
|
536
|
+
},
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
catch (error) {
|
|
540
|
+
if (error instanceof NestedRefusal) {
|
|
541
|
+
return { kind: "refused", response: policyRefusal(error.message) };
|
|
542
|
+
}
|
|
543
|
+
throw error;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { AvAdapterModel } from "../adapters/adapter-model.js";
|
|
2
|
+
import type { AvContractCore } from "../contracts/contract.js";
|
|
3
|
+
import type { AvOperationIdentity } from "../operations/operation-identity.js";
|
|
4
|
+
import type { PlannedReadOperation, ReadProjectionPlan } from "../runtime/computed-read-runtime.js";
|
|
5
|
+
import type { AvOperationFailureResponse } from "../runtime/operation-response.js";
|
|
6
|
+
import { type PolicyCondition } from "./policy-condition.js";
|
|
7
|
+
import { type PolicySession } from "./policy-session.js";
|
|
8
|
+
/** The answer once the policy checked it: its rows, or the refusal. */
|
|
9
|
+
export type PolicySettlement = {
|
|
10
|
+
readonly ok: true;
|
|
11
|
+
readonly data: unknown;
|
|
12
|
+
} | {
|
|
13
|
+
readonly ok: false;
|
|
14
|
+
readonly response: AvOperationFailureResponse;
|
|
15
|
+
};
|
|
16
|
+
/** What one read runs once its policy is applied. */
|
|
17
|
+
export type PolicyRead = {
|
|
18
|
+
/** The policy refuses the read before it runs. */
|
|
19
|
+
readonly kind: "refused";
|
|
20
|
+
readonly response: AvOperationFailureResponse;
|
|
21
|
+
} | {
|
|
22
|
+
/** The read reaches no row: its answer, without the adapter. */
|
|
23
|
+
readonly kind: "empty";
|
|
24
|
+
readonly data: unknown;
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: "run";
|
|
27
|
+
/** The operation the adapter runs: the request's own, or a unique read's first-style form. */
|
|
28
|
+
readonly identity: AvOperationIdentity;
|
|
29
|
+
readonly arguments: unknown;
|
|
30
|
+
readonly plan: PlannedReadOperation;
|
|
31
|
+
/**
|
|
32
|
+
* What the returned rows are checked for after the adapter answers (an
|
|
33
|
+
* `exists` per level): the root level's conditions, each a row it must
|
|
34
|
+
* not meet, and whether a level below the root is checked.
|
|
35
|
+
*/
|
|
36
|
+
readonly checks: {
|
|
37
|
+
readonly root: readonly PolicyCondition[];
|
|
38
|
+
readonly nested: boolean;
|
|
39
|
+
};
|
|
40
|
+
/** The returned rows checked, and every emptied relation filled. */
|
|
41
|
+
readonly settle: (data: unknown) => Promise<PolicySettlement>;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* How a write's root rows are reached (`policy-write.runtime.ts`): the
|
|
45
|
+
* condition ANDed into its `where`, or `undefined` for a write whose `where`
|
|
46
|
+
* stays as sent (a create has none, an upsert's is checked before it runs);
|
|
47
|
+
* and whether each returned row carries its identifier, for the checks that
|
|
48
|
+
* follow the write.
|
|
49
|
+
*/
|
|
50
|
+
type PolicyRootRows = {
|
|
51
|
+
readonly scope: PolicyCondition | undefined;
|
|
52
|
+
readonly identified: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* The fields each returned root row carries besides the caller's projection (its
|
|
55
|
+
* identifier among them when `identified`): what the after check asks `can` about,
|
|
56
|
+
* read from the write's own answer rather than read again. Trimmed before the
|
|
57
|
+
* caller sees it.
|
|
58
|
+
*/
|
|
59
|
+
readonly carried?: readonly string[];
|
|
60
|
+
};
|
|
61
|
+
type Row = Readonly<Record<string, unknown>>;
|
|
62
|
+
/**
|
|
63
|
+
* One read's policy (see the module comment): what the adapter runs, and the
|
|
64
|
+
* checks its answer goes through; or the empty answer when the read reaches no
|
|
65
|
+
* row. A write's answer is read the same way, its root reached as `rows`
|
|
66
|
+
* says (`policy-write.runtime.ts`); a read's root is reached by its read scope.
|
|
67
|
+
*/
|
|
68
|
+
export declare function enforceReadPolicy(session: PolicySession, core: AvContractCore, model: AvAdapterModel, request: AvOperationIdentity & {
|
|
69
|
+
readonly resource: string;
|
|
70
|
+
}, plan: PlannedReadOperation, adapterArguments: unknown, rows?: PolicyRootRows): Promise<PolicyRead>;
|
|
71
|
+
/**
|
|
72
|
+
* What the rows an answer returns are checked for after the adapter answers, by
|
|
73
|
+
* the projection level that returns them: each level's conditions, each a row
|
|
74
|
+
* it must not meet (a to-one level's rows outside their read scope, a level's
|
|
75
|
+
* rows outside the grant of a field it returns that is readable on some rows
|
|
76
|
+
* only). Empty when nothing is checked after the answer.
|
|
77
|
+
*/
|
|
78
|
+
export declare function returnedRowChecks(session: PolicySession, core: AvContractCore, plan: PlannedReadOperation): Promise<Map<ReadProjectionPlan, readonly PolicyCondition[]>>;
|
|
79
|
+
/** A condition naming exactly `rows` of `resource` by their identifier. */
|
|
80
|
+
export declare function identifiedRows(core: AvContractCore, identifier: readonly string[], resource: string, rows: readonly Row[]): PolicyCondition;
|
|
81
|
+
export {};
|