@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.
Files changed (228) hide show
  1. package/CHANGELOG.md +943 -16
  2. package/ERRORS.md +142 -0
  3. package/README.md +274 -16
  4. package/dist/adapters/adapter-discovery-diagnostic.guidance.d.ts +6 -0
  5. package/dist/adapters/adapter-discovery-diagnostic.guidance.js +7 -0
  6. package/dist/adapters/adapter-field.d.ts +22 -0
  7. package/dist/adapters/adapter-identifier.d.ts +9 -0
  8. package/dist/adapters/adapter-identifier.js +6 -1
  9. package/dist/adapters/adapter-model.d.ts +9 -0
  10. package/dist/adapters/adapter-operation.d.ts +18 -3
  11. package/dist/adapters/adapter-resource.d.ts +5 -0
  12. package/dist/adapters/adapter.interface.d.ts +10 -0
  13. package/dist/avz/avz.d.ts +1 -1
  14. package/dist/avz/json-shape.registry.d.ts +2 -1
  15. package/dist/compiler/contract-compiler-diagnostic.guidance.d.ts +6 -0
  16. package/dist/compiler/contract-compiler-diagnostic.guidance.js +97 -0
  17. package/dist/compiler/contract.compiler.d.ts +6 -0
  18. package/dist/compiler/contract.compiler.js +227 -24
  19. package/dist/compiler/managed-elsewhere.projector.d.ts +52 -0
  20. package/dist/compiler/managed-elsewhere.projector.js +179 -0
  21. package/dist/compiler/managed-elsewhere.validator.d.ts +9 -0
  22. package/dist/compiler/managed-elsewhere.validator.js +74 -0
  23. package/dist/config/computed-fields.d.ts +62 -0
  24. package/dist/config/computed-fields.js +73 -0
  25. package/dist/config/config-diagnostic.guidance.d.ts +6 -0
  26. package/dist/config/config-diagnostic.guidance.js +32 -0
  27. package/dist/config/config.resolver.d.ts +24 -0
  28. package/dist/config/config.resolver.js +65 -0
  29. package/dist/config/config.validator.js +222 -8
  30. package/dist/config/define-resource.d.ts +1 -5
  31. package/dist/config/entrypoint.normalizer.d.ts +3 -2
  32. package/dist/config/exact-framework-config.d.ts +67 -22
  33. package/dist/config/field-compute.resolver.d.ts +2 -0
  34. package/dist/config/field-compute.resolver.js +8 -0
  35. package/dist/config/framework-config.d.ts +53 -15
  36. package/dist/config/framework-resources-config.d.ts +25 -14
  37. package/dist/config/pipeline-config.d.ts +4 -4
  38. package/dist/config/pipeline-tree.resolver.d.ts +2 -2
  39. package/dist/contracts/access-route.rules.d.ts +10 -0
  40. package/dist/contracts/access-route.rules.js +45 -0
  41. package/dist/contracts/capability.d.ts +26 -1
  42. package/dist/contracts/capability.js +20 -0
  43. package/dist/contracts/client-contract-structure.validator.d.ts +301 -89
  44. package/dist/contracts/client-contract-structure.validator.js +37 -2
  45. package/dist/contracts/contract-access.d.ts +29 -0
  46. package/dist/contracts/contract-access.js +1 -0
  47. package/dist/contracts/contract-canonicalizer.d.ts +9 -1
  48. package/dist/contracts/contract-canonicalizer.js +30 -4
  49. package/dist/contracts/contract-hash.d.ts +0 -1
  50. package/dist/contracts/contract-hash.js +1 -1
  51. package/dist/contracts/contract.d.ts +27 -1
  52. package/dist/contracts/field.d.ts +58 -1
  53. package/dist/contracts/json-schema.projector.d.ts +11 -0
  54. package/dist/contracts/json-schema.projector.js +78 -0
  55. package/dist/contracts/scalar-value-type.d.ts +3 -1
  56. package/dist/framework/access-facade.d.ts +116 -0
  57. package/dist/framework/access-facade.js +120 -0
  58. package/dist/framework/contract-view.d.ts +120 -16
  59. package/dist/framework/framework-construction.error.d.ts +14 -4
  60. package/dist/framework/framework-construction.error.js +18 -2
  61. package/dist/framework/framework.d.ts +26 -5
  62. package/dist/framework/framework.factory.d.ts +1 -1
  63. package/dist/framework/framework.factory.js +115 -23
  64. package/dist/framework/operation-facade.d.ts +80 -13
  65. package/dist/framework/operation-facade.js +2 -1
  66. package/dist/index.d.ts +17 -5
  67. package/dist/index.js +1 -0
  68. package/dist/internal.d.ts +7 -2
  69. package/dist/internal.js +6 -0
  70. package/dist/operations/database-refusal.d.ts +51 -0
  71. package/dist/operations/database-refusal.js +45 -0
  72. package/dist/operations/operation-arguments.d.ts +148 -60
  73. package/dist/operations/operation-call.d.ts +9 -5
  74. package/dist/operations/operation-result.d.ts +6 -4
  75. package/dist/operations/operation-validator-core.js +159 -36
  76. package/dist/operations/operation-validator.d.ts +1 -1
  77. package/dist/operations/structural-limits.walker.d.ts +1 -1
  78. package/dist/operations/validation-code.guidance.d.ts +6 -0
  79. package/dist/operations/validation-code.guidance.js +90 -0
  80. package/dist/operations/write-scope.resolver.d.ts +4 -0
  81. package/dist/operations/write-scope.resolver.js +2 -0
  82. package/dist/policy/access-provider.interface.d.ts +91 -0
  83. package/dist/policy/access-provider.interface.js +1 -0
  84. package/dist/policy/access-provider.registry.d.ts +17 -0
  85. package/dist/policy/access-provider.registry.js +15 -0
  86. package/dist/policy/access-question.d.ts +20 -0
  87. package/dist/policy/access-question.js +1 -0
  88. package/dist/policy/access-question.registry.d.ts +5 -0
  89. package/dist/policy/access-question.registry.js +19 -0
  90. package/dist/policy/access-question.runtime.d.ts +36 -0
  91. package/dist/policy/access-question.runtime.js +297 -0
  92. package/dist/policy/access-verb.table.d.ts +56 -0
  93. package/dist/policy/access-verb.table.js +21 -0
  94. package/dist/policy/access-verbs.d.ts +46 -0
  95. package/dist/policy/access-verbs.js +1 -0
  96. package/dist/policy/policy-admission.d.ts +20 -0
  97. package/dist/policy/policy-admission.js +409 -0
  98. package/dist/policy/policy-after-update.runtime.d.ts +79 -0
  99. package/dist/policy/policy-after-update.runtime.js +208 -0
  100. package/dist/policy/policy-condition.d.ts +15 -0
  101. package/dist/policy/policy-condition.js +36 -0
  102. package/dist/policy/policy-engine.d.ts +205 -0
  103. package/dist/policy/policy-engine.js +1 -0
  104. package/dist/policy/policy-named-rows.d.ts +18 -0
  105. package/dist/policy/policy-named-rows.js +51 -0
  106. package/dist/policy/policy-nested-write.runtime.d.ts +44 -0
  107. package/dist/policy/policy-nested-write.runtime.js +545 -0
  108. package/dist/policy/policy-read.runtime.d.ts +81 -0
  109. package/dist/policy/policy-read.runtime.js +503 -0
  110. package/dist/policy/policy-reference-write.runtime.d.ts +51 -0
  111. package/dist/policy/policy-reference-write.runtime.js +345 -0
  112. package/dist/policy/policy-request.d.ts +41 -0
  113. package/dist/policy/policy-request.js +63 -0
  114. package/dist/policy/policy-session.d.ts +106 -0
  115. package/dist/policy/policy-session.js +295 -0
  116. package/dist/policy/policy-startup.validator.d.ts +10 -0
  117. package/dist/policy/policy-startup.validator.js +112 -0
  118. package/dist/policy/policy-write.runtime.d.ts +39 -0
  119. package/dist/policy/policy-write.runtime.js +532 -0
  120. package/dist/prebuilt/core-version.generated.d.ts +2 -0
  121. package/dist/prebuilt/core-version.generated.js +1 -0
  122. package/dist/prebuilt/prebuilt-changes.d.ts +29 -0
  123. package/dist/prebuilt/prebuilt-changes.js +65 -0
  124. package/dist/prebuilt/prebuilt-contracts.d.ts +40 -0
  125. package/dist/prebuilt/prebuilt-contracts.emitter.d.ts +16 -0
  126. package/dist/prebuilt/prebuilt-contracts.emitter.js +50 -0
  127. package/dist/prebuilt/prebuilt-contracts.js +1 -0
  128. package/dist/prebuilt/prebuilt-contracts.loader.d.ts +19 -0
  129. package/dist/prebuilt/prebuilt-contracts.loader.js +77 -0
  130. package/dist/prebuilt/prebuilt-inputs.fingerprint.d.ts +16 -0
  131. package/dist/prebuilt/prebuilt-inputs.fingerprint.js +229 -0
  132. package/dist/protocol/absent-route.responder.d.ts +9 -27
  133. package/dist/protocol/absent-route.responder.js +36 -3
  134. package/dist/protocol/contract-document.responder.d.ts +2 -1
  135. package/dist/protocol/http-request.decoder.d.ts +19 -3
  136. package/dist/protocol/http-request.decoder.js +24 -1
  137. package/dist/protocol/http-request.reader.d.ts +6 -3
  138. package/dist/protocol/http-request.reader.js +1 -0
  139. package/dist/protocol/http-response.encoder.js +2 -2
  140. package/dist/protocol/protocol-headers.d.ts +3 -3
  141. package/dist/protocol/protocol-surface.d.ts +6 -1
  142. package/dist/protocol/protocol-surface.js +7 -0
  143. package/dist/protocol/protocol.binder.d.ts +16 -1
  144. package/dist/protocol/protocol.binder.js +4 -1
  145. package/dist/protocol/protocol.handler.d.ts +12 -1
  146. package/dist/protocol/protocol.handler.js +86 -17
  147. package/dist/runtime/caller-data.copier.d.ts +18 -3
  148. package/dist/runtime/caller-data.copier.js +21 -8
  149. package/dist/runtime/caller-data.door.d.ts +13 -6
  150. package/dist/runtime/computed-read-runtime.d.ts +6 -1
  151. package/dist/runtime/computed-read-runtime.js +232 -45
  152. package/dist/runtime/computed-value.error.d.ts +3 -2
  153. package/dist/runtime/computed-value.error.js +3 -2
  154. package/dist/runtime/context-executor.runtime.d.ts +4 -3
  155. package/dist/runtime/context-executor.runtime.js +4 -2
  156. package/dist/runtime/core-class.brand.d.ts +10 -0
  157. package/dist/runtime/core-class.brand.js +21 -0
  158. package/dist/runtime/create-requirement.d.ts +31 -5
  159. package/dist/runtime/create-requirement.js +129 -21
  160. package/dist/runtime/decimal.d.ts +14 -0
  161. package/dist/runtime/decimal.js +21 -0
  162. package/dist/runtime/diagnostics-reporter.d.ts +44 -9
  163. package/dist/runtime/diagnostics-reporter.js +13 -1
  164. package/dist/runtime/error-boundary.d.ts +9 -2
  165. package/dist/runtime/error-boundary.js +12 -0
  166. package/dist/runtime/error-guidance.d.ts +12 -0
  167. package/dist/runtime/error-guidance.js +1 -0
  168. package/dist/runtime/error.catalog.d.ts +49 -0
  169. package/dist/runtime/error.catalog.js +49 -0
  170. package/dist/runtime/execution-options.d.ts +8 -0
  171. package/dist/runtime/field-check.evaluator.d.ts +0 -2
  172. package/dist/runtime/field-check.evaluator.js +1 -1
  173. package/dist/runtime/field-compute.d.ts +11 -10
  174. package/dist/runtime/field-stage.runtime.d.ts +3 -1
  175. package/dist/runtime/field-stage.runtime.js +8 -4
  176. package/dist/runtime/field-validator.runtime.d.ts +19 -1
  177. package/dist/runtime/field-validator.runtime.js +47 -8
  178. package/dist/runtime/framework-error.js +9 -0
  179. package/dist/runtime/framework-warning.emitter.d.ts +6 -0
  180. package/dist/runtime/framework-warning.emitter.js +8 -0
  181. package/dist/runtime/json-value.d.ts +10 -5
  182. package/dist/runtime/level-lifecycle.runtime.d.ts +4 -3
  183. package/dist/runtime/level-lifecycle.runtime.js +3 -5
  184. package/dist/runtime/mutation-planner.d.ts +1 -6
  185. package/dist/runtime/mutation-planner.js +3 -14
  186. package/dist/runtime/operation-code.guidance.d.ts +6 -0
  187. package/dist/runtime/operation-code.guidance.js +167 -0
  188. package/dist/runtime/operation-request.d.ts +16 -9
  189. package/dist/runtime/operation-runtime.d.ts +35 -17
  190. package/dist/runtime/operation-runtime.js +220 -49
  191. package/dist/runtime/outcome-mapper.d.ts +2 -1
  192. package/dist/runtime/output-validator.d.ts +1 -1
  193. package/dist/runtime/owned-execution.registry.d.ts +13 -3
  194. package/dist/runtime/pipe-output.validator.d.ts +10 -0
  195. package/dist/runtime/pipe-output.validator.js +4 -2
  196. package/dist/runtime/pipeline.d.ts +47 -22
  197. package/dist/runtime/readonly-value.d.ts +20 -5
  198. package/dist/runtime/readonly-value.js +74 -26
  199. package/dist/runtime/referential-action.d.ts +1 -1
  200. package/dist/runtime/scalar-runtime.d.ts +4 -11
  201. package/dist/runtime/scalar-runtime.js +91 -22
  202. package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
  203. package/dist/runtime/unsent-value.normalizer.js +128 -0
  204. package/dist/transactions/operation-arguments-encoder.js +8 -3
  205. package/dist/transactions/operation-fingerprint.js +2 -2
  206. package/dist/transactions/sql-statement.d.ts +10 -2
  207. package/dist/transactions/sql-statement.validator.d.ts +2 -2
  208. package/dist/transactions/sql-statement.validator.js +38 -5
  209. package/dist/transactions/transaction-envelope.d.ts +5 -4
  210. package/dist/transactions/transaction-integrity-error.js +2 -0
  211. package/dist/transactions/transaction-plan-assembler.d.ts +12 -5
  212. package/dist/transactions/transaction-plan-assembler.js +48 -10
  213. package/dist/transactions/transaction-plan-validator.d.ts +23 -7
  214. package/dist/transactions/transaction-plan-validator.js +27 -1
  215. package/dist/transactions/transaction-reference-resolver.d.ts +3 -2
  216. package/dist/transactions/transaction-reference-validator.d.ts +43 -3
  217. package/dist/transactions/transaction-reference-validator.js +83 -57
  218. package/dist/transactions/transaction-reference.d.ts +1 -1
  219. package/dist/transactions/transaction-reference.js +5 -0
  220. package/dist/transactions/transaction-request.d.ts +8 -8
  221. package/dist/transactions/transaction-runtime.d.ts +5 -2
  222. package/dist/transactions/transaction-runtime.js +79 -27
  223. package/dist/transactions/transaction-settlement.runtime.d.ts +4 -3
  224. package/dist/transactions/transaction-slot-path-error.js +2 -0
  225. package/dist/transactions/transaction-stage.runtime.d.ts +5 -3
  226. package/dist/transactions/transaction-stage.runtime.js +7 -4
  227. package/package.json +6 -5
  228. package/src/runtime/field-check.evaluator.ts +1 -1
@@ -0,0 +1,295 @@
1
+ import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
2
+ import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
3
+ import { validateOperationArguments } from "../operations/operation-validator-core.js";
4
+ import { normalizeOperationScalarValues } from "../runtime/scalar-runtime.js";
5
+ import { encodeOperationArgumentsForWire } from "../transactions/operation-arguments-encoder.js";
6
+ class PolicyDefectError extends Error {
7
+ name = "PolicyDefectError";
8
+ }
9
+ function isEmptyRecord(value) {
10
+ return (value !== null &&
11
+ typeof value === "object" &&
12
+ !Array.isArray(value) &&
13
+ Object.keys(value).length === 0);
14
+ }
15
+ function isRecord(value) {
16
+ return value !== null && typeof value === "object" && !Array.isArray(value);
17
+ }
18
+ const FRAGMENT_WIRE_ORIGIN = "client";
19
+ function checkedFragment(core, resource, where, what) {
20
+ if (!isRecord(where)) {
21
+ throw new PolicyDefectError(`${what} must be a where object.`);
22
+ }
23
+ const request = {
24
+ resource,
25
+ family: "find",
26
+ variant: "many",
27
+ arguments: { where },
28
+ };
29
+ const validation = validateOperationArguments(core, request);
30
+ const normalized = validation.ok
31
+ ? normalizeOperationScalarValues(core, {
32
+ ...request,
33
+ arguments: encodeOperationArgumentsForWire(core, request, validation.arguments),
34
+ }, FRAGMENT_WIRE_ORIGIN)
35
+ : validation;
36
+ if (!normalized.ok) {
37
+ const issues = normalized.issues
38
+ .map((issue) => `${issue.code} at ${(issue.path ?? []).slice(2).join(".") || "where"}: ${issue.message}`)
39
+ .join("; ");
40
+ throw new PolicyDefectError(`${what} is no where of Resource "${resource}" in the Core Contract (${issues}).`);
41
+ }
42
+ return normalized.arguments.where;
43
+ }
44
+ function conditionOf(core, resource, answer, what) {
45
+ if (typeof answer === "boolean") {
46
+ return answer;
47
+ }
48
+ if (isEmptyRecord(answer)) {
49
+ return true;
50
+ }
51
+ return checkedFragment(core, resource, answer, what);
52
+ }
53
+ function fieldsAnswer(core, resource, answer) {
54
+ const what = `fieldsOf(${resource})`;
55
+ if (!isRecord(answer)) {
56
+ throw new PolicyDefectError(`${what} must answer { always, perRow }.`);
57
+ }
58
+ const { perRow } = answer;
59
+ const always = Array.isArray(answer.always)
60
+ ? Array.from(answer.always)
61
+ : answer.always;
62
+ if (always !== "all" &&
63
+ !(Array.isArray(always) && always.every((name) => typeof name === "string"))) {
64
+ throw new PolicyDefectError(`${what}.always must be "all" or a list of field names.`);
65
+ }
66
+ if (!isRecord(perRow)) {
67
+ throw new PolicyDefectError(`${what}.perRow must be an object.`);
68
+ }
69
+ const listed = new Set(always === "all" ? [] : always);
70
+ const grants = new Map();
71
+ for (const [field, grant] of Object.entries(perRow)) {
72
+ if (listed.has(field) || grant === undefined) {
73
+ continue;
74
+ }
75
+ const condition = conditionOf(core, resource, grant, `${what}.perRow.${field}`);
76
+ if (condition === true) {
77
+ listed.add(field);
78
+ }
79
+ else if (condition !== false) {
80
+ grants.set(field, condition);
81
+ }
82
+ }
83
+ return { all: always === "all", listed, perRow: grants };
84
+ }
85
+ function questionKey(resource, where) {
86
+ try {
87
+ return `${resource}\u0000${JSON.stringify(where, (_key, value) => typeof value === "bigint" ? `#bigint(${value})` : value)}`;
88
+ }
89
+ catch {
90
+ return undefined;
91
+ }
92
+ }
93
+ function existsOperation(core, model, resource, where) {
94
+ const contract = lookupResource(core, resource);
95
+ const identifier = adapterPrimaryIdentifier(model, resource);
96
+ const first = contract?.operations.find?.first !== undefined;
97
+ return first && identifier.length > 0
98
+ ? {
99
+ resource,
100
+ family: "find",
101
+ variant: "first",
102
+ arguments: { where, select: [...identifier] },
103
+ }
104
+ : {
105
+ resource,
106
+ family: "find",
107
+ variant: "count",
108
+ arguments: { where },
109
+ };
110
+ }
111
+ export function openPolicySession(policy, core, model, principal, origin, execution) {
112
+ const caches = {
113
+ scopes: new Map(),
114
+ createGrants: new Map(),
115
+ fields: new Map(),
116
+ exists: new WeakMap(),
117
+ };
118
+ return sessionOn(policy, core, model, principal, origin, execution, caches);
119
+ }
120
+ function sessionOn(policy, core, model, principal, origin, execution, caches) {
121
+ const { engine } = policy;
122
+ const exists = (resource, where) => {
123
+ const key = questionKey(resource, where);
124
+ let answers = caches.exists.get(execution.executor);
125
+ if (answers === undefined) {
126
+ answers = new Map();
127
+ caches.exists.set(execution.executor, answers);
128
+ }
129
+ const known = key === undefined ? undefined : answers.get(key);
130
+ if (known !== undefined) {
131
+ return known;
132
+ }
133
+ const operation = existsOperation(core, model, resource, where);
134
+ const answer = execution.boundary
135
+ .execute(() => execution.executor.execute(operation))
136
+ .then((result) => typeof result === "number" ? result > 0 : result != null);
137
+ if (key !== undefined) {
138
+ answers.set(key, answer);
139
+ }
140
+ return answer;
141
+ };
142
+ const engineExists = async (resource, where) => {
143
+ if (typeof resource !== "string" ||
144
+ lookupResource(core, resource) === undefined) {
145
+ throw new PolicyDefectError(`exists was asked about "${String(resource)}", which is no Resource of the Core Contract.`);
146
+ }
147
+ const condition = conditionOf(core, resource, where, "exists's where");
148
+ if (typeof condition === "boolean") {
149
+ return exists(resource, {});
150
+ }
151
+ return exists(resource, condition);
152
+ };
153
+ return {
154
+ principal,
155
+ origin,
156
+ execution,
157
+ async can(resource, action, row, before) {
158
+ const answer = await engine.can({
159
+ principal,
160
+ origin,
161
+ resource,
162
+ action,
163
+ ...(row === undefined ? {} : { row }),
164
+ ...(before === undefined ? {} : { before: before.row }),
165
+ ...(before?.grants === undefined
166
+ ? {}
167
+ : { beforeGrants: before.grants }),
168
+ exists: engineExists,
169
+ });
170
+ if (typeof answer !== "boolean") {
171
+ throw new PolicyDefectError(`can(${resource}, ${action}) must answer true or false.`);
172
+ }
173
+ return answer;
174
+ },
175
+ async grants(resource, row) {
176
+ if (engine.grantsOf === undefined) {
177
+ return undefined;
178
+ }
179
+ const answer = await engine.grantsOf({
180
+ principal,
181
+ origin,
182
+ resource,
183
+ action: "update",
184
+ row,
185
+ exists: engineExists,
186
+ });
187
+ const keys = Array.isArray(answer)
188
+ ? Array.from(answer)
189
+ : undefined;
190
+ if (keys === undefined || !keys.every((key) => typeof key === "string")) {
191
+ throw new PolicyDefectError(`grantsOf(${resource}) must answer a list of strings.`);
192
+ }
193
+ return Object.freeze(keys);
194
+ },
195
+ scope(resource, action) {
196
+ const key = `${resource}\u0000${action}`;
197
+ let scope = caches.scopes.get(key);
198
+ if (scope === undefined) {
199
+ scope = Promise.resolve()
200
+ .then(() => engine.scopeOf({ principal, origin, resource, action }))
201
+ .then((answer) => conditionOf(core, resource, answer, `scopeOf(${resource}, ${action})`));
202
+ caches.scopes.set(key, scope);
203
+ }
204
+ return scope;
205
+ },
206
+ fields(resource, access) {
207
+ const key = `${resource}\u0000${access.access}\u0000${access.access === "write" ? access.action : ""}`;
208
+ let fields = caches.fields.get(key);
209
+ if (fields === undefined) {
210
+ fields = Promise.resolve()
211
+ .then(() => engine.fieldsOf({ principal, origin, resource, ...access }))
212
+ .then((answer) => fieldsAnswer(core, resource, answer));
213
+ caches.fields.set(key, fields);
214
+ }
215
+ return fields;
216
+ },
217
+ exists,
218
+ async count(resource, where) {
219
+ const find = lookupResource(core, resource)?.operations.find;
220
+ const identifier = adapterPrimaryIdentifier(model, resource);
221
+ const operation = find?.count !== undefined
222
+ ? {
223
+ resource,
224
+ family: "find",
225
+ variant: "count",
226
+ arguments: { where },
227
+ }
228
+ : find?.many !== undefined && identifier.length > 0
229
+ ? {
230
+ resource,
231
+ family: "find",
232
+ variant: "many",
233
+ arguments: { where, select: [...identifier] },
234
+ }
235
+ : undefined;
236
+ if (operation === undefined) {
237
+ return undefined;
238
+ }
239
+ const answer = await execution.boundary.execute(() => execution.executor.execute(operation));
240
+ if (typeof answer === "number") {
241
+ return answer;
242
+ }
243
+ if (Array.isArray(answer)) {
244
+ return answer.length;
245
+ }
246
+ throw new PolicyDefectError(`The adapter answered the count of Resource "${resource}" with no number.`);
247
+ },
248
+ async rows(resource, where, select) {
249
+ if (lookupResource(core, resource)?.operations.find?.many === undefined) {
250
+ throw new PolicyDefectError(`The policy needs the rows of Resource "${resource}", and its Core Contract offers no find.many.`);
251
+ }
252
+ const operation = {
253
+ resource,
254
+ family: "find",
255
+ variant: "many",
256
+ arguments: { where, select: [...select] },
257
+ };
258
+ const answer = await execution.boundary.execute(() => execution.executor.execute(operation));
259
+ return Array.isArray(answer)
260
+ ? answer.filter((row) => isRecord(row))
261
+ : [];
262
+ },
263
+ grantCreate(field, grant) {
264
+ caches.createGrants.set(field, grant);
265
+ },
266
+ createGrants() {
267
+ return caches.createGrants;
268
+ },
269
+ checksAfter(resource, action) {
270
+ return (Object.hasOwn(policy.afterChecks, resource) &&
271
+ policy.afterChecks[resource]?.includes(action) === true);
272
+ },
273
+ unheld: policy.unheld,
274
+ on(next) {
275
+ return sessionOn(policy, core, model, principal, origin, next, caches);
276
+ },
277
+ renewed() {
278
+ return sessionOn(policy, core, model, principal, origin, execution, {
279
+ ...caches,
280
+ exists: new WeakMap(),
281
+ });
282
+ },
283
+ };
284
+ }
285
+ function alwaysReadable(core, resource, fields, field) {
286
+ return (fields.listed.has(field) ||
287
+ (fields.all &&
288
+ lookupField(lookupResource(core, resource), field)?.governed !== true));
289
+ }
290
+ export function fieldAccess(core, resource, fields, field) {
291
+ if (alwaysReadable(core, resource, fields, field)) {
292
+ return "always";
293
+ }
294
+ return fields.perRow.get(field) ?? "never";
295
+ }
@@ -0,0 +1,10 @@
1
+ import type { AvAdapterModel } from "../adapters/adapter-model.js";
2
+ import type { ResolvedPolicyConfig } from "../config/config.resolver.js";
3
+ import type { AvConfigDiagnostic } from "../config/config-diagnostic.js";
4
+ import type { AvContractCore } from "../contracts/contract.js";
5
+ /**
6
+ * The policy's governed set, rule paths and after checks checked against the
7
+ * Core Contract, and its unheld Resources against the data source's model:
8
+ * every problem, naming the Resource or the rule, or none.
9
+ */
10
+ export declare function policyStartupDiagnostics(core: AvContractCore, policy: ResolvedPolicyConfig, model: AvAdapterModel): readonly AvConfigDiagnostic[];
@@ -0,0 +1,112 @@
1
+ import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
+ const TO_MANY_OPERATORS = new Set(["some", "every", "none"]);
3
+ function invalid(path, message) {
4
+ return { code: "CONFIG_INVALID_VALUE", path, message };
5
+ }
6
+ function governedDiagnostics(core, { at, governed }) {
7
+ const diagnostics = [];
8
+ for (const [resourceName, names] of Object.entries(governed)) {
9
+ const path = `${at}.governed.${resourceName}`;
10
+ const resource = lookupResource(core, resourceName);
11
+ if (resource === undefined) {
12
+ diagnostics.push(invalid(path, `"${resourceName}" is no Resource of the Core Contract`));
13
+ continue;
14
+ }
15
+ for (const name of names) {
16
+ if (lookupField(resource, name) === undefined) {
17
+ diagnostics.push(invalid(path, `"${name}" is no field or relation of Resource "${resourceName}" in the Core Contract`));
18
+ }
19
+ }
20
+ }
21
+ return diagnostics;
22
+ }
23
+ function afterCheckDiagnostics(core, { at, afterChecks }) {
24
+ return Object.keys(afterChecks).flatMap((resourceName) => {
25
+ const path = `${at}.afterChecks.${resourceName}`;
26
+ return lookupResource(core, resourceName) === undefined
27
+ ? [invalid(path, `"${resourceName}" is no Resource of the Core Contract`)]
28
+ : [];
29
+ });
30
+ }
31
+ function pathFault(core, path) {
32
+ const [resourceName, ...segments] = path;
33
+ let resource = resourceName === undefined ? undefined : lookupResource(core, resourceName);
34
+ if (resource === undefined) {
35
+ return `"${resourceName ?? ""}" is no Resource of the Core Contract`;
36
+ }
37
+ let current = resourceName;
38
+ for (let index = 0; index < segments.length; index += 1) {
39
+ const segment = segments[index];
40
+ const field = lookupField(resource, segment);
41
+ if (field === undefined) {
42
+ return `"${segment}" is no field or relation of Resource "${current}"`;
43
+ }
44
+ const filters = field.capabilities.filter ?? [];
45
+ if (field.kind === "scalar") {
46
+ if (filters.length === 0) {
47
+ return `field "${current}.${segment}" cannot be filtered`;
48
+ }
49
+ const operator = segments[index + 1];
50
+ if (operator !== undefined && !filters.includes(operator)) {
51
+ return `field "${current}.${segment}" has no filter operator "${operator}"`;
52
+ }
53
+ if (index + 2 < segments.length) {
54
+ return `the path goes on past scalar field "${current}.${segment}"`;
55
+ }
56
+ return undefined;
57
+ }
58
+ const target = lookupResource(core, field.target);
59
+ if (target === undefined) {
60
+ return `relation "${current}.${segment}" leads to no Resource of the Core Contract`;
61
+ }
62
+ if (field.cardinality === "many") {
63
+ const operator = segments[index + 1];
64
+ if (operator === undefined || !TO_MANY_OPERATORS.has(operator)) {
65
+ return `to-many relation "${current}.${segment}" must be followed by some, every or none`;
66
+ }
67
+ if (!filters.includes(operator)) {
68
+ return `relation "${current}.${segment}" has no "${operator}" filter`;
69
+ }
70
+ index += 1;
71
+ }
72
+ else if (index + 1 === segments.length) {
73
+ return filters.includes("null")
74
+ ? undefined
75
+ : `relation "${current}.${segment}" has no null filter`;
76
+ }
77
+ else if (!filters.includes("match")) {
78
+ return `relation "${current}.${segment}" cannot filter its target directly`;
79
+ }
80
+ resource = target;
81
+ current = field.target;
82
+ }
83
+ return `the path ends at Resource "${current}", not at a field`;
84
+ }
85
+ function pathDiagnostics(core, policy) {
86
+ const diagnostics = [];
87
+ for (const [rule, list] of Object.entries(policy.paths)) {
88
+ const at = `${policy.at}.paths.${rule}`;
89
+ for (const path of list) {
90
+ const fault = pathFault(core, path);
91
+ if (fault !== undefined) {
92
+ diagnostics.push(invalid(at, `rule "${rule}" filters on "${path.join(".")}": ${fault}`));
93
+ }
94
+ }
95
+ }
96
+ return diagnostics;
97
+ }
98
+ function unheldDiagnostics(model, { at, unheld }) {
99
+ return unheld.flatMap((resourceName) => Object.hasOwn(model.resources, resourceName)
100
+ ? []
101
+ : [
102
+ invalid(`${at}.unheld`, `"${resourceName}" is no Resource of the data source's model`),
103
+ ]);
104
+ }
105
+ export function policyStartupDiagnostics(core, policy, model) {
106
+ return [
107
+ ...governedDiagnostics(core, policy),
108
+ ...pathDiagnostics(core, policy),
109
+ ...afterCheckDiagnostics(core, policy),
110
+ ...unheldDiagnostics(model, policy),
111
+ ];
112
+ }
@@ -0,0 +1,39 @@
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 } from "../runtime/computed-read-runtime.js";
5
+ import type { AvOperationFailureResponse } from "../runtime/operation-response.js";
6
+ import { type PolicyRead } from "./policy-read.runtime.js";
7
+ import type { PolicySession } from "./policy-session.js";
8
+ /** A write's policy: what the adapter runs, and how a unique write's miss answers. */
9
+ export type PolicyWrite = PolicyRead & {
10
+ /** A unique write that matched nothing: `A4002` for a row the caller can read, `A2003` otherwise. */
11
+ readonly miss?: () => Promise<AvOperationFailureResponse>;
12
+ /**
13
+ * A write holding nested writes or links whose adapter call failed with `error`:
14
+ * what the failure answers instead, or `undefined` when it stands. A unique
15
+ * update's miss when its row is outside the scopes (an adapter may fail the nested
16
+ * write there instead of answering the miss); the refusal when a row a to-one
17
+ * `$update` was held to changed before the call, or when the adapter refused a
18
+ * link held to its target's read scope.
19
+ */
20
+ readonly failed?: (error: unknown) => Promise<AvOperationFailureResponse | undefined>;
21
+ };
22
+ /**
23
+ * Whether a write the policy checks needs an interactive transaction to roll
24
+ * it back in. Where it has none, {@link enforceWritePolicy} refuses exactly
25
+ * these writes before they run. `planned` plans the request's read, asked only
26
+ * when the answer depends on the rows it returns.
27
+ */
28
+ export declare function writeNeedsTransaction(session: PolicySession, core: AvContractCore, request: AvOperationIdentity & {
29
+ readonly resource: string;
30
+ }, planned: () => PlannedReadOperation, adapterArguments: unknown): Promise<boolean>;
31
+ /**
32
+ * One write's policy (see the module comment): the refusal, the answer when it
33
+ * reaches no row, or what the adapter runs and how its answer is checked.
34
+ * `inTransaction` says whether the write runs in an interactive transaction a
35
+ * failed check rolls back.
36
+ */
37
+ export declare function enforceWritePolicy(session: PolicySession, core: AvContractCore, model: AvAdapterModel, request: AvOperationIdentity & {
38
+ readonly resource: string;
39
+ }, plan: PlannedReadOperation, adapterArguments: unknown, inTransaction: boolean): Promise<PolicyWrite>;