@aventara/core 0.1.0-pilot.4 → 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 (255) hide show
  1. package/CHANGELOG.md +1478 -0
  2. package/README.md +275 -11
  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 +17 -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 -25
  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/avz/avz.js +354 -0
  19. package/dist/avz/index.d.ts +7 -0
  20. package/dist/avz/index.js +1 -0
  21. package/dist/avz/json-shape.registry.d.ts +22 -0
  22. package/dist/avz/json-shape.registry.js +16 -0
  23. package/dist/avz/zod-issue.translator.d.ts +57 -0
  24. package/dist/avz/zod-issue.translator.js +149 -0
  25. package/dist/compiler/contract-compiler-diagnostic.d.ts +37 -5
  26. package/dist/compiler/contract.compiler.d.ts +18 -18
  27. package/dist/compiler/contract.compiler.js +595 -421
  28. package/dist/compiler/create-requirements.projector.d.ts +49 -0
  29. package/dist/compiler/create-requirements.projector.js +423 -0
  30. package/dist/compiler/field-validator.compiler.d.ts +47 -0
  31. package/dist/compiler/field-validator.compiler.js +238 -0
  32. package/dist/config/any-framework-config.d.ts +19 -12
  33. package/dist/config/config-diagnostic.d.ts +11 -4
  34. package/dist/config/config-layer.d.ts +15 -6
  35. package/dist/config/config.resolver.d.ts +75 -29
  36. package/dist/config/config.resolver.js +109 -44
  37. package/dist/config/config.validator.d.ts +7 -7
  38. package/dist/config/config.validator.js +586 -71
  39. package/dist/config/define-resource.d.ts +57 -0
  40. package/dist/config/define-resource.js +14 -0
  41. package/dist/config/effective-fields.d.ts +19 -23
  42. package/dist/config/entrypoint.normalizer.d.ts +1 -1
  43. package/dist/config/exact-framework-config.d.ts +280 -0
  44. package/dist/config/field-compute.resolver.d.ts +19 -0
  45. package/dist/config/field-compute.resolver.js +115 -0
  46. package/dist/config/field-pipeline-config.d.ts +47 -0
  47. package/dist/config/forced-operation.validator.d.ts +10 -0
  48. package/dist/config/forced-operation.validator.js +66 -0
  49. package/dist/config/framework-config.d.ts +111 -37
  50. package/dist/config/framework-exemptions-config.d.ts +91 -0
  51. package/dist/config/framework-exemptions-config.js +1 -0
  52. package/dist/config/framework-field-definition.d.ts +78 -0
  53. package/dist/config/framework-field-definition.js +28 -0
  54. package/dist/config/framework-resources-config.d.ts +116 -0
  55. package/dist/config/framework-resources-config.js +1 -0
  56. package/dist/config/pipeline-config.d.ts +56 -20
  57. package/dist/config/pipeline-tree.resolver.d.ts +79 -0
  58. package/dist/config/pipeline-tree.resolver.js +220 -0
  59. package/dist/config/stage-type.eraser.d.ts +65 -0
  60. package/dist/config/stage-type.eraser.js +1 -0
  61. package/dist/contracts/capability.d.ts +11 -11
  62. package/dist/contracts/client-contract-structure.validator.d.ts +344 -29
  63. package/dist/contracts/client-contract-structure.validator.js +248 -7
  64. package/dist/contracts/contract-canonicalizer.d.ts +19 -3
  65. package/dist/contracts/contract-canonicalizer.js +172 -27
  66. package/dist/contracts/contract-hash.d.ts +12 -7
  67. package/dist/contracts/contract-hash.js +3 -2
  68. package/dist/contracts/contract-limits.d.ts +4 -1
  69. package/dist/contracts/contract-limits.js +1 -0
  70. package/dist/contracts/contract-lookup.d.ts +5 -5
  71. package/dist/contracts/contract-protocol.d.ts +4 -4
  72. package/dist/contracts/contract.d.ts +31 -16
  73. package/dist/contracts/create-requirements.d.ts +35 -0
  74. package/dist/contracts/create-requirements.js +1 -0
  75. package/dist/contracts/enum-definition.d.ts +1 -1
  76. package/dist/contracts/field-check.d.ts +101 -0
  77. package/dist/contracts/field-check.js +1 -0
  78. package/dist/contracts/field.d.ts +61 -41
  79. package/dist/contracts/identifier.d.ts +4 -4
  80. package/dist/contracts/json-shape.d.ts +76 -0
  81. package/dist/contracts/json-shape.js +1 -0
  82. package/dist/contracts/lifecycle-trait.d.ts +2 -2
  83. package/dist/contracts/reference-write-limit.d.ts +7 -0
  84. package/dist/contracts/reference-write-limit.js +1 -0
  85. package/dist/contracts/resource-operations.d.ts +28 -28
  86. package/dist/contracts/resource.d.ts +15 -9
  87. package/dist/contracts/scalar-value-type.d.ts +25 -14
  88. package/dist/contracts/scalar.d.ts +3 -3
  89. package/dist/contracts/transaction-capability.d.ts +1 -1
  90. package/dist/contracts/wire-scalar-format.d.ts +4 -4
  91. package/dist/framework/contract-view.d.ts +234 -63
  92. package/dist/framework/framework-construction.error.d.ts +10 -10
  93. package/dist/framework/framework-construction.error.js +2 -2
  94. package/dist/framework/framework.d.ts +50 -24
  95. package/dist/framework/framework.factory.d.ts +8 -6
  96. package/dist/framework/framework.factory.js +95 -32
  97. package/dist/framework/operation-facade.d.ts +123 -52
  98. package/dist/framework/operation-facade.js +9 -1
  99. package/dist/index.d.ts +55 -82
  100. package/dist/index.js +6 -26
  101. package/dist/internal.d.ts +40 -0
  102. package/dist/internal.js +18 -0
  103. package/dist/operations/operation-arguments.d.ts +316 -201
  104. package/dist/operations/operation-call.d.ts +27 -28
  105. package/dist/operations/operation-identity.d.ts +12 -10
  106. package/dist/operations/operation-identity.js +3 -0
  107. package/dist/operations/operation-result.d.ts +21 -21
  108. package/dist/operations/operation-validator-core.d.ts +6 -6
  109. package/dist/operations/operation-validator-core.js +41 -12
  110. package/dist/operations/operation-validator.d.ts +24 -9
  111. package/dist/operations/operation-validator.js +26 -10
  112. package/dist/operations/projection-scope.resolver.d.ts +7 -7
  113. package/dist/operations/structural-limits.walker.d.ts +19 -0
  114. package/dist/operations/structural-limits.walker.js +489 -0
  115. package/dist/operations/validation-code.d.ts +11 -6
  116. package/dist/operations/validation-code.js +4 -0
  117. package/dist/operations/write-scope.resolver.d.ts +6 -6
  118. package/dist/protocol/absent-route.responder.d.ts +13 -11
  119. package/dist/protocol/absent-route.responder.js +42 -16
  120. package/dist/protocol/contract-document.responder.d.ts +3 -3
  121. package/dist/protocol/contract-document.responder.js +2 -2
  122. package/dist/protocol/http-request.decoder.d.ts +23 -13
  123. package/dist/protocol/http-request.decoder.js +19 -11
  124. package/dist/protocol/http-request.reader.d.ts +3 -3
  125. package/dist/protocol/http-response.encoder.d.ts +4 -4
  126. package/dist/protocol/http-response.encoder.js +94 -55
  127. package/dist/protocol/http-status.table.d.ts +2 -2
  128. package/dist/protocol/operation-response.guard.d.ts +2 -2
  129. package/dist/protocol/protocol-surface.d.ts +4 -4
  130. package/dist/protocol/protocol.binder.d.ts +52 -27
  131. package/dist/protocol/protocol.binder.js +14 -9
  132. package/dist/protocol/protocol.handler.d.ts +28 -15
  133. package/dist/protocol/protocol.handler.js +55 -10
  134. package/dist/protocol/unmounted-route.error.d.ts +19 -0
  135. package/dist/protocol/unmounted-route.error.js +10 -0
  136. package/dist/runtime/caller-data.copier.d.ts +59 -0
  137. package/dist/runtime/caller-data.copier.js +470 -0
  138. package/dist/runtime/caller-data.door.d.ts +56 -0
  139. package/dist/runtime/caller-data.door.js +67 -0
  140. package/dist/runtime/computed-read-runtime.d.ts +7 -6
  141. package/dist/runtime/computed-read-runtime.js +61 -55
  142. package/dist/runtime/computed-value.error.d.ts +13 -0
  143. package/dist/runtime/computed-value.error.js +9 -0
  144. package/dist/runtime/context-executor.runtime.d.ts +59 -0
  145. package/dist/runtime/context-executor.runtime.js +275 -0
  146. package/dist/runtime/create-requirement.d.ts +22 -57
  147. package/dist/runtime/create-requirement.js +140 -37
  148. package/dist/runtime/decimal.d.ts +2 -2
  149. package/dist/runtime/decimal.js +1 -1
  150. package/dist/runtime/diagnostics-reporter.d.ts +64 -22
  151. package/dist/runtime/diagnostics-reporter.js +35 -4
  152. package/dist/runtime/error-boundary.d.ts +73 -8
  153. package/dist/runtime/error-boundary.js +104 -48
  154. package/dist/runtime/execution-options.d.ts +10 -2
  155. package/dist/runtime/field-check.evaluator.d.ts +29 -0
  156. package/dist/runtime/field-check.evaluator.js +521 -0
  157. package/dist/runtime/field-compute.d.ts +64 -0
  158. package/dist/runtime/field-compute.js +1 -0
  159. package/dist/runtime/field-stage.plan.d.ts +15 -0
  160. package/dist/runtime/field-stage.plan.js +27 -0
  161. package/dist/runtime/field-stage.runtime.d.ts +35 -0
  162. package/dist/runtime/field-stage.runtime.js +143 -0
  163. package/dist/runtime/field-validator.runtime.d.ts +11 -0
  164. package/dist/runtime/field-validator.runtime.js +87 -0
  165. package/dist/runtime/framework-error-class.table.d.ts +33 -0
  166. package/dist/runtime/framework-error-class.table.js +25 -0
  167. package/dist/runtime/framework-error.d.ts +79 -7
  168. package/dist/runtime/framework-error.factory.d.ts +4 -0
  169. package/dist/runtime/framework-error.factory.js +15 -0
  170. package/dist/runtime/framework-error.js +23 -2
  171. package/dist/runtime/interceptor-next.error.d.ts +5 -0
  172. package/dist/runtime/interceptor-next.error.js +6 -0
  173. package/dist/runtime/json-value.d.ts +3 -3
  174. package/dist/runtime/level-lifecycle.runtime.d.ts +25 -0
  175. package/dist/runtime/level-lifecycle.runtime.js +42 -0
  176. package/dist/runtime/misuse-reporter.d.ts +32 -0
  177. package/dist/runtime/misuse-reporter.js +19 -0
  178. package/dist/runtime/mutation-planner.d.ts +19 -10
  179. package/dist/runtime/mutation-planner.js +62 -36
  180. package/dist/runtime/nested-write.walker.d.ts +32 -0
  181. package/dist/runtime/nested-write.walker.js +73 -0
  182. package/dist/runtime/operation-request.d.ts +35 -15
  183. package/dist/runtime/operation-response.d.ts +17 -17
  184. package/dist/runtime/operation-runtime.d.ts +125 -30
  185. package/dist/runtime/operation-runtime.js +423 -83
  186. package/dist/runtime/outcome-mapper.d.ts +16 -5
  187. package/dist/runtime/outcome-mapper.js +35 -8
  188. package/dist/runtime/output-validator.d.ts +16 -6
  189. package/dist/runtime/output-validator.js +12 -1
  190. package/dist/runtime/owned-execution.registry.d.ts +13 -0
  191. package/dist/runtime/owned-execution.registry.js +7 -0
  192. package/dist/runtime/pipe-output.validator.d.ts +33 -18
  193. package/dist/runtime/pipe-output.validator.js +12 -12
  194. package/dist/runtime/pipeline.d.ts +202 -28
  195. package/dist/runtime/readonly-value.d.ts +32 -0
  196. package/dist/runtime/readonly-value.js +141 -0
  197. package/dist/runtime/referential-action.d.ts +7 -0
  198. package/dist/runtime/request-envelope.validator.d.ts +5 -0
  199. package/dist/runtime/request-envelope.validator.js +123 -0
  200. package/dist/runtime/request-id.d.ts +1 -1
  201. package/dist/runtime/request-state.d.ts +14 -0
  202. package/dist/runtime/request-state.js +1 -0
  203. package/dist/runtime/request-state.resolver.d.ts +12 -0
  204. package/dist/runtime/request-state.resolver.js +20 -0
  205. package/dist/runtime/scalar-runtime.d.ts +56 -13
  206. package/dist/runtime/scalar-runtime.js +167 -86
  207. package/dist/transactions/deferred-operation.d.ts +35 -20
  208. package/dist/transactions/operation-arguments-encoder.d.ts +3 -3
  209. package/dist/transactions/operation-arguments-encoder.js +2 -2
  210. package/dist/transactions/sql-statement.d.ts +77 -0
  211. package/dist/transactions/sql-statement.js +47 -0
  212. package/dist/transactions/sql-statement.validator.d.ts +44 -0
  213. package/dist/transactions/sql-statement.validator.js +134 -0
  214. package/dist/transactions/transaction-envelope.d.ts +4 -4
  215. package/dist/transactions/transaction-integrity-error.d.ts +6 -6
  216. package/dist/transactions/transaction-integrity-error.js +5 -2
  217. package/dist/transactions/transaction-plan-assembler.d.ts +23 -9
  218. package/dist/transactions/transaction-plan-assembler.js +195 -82
  219. package/dist/transactions/transaction-plan-validator.d.ts +53 -21
  220. package/dist/transactions/transaction-plan-validator.js +246 -163
  221. package/dist/transactions/transaction-reference-collector.d.ts +30 -0
  222. package/dist/transactions/transaction-reference-collector.js +204 -0
  223. package/dist/transactions/transaction-reference-resolver.d.ts +19 -2
  224. package/dist/transactions/transaction-reference-resolver.js +27 -10
  225. package/dist/transactions/transaction-reference-validator.d.ts +34 -4
  226. package/dist/transactions/transaction-reference-validator.js +82 -43
  227. package/dist/transactions/transaction-reference.d.ts +30 -58
  228. package/dist/transactions/transaction-request.d.ts +20 -7
  229. package/dist/transactions/transaction-response.d.ts +12 -7
  230. package/dist/transactions/transaction-response.js +24 -1
  231. package/dist/transactions/transaction-runtime.d.ts +11 -8
  232. package/dist/transactions/transaction-runtime.js +167 -50
  233. package/dist/transactions/transaction-settlement.runtime.d.ts +51 -0
  234. package/dist/transactions/transaction-settlement.runtime.js +120 -0
  235. package/dist/transactions/transaction-slot-path-error.d.ts +4 -4
  236. package/dist/transactions/transaction-slot-path-error.js +2 -2
  237. package/dist/transactions/transaction-slot-writer.js +2 -2
  238. package/dist/transactions/transaction-stage.runtime.d.ts +81 -0
  239. package/dist/transactions/transaction-stage.runtime.js +123 -0
  240. package/package.json +18 -3
  241. package/src/runtime/field-check.evaluator.ts +720 -0
  242. package/dist/config/define-fields.d.ts +0 -35
  243. package/dist/config/define-fields.js +0 -3
  244. package/dist/config/framework-behaviors-config.d.ts +0 -30
  245. package/dist/config/framework-fields-config.d.ts +0 -36
  246. package/dist/config/framework-restrictions-config.d.ts +0 -142
  247. package/dist/operations/operation-boolean-nodes.d.ts +0 -11
  248. package/dist/operations/operation-boolean-nodes.js +0 -283
  249. package/dist/operations/operation-nesting.d.ts +0 -8
  250. package/dist/operations/operation-nesting.js +0 -307
  251. package/dist/runtime/field-behavior.d.ts +0 -48
  252. /package/dist/{config/framework-behaviors-config.js → adapters/adapter-sql-statement.js} +0 -0
  253. /package/dist/{config/framework-fields-config.js → adapters/adapter-transaction-executor.interface.js} +0 -0
  254. /package/dist/config/{framework-restrictions-config.js → exact-framework-config.js} +0 -0
  255. /package/dist/{runtime/field-behavior.js → config/field-pipeline-config.js} +0 -0
@@ -1,11 +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";
10
+ import { checkCreateRequirements, checkHiddenRequirements, projectCreateRequirements, } from "./create-requirements.projector.js";
11
+ import { applyValidators, bindFieldValidators, checkLayerValidators, compileValidatorTable, } from "./field-validator.compiler.js";
9
12
  const UNIQUE_VARIANTS = [
10
13
  ["find", "unique"],
11
14
  ["update", "unique"],
@@ -99,10 +102,13 @@ function createBaseline(model) {
99
102
  ])),
100
103
  limits: { ...DEFAULT_CONTRACT_LIMITS },
101
104
  transactions: model.transactions,
105
+ ...(model.referenceWrites === undefined
106
+ ? {}
107
+ : { referenceWrites: model.referenceWrites }),
102
108
  };
103
109
  }
104
- function scalarFieldPath(scope, resource, field) {
105
- return `${scope}.resources.${resource}.fields.${field}`;
110
+ function definitionPath(resource, field) {
111
+ return `root.resources.${resource}.fields.${field}.definition`;
106
112
  }
107
113
  function structuralValueMatches(field, config, key) {
108
114
  if (config[key] === undefined) {
@@ -110,7 +116,18 @@ function structuralValueMatches(field, config, key) {
110
116
  }
111
117
  return JSON.stringify(field[key]) === JSON.stringify(config[key]);
112
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
+ }
113
126
  function addConfiguredScalarField(config, path, diagnostics) {
127
+ if (config.json !== undefined) {
128
+ refuseJsonShape(path, "is not a field of the adapter model", diagnostics);
129
+ return undefined;
130
+ }
114
131
  const missing = ["type", "nullable", "list"].filter((key) => config[key] === undefined);
115
132
  if (missing.length > 0) {
116
133
  diagnostics.push(diagnostic("COMPILER_INCOMPLETE_FIELD", path, `framework-added scalar field is missing required properties: ${missing.join(", ")}`));
@@ -120,7 +137,7 @@ function addConfiguredScalarField(config, path, diagnostics) {
120
137
  if (configuredLifecycle === undefined ||
121
138
  !configuredLifecycle.includes("VIRTUAL") ||
122
139
  configuredLifecycle.some((trait) => trait !== "VIRTUAL")) {
123
- 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"));
124
141
  return undefined;
125
142
  }
126
143
  return {
@@ -132,86 +149,38 @@ function addConfiguredScalarField(config, path, diagnostics) {
132
149
  lifecycle: [...(config.lifecycle ?? [])],
133
150
  capabilities: {
134
151
  select: config.capabilities?.select ?? true,
135
- ...(config.capabilities?.filter !== undefined
136
- ? { filter: [...config.capabilities.filter] }
137
- : {}),
138
- ...(config.capabilities?.order !== undefined
139
- ? { order: config.capabilities.order }
140
- : {}),
141
- ...(config.capabilities?.orderNulls !== undefined
142
- ? { orderNulls: config.capabilities.orderNulls }
143
- : {}),
144
- ...(config.capabilities?.create !== undefined
145
- ? { create: [...config.capabilities.create] }
146
- : {}),
147
- ...(config.capabilities?.update !== undefined
148
- ? { update: [...config.capabilities.update] }
149
- : {}),
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"),
150
157
  },
151
158
  };
152
159
  }
153
- export function capabilityAddsBaseline(fieldKind, baseline, key, configured) {
154
- const axis = capabilityAxisFor(fieldKind, key);
155
- if (axis === undefined) {
156
- return false;
157
- }
158
- const baselineValue = baseline[key];
159
- switch (axis.kind) {
160
- case "flag":
161
- return configured === true && baselineValue !== true;
162
- case "tokens": {
163
- if (!isTokenList(configured)) {
164
- return false;
165
- }
166
- const advertised = isTokenList(baselineValue) ? baselineValue : [];
167
- return configured.some((token) => !advertised.includes(token));
168
- }
169
- case "scopes": {
170
- const claimed = configured === true
171
- ? axis.axis
172
- : isTokenList(configured)
173
- ? configured
174
- : undefined;
175
- return (claimed?.some((position) => axis.axis.includes(position) &&
176
- !holdsAt(baselineValue, position)) === true);
177
- }
178
- case "scopedTokens": {
179
- const claim = scopedTokenClaim(axis, configured);
180
- if (claim === undefined) {
181
- return false;
182
- }
183
- return claim.positions.some((position) => {
184
- if (!axis.axis.includes(position)) {
185
- return false;
186
- }
187
- const advertised = advertisedTokensAt(axis.tokenKey, baselineValue, position);
188
- return (advertised === undefined ||
189
- claim.tokens.some((token) => !advertised.includes(token)));
190
- });
191
- }
192
- }
160
+ function fieldLabel(path) {
161
+ const [, resource, , field] = path.split(".").slice(1);
162
+ return `${resource}.${field}`;
193
163
  }
194
- function scopedTokenClaim(axis, configured) {
195
- if (isTokenList(configured)) {
196
- return { tokens: configured, positions: axis.axis };
164
+ function refuseJsonShape(path, reason, diagnostics) {
165
+ diagnostics.push(diagnostic("COMPILER_INVALID_JSON_SHAPE", `${path}.json`, `field "${fieldLabel(path)}" ${reason}; only a Json field the adapter discovered may declare a "json" shape`));
166
+ }
167
+ function applyJsonShape(field, baselineField, shape, path, diagnostics) {
168
+ const type = baselineField.type;
169
+ if (type.scalar !== "json") {
170
+ refuseJsonShape(path, `is ${type.scalar === undefined ? "an enum" : `${/^[aeiou]/.test(type.scalar) ? "an" : "a"} ${type.scalar}`} field`, diagnostics);
171
+ return;
197
172
  }
198
- if (isRecord(configured) &&
199
- isTokenList(configured[axis.tokenKey]) &&
200
- isTokenList(configured.scopes)) {
201
- return {
202
- tokens: configured[axis.tokenKey],
203
- positions: configured.scopes,
204
- };
173
+ const runtime = jsonShapeOf(shape);
174
+ if (runtime === undefined) {
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`));
176
+ return;
205
177
  }
206
- return undefined;
207
- }
208
- function advertisedTokensAt(tokenKey, baselineValue, position) {
209
- if (!isTokenList(baselineValue) && !isRecord(baselineValue)) {
210
- return undefined;
178
+ if (runtime.validate(null).length === 0 ||
179
+ runtime.validate(undefined).length === 0) {
180
+ diagnostics.push(diagnostic("COMPILER_INVALID_JSON_SHAPE", `${path}.json`, `field "${fieldLabel(path)}": a field's shape cannot admit null or absence at its top level; whether the field may be null comes from the adapter model`));
181
+ return;
211
182
  }
212
- return tokenKey === "directives"
213
- ? advertisedDirectivesAt(baselineValue, position)
214
- : advertisedReducersAt(baselineValue, position);
183
+ field.json = cloneValue(runtime.schema);
215
184
  }
216
185
  function applyExistingScalarConfig(field, baselineField, config, path, diagnostics) {
217
186
  for (const key of ["type", "nullable", "list"]) {
@@ -220,98 +189,48 @@ function applyExistingScalarConfig(field, baselineField, config, path, diagnosti
220
189
  }
221
190
  }
222
191
  if (config.format !== undefined) {
223
- 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"));
224
193
  }
225
- if (config.lifecycle !== undefined) {
226
- diagnostics.push(diagnostic("COMPILER_INVALID_LIFECYCLE", `${path}.lifecycle`, "existing adapter field lifecycle cannot be redefined by framework field configuration"));
227
- }
228
- if (config.capabilities !== undefined) {
229
- const current = field.capabilities;
230
- const baseline = baselineField.capabilities;
231
- for (const [key, value] of Object.entries(config.capabilities)) {
232
- if (capabilityAddsBaseline(baselineField.kind, baseline, key, value)) {
233
- diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_CAPABILITY", `${path}.capabilities.${key}`, `capability "${key}" is not available in the canonical baseline`));
234
- continue;
235
- }
236
- current[key] = cloneValue(value);
237
- }
238
- }
239
- }
240
- function applyFrameworkOwnedScalarConfig(field, config, path, diagnostics) {
241
- for (const key of ["type", "nullable", "list"]) {
242
- if (config[key] !== undefined) {
243
- field[key] = cloneValue(config[key]);
244
- }
245
- }
246
- if (config.format !== undefined) {
247
- field.format = config.format;
194
+ if (config.json !== undefined) {
195
+ applyJsonShape(field, baselineField, config.json, path, diagnostics);
248
196
  }
249
197
  if (config.lifecycle !== undefined) {
250
- const configuredLifecycle = config.lifecycle;
251
- if (!configuredLifecycle.includes("VIRTUAL") ||
252
- configuredLifecycle.some((trait) => trait !== "VIRTUAL")) {
253
- diagnostics.push(diagnostic("COMPILER_INVALID_LIFECYCLE", `${path}.lifecycle`, "framework-owned scalar field may declare only VIRTUAL lifecycle; computed lifecycle comes from behaviors"));
254
- }
255
- else {
256
- field.lifecycle = [...configuredLifecycle];
257
- }
198
+ diagnostics.push(diagnostic("COMPILER_INVALID_LIFECYCLE", `${path}.lifecycle`, "existing adapter field lifecycle cannot be redefined by framework field configuration"));
258
199
  }
259
200
  if (config.capabilities !== undefined) {
260
- const current = field.capabilities;
261
- for (const [key, value] of Object.entries(config.capabilities)) {
262
- current[key] = cloneValue(value);
263
- }
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"));
264
202
  }
265
203
  }
266
- function applyFieldConfig(model, projection, scope, config, diagnostics, allowRestrictedMissing = false) {
267
- if (config === undefined) {
268
- return;
269
- }
204
+ function applyFieldDefinitions(model, projection, definitions, diagnostics) {
270
205
  const providerBaseline = createBaseline(model);
271
- for (const [resourceName, resourceConfig] of Object.entries(config)) {
272
- if (resourceConfig === undefined) {
273
- continue;
274
- }
206
+ for (const [resourceName, resourceDefinitions] of Object.entries(definitions)) {
275
207
  const resource = projection.resources[resourceName];
276
208
  const providerResource = providerBaseline.resources[resourceName];
277
209
  if (resource === undefined || providerResource === undefined) {
278
- if (allowRestrictedMissing && providerResource !== undefined) {
279
- continue;
280
- }
281
- diagnostics.push(diagnostic("COMPILER_UNKNOWN_RESOURCE", `${scope}.resources.${resourceName}`, `resource "${resourceName}" is not present in the canonical adapter model`));
282
210
  continue;
283
211
  }
284
- for (const [fieldName, rawConfig] of Object.entries(resourceConfig)) {
285
- const fieldConfig = rawConfig;
286
- const path = scalarFieldPath(scope, resourceName, fieldName);
212
+ for (const [fieldName, definition] of Object.entries(resourceDefinitions)) {
213
+ const path = definitionPath(resourceName, fieldName);
287
214
  const existing = resource.fields[fieldName];
288
215
  const providerField = providerResource.fields[fieldName];
289
216
  if (existing === undefined) {
290
- if (allowRestrictedMissing && providerField !== undefined) {
291
- continue;
292
- }
293
- const added = addConfiguredScalarField(fieldConfig, path, diagnostics);
217
+ const added = addConfiguredScalarField(definition, path, diagnostics);
294
218
  if (added !== undefined) {
295
219
  resource.fields[fieldName] = added;
296
220
  }
297
221
  continue;
298
222
  }
299
223
  if (existing.kind === "relation") {
300
- 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"));
301
225
  continue;
302
226
  }
303
227
  if (providerField?.kind === "scalar") {
304
- applyExistingScalarConfig(existing, providerField, fieldConfig, path, diagnostics);
305
- continue;
228
+ applyExistingScalarConfig(existing, providerField, definition, path, diagnostics);
306
229
  }
307
- applyFrameworkOwnedScalarConfig(existing, fieldConfig, path, diagnostics);
308
230
  }
309
231
  }
310
232
  }
311
- function isActiveBehaviorStage(value) {
312
- return value !== undefined && value !== false;
313
- }
314
- function readBehaviorDependencies(value) {
233
+ function readComputeDependencies(value) {
315
234
  if (!isRecord(value) || typeof value.compute !== "function") {
316
235
  return undefined;
317
236
  }
@@ -319,54 +238,114 @@ function readBehaviorDependencies(value) {
319
238
  ? value.dependsOn.filter((entry) => typeof entry === "string")
320
239
  : undefined;
321
240
  }
322
- function applyFieldBehavior(resource, fieldName, behavior, path, diagnostics, allowRestrictedMissing = false) {
323
- const field = resource.fields[fieldName];
324
- if (field === undefined || field.kind !== "scalar") {
325
- if (allowRestrictedMissing && field === undefined) {
326
- 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;
327
263
  }
328
- diagnostics.push(diagnostic("COMPILER_UNKNOWN_FIELD", path, `behavior field "${fieldName}" must resolve to a scalar field`));
329
- return;
330
- }
331
- const lifecycle = field.lifecycle.filter((trait) => !COMPUTED_TRAITS.has(trait));
332
- if (isActiveBehaviorStage(behavior.create)) {
333
- lifecycle.push("COMPUTED_ON_CREATE");
334
264
  }
335
- if (isActiveBehaviorStage(behavior.update)) {
336
- 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;
337
277
  }
338
- if (isActiveBehaviorStage(behavior.read)) {
339
- 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;
340
281
  }
341
- field.lifecycle = lifecycle;
342
- for (const dependency of readBehaviorDependencies(behavior.read) ?? []) {
343
- const dependencyField = resource.fields[dependency];
344
- if (dependencyField === undefined || dependencyField.kind !== "scalar") {
345
- 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}"`));
346
285
  }
347
286
  }
348
287
  }
349
- function applyBehaviors(projection, scope, config, diagnostics, allowRestrictedMissing = false) {
350
- if (config === undefined) {
288
+ function checkResourceTree(model, baseline, scope, tree, definitions, diagnostics) {
289
+ if (!isRecord(tree)) {
351
290
  return;
352
291
  }
353
- for (const [resourceName, rawResourceConfig] of Object.entries(config)) {
354
- 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`));
355
297
  continue;
356
298
  }
357
- const resource = projection.resources[resourceName];
358
- if (resource === undefined) {
359
- if (allowRestrictedMissing) {
360
- 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
+ }
361
321
  }
362
- 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)) {
363
324
  continue;
364
325
  }
365
- for (const [fieldName, behavior] of Object.entries(rawResourceConfig.fields)) {
366
- 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`));
367
331
  continue;
368
332
  }
369
- 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`));
370
349
  }
371
350
  }
372
351
  }
@@ -391,6 +370,7 @@ const RELATION_CAPABILITY_AXES = {
391
370
  },
392
371
  create: { kind: "scopedTokens", tokenKey: "directives", axis: WRITE_SCOPES },
393
372
  update: { kind: "scopedTokens", tokenKey: "directives", axis: WRITE_SCOPES },
373
+ nestedWriteFilter: { kind: "limit", values: ["scalar"] },
394
374
  };
395
375
  function capabilityAxisFor(kind, key) {
396
376
  const axes = kind === "scalar" ? SCALAR_CAPABILITY_AXES : RELATION_CAPABILITY_AXES;
@@ -399,222 +379,366 @@ function capabilityAxisFor(kind, key) {
399
379
  function unavailableCapability(path, message, diagnostics) {
400
380
  diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_CAPABILITY", path, message));
401
381
  }
402
- function narrowedTokens(baselineTokens, requested, key, path, diagnostics) {
403
- const unavailable = requested.filter((entry) => !baselineTokens.includes(entry));
404
- if (unavailable.length > 0) {
405
- unavailableCapability(path, `capability "${key}" requested unavailable values: ${unavailable.join(", ")}`, diagnostics);
406
- return undefined;
407
- }
408
- return [...requested];
382
+ function otherOverlay(layer) {
383
+ return layer === "client" ? "application" : "client";
409
384
  }
410
- function narrowedScopes(axis, holdsAtScope, requested, key, path, diagnostics) {
411
- if (requested.length === 0) {
412
- return { outcome: "denied" };
413
- }
414
- const unavailable = requested.filter((scope) => !axis.includes(scope) || !holdsAtScope(scope));
415
- if (unavailable.length > 0) {
416
- unavailableCapability(path, `capability "${key}" requested unavailable scopes: ${unavailable.join(", ")}`, diagnostics);
417
- 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 [];
418
401
  }
419
- return { outcome: "narrowed", scopes: [...requested] };
402
+ return entry.flatMap((name, index) => typeof name === "string"
403
+ ? [[name, `${path}[${index}]`]]
404
+ : []);
420
405
  }
421
- function applyScopeRestriction(axis, baselineValue, current, key, requested, path, diagnostics) {
422
- const baseline = baselineValue;
423
- const narrowing = narrowedScopes(axis, (scope) => holdsAt(baseline, scope), requested, key, path, diagnostics);
424
- if (narrowing.outcome === "refused") {
425
- return;
406
+ function memberOffered(axis, value) {
407
+ if (value === undefined || value === false) {
408
+ return false;
426
409
  }
427
- if (narrowing.outcome === "denied") {
428
- delete current[key];
429
- 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;
430
419
  }
431
- current[key] = narrowing.scopes;
432
420
  }
433
- function applyScopedTokenRestriction(tokenKey, axis, baselineValue, current, key, value, path, diagnostics) {
434
- const requestedTokens = Array.isArray(value)
435
- ? value
436
- : isRecord(value)
437
- ? value[tokenKey]
438
- : undefined;
439
- const requestedScopes = isRecord(value) ? value.scopes : undefined;
440
- if ((requestedTokens !== undefined && !Array.isArray(requestedTokens)) ||
441
- (requestedScopes !== undefined && !Array.isArray(requestedScopes))) {
442
- return false;
421
+ function memberTokens(axis, value) {
422
+ if (axis.kind === "tokens") {
423
+ return Array.isArray(value) ? value : undefined;
443
424
  }
444
- const baselineTokens = tokenKey === "directives"
445
- ? advertisedDirectives(baselineValue)
446
- : advertisedReducers(baselineValue);
447
- if (baselineTokens === undefined) {
448
- unavailableCapability(path, `capability "${key}" is not available in the canonical baseline`, diagnostics);
449
- return true;
425
+ if (axis.kind === "scopedTokens") {
426
+ return axis.tokenKey === "directives"
427
+ ? advertisedDirectives(value)
428
+ : advertisedReducers(value);
450
429
  }
451
- const tokens = requestedTokens === undefined
452
- ? [...baselineTokens]
453
- : narrowedTokens(baselineTokens, requestedTokens, key, path, diagnostics);
454
- if (tokens === undefined) {
455
- 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
+ };
456
440
  }
457
- if (requestedScopes === undefined) {
458
- const baselineScopes = isRecord(baselineValue) && Array.isArray(baselineValue.scopes)
459
- ? [...baselineValue.scopes]
460
- : undefined;
461
- current[key] =
462
- baselineScopes === undefined
463
- ? tokens
464
- : { [tokenKey]: tokens, scopes: baselineScopes };
465
- 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;
466
456
  }
467
- const narrowing = narrowedScopes(axis, (scope) => (tokenKey === "directives"
468
- ? advertisedDirectivesAt(baselineValue, scope)
469
- : advertisedReducersAt(baselineValue, scope)) !== undefined, requestedScopes, key, path, diagnostics);
470
- if (narrowing.outcome === "refused") {
471
- 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;
472
461
  }
473
- if (narrowing.outcome === "denied") {
474
- delete current[key];
475
- 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);
476
473
  }
477
- current[key] = { [tokenKey]: tokens, scopes: narrowing.scopes };
478
- return true;
474
+ return tokens;
479
475
  }
480
- function applyCapabilityRestriction(fieldKind, inherited, restoration, current, key, value, path, diagnostics) {
481
- if (value === false) {
482
- delete current[key];
476
+ function exemptCapability(walk, universeField, parentCapabilities, result, capability, entry, label, path) {
477
+ if (entry === undefined) {
483
478
  return;
484
479
  }
485
- const axis = capabilityAxisFor(fieldKind, key);
486
- const baselineValue = inherited?.[key] ?? restoration[key];
487
- if (axis === undefined ||
488
- baselineValue === undefined ||
489
- baselineValue === false) {
490
- unavailableCapability(path, `capability "${key}" is not available in the canonical baseline`, diagnostics);
480
+ const axis = exemptableAxis(walk, universeField, capability, label, path);
481
+ if (axis === undefined || entry === false) {
491
482
  return;
492
483
  }
493
- if (value === true) {
494
- current[key] = cloneValue(baselineValue);
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);
495
489
  return;
496
490
  }
497
- if (axis.kind === "tokens" &&
498
- Array.isArray(value) &&
499
- Array.isArray(baselineValue)) {
500
- const tokens = narrowedTokens(baselineValue, value, key, path, diagnostics);
501
- if (tokens !== undefined) {
502
- current[key] = tokens;
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) {
496
+ return;
497
+ }
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;
503
507
  }
508
+ const kept = (memberTokens(axis, parentValue) ?? []).filter((token) => !tokens.includes(token));
509
+ result[capability] = withTokens(axis, parentValue, kept);
504
510
  return;
505
511
  }
506
- if (axis.kind === "scopes" && Array.isArray(value)) {
507
- applyScopeRestriction(axis.axis, baselineValue, current, key, value, path, diagnostics);
512
+ if (tokens !== undefined && tokens.length === 0) {
508
513
  return;
509
514
  }
510
- if (axis.kind === "scopedTokens") {
511
- if (applyScopedTokenRestriction(axis.tokenKey, axis.axis, baselineValue, current, key, value, path, diagnostics)) {
512
- return;
513
- }
515
+ if (!parentHas) {
516
+ widening(walk, "COMPILER_UNAVAILABLE_CAPABILITY", path, `capability "${capability}" of field ${label}`);
517
+ return;
514
518
  }
515
- unavailableCapability(path, `capability "${key}" does not admit this restriction shape`, diagnostics);
516
- }
517
- function applyCapabilityRestrictions(inheritedField, restorationField, field, restrictions, path, diagnostics) {
518
- const capabilityRestrictions = restrictions.capabilities;
519
- if (capabilityRestrictions === undefined) {
519
+ if (tokens === undefined) {
520
+ result[capability] = cloneValue(parentValue);
520
521
  return;
521
522
  }
522
- const inherited = inheritedField?.capabilities;
523
- const restoration = restorationField.capabilities;
524
- const current = field.capabilities;
525
- for (const [key, value] of Object.entries(capabilityRestrictions)) {
526
- if (value === undefined) {
527
- 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);
528
544
  }
529
- applyCapabilityRestriction(restorationField.kind, inherited, restoration, current, key, value, `${path}.capabilities.${key}`, diagnostics);
530
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 };
531
559
  }
532
- function applyOperationRestrictions(restoration, current, restrictions, path, diagnostics) {
533
- if (restrictions === undefined) {
534
- 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
+ }
535
566
  }
536
- for (const [family, variants] of Object.entries(restrictions)) {
537
- 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) {
538
571
  continue;
539
572
  }
540
- const baselineFamily = restoration[family];
541
- let currentFamily = current[family];
542
- if (!isRecord(currentFamily)) {
543
- currentFamily = {};
544
- 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;
545
581
  }
546
- for (const [variant, enabled] of Object.entries(variants)) {
547
- if (typeof enabled !== "boolean") {
548
- continue;
549
- }
550
- if (!enabled) {
551
- delete currentFamily[variant];
552
- 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"));
553
620
  }
554
- const baselineAvailability = isRecord(baselineFamily)
555
- ? baselineFamily[variant]
556
- : undefined;
557
- if (!isOperationVariantAvailable(baselineAvailability)) {
558
- diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", `${path}.${family}.${variant}`, `operation "${family}.${variant}" is not available in the canonical baseline`));
621
+ if (value !== true) {
559
622
  continue;
560
623
  }
561
- const target = currentFamily;
562
- if (!isOperationVariantAvailable(target[variant])) {
563
- target[variant] = cloneValue(baselineAvailability);
564
- }
565
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);
566
631
  }
632
+ return variants;
567
633
  }
568
- function applyRestrictions(baselines, projection, scope, config, diagnostics) {
569
- if (config === undefined) {
570
- return;
571
- }
572
- for (const [resourceName, rawRestrictions] of Object.entries(config)) {
573
- 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}"`));
574
649
  continue;
575
650
  }
576
- const restrictions = rawRestrictions;
577
- const baselineResource = baselines.restoration.resources[resourceName];
578
- if (baselineResource === undefined) {
579
- 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) {
580
652
  continue;
581
653
  }
582
- if (restrictions.hidden === true) {
583
- 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"));
584
656
  continue;
585
657
  }
586
- if (restrictions.hidden === false &&
587
- projection.resources[resourceName] === undefined) {
588
- 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;
589
664
  }
590
- const resource = projection.resources[resourceName];
591
- if (resource === undefined) {
665
+ const variants = exemptVariants(walk, resourceName, family, universeFamily, parentFamily, value, familyPath);
666
+ if (parentFamily === undefined) {
592
667
  continue;
593
668
  }
594
- for (const [fieldName, rawFieldRestrictions] of Object.entries(restrictions.fields ?? {})) {
595
- const fieldRestrictions = rawFieldRestrictions;
596
- const baselineField = baselineResource.fields[fieldName];
597
- const path = `${scope}.resources.${resourceName}.fields.${fieldName}`;
598
- if (baselineField === undefined) {
599
- diagnostics.push(diagnostic("COMPILER_UNKNOWN_FIELD", path, `field "${fieldName}" is not present in the canonical compilation baseline`));
600
- continue;
601
- }
602
- if (fieldRestrictions.hidden === true) {
603
- delete resource.fields[fieldName];
604
- continue;
605
- }
606
- if (fieldRestrictions.hidden === false &&
607
- resource.fields[fieldName] === undefined) {
608
- 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
+ }
609
676
  }
610
- const field = resource.fields[fieldName];
611
- if (field === undefined) {
612
- 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]);
613
683
  }
614
- applyCapabilityRestrictions(baselines.narrowing.resources[resourceName]?.fields[fieldName], baselineField, field, fieldRestrictions, path, diagnostics);
615
684
  }
616
- applyOperationRestrictions(baselineResource.operations, resource.operations, restrictions.operations, `${scope}.resources.${resourceName}.operations`, diagnostics);
685
+ result[family] = target;
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
+ }
617
740
  }
741
+ return { ...cloneValue({ ...parent, resources: {} }), resources };
618
742
  }
619
743
  function pruneEmptyOperationFamilies(operations) {
620
744
  for (const [family, variants] of Object.entries(operations)) {
@@ -623,45 +747,28 @@ function pruneEmptyOperationFamilies(operations) {
623
747
  }
624
748
  }
625
749
  }
626
- function operationIsExplicitlyDisabled(restrictions, family, variant) {
627
- if (restrictions === undefined || !isRecord(restrictions.operations)) {
628
- return false;
629
- }
630
- const familyRestrictions = restrictions.operations[family];
631
- return isRecord(familyRestrictions) && familyRestrictions[variant] === false;
750
+ function identifierLabel(identifier) {
751
+ return identifier.map((name) => `\`${name}\``).join(" and ");
632
752
  }
633
- function reconcileIdentifiers(resource, baseline, restrictions) {
634
- resource.identifiers = baseline.identifiers.filter((identifier) => identifier.every((fieldName) => {
635
- const field = resource.fields[fieldName];
636
- return field !== undefined && field.kind === "scalar";
637
- }));
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
+ }
638
761
  for (const [family, variant] of UNIQUE_VARIANTS) {
639
762
  const currentFamily = resource.operations[family];
640
- if (resource.identifiers.length === 0) {
641
- if (isRecord(currentFamily)) {
642
- delete currentFamily[variant];
643
- }
763
+ if (!isRecord(currentFamily) ||
764
+ !isOperationVariantAvailable(currentFamily[variant])) {
644
765
  continue;
645
766
  }
646
- if (operationIsExplicitlyDisabled(restrictions, family, variant)) {
647
- if (isRecord(currentFamily)) {
648
- delete currentFamily[variant];
649
- }
650
- continue;
651
- }
652
- const baselineFamily = baseline.operations[family];
653
- const baselineAvailability = isRecord(baselineFamily)
654
- ? baselineFamily[variant]
655
- : undefined;
656
- if (isOperationVariantAvailable(baselineAvailability)) {
657
- const targetFamily = isRecord(currentFamily) ? currentFamily : {};
658
- if (!isOperationVariantAvailable(targetFamily[variant])) {
659
- targetFamily[variant] = cloneValue(baselineAvailability);
660
- }
661
- resource.operations[family] = targetFamily;
662
- }
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`));
663
771
  }
664
- pruneEmptyOperationFamilies(resource.operations);
665
772
  }
666
773
  function validateScalarReference(projection, field, path, diagnostics) {
667
774
  const type = field.type;
@@ -711,7 +818,7 @@ function validateFieldLifecycle(field, path, diagnostics) {
711
818
  if (lifecycle.includes("VIRTUAL") &&
712
819
  holdsInAnyScope(capabilities.select) &&
713
820
  !lifecycle.includes("COMPUTED_ON_READ")) {
714
- 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)"));
715
822
  }
716
823
  }
717
824
  function isTokenList(value) {
@@ -728,6 +835,10 @@ function positionListFault(axis, positions) {
728
835
  }
729
836
  function representationFault(axis, value) {
730
837
  switch (axis.kind) {
838
+ case "limit":
839
+ return typeof value === "string" && axis.values.includes(value)
840
+ ? undefined
841
+ : `one of ${axis.values.join(", ")} is expected`;
731
842
  case "flag":
732
843
  return typeof value === "boolean" ? undefined : "a boolean is expected";
733
844
  case "tokens":
@@ -765,12 +876,8 @@ function validateCapabilityRepresentation(field, path, diagnostics) {
765
876
  }
766
877
  }
767
878
  }
768
- function validateProjection(projection, restoration, scope, restrictions, diagnostics) {
879
+ function validateProjection(projection, scope, diagnostics) {
769
880
  for (const [resourceName, resource] of Object.entries(projection.resources)) {
770
- const baselineResource = restoration.resources[resourceName];
771
- if (baselineResource !== undefined) {
772
- reconcileIdentifiers(resource, baselineResource, restrictions?.[resourceName]);
773
- }
774
881
  for (const [fieldName, field] of Object.entries(resource.fields)) {
775
882
  const fieldPath = `${scope}.resources.${resourceName}.fields.${fieldName}`;
776
883
  validateCapabilityRepresentation(field, fieldPath, diagnostics);
@@ -791,73 +898,140 @@ function resolveLimits(scope) {
791
898
  return { ...DEFAULT_CONTRACT_LIMITS, ...(scope.limits ?? {}) };
792
899
  }
793
900
  function resolveTransactions(model, scopeName, scope, diagnostics) {
794
- const requested = scope.transactions ?? model.transactions;
901
+ const requested = scope.transactionsStyle ?? model.transactions;
795
902
  if (requested === "interactive" && model.transactions === "none") {
796
- 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'));
797
904
  }
798
905
  return requested;
799
906
  }
800
907
  function semanticObject(contract) {
801
908
  return JSON.parse(canonicalizeContract(contract));
802
909
  }
803
- 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;
804
915
  const providerBaseline = createBaseline(model);
805
916
  const baseline = cloneValue(providerBaseline);
806
917
  baseline.limits = resolveLimits(scope);
807
918
  baseline.transactions = resolveTransactions(model, "root", scope, diagnostics);
808
- applyFieldConfig(model, baseline, "root", scope.fields, diagnostics);
809
- applyBehaviors(baseline, "root", scope.behaviors, diagnostics);
810
- const projection = cloneValue(baseline);
811
- applyRestrictions({ narrowing: baseline, restoration: baseline }, projection, "root", scope.restrictions, diagnostics);
812
- validateProjection(projection, baseline, "root", scope.restrictions, diagnostics);
813
- return { baseline, projection };
814
- }
815
- 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];
816
933
  const baseline = cloneValue(core.baseline);
817
934
  baseline.limits = resolveLimits(scope);
818
- baseline.transactions = resolveTransactions(model, scopeName, scope, diagnostics);
819
- applyFieldConfig(model, baseline, scopeName, scope.fields, diagnostics);
820
- applyBehaviors(baseline, scopeName, scope.behaviors, diagnostics);
821
- const projection = cloneValue(core.projection);
822
- projection.limits = resolveLimits(scope);
823
- projection.transactions = baseline.transactions;
824
- applyFieldConfig(model, projection, scopeName, scope.fields, diagnostics, true);
825
- applyBehaviors(projection, scopeName, scope.behaviors, diagnostics, true);
826
- applyRestrictions({ narrowing: cloneValue(projection), restoration: baseline }, projection, scopeName, scope.restrictions, diagnostics);
827
- validateProjection(projection, baseline, scopeName, scope.restrictions, diagnostics);
828
- return projection;
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) {
960
+ for (const [resourceName, resource] of Object.entries(contract.resources)) {
961
+ for (const [fieldName, field] of Object.entries(resource.fields)) {
962
+ if (field.kind !== "scalar" || field.json === undefined) {
963
+ continue;
964
+ }
965
+ const shape = jsonShapeOf(definitions[resourceName]?.[fieldName]?.json);
966
+ if (shape === undefined) {
967
+ throw new Error(`the Contract compiler found no shape for shaped field "${resourceName}.${fieldName}"`);
968
+ }
969
+ bindShapedField(field, shape);
970
+ }
971
+ }
829
972
  }
830
973
  export async function compileContracts(model, config) {
831
974
  const compilerConfig = config;
832
975
  const diagnostics = [];
833
- const coreCompilation = projectCore(model, compilerConfig.root, diagnostics);
834
- const applicationProjection = projectDerivedScope(model, "application", coreCompilation, compilerConfig.application, diagnostics);
835
- 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);
836
987
  if (diagnostics.length > 0) {
837
988
  return { ok: false, diagnostics };
838
989
  }
839
990
  const core = semanticObject(coreCompilation.projection);
840
- const application = semanticObject(applicationProjection);
841
- const clientHashInput = semanticObject({
842
- ...clientProjection,
843
- protocol: { version: AVENTARA_PROTOCOL_VERSION },
844
- });
845
- const hash = await computeClientContractHash(clientHashInput);
846
- const client = semanticObject({
847
- ...clientHashInput,
848
- protocol: { version: AVENTARA_PROTOCOL_VERSION, hash },
849
- });
850
- const structure = validateClientContractStructure(client);
851
- if (!structure.valid) {
852
- return {
853
- ok: false,
854
- diagnostics: [
855
- diagnostic("COMPILER_INVALID_CLIENT_CONTRACT", structure.path, structure.message),
856
- ],
857
- };
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
+ }
858
1009
  }
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);
859
1018
  return {
860
1019
  ok: true,
861
- contracts: { core, application, client },
1020
+ contracts: { core, application, client, agent },
862
1021
  };
863
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
+ }