@aventara/core 1.0.0-rc.0 → 1.0.0-rc.1

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 (242) hide show
  1. package/CHANGELOG.md +1140 -0
  2. package/README.md +224 -57
  3. package/dist/adapters/adapter-discovery-diagnostic.d.ts +4 -4
  4. package/dist/adapters/adapter-field.d.ts +11 -11
  5. package/dist/adapters/adapter-identifier.d.ts +6 -6
  6. package/dist/adapters/adapter-model.d.ts +9 -9
  7. package/dist/adapters/adapter-operation-executor.interface.d.ts +4 -4
  8. package/dist/adapters/adapter-operation.d.ts +3 -3
  9. package/dist/adapters/adapter-resource.d.ts +18 -9
  10. package/dist/adapters/adapter-sql-statement.d.ts +19 -0
  11. package/dist/adapters/adapter-transaction-executor.interface.d.ts +19 -0
  12. package/dist/adapters/adapter.interface.d.ts +49 -29
  13. package/dist/avz/avz-check.factory.d.ts +83 -0
  14. package/dist/avz/avz-check.factory.js +116 -0
  15. package/dist/avz/avz-check.registry.d.ts +35 -0
  16. package/dist/avz/avz-check.registry.js +9 -0
  17. package/dist/avz/avz.d.ts +203 -0
  18. package/dist/{json → avz}/avz.js +150 -8
  19. package/dist/{json → avz}/index.d.ts +2 -1
  20. package/dist/{json → avz}/json-shape.registry.d.ts +7 -7
  21. package/dist/{json → avz}/zod-issue.translator.d.ts +8 -2
  22. package/dist/{json → avz}/zod-issue.translator.js +7 -0
  23. package/dist/compiler/contract-compiler-diagnostic.d.ts +31 -5
  24. package/dist/compiler/contract.compiler.d.ts +18 -18
  25. package/dist/compiler/contract.compiler.js +548 -441
  26. package/dist/compiler/create-requirements.projector.d.ts +49 -0
  27. package/dist/compiler/create-requirements.projector.js +423 -0
  28. package/dist/compiler/field-validator.compiler.d.ts +47 -0
  29. package/dist/compiler/field-validator.compiler.js +238 -0
  30. package/dist/config/any-framework-config.d.ts +19 -12
  31. package/dist/config/config-diagnostic.d.ts +11 -4
  32. package/dist/config/config-layer.d.ts +15 -6
  33. package/dist/config/config.resolver.d.ts +74 -31
  34. package/dist/config/config.resolver.js +108 -44
  35. package/dist/config/config.validator.d.ts +7 -7
  36. package/dist/config/config.validator.js +581 -72
  37. package/dist/config/define-resource.d.ts +57 -0
  38. package/dist/config/define-resource.js +14 -0
  39. package/dist/config/effective-fields.d.ts +19 -23
  40. package/dist/config/entrypoint.normalizer.d.ts +1 -1
  41. package/dist/config/exact-framework-config.d.ts +280 -0
  42. package/dist/config/field-compute.resolver.d.ts +19 -0
  43. package/dist/config/field-compute.resolver.js +115 -0
  44. package/dist/config/field-pipeline-config.d.ts +47 -0
  45. package/dist/config/forced-operation.validator.d.ts +10 -0
  46. package/dist/config/forced-operation.validator.js +66 -0
  47. package/dist/config/framework-config.d.ts +110 -39
  48. package/dist/config/framework-exemptions-config.d.ts +91 -0
  49. package/dist/config/framework-exemptions-config.js +1 -0
  50. package/dist/config/framework-field-definition.d.ts +78 -0
  51. package/dist/config/framework-field-definition.js +28 -0
  52. package/dist/config/framework-resources-config.d.ts +116 -0
  53. package/dist/config/framework-resources-config.js +1 -0
  54. package/dist/config/pipeline-config.d.ts +56 -20
  55. package/dist/config/pipeline-tree.resolver.d.ts +79 -0
  56. package/dist/config/pipeline-tree.resolver.js +220 -0
  57. package/dist/config/stage-type.eraser.d.ts +65 -0
  58. package/dist/config/stage-type.eraser.js +1 -0
  59. package/dist/contracts/capability.d.ts +11 -11
  60. package/dist/contracts/client-contract-structure.validator.d.ts +323 -28
  61. package/dist/contracts/client-contract-structure.validator.js +152 -8
  62. package/dist/contracts/contract-canonicalizer.d.ts +19 -3
  63. package/dist/contracts/contract-canonicalizer.js +141 -28
  64. package/dist/contracts/contract-hash.d.ts +12 -7
  65. package/dist/contracts/contract-hash.js +3 -2
  66. package/dist/contracts/contract-limits.d.ts +4 -1
  67. package/dist/contracts/contract-limits.js +1 -0
  68. package/dist/contracts/contract-lookup.d.ts +5 -5
  69. package/dist/contracts/contract-protocol.d.ts +4 -4
  70. package/dist/contracts/contract.d.ts +24 -16
  71. package/dist/contracts/create-requirements.d.ts +35 -0
  72. package/dist/contracts/create-requirements.js +1 -0
  73. package/dist/contracts/enum-definition.d.ts +1 -1
  74. package/dist/contracts/field-check.d.ts +101 -0
  75. package/dist/contracts/field-check.js +1 -0
  76. package/dist/contracts/field.d.ts +52 -42
  77. package/dist/contracts/identifier.d.ts +4 -4
  78. package/dist/contracts/json-shape.d.ts +12 -1
  79. package/dist/contracts/lifecycle-trait.d.ts +2 -2
  80. package/dist/contracts/resource-operations.d.ts +28 -28
  81. package/dist/contracts/resource.d.ts +15 -9
  82. package/dist/contracts/scalar-value-type.d.ts +15 -15
  83. package/dist/contracts/scalar.d.ts +3 -3
  84. package/dist/contracts/transaction-capability.d.ts +1 -1
  85. package/dist/contracts/wire-scalar-format.d.ts +4 -4
  86. package/dist/framework/contract-view.d.ts +213 -147
  87. package/dist/framework/framework-construction.error.d.ts +10 -10
  88. package/dist/framework/framework-construction.error.js +2 -2
  89. package/dist/framework/framework.d.ts +47 -23
  90. package/dist/framework/framework.factory.d.ts +8 -6
  91. package/dist/framework/framework.factory.js +40 -20
  92. package/dist/framework/operation-facade.d.ts +123 -52
  93. package/dist/framework/operation-facade.js +9 -1
  94. package/dist/index.d.ts +55 -50
  95. package/dist/index.js +6 -6
  96. package/dist/internal.d.ts +15 -10
  97. package/dist/internal.js +3 -1
  98. package/dist/operations/operation-arguments.d.ts +267 -202
  99. package/dist/operations/operation-call.d.ts +27 -28
  100. package/dist/operations/operation-identity.d.ts +10 -10
  101. package/dist/operations/operation-result.d.ts +21 -21
  102. package/dist/operations/operation-validator-core.d.ts +6 -6
  103. package/dist/operations/operation-validator-core.js +13 -2
  104. package/dist/operations/operation-validator.d.ts +9 -9
  105. package/dist/operations/projection-scope.resolver.d.ts +6 -6
  106. package/dist/operations/structural-limits.walker.d.ts +6 -6
  107. package/dist/operations/structural-limits.walker.js +145 -0
  108. package/dist/operations/validation-code.d.ts +11 -6
  109. package/dist/operations/validation-code.js +4 -0
  110. package/dist/operations/write-scope.resolver.d.ts +6 -6
  111. package/dist/protocol/absent-route.responder.d.ts +13 -12
  112. package/dist/protocol/absent-route.responder.js +19 -19
  113. package/dist/protocol/contract-document.responder.d.ts +3 -3
  114. package/dist/protocol/contract-document.responder.js +2 -2
  115. package/dist/protocol/http-request.decoder.d.ts +17 -13
  116. package/dist/protocol/http-request.decoder.js +10 -10
  117. package/dist/protocol/http-request.reader.d.ts +3 -3
  118. package/dist/protocol/http-response.encoder.d.ts +4 -4
  119. package/dist/protocol/http-response.encoder.js +94 -55
  120. package/dist/protocol/http-status.table.d.ts +2 -2
  121. package/dist/protocol/operation-response.guard.d.ts +2 -2
  122. package/dist/protocol/protocol-surface.d.ts +4 -4
  123. package/dist/protocol/protocol.binder.d.ts +50 -27
  124. package/dist/protocol/protocol.binder.js +12 -9
  125. package/dist/protocol/protocol.handler.d.ts +22 -13
  126. package/dist/protocol/protocol.handler.js +44 -9
  127. package/dist/protocol/unmounted-route.error.d.ts +4 -4
  128. package/dist/protocol/unmounted-route.error.js +2 -2
  129. package/dist/runtime/caller-data.copier.d.ts +5 -5
  130. package/dist/runtime/caller-data.copier.js +13 -13
  131. package/dist/runtime/caller-data.door.d.ts +6 -6
  132. package/dist/runtime/caller-data.door.js +6 -4
  133. package/dist/runtime/computed-read-runtime.d.ts +7 -6
  134. package/dist/runtime/computed-read-runtime.js +61 -48
  135. package/dist/runtime/computed-value.error.d.ts +6 -6
  136. package/dist/runtime/computed-value.error.js +3 -3
  137. package/dist/runtime/context-executor.runtime.d.ts +59 -0
  138. package/dist/runtime/context-executor.runtime.js +275 -0
  139. package/dist/runtime/create-requirement.d.ts +10 -81
  140. package/dist/runtime/create-requirement.js +80 -57
  141. package/dist/runtime/decimal.d.ts +2 -2
  142. package/dist/runtime/decimal.js +1 -1
  143. package/dist/runtime/diagnostics-reporter.d.ts +53 -27
  144. package/dist/runtime/diagnostics-reporter.js +24 -0
  145. package/dist/runtime/error-boundary.d.ts +65 -8
  146. package/dist/runtime/error-boundary.js +45 -19
  147. package/dist/runtime/execution-options.d.ts +10 -2
  148. package/dist/runtime/field-check.evaluator.d.ts +29 -0
  149. package/dist/runtime/field-check.evaluator.js +521 -0
  150. package/dist/runtime/field-compute.d.ts +64 -0
  151. package/dist/runtime/field-compute.js +1 -0
  152. package/dist/runtime/field-stage.plan.d.ts +15 -0
  153. package/dist/runtime/field-stage.plan.js +27 -0
  154. package/dist/runtime/field-stage.runtime.d.ts +35 -0
  155. package/dist/runtime/field-stage.runtime.js +143 -0
  156. package/dist/runtime/field-validator.runtime.d.ts +11 -0
  157. package/dist/runtime/field-validator.runtime.js +87 -0
  158. package/dist/runtime/framework-error-class.table.d.ts +33 -0
  159. package/dist/runtime/framework-error-class.table.js +25 -0
  160. package/dist/runtime/framework-error.d.ts +68 -8
  161. package/dist/runtime/framework-error.factory.d.ts +4 -0
  162. package/dist/runtime/framework-error.factory.js +15 -0
  163. package/dist/runtime/framework-error.js +23 -2
  164. package/dist/runtime/interceptor-next.error.d.ts +5 -0
  165. package/dist/runtime/interceptor-next.error.js +6 -0
  166. package/dist/runtime/json-value.d.ts +3 -3
  167. package/dist/runtime/level-lifecycle.runtime.d.ts +25 -0
  168. package/dist/runtime/level-lifecycle.runtime.js +42 -0
  169. package/dist/runtime/misuse-reporter.d.ts +12 -12
  170. package/dist/runtime/mutation-planner.d.ts +19 -10
  171. package/dist/runtime/mutation-planner.js +54 -30
  172. package/dist/runtime/nested-write.walker.d.ts +32 -0
  173. package/dist/runtime/nested-write.walker.js +73 -0
  174. package/dist/runtime/operation-request.d.ts +33 -6
  175. package/dist/runtime/operation-response.d.ts +17 -17
  176. package/dist/runtime/operation-runtime.d.ts +113 -25
  177. package/dist/runtime/operation-runtime.js +395 -74
  178. package/dist/runtime/outcome-mapper.d.ts +16 -5
  179. package/dist/runtime/outcome-mapper.js +23 -6
  180. package/dist/runtime/output-validator.d.ts +10 -10
  181. package/dist/runtime/output-validator.js +3 -3
  182. package/dist/runtime/owned-execution.registry.d.ts +6 -6
  183. package/dist/runtime/pipe-output.validator.d.ts +33 -18
  184. package/dist/runtime/pipe-output.validator.js +12 -12
  185. package/dist/runtime/pipeline.d.ts +196 -34
  186. package/dist/runtime/readonly-value.d.ts +32 -0
  187. package/dist/runtime/readonly-value.js +141 -0
  188. package/dist/runtime/request-envelope.validator.d.ts +3 -3
  189. package/dist/runtime/request-envelope.validator.js +12 -7
  190. package/dist/runtime/request-id.d.ts +1 -1
  191. package/dist/runtime/request-state.d.ts +14 -0
  192. package/dist/runtime/request-state.js +1 -0
  193. package/dist/runtime/request-state.resolver.d.ts +12 -0
  194. package/dist/runtime/request-state.resolver.js +20 -0
  195. package/dist/runtime/scalar-runtime.d.ts +31 -16
  196. package/dist/runtime/scalar-runtime.js +16 -18
  197. package/dist/transactions/deferred-operation.d.ts +35 -20
  198. package/dist/transactions/operation-arguments-encoder.d.ts +3 -3
  199. package/dist/transactions/operation-arguments-encoder.js +2 -2
  200. package/dist/transactions/sql-statement.d.ts +77 -0
  201. package/dist/transactions/sql-statement.js +47 -0
  202. package/dist/transactions/sql-statement.validator.d.ts +44 -0
  203. package/dist/transactions/sql-statement.validator.js +134 -0
  204. package/dist/transactions/transaction-envelope.d.ts +4 -4
  205. package/dist/transactions/transaction-integrity-error.d.ts +6 -6
  206. package/dist/transactions/transaction-integrity-error.js +5 -2
  207. package/dist/transactions/transaction-plan-assembler.d.ts +21 -11
  208. package/dist/transactions/transaction-plan-assembler.js +148 -34
  209. package/dist/transactions/transaction-plan-validator.d.ts +29 -20
  210. package/dist/transactions/transaction-plan-validator.js +70 -38
  211. package/dist/transactions/transaction-reference-collector.d.ts +5 -5
  212. package/dist/transactions/transaction-reference-resolver.d.ts +19 -2
  213. package/dist/transactions/transaction-reference-resolver.js +26 -14
  214. package/dist/transactions/transaction-reference-validator.d.ts +34 -4
  215. package/dist/transactions/transaction-reference-validator.js +58 -26
  216. package/dist/transactions/transaction-reference.d.ts +13 -13
  217. package/dist/transactions/transaction-request.d.ts +18 -5
  218. package/dist/transactions/transaction-response.d.ts +6 -6
  219. package/dist/transactions/transaction-runtime.d.ts +8 -7
  220. package/dist/transactions/transaction-runtime.js +166 -50
  221. package/dist/transactions/transaction-settlement.runtime.d.ts +51 -0
  222. package/dist/transactions/transaction-settlement.runtime.js +120 -0
  223. package/dist/transactions/transaction-slot-path-error.d.ts +4 -4
  224. package/dist/transactions/transaction-slot-path-error.js +2 -2
  225. package/dist/transactions/transaction-slot-writer.js +2 -2
  226. package/dist/transactions/transaction-stage.runtime.d.ts +81 -0
  227. package/dist/transactions/transaction-stage.runtime.js +123 -0
  228. package/package.json +6 -5
  229. package/src/runtime/field-check.evaluator.ts +720 -0
  230. package/dist/config/define-fields.d.ts +0 -42
  231. package/dist/config/define-fields.js +0 -3
  232. package/dist/config/framework-behaviors-config.d.ts +0 -30
  233. package/dist/config/framework-fields-config.d.ts +0 -42
  234. package/dist/config/framework-restrictions-config.d.ts +0 -142
  235. package/dist/json/avz.d.ts +0 -96
  236. package/dist/runtime/field-behavior.d.ts +0 -48
  237. /package/dist/{config/framework-behaviors-config.js → adapters/adapter-sql-statement.js} +0 -0
  238. /package/dist/{config/framework-fields-config.js → adapters/adapter-transaction-executor.interface.js} +0 -0
  239. /package/dist/{json → avz}/index.js +0 -0
  240. /package/dist/{json → avz}/json-shape.registry.js +0 -0
  241. /package/dist/config/{framework-restrictions-config.js → exact-framework-config.js} +0 -0
  242. /package/dist/{runtime/field-behavior.js → config/field-pipeline-config.js} +0 -0
@@ -1,12 +1,14 @@
1
- import { PROJECTION_SCOPES, WRITE_SCOPES, } from "../contracts/capability.js";
1
+ import { bindShapedField, jsonShapeOf } from "../avz/json-shape.registry.js";
2
+ import { PROJECTION_SCOPES, WRITE_SCOPES } from "../contracts/capability.js";
2
3
  import { validateClientContractStructure } from "../contracts/client-contract-structure.validator.js";
3
- import { canonicalizeContract } from "../contracts/contract-canonicalizer.js";
4
- import { computeClientContractHash, } from "../contracts/contract-hash.js";
4
+ import { canonicalizeContract, canonicalizeContractMembers, canonicalizeJson, joinCanonicalContractMembers, recordCanonicalContractText, } from "../contracts/contract-canonicalizer.js";
5
+ import { hashCanonicalContractText, } from "../contracts/contract-hash.js";
5
6
  import { DEFAULT_CONTRACT_LIMITS, } from "../contracts/contract-limits.js";
6
7
  import { AVENTARA_PROTOCOL_VERSION } from "../contracts/contract-protocol.js";
7
- import { advertisedDirectives, advertisedDirectivesAt, advertisedReducers, advertisedReducersAt, holdsAt, holdsInAnyScope, } from "../contracts/field.js";
8
+ import { advertisedDirectives, advertisedReducers, holdsInAnyScope, } from "../contracts/field.js";
8
9
  import { isOperationVariantAvailable, } from "../contracts/resource-operations.js";
9
- import { bindShapedField, jsonShapeOf } from "../json/json-shape.registry.js";
10
+ import { checkCreateRequirements, checkHiddenRequirements, projectCreateRequirements, } from "./create-requirements.projector.js";
11
+ import { applyValidators, bindFieldValidators, checkLayerValidators, compileValidatorTable, } from "./field-validator.compiler.js";
10
12
  const UNIQUE_VARIANTS = [
11
13
  ["find", "unique"],
12
14
  ["update", "unique"],
@@ -105,8 +107,8 @@ function createBaseline(model) {
105
107
  : { referenceWrites: model.referenceWrites }),
106
108
  };
107
109
  }
108
- function scalarFieldPath(scope, resource, field) {
109
- return `${scope}.resources.${resource}.fields.${field}`;
110
+ function definitionPath(resource, field) {
111
+ return `root.resources.${resource}.fields.${field}.definition`;
110
112
  }
111
113
  function structuralValueMatches(field, config, key) {
112
114
  if (config[key] === undefined) {
@@ -114,6 +116,13 @@ function structuralValueMatches(field, config, key) {
114
116
  }
115
117
  return JSON.stringify(field[key]) === JSON.stringify(config[key]);
116
118
  }
119
+ function configuredMember(capabilities, key) {
120
+ const value = capabilities?.[key];
121
+ if (value === undefined) {
122
+ return {};
123
+ }
124
+ return { [key]: Array.isArray(value) ? [...value] : cloneValue(value) };
125
+ }
117
126
  function addConfiguredScalarField(config, path, diagnostics) {
118
127
  if (config.json !== undefined) {
119
128
  refuseJsonShape(path, "is not a field of the adapter model", diagnostics);
@@ -128,7 +137,7 @@ function addConfiguredScalarField(config, path, diagnostics) {
128
137
  if (configuredLifecycle === undefined ||
129
138
  !configuredLifecycle.includes("VIRTUAL") ||
130
139
  configuredLifecycle.some((trait) => trait !== "VIRTUAL")) {
131
- diagnostics.push(diagnostic("COMPILER_INVALID_LIFECYCLE", `${path}.lifecycle`, "framework-added scalar field must declare only VIRTUAL lifecycle; generated lifecycle comes from adapters and computed lifecycle comes from behaviors"));
140
+ diagnostics.push(diagnostic("COMPILER_INVALID_LIFECYCLE", `${path}.lifecycle`, "framework-added scalar field must declare only VIRTUAL lifecycle; generated lifecycle comes from adapters and computed lifecycle comes from compute"));
132
141
  return undefined;
133
142
  }
134
143
  return {
@@ -140,89 +149,14 @@ function addConfiguredScalarField(config, path, diagnostics) {
140
149
  lifecycle: [...(config.lifecycle ?? [])],
141
150
  capabilities: {
142
151
  select: config.capabilities?.select ?? true,
143
- ...(config.capabilities?.filter !== undefined
144
- ? { filter: [...config.capabilities.filter] }
145
- : {}),
146
- ...(config.capabilities?.order !== undefined
147
- ? { order: config.capabilities.order }
148
- : {}),
149
- ...(config.capabilities?.orderNulls !== undefined
150
- ? { orderNulls: config.capabilities.orderNulls }
151
- : {}),
152
- ...(config.capabilities?.create !== undefined
153
- ? { create: [...config.capabilities.create] }
154
- : {}),
155
- ...(config.capabilities?.update !== undefined
156
- ? { update: [...config.capabilities.update] }
157
- : {}),
152
+ ...configuredMember(config.capabilities, "filter"),
153
+ ...configuredMember(config.capabilities, "order"),
154
+ ...configuredMember(config.capabilities, "orderNulls"),
155
+ ...configuredMember(config.capabilities, "create"),
156
+ ...configuredMember(config.capabilities, "update"),
158
157
  },
159
158
  };
160
159
  }
161
- export function capabilityAddsBaseline(fieldKind, baseline, key, configured) {
162
- const axis = capabilityAxisFor(fieldKind, key);
163
- if (axis === undefined) {
164
- return false;
165
- }
166
- const baselineValue = baseline[key];
167
- switch (axis.kind) {
168
- case "limit":
169
- return baselineValue !== undefined && configured !== baselineValue;
170
- case "flag":
171
- return configured === true && baselineValue !== true;
172
- case "tokens": {
173
- if (!isTokenList(configured)) {
174
- return false;
175
- }
176
- const advertised = isTokenList(baselineValue) ? baselineValue : [];
177
- return configured.some((token) => !advertised.includes(token));
178
- }
179
- case "scopes": {
180
- const claimed = configured === true
181
- ? axis.axis
182
- : isTokenList(configured)
183
- ? configured
184
- : undefined;
185
- return (claimed?.some((position) => axis.axis.includes(position) &&
186
- !holdsAt(baselineValue, position)) === true);
187
- }
188
- case "scopedTokens": {
189
- const claim = scopedTokenClaim(axis, configured);
190
- if (claim === undefined) {
191
- return false;
192
- }
193
- return claim.positions.some((position) => {
194
- if (!axis.axis.includes(position)) {
195
- return false;
196
- }
197
- const advertised = advertisedTokensAt(axis.tokenKey, baselineValue, position);
198
- return (advertised === undefined ||
199
- claim.tokens.some((token) => !advertised.includes(token)));
200
- });
201
- }
202
- }
203
- }
204
- function scopedTokenClaim(axis, configured) {
205
- if (isTokenList(configured)) {
206
- return { tokens: configured, positions: axis.axis };
207
- }
208
- if (isRecord(configured) &&
209
- isTokenList(configured[axis.tokenKey]) &&
210
- isTokenList(configured.scopes)) {
211
- return {
212
- tokens: configured[axis.tokenKey],
213
- positions: configured.scopes,
214
- };
215
- }
216
- return undefined;
217
- }
218
- function advertisedTokensAt(tokenKey, baselineValue, position) {
219
- if (!isTokenList(baselineValue) && !isRecord(baselineValue)) {
220
- return undefined;
221
- }
222
- return tokenKey === "directives"
223
- ? advertisedDirectivesAt(baselineValue, position)
224
- : advertisedReducersAt(baselineValue, position);
225
- }
226
160
  function fieldLabel(path) {
227
161
  const [, resource, , field] = path.split(".").slice(1);
228
162
  return `${resource}.${field}`;
@@ -238,7 +172,7 @@ function applyJsonShape(field, baselineField, shape, path, diagnostics) {
238
172
  }
239
173
  const runtime = jsonShapeOf(shape);
240
174
  if (runtime === undefined) {
241
- diagnostics.push(diagnostic("COMPILER_INVALID_JSON_SHAPE", `${path}.json`, `field "${fieldLabel(path)}": "json" must be a shape built with AvZ from "@aventara/core/json"; a Zod schema or any other value is not accepted`));
175
+ diagnostics.push(diagnostic("COMPILER_INVALID_JSON_SHAPE", `${path}.json`, `field "${fieldLabel(path)}": "json" must be a shape built with AvZ from "@aventara/core/avz"; a Zod schema or any other value is not accepted`));
242
176
  return;
243
177
  }
244
178
  if (runtime.validate(null).length === 0 ||
@@ -255,7 +189,7 @@ function applyExistingScalarConfig(field, baselineField, config, path, diagnosti
255
189
  }
256
190
  }
257
191
  if (config.format !== undefined) {
258
- field.format = config.format;
192
+ diagnostics.push(diagnostic("COMPILER_STRUCTURAL_FIELD_OVERRIDE", `${path}.format`, "a discovered field's format is the adapter's and cannot be set; format is for configured (VIRTUAL) fields"));
259
193
  }
260
194
  if (config.json !== undefined) {
261
195
  applyJsonShape(field, baselineField, config.json, path, diagnostics);
@@ -264,95 +198,39 @@ function applyExistingScalarConfig(field, baselineField, config, path, diagnosti
264
198
  diagnostics.push(diagnostic("COMPILER_INVALID_LIFECYCLE", `${path}.lifecycle`, "existing adapter field lifecycle cannot be redefined by framework field configuration"));
265
199
  }
266
200
  if (config.capabilities !== undefined) {
267
- const current = field.capabilities;
268
- const baseline = baselineField.capabilities;
269
- for (const [key, value] of Object.entries(config.capabilities)) {
270
- if (capabilityAddsBaseline(baselineField.kind, baseline, key, value)) {
271
- diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_CAPABILITY", `${path}.capabilities.${key}`, `capability "${key}" is not available in the canonical baseline`));
272
- continue;
273
- }
274
- current[key] = cloneValue(value);
275
- }
276
- }
277
- }
278
- function applyFrameworkOwnedScalarConfig(field, config, path, diagnostics) {
279
- for (const key of ["type", "nullable", "list"]) {
280
- if (config[key] !== undefined) {
281
- field[key] = cloneValue(config[key]);
282
- }
283
- }
284
- if (config.json !== undefined) {
285
- refuseJsonShape(path, "is not a field of the adapter model", diagnostics);
286
- }
287
- if (config.format !== undefined) {
288
- field.format = config.format;
289
- }
290
- if (config.lifecycle !== undefined) {
291
- const configuredLifecycle = config.lifecycle;
292
- if (!configuredLifecycle.includes("VIRTUAL") ||
293
- configuredLifecycle.some((trait) => trait !== "VIRTUAL")) {
294
- diagnostics.push(diagnostic("COMPILER_INVALID_LIFECYCLE", `${path}.lifecycle`, "framework-owned scalar field may declare only VIRTUAL lifecycle; computed lifecycle comes from behaviors"));
295
- }
296
- else {
297
- field.lifecycle = [...configuredLifecycle];
298
- }
299
- }
300
- if (config.capabilities !== undefined) {
301
- const current = field.capabilities;
302
- for (const [key, value] of Object.entries(config.capabilities)) {
303
- current[key] = cloneValue(value);
304
- }
201
+ diagnostics.push(diagnostic("COMPILER_STRUCTURAL_FIELD_OVERRIDE", `${path}.capabilities`, "a discovered field's capabilities are the adapter's and cannot be set; narrow what it exposes with exemptions"));
305
202
  }
306
203
  }
307
- function applyFieldConfig(model, projection, scope, config, diagnostics, allowRestrictedMissing = false) {
308
- if (config === undefined) {
309
- return;
310
- }
204
+ function applyFieldDefinitions(model, projection, definitions, diagnostics) {
311
205
  const providerBaseline = createBaseline(model);
312
- for (const [resourceName, resourceConfig] of Object.entries(config)) {
313
- if (resourceConfig === undefined) {
314
- continue;
315
- }
206
+ for (const [resourceName, resourceDefinitions] of Object.entries(definitions)) {
316
207
  const resource = projection.resources[resourceName];
317
208
  const providerResource = providerBaseline.resources[resourceName];
318
209
  if (resource === undefined || providerResource === undefined) {
319
- if (allowRestrictedMissing && providerResource !== undefined) {
320
- continue;
321
- }
322
- diagnostics.push(diagnostic("COMPILER_UNKNOWN_RESOURCE", `${scope}.resources.${resourceName}`, `resource "${resourceName}" is not present in the canonical adapter model`));
323
210
  continue;
324
211
  }
325
- for (const [fieldName, rawConfig] of Object.entries(resourceConfig)) {
326
- const fieldConfig = rawConfig;
327
- const path = scalarFieldPath(scope, resourceName, fieldName);
212
+ for (const [fieldName, definition] of Object.entries(resourceDefinitions)) {
213
+ const path = definitionPath(resourceName, fieldName);
328
214
  const existing = resource.fields[fieldName];
329
215
  const providerField = providerResource.fields[fieldName];
330
216
  if (existing === undefined) {
331
- if (allowRestrictedMissing && providerField !== undefined) {
332
- continue;
333
- }
334
- const added = addConfiguredScalarField(fieldConfig, path, diagnostics);
217
+ const added = addConfiguredScalarField(definition, path, diagnostics);
335
218
  if (added !== undefined) {
336
219
  resource.fields[fieldName] = added;
337
220
  }
338
221
  continue;
339
222
  }
340
223
  if (existing.kind === "relation") {
341
- diagnostics.push(diagnostic("COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED", path, "framework field augmentation applies only to scalar fields"));
224
+ diagnostics.push(diagnostic("COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED", path, "a relation field takes no definition"));
342
225
  continue;
343
226
  }
344
227
  if (providerField?.kind === "scalar") {
345
- applyExistingScalarConfig(existing, providerField, fieldConfig, path, diagnostics);
346
- continue;
228
+ applyExistingScalarConfig(existing, providerField, definition, path, diagnostics);
347
229
  }
348
- applyFrameworkOwnedScalarConfig(existing, fieldConfig, path, diagnostics);
349
230
  }
350
231
  }
351
232
  }
352
- function isActiveBehaviorStage(value) {
353
- return value !== undefined && value !== false;
354
- }
355
- function readBehaviorDependencies(value) {
233
+ function readComputeDependencies(value) {
356
234
  if (!isRecord(value) || typeof value.compute !== "function") {
357
235
  return undefined;
358
236
  }
@@ -360,54 +238,114 @@ function readBehaviorDependencies(value) {
360
238
  ? value.dependsOn.filter((entry) => typeof entry === "string")
361
239
  : undefined;
362
240
  }
363
- function applyFieldBehavior(resource, fieldName, behavior, path, diagnostics, allowRestrictedMissing = false) {
364
- const field = resource.fields[fieldName];
365
- if (field === undefined || field.kind !== "scalar") {
366
- if (allowRestrictedMissing && field === undefined) {
367
- return;
241
+ function applyCompute(projection, compute) {
242
+ for (const [resourceName, fields] of Object.entries(compute)) {
243
+ const resource = projection.resources[resourceName];
244
+ if (resource === undefined) {
245
+ continue;
246
+ }
247
+ for (const [fieldName, fieldCompute] of Object.entries(fields)) {
248
+ const field = resource.fields[fieldName];
249
+ if (field?.kind !== "scalar") {
250
+ continue;
251
+ }
252
+ const lifecycle = field.lifecycle.filter((trait) => !COMPUTED_TRAITS.has(trait));
253
+ if (fieldCompute.create !== undefined) {
254
+ lifecycle.push("COMPUTED_ON_CREATE");
255
+ }
256
+ if (fieldCompute.update !== undefined) {
257
+ lifecycle.push("COMPUTED_ON_UPDATE");
258
+ }
259
+ if (fieldCompute.read !== undefined) {
260
+ lifecycle.push("COMPUTED_ON_READ");
261
+ }
262
+ field.lifecycle = lifecycle;
368
263
  }
369
- diagnostics.push(diagnostic("COMPILER_UNKNOWN_FIELD", path, `behavior field "${fieldName}" must resolve to a scalar field`));
370
- return;
371
- }
372
- const lifecycle = field.lifecycle.filter((trait) => !COMPUTED_TRAITS.has(trait));
373
- if (isActiveBehaviorStage(behavior.create)) {
374
- lifecycle.push("COMPUTED_ON_CREATE");
375
264
  }
376
- if (isActiveBehaviorStage(behavior.update)) {
377
- lifecycle.push("COMPUTED_ON_UPDATE");
265
+ }
266
+ function storedScalar(model, resource, field) {
267
+ const fields = Object.hasOwn(model.resources, resource)
268
+ ? model.resources[resource]?.fields
269
+ : undefined;
270
+ return (fields !== undefined &&
271
+ Object.hasOwn(fields, field) &&
272
+ fields[field]?.kind === "scalar");
273
+ }
274
+ function checkFieldCompute(model, resourceName, fieldName, field, compute, path, diagnostics) {
275
+ if (!isRecord(compute)) {
276
+ return;
378
277
  }
379
- if (isActiveBehaviorStage(behavior.read)) {
380
- lifecycle.push("COMPUTED_ON_READ");
278
+ if (field?.kind === "relation") {
279
+ diagnostics.push(diagnostic("COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED", path, `relation field "${fieldName}" takes write guards only; compute derives a scalar field's value`));
280
+ return;
381
281
  }
382
- field.lifecycle = lifecycle;
383
- for (const dependency of readBehaviorDependencies(behavior.read) ?? []) {
384
- const dependencyField = resource.fields[dependency];
385
- if (dependencyField === undefined || dependencyField.kind !== "scalar") {
386
- diagnostics.push(diagnostic("COMPILER_INVALID_BEHAVIOR_DEPENDENCY", `${path}.read.dependsOn`, `read behavior dependency "${dependency}" must reference a scalar field in the same Resource`));
282
+ for (const dependency of readComputeDependencies(compute.read) ?? []) {
283
+ if (!storedScalar(model, resourceName, dependency)) {
284
+ diagnostics.push(diagnostic("COMPILER_INVALID_COMPUTE_DEPENDENCY", `${path}.read.dependsOn`, `compute.read dependency "${dependency}" must name a scalar field the adapter stores on resource "${resourceName}"`));
387
285
  }
388
286
  }
389
287
  }
390
- function applyBehaviors(projection, scope, config, diagnostics, allowRestrictedMissing = false) {
391
- if (config === undefined) {
288
+ function checkResourceTree(model, baseline, scope, tree, definitions, diagnostics) {
289
+ if (!isRecord(tree)) {
392
290
  return;
393
291
  }
394
- for (const [resourceName, rawResourceConfig] of Object.entries(config)) {
395
- if (rawResourceConfig === undefined) {
292
+ for (const [resourceName, entry] of Object.entries(tree)) {
293
+ const resourcePath = `${scope}.resources.${resourceName}`;
294
+ const resource = baseline.resources[resourceName];
295
+ if (resource === undefined) {
296
+ diagnostics.push(diagnostic("COMPILER_UNKNOWN_RESOURCE", resourcePath, `resource "${resourceName}" is not present in the canonical adapter model`));
396
297
  continue;
397
298
  }
398
- const resource = projection.resources[resourceName];
399
- if (resource === undefined) {
400
- if (allowRestrictedMissing) {
401
- continue;
299
+ if (!isRecord(entry)) {
300
+ continue;
301
+ }
302
+ if (isRecord(entry.operations)) {
303
+ for (const [family, level] of Object.entries(entry.operations)) {
304
+ const familyPath = `${resourcePath}.operations.${family}`;
305
+ const variants = resource.operations[family];
306
+ if (!isRecord(variants)) {
307
+ diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", familyPath, `operation family "${family}" is not available for resource "${resourceName}"`));
308
+ continue;
309
+ }
310
+ if (!isRecord(level)) {
311
+ continue;
312
+ }
313
+ for (const variant of Object.keys(level)) {
314
+ if (variant === "pipelines" || variant === "forceTransaction") {
315
+ continue;
316
+ }
317
+ if (!isOperationVariantAvailable(variants[variant])) {
318
+ diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", `${familyPath}.${variant}`, `operation "${family}.${variant}" is not available for resource "${resourceName}"`));
319
+ }
320
+ }
402
321
  }
403
- diagnostics.push(diagnostic("COMPILER_UNKNOWN_RESOURCE", `${scope}.resources.${resourceName}`, `resource "${resourceName}" is not present in the canonical adapter model`));
322
+ }
323
+ if (!isRecord(entry.fields)) {
404
324
  continue;
405
325
  }
406
- for (const [fieldName, behavior] of Object.entries(rawResourceConfig.fields)) {
407
- if (behavior === undefined) {
326
+ for (const [fieldName, fieldEntry] of Object.entries(entry.fields)) {
327
+ const field = resource.fields[fieldName];
328
+ if (field === undefined &&
329
+ definitions[resourceName]?.[fieldName] === undefined) {
330
+ diagnostics.push(diagnostic("COMPILER_UNKNOWN_FIELD", `${resourcePath}.fields.${fieldName}`, `field "${fieldName}" is neither discovered by the adapter nor defined at the root`));
408
331
  continue;
409
332
  }
410
- applyFieldBehavior(resource, fieldName, behavior, `${scope}.resources.${resourceName}.fields.${fieldName}.behaviors`, diagnostics, allowRestrictedMissing);
333
+ if (!isRecord(fieldEntry) || !isRecord(fieldEntry.pipelines)) {
334
+ continue;
335
+ }
336
+ const pipelinesPath = `${resourcePath}.fields.${fieldName}.pipelines`;
337
+ if (field?.kind === "relation" &&
338
+ fieldEntry.pipelines.pipes !== undefined) {
339
+ diagnostics.push(diagnostic("COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED", `${pipelinesPath}.pipes`, `relation field "${fieldName}" takes write guards only; a field pipe transforms a scalar field's value`));
340
+ }
341
+ checkFieldCompute(model, resourceName, fieldName, field, fieldEntry.pipelines.compute, `${pipelinesPath}.compute`, diagnostics);
342
+ }
343
+ }
344
+ }
345
+ function checkReservedResourceNames(model, diagnostics) {
346
+ for (const resourceName of Object.keys(model.resources)) {
347
+ if (resourceName.startsWith("$")) {
348
+ diagnostics.push(diagnostic("COMPILER_RESERVED_RESOURCE_NAME", `adapter.model.resources.${resourceName}`, `resource "${resourceName}" starts with "$", which is reserved for the framework's own names`));
411
349
  }
412
350
  }
413
351
  }
@@ -441,226 +379,366 @@ function capabilityAxisFor(kind, key) {
441
379
  function unavailableCapability(path, message, diagnostics) {
442
380
  diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_CAPABILITY", path, message));
443
381
  }
444
- function narrowedTokens(baselineTokens, requested, key, path, diagnostics) {
445
- const unavailable = requested.filter((entry) => !baselineTokens.includes(entry));
446
- if (unavailable.length > 0) {
447
- unavailableCapability(path, `capability "${key}" requested unavailable values: ${unavailable.join(", ")}`, diagnostics);
448
- return undefined;
449
- }
450
- return [...requested];
382
+ function otherOverlay(layer) {
383
+ return layer === "client" ? "application" : "client";
451
384
  }
452
- function narrowedScopes(axis, holdsAtScope, requested, key, path, diagnostics) {
453
- if (requested.length === 0) {
454
- return { outcome: "denied" };
455
- }
456
- const unavailable = requested.filter((scope) => !axis.includes(scope) || !holdsAtScope(scope));
457
- if (unavailable.length > 0) {
458
- unavailableCapability(path, `capability "${key}" requested unavailable scopes: ${unavailable.join(", ")}`, diagnostics);
459
- return { outcome: "refused" };
385
+ function widening(walk, code, path, what) {
386
+ walk.diagnostics.push(diagnostic(code, path, walk.layer === "agent"
387
+ ? `${what} is hidden by ${walk.parent ?? "client"}, the layer agent extends, and the agent layer only narrows that view; expose it in ${walk.parent ?? "client"}, or extend a layer that exposes it`
388
+ : `${what} is hidden by the root, and an overlay only narrows the root's view; expose it at the root and hide it in ${otherOverlay(walk.layer)} instead`));
389
+ }
390
+ function exemptsWhole(entry) {
391
+ return entry === true;
392
+ }
393
+ function exemptsAnything(entry) {
394
+ return (entry === true ||
395
+ (Array.isArray(entry) && entry.length > 0) ||
396
+ isRecord(entry));
397
+ }
398
+ function listedNames(entry, path) {
399
+ if (!Array.isArray(entry)) {
400
+ return [];
460
401
  }
461
- return { outcome: "narrowed", scopes: [...requested] };
402
+ return entry.flatMap((name, index) => typeof name === "string"
403
+ ? [[name, `${path}[${index}]`]]
404
+ : []);
462
405
  }
463
- function applyScopeRestriction(axis, baselineValue, current, key, requested, path, diagnostics) {
464
- const baseline = baselineValue;
465
- const narrowing = narrowedScopes(axis, (scope) => holdsAt(baseline, scope), requested, key, path, diagnostics);
466
- if (narrowing.outcome === "refused") {
467
- return;
406
+ function memberOffered(axis, value) {
407
+ if (value === undefined || value === false) {
408
+ return false;
468
409
  }
469
- if (narrowing.outcome === "denied") {
470
- delete current[key];
471
- return;
410
+ switch (axis.kind) {
411
+ case "tokens":
412
+ return Array.isArray(value);
413
+ case "scopedTokens":
414
+ return memberTokens(axis, value) !== undefined;
415
+ case "scopes":
416
+ return holdsInAnyScope(value);
417
+ default:
418
+ return true;
472
419
  }
473
- current[key] = narrowing.scopes;
474
420
  }
475
- function applyScopedTokenRestriction(tokenKey, axis, baselineValue, current, key, value, path, diagnostics) {
476
- const requestedTokens = Array.isArray(value)
477
- ? value
478
- : isRecord(value)
479
- ? value[tokenKey]
480
- : undefined;
481
- const requestedScopes = isRecord(value) ? value.scopes : undefined;
482
- if ((requestedTokens !== undefined && !Array.isArray(requestedTokens)) ||
483
- (requestedScopes !== undefined && !Array.isArray(requestedScopes))) {
484
- return false;
421
+ function memberTokens(axis, value) {
422
+ if (axis.kind === "tokens") {
423
+ return Array.isArray(value) ? value : undefined;
485
424
  }
486
- const baselineTokens = tokenKey === "directives"
487
- ? advertisedDirectives(baselineValue)
488
- : advertisedReducers(baselineValue);
489
- if (baselineTokens === undefined) {
490
- unavailableCapability(path, `capability "${key}" is not available in the canonical baseline`, diagnostics);
491
- return true;
425
+ if (axis.kind === "scopedTokens") {
426
+ return axis.tokenKey === "directives"
427
+ ? advertisedDirectives(value)
428
+ : advertisedReducers(value);
492
429
  }
493
- const tokens = requestedTokens === undefined
494
- ? [...baselineTokens]
495
- : narrowedTokens(baselineTokens, requestedTokens, key, path, diagnostics);
496
- if (tokens === undefined) {
497
- return true;
430
+ return undefined;
431
+ }
432
+ function withTokens(axis, parentValue, tokens) {
433
+ if (axis.kind === "scopedTokens" &&
434
+ isRecord(parentValue) &&
435
+ Array.isArray(parentValue.scopes)) {
436
+ return {
437
+ [axis.tokenKey]: [...tokens],
438
+ scopes: [...parentValue.scopes],
439
+ };
498
440
  }
499
- if (requestedScopes === undefined) {
500
- const baselineScopes = isRecord(baselineValue) && Array.isArray(baselineValue.scopes)
501
- ? [...baselineValue.scopes]
502
- : undefined;
503
- current[key] =
504
- baselineScopes === undefined
505
- ? tokens
506
- : { [tokenKey]: tokens, scopes: baselineScopes };
507
- return true;
441
+ return [...tokens];
442
+ }
443
+ function fieldName(resource, field) {
444
+ return `"${resource}.${field}"`;
445
+ }
446
+ function exemptableAxis(walk, universeField, capability, label, path) {
447
+ const axis = capabilityAxisFor(universeField.kind, capability);
448
+ if (axis === undefined || axis.kind === "limit") {
449
+ const names = Object.entries(universeField.kind === "scalar"
450
+ ? SCALAR_CAPABILITY_AXES
451
+ : RELATION_CAPABILITY_AXES)
452
+ .filter(([, entry]) => entry.kind !== "limit")
453
+ .map(([name]) => name);
454
+ unavailableCapability(path, `"${capability}" is not a capability a ${universeField.kind} field's exemption can name; it takes ${names.join(", ")}`, walk.diagnostics);
455
+ return undefined;
508
456
  }
509
- const narrowing = narrowedScopes(axis, (scope) => (tokenKey === "directives"
510
- ? advertisedDirectivesAt(baselineValue, scope)
511
- : advertisedReducersAt(baselineValue, scope)) !== undefined, requestedScopes, key, path, diagnostics);
512
- if (narrowing.outcome === "refused") {
513
- return true;
457
+ const capabilities = universeField.capabilities;
458
+ if (!memberOffered(axis, capabilities[capability])) {
459
+ unavailableCapability(path, `capability "${capability}" is not available for field ${label}: the adapter does not offer it`, walk.diagnostics);
460
+ return undefined;
514
461
  }
515
- if (narrowing.outcome === "denied") {
516
- delete current[key];
517
- return true;
462
+ return axis;
463
+ }
464
+ function offeredTokens(walk, axis, universeValue, listed, capability, label) {
465
+ const offered = memberTokens(axis, universeValue) ?? [];
466
+ const tokens = [];
467
+ for (const [token, tokenPath] of listed) {
468
+ if (!offered.includes(token)) {
469
+ unavailableCapability(tokenPath, `capability "${capability}" of field ${label} does not offer "${token}": the adapter does not offer it`, walk.diagnostics);
470
+ continue;
471
+ }
472
+ tokens.push(token);
518
473
  }
519
- current[key] = { [tokenKey]: tokens, scopes: narrowing.scopes };
520
- return true;
474
+ return tokens;
521
475
  }
522
- function applyCapabilityRestriction(fieldKind, inherited, restoration, current, key, value, path, diagnostics) {
523
- if (capabilityAxisFor(fieldKind, key)?.kind === "limit") {
524
- unavailableCapability(path, `capability "${key}" is an adapter limit and takes no restriction`, diagnostics);
476
+ function exemptCapability(walk, universeField, parentCapabilities, result, capability, entry, label, path) {
477
+ if (entry === undefined) {
525
478
  return;
526
479
  }
527
- if (value === false) {
528
- delete current[key];
480
+ const axis = exemptableAxis(walk, universeField, capability, label, path);
481
+ if (axis === undefined || entry === false) {
529
482
  return;
530
483
  }
531
- const axis = capabilityAxisFor(fieldKind, key);
532
- const baselineValue = inherited?.[key] ?? restoration[key];
533
- if (axis === undefined ||
534
- baselineValue === undefined ||
535
- baselineValue === false) {
536
- unavailableCapability(path, `capability "${key}" is not available in the canonical baseline`, diagnostics);
484
+ const isTokenAxis = axis.kind === "tokens" || axis.kind === "scopedTokens";
485
+ if (entry !== true && !(isTokenAxis && Array.isArray(entry))) {
486
+ unavailableCapability(path, isTokenAxis
487
+ ? `capability "${capability}" takes true, false or a list of its tokens`
488
+ : `capability "${capability}" takes true or false`, walk.diagnostics);
537
489
  return;
538
490
  }
539
- if (value === true) {
540
- current[key] = cloneValue(baselineValue);
491
+ const universeValue = universeField.capabilities[capability];
492
+ const tokens = entry === true
493
+ ? undefined
494
+ : offeredTokens(walk, axis, universeValue, listedNames(entry, path), capability, label);
495
+ if (tokens !== undefined && tokens.length !== entry.length) {
541
496
  return;
542
497
  }
543
- if (axis.kind === "tokens" &&
544
- Array.isArray(value) &&
545
- Array.isArray(baselineValue)) {
546
- const tokens = narrowedTokens(baselineValue, value, key, path, diagnostics);
547
- if (tokens !== undefined) {
548
- current[key] = tokens;
498
+ const parentValue = parentCapabilities[capability];
499
+ const parentHas = memberOffered(axis, parentValue);
500
+ if (walk.style === "omit") {
501
+ if (!parentHas) {
502
+ return;
503
+ }
504
+ if (tokens === undefined) {
505
+ delete result[capability];
506
+ return;
549
507
  }
508
+ const kept = (memberTokens(axis, parentValue) ?? []).filter((token) => !tokens.includes(token));
509
+ result[capability] = withTokens(axis, parentValue, kept);
550
510
  return;
551
511
  }
552
- if (axis.kind === "scopes" && Array.isArray(value)) {
553
- applyScopeRestriction(axis.axis, baselineValue, current, key, value, path, diagnostics);
512
+ if (tokens !== undefined && tokens.length === 0) {
554
513
  return;
555
514
  }
556
- if (axis.kind === "scopedTokens") {
557
- if (applyScopedTokenRestriction(axis.tokenKey, axis.axis, baselineValue, current, key, value, path, diagnostics)) {
558
- return;
559
- }
515
+ if (!parentHas) {
516
+ widening(walk, "COMPILER_UNAVAILABLE_CAPABILITY", path, `capability "${capability}" of field ${label}`);
517
+ return;
560
518
  }
561
- unavailableCapability(path, `capability "${key}" does not admit this restriction shape`, diagnostics);
562
- }
563
- function applyCapabilityRestrictions(inheritedField, restorationField, field, restrictions, path, diagnostics) {
564
- const capabilityRestrictions = restrictions.capabilities;
565
- if (capabilityRestrictions === undefined) {
519
+ if (tokens === undefined) {
520
+ result[capability] = cloneValue(parentValue);
566
521
  return;
567
522
  }
568
- const inherited = inheritedField?.capabilities;
569
- const restoration = restorationField.capabilities;
570
- const current = field.capabilities;
571
- for (const [key, value] of Object.entries(capabilityRestrictions)) {
572
- if (value === undefined) {
573
- continue;
523
+ const parentTokens = memberTokens(axis, parentValue) ?? [];
524
+ const hidden = tokens.filter((token) => !parentTokens.includes(token));
525
+ if (hidden.length > 0) {
526
+ widening(walk, "COMPILER_UNAVAILABLE_CAPABILITY", path, `${hidden.map((token) => `"${token}"`).join(", ")} of capability "${capability}" of field ${label}`);
527
+ return;
528
+ }
529
+ result[capability] = withTokens(axis, parentValue, parentTokens.filter((token) => tokens.includes(token)));
530
+ }
531
+ function exemptField(walk, universeField, parentField, entry, label, path) {
532
+ const parentCapabilities = (parentField?.capabilities ?? {});
533
+ const result = walk.style === "omit" ? cloneValue(parentCapabilities) : {};
534
+ if (walk.style === "pick") {
535
+ for (const [key, value] of Object.entries(parentCapabilities)) {
536
+ if (capabilityAxisFor(universeField.kind, key)?.kind === "limit") {
537
+ result[key] = cloneValue(value);
538
+ }
539
+ }
540
+ }
541
+ if (Array.isArray(entry)) {
542
+ for (const [capability, capabilityPath] of listedNames(entry, path)) {
543
+ exemptCapability(walk, universeField, parentCapabilities, result, capability, true, label, capabilityPath);
574
544
  }
575
- applyCapabilityRestriction(restorationField.kind, inherited, restoration, current, key, value, `${path}.capabilities.${key}`, diagnostics);
576
545
  }
546
+ else if (isRecord(entry)) {
547
+ for (const [capability, value] of Object.entries(entry)) {
548
+ exemptCapability(walk, universeField, parentCapabilities, result, capability, value, label, `${path}.${capability}`);
549
+ }
550
+ }
551
+ if (parentField === undefined) {
552
+ return undefined;
553
+ }
554
+ if (walk.style === "pick" &&
555
+ !Object.keys(result).some((key) => capabilityAxisFor(universeField.kind, key)?.kind !== "limit")) {
556
+ return undefined;
557
+ }
558
+ return { ...cloneValue(parentField), capabilities: result };
577
559
  }
578
- function applyOperationRestrictions(restoration, current, restrictions, path, diagnostics) {
579
- if (restrictions === undefined) {
580
- return;
560
+ function exemptFields(walk, resourceName, universe, parent, entries, path) {
561
+ const named = isRecord(entries) ? entries : {};
562
+ for (const fieldName of Object.keys(named)) {
563
+ if (universe.fields[fieldName] === undefined) {
564
+ walk.diagnostics.push(diagnostic("COMPILER_UNKNOWN_FIELD", `${path}.${fieldName}`, `field "${fieldName}" is neither discovered by the adapter nor defined at the root`));
565
+ }
581
566
  }
582
- for (const [family, variants] of Object.entries(restrictions)) {
583
- if (!isRecord(variants)) {
567
+ const result = {};
568
+ for (const [name, entry] of Object.entries(named)) {
569
+ const universeField = universe.fields[name];
570
+ if (universeField === undefined) {
584
571
  continue;
585
572
  }
586
- const baselineFamily = restoration[family];
587
- let currentFamily = current[family];
588
- if (!isRecord(currentFamily)) {
589
- currentFamily = {};
590
- current[family] = currentFamily;
573
+ const parentField = parent.fields[name];
574
+ const label = fieldName(resourceName, name);
575
+ const fieldPath = `${path}.${name}`;
576
+ if (walk.style === "pick" &&
577
+ exemptsAnything(entry) &&
578
+ parentField === undefined) {
579
+ widening(walk, "COMPILER_UNAVAILABLE_CAPABILITY", fieldPath, `field ${label}`);
580
+ continue;
591
581
  }
592
- for (const [variant, enabled] of Object.entries(variants)) {
593
- if (typeof enabled !== "boolean") {
594
- continue;
595
- }
596
- if (!enabled) {
597
- delete currentFamily[variant];
598
- continue;
582
+ if (exemptsWhole(entry) || !exemptsAnything(entry)) {
583
+ continue;
584
+ }
585
+ const exempted = exemptField(walk, universeField, parentField, entry, label, fieldPath);
586
+ if (exempted !== undefined) {
587
+ result[name] = exempted;
588
+ }
589
+ }
590
+ for (const [name, parentField] of Object.entries(parent.fields)) {
591
+ if (Object.hasOwn(result, name)) {
592
+ continue;
593
+ }
594
+ const entry = Object.hasOwn(named, name) ? named[name] : undefined;
595
+ if (walk.style === "omit" ? !exemptsWhole(entry) : exemptsWhole(entry)) {
596
+ result[name] = cloneValue(parentField);
597
+ }
598
+ }
599
+ return result;
600
+ }
601
+ function exemptVariants(walk, resourceName, family, universeFamily, parentFamily, entry, path) {
602
+ if (entry === true) {
603
+ return "all";
604
+ }
605
+ const named = Array.isArray(entry)
606
+ ? [...listedNames(entry, path)]
607
+ : isRecord(entry)
608
+ ? Object.keys(entry).map((variant) => [variant, `${path}.${variant}`])
609
+ : [];
610
+ const variants = [];
611
+ for (const [variant, variantPath] of named) {
612
+ if (!isOperationVariantAvailable(universeFamily[variant])) {
613
+ walk.diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", variantPath, `operation "${family}.${variant}" is not available for resource "${resourceName}"`));
614
+ continue;
615
+ }
616
+ if (isRecord(entry)) {
617
+ const value = entry[variant];
618
+ if (value !== true && value !== false) {
619
+ walk.diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", variantPath, "an operation variant takes true or false"));
599
620
  }
600
- const baselineAvailability = isRecord(baselineFamily)
601
- ? baselineFamily[variant]
602
- : undefined;
603
- if (!isOperationVariantAvailable(baselineAvailability)) {
604
- diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", `${path}.${family}.${variant}`, `operation "${family}.${variant}" is not available in the canonical baseline`));
621
+ if (value !== true) {
605
622
  continue;
606
623
  }
607
- const target = currentFamily;
608
- if (!isOperationVariantAvailable(target[variant])) {
609
- target[variant] = cloneValue(baselineAvailability);
610
- }
611
624
  }
625
+ if (walk.style === "pick" &&
626
+ !isOperationVariantAvailable(parentFamily?.[variant])) {
627
+ widening(walk, "COMPILER_UNAVAILABLE_OPERATION", variantPath, `operation "${resourceName}.${family}.${variant}"`);
628
+ continue;
629
+ }
630
+ variants.push(variant);
612
631
  }
632
+ return variants;
613
633
  }
614
- function applyRestrictions(baselines, projection, scope, config, diagnostics) {
615
- if (config === undefined) {
616
- return;
617
- }
618
- for (const [resourceName, rawRestrictions] of Object.entries(config)) {
619
- if (rawRestrictions === undefined) {
634
+ function exemptOperations(walk, resourceName, universe, parent, entry, path) {
635
+ const omit = walk.style === "omit";
636
+ if (entry === true) {
637
+ return omit ? {} : cloneValue(parent);
638
+ }
639
+ const families = Array.isArray(entry)
640
+ ? listedNames(entry, path).map(([family, familyPath]) => [family, familyPath, true])
641
+ : isRecord(entry)
642
+ ? Object.entries(entry).map(([family, value]) => [family, `${path}.${family}`, value])
643
+ : [];
644
+ const result = omit ? cloneValue(parent) : {};
645
+ for (const [family, familyPath, value] of families) {
646
+ const universeFamily = universe[family];
647
+ if (!isRecord(universeFamily)) {
648
+ walk.diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", familyPath, `operation family "${family}" is not available for resource "${resourceName}"`));
620
649
  continue;
621
650
  }
622
- const restrictions = rawRestrictions;
623
- const baselineResource = baselines.restoration.resources[resourceName];
624
- if (baselineResource === undefined) {
625
- diagnostics.push(diagnostic("COMPILER_UNKNOWN_RESOURCE", `${scope}.resources.${resourceName}`, `resource "${resourceName}" is not present in the canonical compilation baseline`));
651
+ if (value === false || value === undefined) {
626
652
  continue;
627
653
  }
628
- if (restrictions.hidden === true) {
629
- delete projection.resources[resourceName];
654
+ if (value !== true && !Array.isArray(value) && !isRecord(value)) {
655
+ walk.diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", familyPath, "an operation family takes true, false, a list of its variants or an object of them"));
630
656
  continue;
631
657
  }
632
- if (restrictions.hidden === false &&
633
- projection.resources[resourceName] === undefined) {
634
- projection.resources[resourceName] = cloneValue(baselineResource);
658
+ const parentFamily = isRecord(parent[family])
659
+ ? parent[family]
660
+ : undefined;
661
+ if (value === true && !omit && parentFamily === undefined) {
662
+ widening(walk, "COMPILER_UNAVAILABLE_OPERATION", familyPath, `operation family "${resourceName}.${family}"`);
663
+ continue;
635
664
  }
636
- const resource = projection.resources[resourceName];
637
- if (resource === undefined) {
665
+ const variants = exemptVariants(walk, resourceName, family, universeFamily, parentFamily, value, familyPath);
666
+ if (parentFamily === undefined) {
638
667
  continue;
639
668
  }
640
- for (const [fieldName, rawFieldRestrictions] of Object.entries(restrictions.fields ?? {})) {
641
- const fieldRestrictions = rawFieldRestrictions;
642
- const baselineField = baselineResource.fields[fieldName];
643
- const path = `${scope}.resources.${resourceName}.fields.${fieldName}`;
644
- if (baselineField === undefined) {
645
- diagnostics.push(diagnostic("COMPILER_UNKNOWN_FIELD", path, `field "${fieldName}" is not present in the canonical compilation baseline`));
646
- continue;
647
- }
648
- if (fieldRestrictions.hidden === true) {
649
- delete resource.fields[fieldName];
650
- continue;
651
- }
652
- if (fieldRestrictions.hidden === false &&
653
- resource.fields[fieldName] === undefined) {
654
- resource.fields[fieldName] = cloneValue(baselineField);
669
+ const named = variants === "all" ? Object.keys(parentFamily) : variants;
670
+ if (omit) {
671
+ const target = result[family];
672
+ for (const variant of named) {
673
+ if (target !== undefined) {
674
+ delete target[variant];
675
+ }
655
676
  }
656
- const field = resource.fields[fieldName];
657
- if (field === undefined) {
658
- continue;
677
+ continue;
678
+ }
679
+ const target = (isRecord(result[family]) ? result[family] : {});
680
+ for (const variant of named) {
681
+ if (isOperationVariantAvailable(parentFamily[variant])) {
682
+ target[variant] = cloneValue(parentFamily[variant]);
659
683
  }
660
- applyCapabilityRestrictions(baselines.narrowing.resources[resourceName]?.fields[fieldName], baselineField, field, fieldRestrictions, path, diagnostics);
661
684
  }
662
- applyOperationRestrictions(baselineResource.operations, resource.operations, restrictions.operations, `${scope}.resources.${resourceName}.operations`, diagnostics);
685
+ result[family] = target;
663
686
  }
687
+ pruneEmptyOperationFamilies(result);
688
+ return result;
689
+ }
690
+ function exemptResource(walk, resourceName, universe, parent, entry, path) {
691
+ const resource = {
692
+ fields: exemptFields(walk, resourceName, universe, parent, entry.fields, `${path}.fields`),
693
+ identifiers: [],
694
+ operations: exemptOperations(walk, resourceName, universe.operations, parent.operations, entry.operations, `${path}.operations`),
695
+ };
696
+ reconcileIdentifiers(walk, resource, parent, path);
697
+ return resource;
698
+ }
699
+ function projectExposure(walk, universe, parent, exemptions) {
700
+ const path = `${walk.layer}.exemptions.resources`;
701
+ const entries = isRecord(exemptions) && isRecord(exemptions.resources)
702
+ ? exemptions.resources
703
+ : {};
704
+ const resources = {};
705
+ for (const [resourceName, entry] of Object.entries(entries)) {
706
+ const resourcePath = `${path}.${resourceName}`;
707
+ const universeResource = universe.resources[resourceName];
708
+ if (universeResource === undefined) {
709
+ walk.diagnostics.push(diagnostic("COMPILER_UNKNOWN_RESOURCE", resourcePath, `resource "${resourceName}" is not present in the canonical adapter model`));
710
+ continue;
711
+ }
712
+ const parentResource = parent.resources[resourceName];
713
+ if (walk.style === "pick" &&
714
+ exemptsAnything(entry) &&
715
+ parentResource === undefined) {
716
+ widening(walk, "COMPILER_UNAVAILABLE_CAPABILITY", resourcePath, `resource "${resourceName}"`);
717
+ continue;
718
+ }
719
+ if (!isRecord(entry)) {
720
+ continue;
721
+ }
722
+ const exempted = exemptResource(walk, resourceName, universeResource, parentResource ?? { fields: {}, identifiers: [], operations: {} }, entry, resourcePath);
723
+ if (parentResource !== undefined &&
724
+ (walk.style === "omit" ||
725
+ Object.keys(exempted.fields).length > 0 ||
726
+ Object.keys(exempted.operations).length > 0)) {
727
+ resources[resourceName] = exempted;
728
+ }
729
+ }
730
+ for (const [resourceName, parentResource] of Object.entries(parent.resources)) {
731
+ if (Object.hasOwn(resources, resourceName)) {
732
+ continue;
733
+ }
734
+ const entry = Object.hasOwn(entries, resourceName)
735
+ ? entries[resourceName]
736
+ : undefined;
737
+ if (walk.style === "omit" ? !exemptsWhole(entry) : exemptsWhole(entry)) {
738
+ resources[resourceName] = cloneValue(parentResource);
739
+ }
740
+ }
741
+ return { ...cloneValue({ ...parent, resources: {} }), resources };
664
742
  }
665
743
  function pruneEmptyOperationFamilies(operations) {
666
744
  for (const [family, variants] of Object.entries(operations)) {
@@ -669,45 +747,28 @@ function pruneEmptyOperationFamilies(operations) {
669
747
  }
670
748
  }
671
749
  }
672
- function operationIsExplicitlyDisabled(restrictions, family, variant) {
673
- if (restrictions === undefined || !isRecord(restrictions.operations)) {
674
- return false;
675
- }
676
- const familyRestrictions = restrictions.operations[family];
677
- return isRecord(familyRestrictions) && familyRestrictions[variant] === false;
750
+ function identifierLabel(identifier) {
751
+ return identifier.map((name) => `\`${name}\``).join(" and ");
678
752
  }
679
- function reconcileIdentifiers(resource, baseline, restrictions) {
680
- resource.identifiers = baseline.identifiers.filter((identifier) => identifier.every((fieldName) => {
681
- const field = resource.fields[fieldName];
682
- return field !== undefined && field.kind === "scalar";
683
- }));
753
+ function reconcileIdentifiers(walk, resource, parent, path) {
754
+ resource.identifiers = parent.identifiers
755
+ .filter((identifier) => identifier.every((name) => resource.fields[name]?.kind === "scalar"))
756
+ .map((identifier) => [...identifier]);
757
+ const first = parent.identifiers[0];
758
+ if (resource.identifiers.length > 0 || first === undefined) {
759
+ return;
760
+ }
684
761
  for (const [family, variant] of UNIQUE_VARIANTS) {
685
762
  const currentFamily = resource.operations[family];
686
- if (resource.identifiers.length === 0) {
687
- if (isRecord(currentFamily)) {
688
- delete currentFamily[variant];
689
- }
763
+ if (!isRecord(currentFamily) ||
764
+ !isOperationVariantAvailable(currentFamily[variant])) {
690
765
  continue;
691
766
  }
692
- if (operationIsExplicitlyDisabled(restrictions, family, variant)) {
693
- if (isRecord(currentFamily)) {
694
- delete currentFamily[variant];
695
- }
696
- continue;
697
- }
698
- const baselineFamily = baseline.operations[family];
699
- const baselineAvailability = isRecord(baselineFamily)
700
- ? baselineFamily[variant]
701
- : undefined;
702
- if (isOperationVariantAvailable(baselineAvailability)) {
703
- const targetFamily = isRecord(currentFamily) ? currentFamily : {};
704
- if (!isOperationVariantAvailable(targetFamily[variant])) {
705
- targetFamily[variant] = cloneValue(baselineAvailability);
706
- }
707
- resource.operations[family] = targetFamily;
708
- }
767
+ const variantPath = `${path}.operations.${family}.${variant}`;
768
+ walk.diagnostics.push(diagnostic("COMPILER_MISSING_IDENTIFIER", variantPath, walk.style === "pick"
769
+ ? `${variantPath} needs an identifier field; pick ${identifierLabel(first)} (or another complete identifier) too`
770
+ : `${variantPath} needs an identifier field, and the exemptions hide every complete identifier; keep ${identifierLabel(first)} (or another complete identifier) exposed, or exempt the operation too`));
709
771
  }
710
- pruneEmptyOperationFamilies(resource.operations);
711
772
  }
712
773
  function validateScalarReference(projection, field, path, diagnostics) {
713
774
  const type = field.type;
@@ -757,7 +818,7 @@ function validateFieldLifecycle(field, path, diagnostics) {
757
818
  if (lifecycle.includes("VIRTUAL") &&
758
819
  holdsInAnyScope(capabilities.select) &&
759
820
  !lifecycle.includes("COMPUTED_ON_READ")) {
760
- diagnostics.push(diagnostic("COMPILER_MISSING_READ_BEHAVIOR", path, "selectable VIRTUAL field requires an effective read behavior"));
821
+ diagnostics.push(diagnostic("COMPILER_MISSING_READ_COMPUTE", path, "selectable VIRTUAL field requires a compute.read stage (pipelines.compute.read)"));
761
822
  }
762
823
  }
763
824
  function isTokenList(value) {
@@ -815,12 +876,8 @@ function validateCapabilityRepresentation(field, path, diagnostics) {
815
876
  }
816
877
  }
817
878
  }
818
- function validateProjection(projection, restoration, scope, restrictions, diagnostics) {
879
+ function validateProjection(projection, scope, diagnostics) {
819
880
  for (const [resourceName, resource] of Object.entries(projection.resources)) {
820
- const baselineResource = restoration.resources[resourceName];
821
- if (baselineResource !== undefined) {
822
- reconcileIdentifiers(resource, baselineResource, restrictions?.[resourceName]);
823
- }
824
881
  for (const [fieldName, field] of Object.entries(resource.fields)) {
825
882
  const fieldPath = `${scope}.resources.${resourceName}.fields.${fieldName}`;
826
883
  validateCapabilityRepresentation(field, fieldPath, diagnostics);
@@ -841,49 +898,71 @@ function resolveLimits(scope) {
841
898
  return { ...DEFAULT_CONTRACT_LIMITS, ...(scope.limits ?? {}) };
842
899
  }
843
900
  function resolveTransactions(model, scopeName, scope, diagnostics) {
844
- const requested = scope.transactions ?? model.transactions;
901
+ const requested = scope.transactionsStyle ?? model.transactions;
845
902
  if (requested === "interactive" && model.transactions === "none") {
846
- diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_CAPABILITY", `${scopeName}.transactions`, 'transaction capability "interactive" is not available in the canonical adapter model'));
903
+ diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_CAPABILITY", `${scopeName}.transactionsStyle`, 'transaction capability "interactive" is not available in the canonical adapter model'));
847
904
  }
848
905
  return requested;
849
906
  }
850
907
  function semanticObject(contract) {
851
908
  return JSON.parse(canonicalizeContract(contract));
852
909
  }
853
- function projectCore(model, scope, diagnostics) {
910
+ function exposureStyle(layer) {
911
+ return layer.exposed === false ? "pick" : "omit";
912
+ }
913
+ function projectCore(model, config, diagnostics) {
914
+ const scope = config.root;
854
915
  const providerBaseline = createBaseline(model);
855
916
  const baseline = cloneValue(providerBaseline);
856
917
  baseline.limits = resolveLimits(scope);
857
918
  baseline.transactions = resolveTransactions(model, "root", scope, diagnostics);
858
- applyFieldConfig(model, baseline, "root", scope.fields, diagnostics);
859
- applyBehaviors(baseline, "root", scope.behaviors, diagnostics);
860
- const projection = cloneValue(baseline);
861
- applyRestrictions({ narrowing: baseline, restoration: baseline }, projection, "root", scope.restrictions, diagnostics);
862
- validateProjection(projection, baseline, "root", scope.restrictions, diagnostics);
863
- return { baseline, projection };
864
- }
865
- function projectDerivedScope(model, scopeName, core, scope, diagnostics) {
919
+ checkResourceTree(model, baseline, "root", config.trees.root, config.definitions, diagnostics);
920
+ applyFieldDefinitions(model, baseline, config.definitions, diagnostics);
921
+ applyCompute(baseline, scope.compute);
922
+ checkLayerValidators(baseline.resources, "root", config.trees.root, diagnostics);
923
+ const validators = compileValidatorTable(baseline.resources, scope.pipelines);
924
+ const projection = projectExposure({ layer: "root", style: exposureStyle(scope), diagnostics }, baseline, baseline, scope.exemptions);
925
+ projectCreateRequirements(model, projection.resources);
926
+ checkHiddenRequirements(model, "root", baseline.resources, baseline.resources, projection.resources, diagnostics);
927
+ applyValidators(projection.resources, validators);
928
+ validateProjection(projection, "root", diagnostics);
929
+ return { baseline, projection, validators };
930
+ }
931
+ function projectDerivedScope(model, scopeName, core, parentView, config, diagnostics) {
932
+ const scope = config[scopeName];
866
933
  const baseline = cloneValue(core.baseline);
867
934
  baseline.limits = resolveLimits(scope);
868
- baseline.transactions = resolveTransactions(model, scopeName, scope, diagnostics);
869
- applyFieldConfig(model, baseline, scopeName, scope.fields, diagnostics);
870
- applyBehaviors(baseline, scopeName, scope.behaviors, diagnostics);
871
- const projection = cloneValue(core.projection);
872
- projection.limits = resolveLimits(scope);
873
- projection.transactions = baseline.transactions;
874
- applyFieldConfig(model, projection, scopeName, scope.fields, diagnostics, true);
875
- applyBehaviors(projection, scopeName, scope.behaviors, diagnostics, true);
876
- applyRestrictions({ narrowing: cloneValue(projection), restoration: baseline }, projection, scopeName, scope.restrictions, diagnostics);
877
- validateProjection(projection, baseline, scopeName, scope.restrictions, diagnostics);
878
- return projection;
879
- }
880
- function bindShapedFields(contract, fields) {
935
+ baseline.transactions = resolveTransactions(model, scopeName, scope, scopeName === "agent" &&
936
+ scope.transactionsStyle === config[config.agent.parent].transactionsStyle
937
+ ? []
938
+ : diagnostics);
939
+ checkResourceTree(model, baseline, scopeName, config.trees[scopeName], config.definitions, diagnostics);
940
+ applyCompute(baseline, scope.compute);
941
+ checkLayerValidators(baseline.resources, scopeName, config.trees[scopeName], diagnostics);
942
+ const validators = compileValidatorTable(baseline.resources, scope.pipelines);
943
+ const parent = cloneValue(parentView);
944
+ parent.limits = resolveLimits(scope);
945
+ parent.transactions = baseline.transactions;
946
+ applyCompute(parent, scope.compute);
947
+ const projection = projectExposure({
948
+ layer: scopeName,
949
+ style: exposureStyle(scope),
950
+ diagnostics,
951
+ ...(scopeName === "agent" ? { parent: config.agent.parent } : {}),
952
+ }, baseline, parent, scope.exemptions);
953
+ projectCreateRequirements(model, projection.resources);
954
+ checkHiddenRequirements(model, scopeName, baseline.resources, parent.resources, projection.resources, diagnostics);
955
+ applyValidators(projection.resources, validators);
956
+ validateProjection(projection, scopeName, diagnostics);
957
+ return { projection, validators };
958
+ }
959
+ function bindShapedFields(contract, definitions) {
881
960
  for (const [resourceName, resource] of Object.entries(contract.resources)) {
882
961
  for (const [fieldName, field] of Object.entries(resource.fields)) {
883
962
  if (field.kind !== "scalar" || field.json === undefined) {
884
963
  continue;
885
964
  }
886
- const shape = jsonShapeOf(fields?.[resourceName]?.[fieldName]?.json);
965
+ const shape = jsonShapeOf(definitions[resourceName]?.[fieldName]?.json);
887
966
  if (shape === undefined) {
888
967
  throw new Error(`the Contract compiler found no shape for shaped field "${resourceName}.${fieldName}"`);
889
968
  }
@@ -894,37 +973,65 @@ function bindShapedFields(contract, fields) {
894
973
  export async function compileContracts(model, config) {
895
974
  const compilerConfig = config;
896
975
  const diagnostics = [];
897
- const coreCompilation = projectCore(model, compilerConfig.root, diagnostics);
898
- const applicationProjection = projectDerivedScope(model, "application", coreCompilation, compilerConfig.application, diagnostics);
899
- const clientProjection = projectDerivedScope(model, "client", coreCompilation, compilerConfig.client, diagnostics);
976
+ checkReservedResourceNames(model, diagnostics);
977
+ checkCreateRequirements(model, diagnostics);
978
+ const coreCompilation = projectCore(model, compilerConfig, diagnostics);
979
+ const applicationProjection = projectDerivedScope(model, "application", coreCompilation, coreCompilation.projection, compilerConfig, diagnostics);
980
+ const clientProjection = projectDerivedScope(model, "client", coreCompilation, coreCompilation.projection, compilerConfig, diagnostics);
981
+ const agentParent = compilerConfig.agent.parent;
982
+ const agentProjection = projectDerivedScope(model, "agent", coreCompilation, agentParent === "root"
983
+ ? coreCompilation.projection
984
+ : agentParent === "application"
985
+ ? applicationProjection.projection
986
+ : clientProjection.projection, compilerConfig, diagnostics);
900
987
  if (diagnostics.length > 0) {
901
988
  return { ok: false, diagnostics };
902
989
  }
903
990
  const core = semanticObject(coreCompilation.projection);
904
- const application = semanticObject(applicationProjection);
905
- const clientHashInput = semanticObject({
906
- ...clientProjection,
907
- protocol: { version: AVENTARA_PROTOCOL_VERSION },
908
- });
909
- const hash = await computeClientContractHash(clientHashInput);
910
- const client = semanticObject({
911
- ...clientHashInput,
912
- protocol: { version: AVENTARA_PROTOCOL_VERSION, hash },
913
- });
914
- const structure = validateClientContractStructure(client);
915
- if (!structure.valid) {
916
- return {
917
- ok: false,
918
- diagnostics: [
919
- diagnostic("COMPILER_INVALID_CLIENT_CONTRACT", structure.path, structure.message),
920
- ],
921
- };
991
+ const application = semanticObject(applicationProjection.projection);
992
+ const client = await identifiedContract(clientProjection.projection);
993
+ const agent = await identifiedContract(agentProjection.projection);
994
+ for (const [layer, contract] of [
995
+ ["client", client],
996
+ ["agent", agent],
997
+ ]) {
998
+ const structure = validateClientContractStructure(contract);
999
+ if (!structure.valid) {
1000
+ return {
1001
+ ok: false,
1002
+ diagnostics: [
1003
+ diagnostic("COMPILER_INVALID_CLIENT_CONTRACT", structure.path, layer === "client"
1004
+ ? structure.message
1005
+ : `Agent Contract: ${structure.message}`),
1006
+ ],
1007
+ };
1008
+ }
922
1009
  }
923
- bindShapedFields(core, compilerConfig.root.fields);
924
- bindShapedFields(application, compilerConfig.application.fields);
925
- bindShapedFields(client, compilerConfig.client.fields);
1010
+ bindShapedFields(core, compilerConfig.definitions);
1011
+ bindShapedFields(application, compilerConfig.definitions);
1012
+ bindShapedFields(client, compilerConfig.definitions);
1013
+ bindShapedFields(agent, compilerConfig.definitions);
1014
+ bindFieldValidators(core, coreCompilation.validators);
1015
+ bindFieldValidators(application, applicationProjection.validators);
1016
+ bindFieldValidators(client, clientProjection.validators);
1017
+ bindFieldValidators(agent, agentProjection.validators);
926
1018
  return {
927
1019
  ok: true,
928
- contracts: { core, application, client },
1020
+ contracts: { core, application, client, agent },
929
1021
  };
930
1022
  }
1023
+ async function identifiedContract(projection) {
1024
+ const members = canonicalizeContractMembers({
1025
+ ...projection,
1026
+ protocol: { version: AVENTARA_PROTOCOL_VERSION },
1027
+ });
1028
+ const hash = await hashCanonicalContractText(joinCanonicalContractMembers(members));
1029
+ const protocol = canonicalizeJson({
1030
+ version: AVENTARA_PROTOCOL_VERSION,
1031
+ hash,
1032
+ });
1033
+ const text = joinCanonicalContractMembers(members.map((member) => member.key === "protocol" ? { key: "protocol", text: protocol } : member));
1034
+ const contract = JSON.parse(text);
1035
+ recordCanonicalContractText(contract, text);
1036
+ return contract;
1037
+ }