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

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 (267) hide show
  1. package/CHANGELOG.md +1434 -0
  2. package/ERRORS.md +142 -0
  3. package/README.md +247 -57
  4. package/dist/adapters/adapter-discovery-diagnostic.d.ts +4 -4
  5. package/dist/adapters/adapter-discovery-diagnostic.guidance.d.ts +6 -0
  6. package/dist/adapters/adapter-discovery-diagnostic.guidance.js +7 -0
  7. package/dist/adapters/adapter-field.d.ts +25 -11
  8. package/dist/adapters/adapter-identifier.d.ts +6 -6
  9. package/dist/adapters/adapter-model.d.ts +18 -9
  10. package/dist/adapters/adapter-operation-executor.interface.d.ts +4 -4
  11. package/dist/adapters/adapter-operation.d.ts +3 -3
  12. package/dist/adapters/adapter-resource.d.ts +18 -9
  13. package/dist/adapters/adapter-sql-statement.d.ts +19 -0
  14. package/dist/adapters/adapter-transaction-executor.interface.d.ts +19 -0
  15. package/dist/adapters/adapter.interface.d.ts +59 -29
  16. package/dist/avz/avz-check.factory.d.ts +83 -0
  17. package/dist/avz/avz-check.factory.js +116 -0
  18. package/dist/avz/avz-check.registry.d.ts +35 -0
  19. package/dist/avz/avz-check.registry.js +9 -0
  20. package/dist/avz/avz.d.ts +203 -0
  21. package/dist/{json → avz}/avz.js +150 -8
  22. package/dist/{json → avz}/index.d.ts +2 -1
  23. package/dist/{json → avz}/json-shape.registry.d.ts +7 -7
  24. package/dist/{json → avz}/zod-issue.translator.d.ts +8 -2
  25. package/dist/{json → avz}/zod-issue.translator.js +7 -0
  26. package/dist/compiler/contract-compiler-diagnostic.d.ts +31 -5
  27. package/dist/compiler/contract-compiler-diagnostic.guidance.d.ts +6 -0
  28. package/dist/compiler/contract-compiler-diagnostic.guidance.js +97 -0
  29. package/dist/compiler/contract.compiler.d.ts +18 -18
  30. package/dist/compiler/contract.compiler.js +637 -442
  31. package/dist/compiler/create-requirements.projector.d.ts +49 -0
  32. package/dist/compiler/create-requirements.projector.js +423 -0
  33. package/dist/compiler/field-validator.compiler.d.ts +47 -0
  34. package/dist/compiler/field-validator.compiler.js +238 -0
  35. package/dist/config/any-framework-config.d.ts +19 -12
  36. package/dist/config/computed-fields.d.ts +62 -0
  37. package/dist/config/computed-fields.js +80 -0
  38. package/dist/config/config-diagnostic.d.ts +11 -4
  39. package/dist/config/config-diagnostic.guidance.d.ts +6 -0
  40. package/dist/config/config-diagnostic.guidance.js +32 -0
  41. package/dist/config/config-layer.d.ts +15 -6
  42. package/dist/config/config.resolver.d.ts +74 -31
  43. package/dist/config/config.resolver.js +108 -44
  44. package/dist/config/config.validator.d.ts +7 -7
  45. package/dist/config/config.validator.js +581 -72
  46. package/dist/config/define-resource.d.ts +57 -0
  47. package/dist/config/define-resource.js +14 -0
  48. package/dist/config/effective-fields.d.ts +19 -23
  49. package/dist/config/entrypoint.normalizer.d.ts +1 -1
  50. package/dist/config/exact-framework-config.d.ts +308 -0
  51. package/dist/config/field-compute.resolver.d.ts +21 -0
  52. package/dist/config/field-compute.resolver.js +123 -0
  53. package/dist/config/field-pipeline-config.d.ts +47 -0
  54. package/dist/config/forced-operation.validator.d.ts +10 -0
  55. package/dist/config/forced-operation.validator.js +66 -0
  56. package/dist/config/framework-config.d.ts +111 -39
  57. package/dist/config/framework-exemptions-config.d.ts +91 -0
  58. package/dist/config/framework-exemptions-config.js +1 -0
  59. package/dist/config/framework-field-definition.d.ts +78 -0
  60. package/dist/config/framework-field-definition.js +28 -0
  61. package/dist/config/framework-resources-config.d.ts +122 -0
  62. package/dist/config/framework-resources-config.js +1 -0
  63. package/dist/config/pipeline-config.d.ts +56 -20
  64. package/dist/config/pipeline-tree.resolver.d.ts +79 -0
  65. package/dist/config/pipeline-tree.resolver.js +220 -0
  66. package/dist/config/stage-type.eraser.d.ts +65 -0
  67. package/dist/config/stage-type.eraser.js +1 -0
  68. package/dist/contracts/capability.d.ts +21 -12
  69. package/dist/contracts/capability.js +2 -0
  70. package/dist/contracts/client-contract-structure.validator.d.ts +388 -63
  71. package/dist/contracts/client-contract-structure.validator.js +156 -10
  72. package/dist/contracts/contract-canonicalizer.d.ts +26 -3
  73. package/dist/contracts/contract-canonicalizer.js +156 -29
  74. package/dist/contracts/contract-hash.d.ts +12 -7
  75. package/dist/contracts/contract-hash.js +3 -2
  76. package/dist/contracts/contract-limits.d.ts +4 -1
  77. package/dist/contracts/contract-limits.js +1 -0
  78. package/dist/contracts/contract-lookup.d.ts +5 -5
  79. package/dist/contracts/contract-protocol.d.ts +4 -4
  80. package/dist/contracts/contract.d.ts +24 -16
  81. package/dist/contracts/create-requirements.d.ts +35 -0
  82. package/dist/contracts/create-requirements.js +1 -0
  83. package/dist/contracts/enum-definition.d.ts +1 -1
  84. package/dist/contracts/field-check.d.ts +101 -0
  85. package/dist/contracts/field-check.js +1 -0
  86. package/dist/contracts/field.d.ts +81 -42
  87. package/dist/contracts/identifier.d.ts +4 -4
  88. package/dist/contracts/json-shape.d.ts +12 -1
  89. package/dist/contracts/lifecycle-trait.d.ts +2 -2
  90. package/dist/contracts/resource-operations.d.ts +28 -28
  91. package/dist/contracts/resource.d.ts +15 -9
  92. package/dist/contracts/scalar-value-type.d.ts +15 -15
  93. package/dist/contracts/scalar.d.ts +3 -3
  94. package/dist/contracts/transaction-capability.d.ts +1 -1
  95. package/dist/contracts/wire-scalar-format.d.ts +4 -4
  96. package/dist/framework/contract-view.d.ts +213 -147
  97. package/dist/framework/framework-construction.error.d.ts +19 -10
  98. package/dist/framework/framework-construction.error.js +7 -3
  99. package/dist/framework/framework.d.ts +47 -23
  100. package/dist/framework/framework.factory.d.ts +8 -6
  101. package/dist/framework/framework.factory.js +49 -22
  102. package/dist/framework/operation-facade.d.ts +123 -52
  103. package/dist/framework/operation-facade.js +9 -1
  104. package/dist/index.d.ts +57 -50
  105. package/dist/index.js +7 -6
  106. package/dist/internal.d.ts +17 -10
  107. package/dist/internal.js +4 -1
  108. package/dist/operations/database-refusal.d.ts +51 -0
  109. package/dist/operations/database-refusal.js +45 -0
  110. package/dist/operations/operation-arguments.d.ts +343 -207
  111. package/dist/operations/operation-call.d.ts +27 -28
  112. package/dist/operations/operation-identity.d.ts +10 -10
  113. package/dist/operations/operation-result.d.ts +21 -21
  114. package/dist/operations/operation-validator-core.d.ts +6 -6
  115. package/dist/operations/operation-validator-core.js +99 -6
  116. package/dist/operations/operation-validator.d.ts +9 -9
  117. package/dist/operations/projection-scope.resolver.d.ts +6 -6
  118. package/dist/operations/structural-limits.walker.d.ts +6 -6
  119. package/dist/operations/structural-limits.walker.js +145 -0
  120. package/dist/operations/validation-code.d.ts +11 -6
  121. package/dist/operations/validation-code.guidance.d.ts +6 -0
  122. package/dist/operations/validation-code.guidance.js +90 -0
  123. package/dist/operations/validation-code.js +4 -0
  124. package/dist/operations/write-scope.resolver.d.ts +10 -6
  125. package/dist/operations/write-scope.resolver.js +2 -0
  126. package/dist/protocol/absent-route.responder.d.ts +13 -12
  127. package/dist/protocol/absent-route.responder.js +19 -19
  128. package/dist/protocol/contract-document.responder.d.ts +3 -3
  129. package/dist/protocol/contract-document.responder.js +2 -2
  130. package/dist/protocol/http-request.decoder.d.ts +17 -13
  131. package/dist/protocol/http-request.decoder.js +10 -10
  132. package/dist/protocol/http-request.reader.d.ts +3 -3
  133. package/dist/protocol/http-response.encoder.d.ts +4 -4
  134. package/dist/protocol/http-response.encoder.js +94 -55
  135. package/dist/protocol/http-status.table.d.ts +2 -2
  136. package/dist/protocol/operation-response.guard.d.ts +2 -2
  137. package/dist/protocol/protocol-surface.d.ts +4 -4
  138. package/dist/protocol/protocol.binder.d.ts +50 -27
  139. package/dist/protocol/protocol.binder.js +12 -9
  140. package/dist/protocol/protocol.handler.d.ts +22 -13
  141. package/dist/protocol/protocol.handler.js +91 -17
  142. package/dist/protocol/unmounted-route.error.d.ts +4 -4
  143. package/dist/protocol/unmounted-route.error.js +2 -2
  144. package/dist/runtime/caller-data.copier.d.ts +22 -5
  145. package/dist/runtime/caller-data.copier.js +31 -14
  146. package/dist/runtime/caller-data.door.d.ts +17 -11
  147. package/dist/runtime/caller-data.door.js +6 -4
  148. package/dist/runtime/computed-read-runtime.d.ts +7 -6
  149. package/dist/runtime/computed-read-runtime.js +76 -50
  150. package/dist/runtime/computed-value.error.d.ts +7 -6
  151. package/dist/runtime/computed-value.error.js +5 -4
  152. package/dist/runtime/context-executor.runtime.d.ts +59 -0
  153. package/dist/runtime/context-executor.runtime.js +275 -0
  154. package/dist/runtime/create-requirement.d.ts +44 -79
  155. package/dist/runtime/create-requirement.js +213 -73
  156. package/dist/runtime/decimal.d.ts +2 -2
  157. package/dist/runtime/decimal.js +1 -1
  158. package/dist/runtime/diagnostics-reporter.d.ts +54 -27
  159. package/dist/runtime/diagnostics-reporter.js +24 -0
  160. package/dist/runtime/error-boundary.d.ts +65 -8
  161. package/dist/runtime/error-boundary.js +49 -19
  162. package/dist/runtime/error-guidance.d.ts +12 -0
  163. package/dist/runtime/error-guidance.js +1 -0
  164. package/dist/runtime/error.catalog.d.ts +49 -0
  165. package/dist/runtime/error.catalog.js +49 -0
  166. package/dist/runtime/execution-options.d.ts +10 -2
  167. package/dist/runtime/field-check.evaluator.d.ts +29 -0
  168. package/dist/runtime/field-check.evaluator.js +521 -0
  169. package/dist/runtime/field-compute.d.ts +64 -0
  170. package/dist/runtime/field-compute.js +1 -0
  171. package/dist/runtime/field-stage.plan.d.ts +15 -0
  172. package/dist/runtime/field-stage.plan.js +27 -0
  173. package/dist/runtime/field-stage.runtime.d.ts +37 -0
  174. package/dist/runtime/field-stage.runtime.js +147 -0
  175. package/dist/runtime/field-validator.runtime.d.ts +19 -0
  176. package/dist/runtime/field-validator.runtime.js +92 -0
  177. package/dist/runtime/framework-error-class.table.d.ts +33 -0
  178. package/dist/runtime/framework-error-class.table.js +25 -0
  179. package/dist/runtime/framework-error.d.ts +68 -8
  180. package/dist/runtime/framework-error.factory.d.ts +4 -0
  181. package/dist/runtime/framework-error.factory.js +15 -0
  182. package/dist/runtime/framework-error.js +23 -2
  183. package/dist/runtime/interceptor-next.error.d.ts +5 -0
  184. package/dist/runtime/interceptor-next.error.js +6 -0
  185. package/dist/runtime/json-value.d.ts +11 -6
  186. package/dist/runtime/level-lifecycle.runtime.d.ts +25 -0
  187. package/dist/runtime/level-lifecycle.runtime.js +39 -0
  188. package/dist/runtime/misuse-reporter.d.ts +12 -12
  189. package/dist/runtime/mutation-planner.d.ts +19 -10
  190. package/dist/runtime/mutation-planner.js +48 -35
  191. package/dist/runtime/nested-write.walker.d.ts +32 -0
  192. package/dist/runtime/nested-write.walker.js +73 -0
  193. package/dist/runtime/operation-code.guidance.d.ts +6 -0
  194. package/dist/runtime/operation-code.guidance.js +167 -0
  195. package/dist/runtime/operation-request.d.ts +33 -6
  196. package/dist/runtime/operation-response.d.ts +17 -17
  197. package/dist/runtime/operation-runtime.d.ts +127 -25
  198. package/dist/runtime/operation-runtime.js +441 -78
  199. package/dist/runtime/outcome-mapper.d.ts +16 -5
  200. package/dist/runtime/outcome-mapper.js +23 -6
  201. package/dist/runtime/output-validator.d.ts +10 -10
  202. package/dist/runtime/output-validator.js +3 -3
  203. package/dist/runtime/owned-execution.registry.d.ts +16 -6
  204. package/dist/runtime/pipe-output.validator.d.ts +43 -18
  205. package/dist/runtime/pipe-output.validator.js +15 -13
  206. package/dist/runtime/pipeline.d.ts +223 -34
  207. package/dist/runtime/readonly-value.d.ts +43 -0
  208. package/dist/runtime/readonly-value.js +150 -0
  209. package/dist/runtime/request-envelope.validator.d.ts +3 -3
  210. package/dist/runtime/request-envelope.validator.js +12 -7
  211. package/dist/runtime/request-id.d.ts +1 -1
  212. package/dist/runtime/request-state.d.ts +14 -0
  213. package/dist/runtime/request-state.js +1 -0
  214. package/dist/runtime/request-state.resolver.d.ts +12 -0
  215. package/dist/runtime/request-state.resolver.js +20 -0
  216. package/dist/runtime/scalar-runtime.d.ts +35 -16
  217. package/dist/runtime/scalar-runtime.js +102 -34
  218. package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
  219. package/dist/runtime/unsent-value.normalizer.js +128 -0
  220. package/dist/transactions/deferred-operation.d.ts +35 -20
  221. package/dist/transactions/operation-arguments-encoder.d.ts +3 -3
  222. package/dist/transactions/operation-arguments-encoder.js +2 -2
  223. package/dist/transactions/operation-fingerprint.js +2 -2
  224. package/dist/transactions/sql-statement.d.ts +85 -0
  225. package/dist/transactions/sql-statement.js +47 -0
  226. package/dist/transactions/sql-statement.validator.d.ts +44 -0
  227. package/dist/transactions/sql-statement.validator.js +167 -0
  228. package/dist/transactions/transaction-envelope.d.ts +4 -4
  229. package/dist/transactions/transaction-integrity-error.d.ts +6 -6
  230. package/dist/transactions/transaction-integrity-error.js +5 -2
  231. package/dist/transactions/transaction-plan-assembler.d.ts +21 -11
  232. package/dist/transactions/transaction-plan-assembler.js +169 -32
  233. package/dist/transactions/transaction-plan-validator.d.ts +45 -20
  234. package/dist/transactions/transaction-plan-validator.js +96 -38
  235. package/dist/transactions/transaction-reference-collector.d.ts +5 -5
  236. package/dist/transactions/transaction-reference-resolver.d.ts +19 -2
  237. package/dist/transactions/transaction-reference-resolver.js +26 -14
  238. package/dist/transactions/transaction-reference-validator.d.ts +76 -4
  239. package/dist/transactions/transaction-reference-validator.js +129 -71
  240. package/dist/transactions/transaction-reference.d.ts +13 -13
  241. package/dist/transactions/transaction-reference.js +5 -0
  242. package/dist/transactions/transaction-request.d.ts +18 -5
  243. package/dist/transactions/transaction-response.d.ts +6 -6
  244. package/dist/transactions/transaction-runtime.d.ts +8 -7
  245. package/dist/transactions/transaction-runtime.js +191 -50
  246. package/dist/transactions/transaction-settlement.runtime.d.ts +51 -0
  247. package/dist/transactions/transaction-settlement.runtime.js +120 -0
  248. package/dist/transactions/transaction-slot-path-error.d.ts +4 -4
  249. package/dist/transactions/transaction-slot-path-error.js +2 -2
  250. package/dist/transactions/transaction-slot-writer.js +2 -2
  251. package/dist/transactions/transaction-stage.runtime.d.ts +82 -0
  252. package/dist/transactions/transaction-stage.runtime.js +126 -0
  253. package/package.json +9 -7
  254. package/src/runtime/field-check.evaluator.ts +720 -0
  255. package/dist/config/define-fields.d.ts +0 -42
  256. package/dist/config/define-fields.js +0 -3
  257. package/dist/config/framework-behaviors-config.d.ts +0 -30
  258. package/dist/config/framework-fields-config.d.ts +0 -42
  259. package/dist/config/framework-restrictions-config.d.ts +0 -142
  260. package/dist/json/avz.d.ts +0 -96
  261. package/dist/runtime/field-behavior.d.ts +0 -48
  262. /package/dist/{config/framework-behaviors-config.js → adapters/adapter-sql-statement.js} +0 -0
  263. /package/dist/{config/framework-fields-config.js → adapters/adapter-transaction-executor.interface.js} +0 -0
  264. /package/dist/{json → avz}/index.js +0 -0
  265. /package/dist/{json → avz}/json-shape.registry.js +0 -0
  266. /package/dist/config/{framework-restrictions-config.js → exact-framework-config.js} +0 -0
  267. /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 { FILTER_MODES, 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"],
@@ -39,6 +41,12 @@ function normalizeAdapterScalarCapabilities(capabilities) {
39
41
  ...(capabilities.filter !== undefined
40
42
  ? { filter: [...capabilities.filter] }
41
43
  : {}),
44
+ ...(capabilities.filterModes !== undefined
45
+ ? { filterModes: [...capabilities.filterModes] }
46
+ : {}),
47
+ ...(capabilities.pathFilterModes !== undefined
48
+ ? { pathFilterModes: [...capabilities.pathFilterModes] }
49
+ : {}),
42
50
  ...(capabilities.order !== undefined ? { order: capabilities.order } : {}),
43
51
  ...(capabilities.orderNulls !== undefined
44
52
  ? { orderNulls: capabilities.orderNulls }
@@ -69,8 +77,42 @@ function cloneAdapterField(field) {
69
77
  cardinality: field.cardinality,
70
78
  nullable: field.nullable,
71
79
  capabilities: cloneValue(field.capabilities),
80
+ ...(typeof field.inverse === "string" ? { inverse: field.inverse } : {}),
81
+ ...(Array.isArray(field.referenceFields) && field.referenceFields.length > 0
82
+ ? { referenceFields: [...field.referenceFields] }
83
+ : {}),
72
84
  };
73
85
  }
86
+ function pruneHiddenPairings(projection) {
87
+ const resources = projection.resources;
88
+ for (const [name, resource] of Object.entries(resources)) {
89
+ for (const field of Object.values(resource.fields)) {
90
+ if (field.kind !== "relation") {
91
+ continue;
92
+ }
93
+ if (Array.isArray(field.referenceFields)) {
94
+ const shown = field.referenceFields.filter((reference) => typeof reference === "string" &&
95
+ Object.hasOwn(resource.fields, reference) &&
96
+ resource.fields[reference]?.kind === "scalar");
97
+ if (shown.length > 0) {
98
+ field.referenceFields = shown;
99
+ }
100
+ else {
101
+ delete field.referenceFields;
102
+ }
103
+ }
104
+ if (typeof field.inverse !== "string") {
105
+ continue;
106
+ }
107
+ const other = Object.hasOwn(resources, String(field.target))
108
+ ? resources[String(field.target)]?.fields[field.inverse]
109
+ : undefined;
110
+ if (other?.kind !== "relation" || other.target !== name) {
111
+ delete field.inverse;
112
+ }
113
+ }
114
+ }
115
+ }
74
116
  function cloneOperations(operations) {
75
117
  return cloneValue(operations);
76
118
  }
@@ -84,8 +126,40 @@ function cloneAdapterResource(resource) {
84
126
  operations: cloneOperations(resource.operations),
85
127
  };
86
128
  }
129
+ function canonicalModes(modes) {
130
+ const stated = Array.isArray(modes) ? modes : [];
131
+ const known = FILTER_MODES.filter((mode) => mode === "default" || stated.includes(mode));
132
+ const unknown = stated.filter((mode) => !FILTER_MODES.includes(mode));
133
+ return [...known, ...unknown];
134
+ }
135
+ function qualifyFilterModes(projection) {
136
+ for (const resource of Object.values(projection.resources)) {
137
+ for (const field of Object.values(resource.fields)) {
138
+ if (field.kind !== "scalar") {
139
+ continue;
140
+ }
141
+ const capabilities = field.capabilities;
142
+ const filter = Array.isArray(capabilities.filter)
143
+ ? capabilities.filter
144
+ : [];
145
+ if (!filter.includes("mode")) {
146
+ delete capabilities.filterModes;
147
+ delete capabilities.pathFilterModes;
148
+ continue;
149
+ }
150
+ const stated = capabilities.filterModes;
151
+ capabilities.filterModes = canonicalModes(stated);
152
+ if (filter.includes("path")) {
153
+ capabilities.pathFilterModes = canonicalModes(capabilities.pathFilterModes ?? stated);
154
+ }
155
+ else {
156
+ delete capabilities.pathFilterModes;
157
+ }
158
+ }
159
+ }
160
+ }
87
161
  function createBaseline(model) {
88
- return {
162
+ const baseline = {
89
163
  scalars: Object.fromEntries(Object.entries(model.scalars).map(([key, value]) => [
90
164
  key,
91
165
  cloneValue(value),
@@ -104,9 +178,11 @@ function createBaseline(model) {
104
178
  ? {}
105
179
  : { referenceWrites: model.referenceWrites }),
106
180
  };
181
+ qualifyFilterModes(baseline);
182
+ return baseline;
107
183
  }
108
- function scalarFieldPath(scope, resource, field) {
109
- return `${scope}.resources.${resource}.fields.${field}`;
184
+ function definitionPath(resource, field) {
185
+ return `root.resources.${resource}.fields.${field}.definition`;
110
186
  }
111
187
  function structuralValueMatches(field, config, key) {
112
188
  if (config[key] === undefined) {
@@ -114,6 +190,13 @@ function structuralValueMatches(field, config, key) {
114
190
  }
115
191
  return JSON.stringify(field[key]) === JSON.stringify(config[key]);
116
192
  }
193
+ function configuredMember(capabilities, key) {
194
+ const value = capabilities?.[key];
195
+ if (value === undefined) {
196
+ return {};
197
+ }
198
+ return { [key]: Array.isArray(value) ? [...value] : cloneValue(value) };
199
+ }
117
200
  function addConfiguredScalarField(config, path, diagnostics) {
118
201
  if (config.json !== undefined) {
119
202
  refuseJsonShape(path, "is not a field of the adapter model", diagnostics);
@@ -128,7 +211,7 @@ function addConfiguredScalarField(config, path, diagnostics) {
128
211
  if (configuredLifecycle === undefined ||
129
212
  !configuredLifecycle.includes("VIRTUAL") ||
130
213
  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"));
214
+ 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
215
  return undefined;
133
216
  }
134
217
  return {
@@ -140,89 +223,14 @@ function addConfiguredScalarField(config, path, diagnostics) {
140
223
  lifecycle: [...(config.lifecycle ?? [])],
141
224
  capabilities: {
142
225
  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
- : {}),
226
+ ...configuredMember(config.capabilities, "filter"),
227
+ ...configuredMember(config.capabilities, "order"),
228
+ ...configuredMember(config.capabilities, "orderNulls"),
229
+ ...configuredMember(config.capabilities, "create"),
230
+ ...configuredMember(config.capabilities, "update"),
158
231
  },
159
232
  };
160
233
  }
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
234
  function fieldLabel(path) {
227
235
  const [, resource, , field] = path.split(".").slice(1);
228
236
  return `${resource}.${field}`;
@@ -238,7 +246,7 @@ function applyJsonShape(field, baselineField, shape, path, diagnostics) {
238
246
  }
239
247
  const runtime = jsonShapeOf(shape);
240
248
  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`));
249
+ 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
250
  return;
243
251
  }
244
252
  if (runtime.validate(null).length === 0 ||
@@ -255,7 +263,7 @@ function applyExistingScalarConfig(field, baselineField, config, path, diagnosti
255
263
  }
256
264
  }
257
265
  if (config.format !== undefined) {
258
- field.format = config.format;
266
+ 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
267
  }
260
268
  if (config.json !== undefined) {
261
269
  applyJsonShape(field, baselineField, config.json, path, diagnostics);
@@ -264,95 +272,39 @@ function applyExistingScalarConfig(field, baselineField, config, path, diagnosti
264
272
  diagnostics.push(diagnostic("COMPILER_INVALID_LIFECYCLE", `${path}.lifecycle`, "existing adapter field lifecycle cannot be redefined by framework field configuration"));
265
273
  }
266
274
  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
- }
275
+ 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"));
276
276
  }
277
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
- }
305
- }
306
- }
307
- function applyFieldConfig(model, projection, scope, config, diagnostics, allowRestrictedMissing = false) {
308
- if (config === undefined) {
309
- return;
310
- }
278
+ function applyFieldDefinitions(model, projection, definitions, diagnostics) {
311
279
  const providerBaseline = createBaseline(model);
312
- for (const [resourceName, resourceConfig] of Object.entries(config)) {
313
- if (resourceConfig === undefined) {
314
- continue;
315
- }
280
+ for (const [resourceName, resourceDefinitions] of Object.entries(definitions)) {
316
281
  const resource = projection.resources[resourceName];
317
282
  const providerResource = providerBaseline.resources[resourceName];
318
283
  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
284
  continue;
324
285
  }
325
- for (const [fieldName, rawConfig] of Object.entries(resourceConfig)) {
326
- const fieldConfig = rawConfig;
327
- const path = scalarFieldPath(scope, resourceName, fieldName);
286
+ for (const [fieldName, definition] of Object.entries(resourceDefinitions)) {
287
+ const path = definitionPath(resourceName, fieldName);
328
288
  const existing = resource.fields[fieldName];
329
289
  const providerField = providerResource.fields[fieldName];
330
290
  if (existing === undefined) {
331
- if (allowRestrictedMissing && providerField !== undefined) {
332
- continue;
333
- }
334
- const added = addConfiguredScalarField(fieldConfig, path, diagnostics);
291
+ const added = addConfiguredScalarField(definition, path, diagnostics);
335
292
  if (added !== undefined) {
336
293
  resource.fields[fieldName] = added;
337
294
  }
338
295
  continue;
339
296
  }
340
297
  if (existing.kind === "relation") {
341
- diagnostics.push(diagnostic("COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED", path, "framework field augmentation applies only to scalar fields"));
298
+ diagnostics.push(diagnostic("COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED", path, "a relation field takes no definition"));
342
299
  continue;
343
300
  }
344
301
  if (providerField?.kind === "scalar") {
345
- applyExistingScalarConfig(existing, providerField, fieldConfig, path, diagnostics);
346
- continue;
302
+ applyExistingScalarConfig(existing, providerField, definition, path, diagnostics);
347
303
  }
348
- applyFrameworkOwnedScalarConfig(existing, fieldConfig, path, diagnostics);
349
304
  }
350
305
  }
351
306
  }
352
- function isActiveBehaviorStage(value) {
353
- return value !== undefined && value !== false;
354
- }
355
- function readBehaviorDependencies(value) {
307
+ function readComputeDependencies(value) {
356
308
  if (!isRecord(value) || typeof value.compute !== "function") {
357
309
  return undefined;
358
310
  }
@@ -360,60 +312,125 @@ function readBehaviorDependencies(value) {
360
312
  ? value.dependsOn.filter((entry) => typeof entry === "string")
361
313
  : undefined;
362
314
  }
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;
315
+ function applyCompute(projection, compute) {
316
+ for (const [resourceName, fields] of Object.entries(compute)) {
317
+ const resource = projection.resources[resourceName];
318
+ if (resource === undefined) {
319
+ continue;
320
+ }
321
+ for (const [fieldName, fieldCompute] of Object.entries(fields)) {
322
+ const field = resource.fields[fieldName];
323
+ if (field?.kind !== "scalar") {
324
+ continue;
325
+ }
326
+ const lifecycle = field.lifecycle.filter((trait) => !COMPUTED_TRAITS.has(trait));
327
+ if (fieldCompute.create !== undefined) {
328
+ lifecycle.push("COMPUTED_ON_CREATE");
329
+ }
330
+ if (fieldCompute.update !== undefined) {
331
+ lifecycle.push("COMPUTED_ON_UPDATE");
332
+ }
333
+ if (fieldCompute.read !== undefined) {
334
+ lifecycle.push("COMPUTED_ON_READ");
335
+ }
336
+ field.lifecycle = lifecycle;
368
337
  }
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
338
  }
376
- if (isActiveBehaviorStage(behavior.update)) {
377
- lifecycle.push("COMPUTED_ON_UPDATE");
339
+ }
340
+ function storedScalar(model, resource, field) {
341
+ const fields = Object.hasOwn(model.resources, resource)
342
+ ? model.resources[resource]?.fields
343
+ : undefined;
344
+ return (fields !== undefined &&
345
+ Object.hasOwn(fields, field) &&
346
+ fields[field]?.kind === "scalar");
347
+ }
348
+ function checkFieldCompute(model, resourceName, fieldName, field, compute, path, diagnostics) {
349
+ if (!isRecord(compute)) {
350
+ return;
378
351
  }
379
- if (isActiveBehaviorStage(behavior.read)) {
380
- lifecycle.push("COMPUTED_ON_READ");
352
+ if (field?.kind === "relation") {
353
+ diagnostics.push(diagnostic("COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED", path, `relation field "${fieldName}" takes write guards only; compute derives a scalar field's value`));
354
+ return;
381
355
  }
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`));
356
+ for (const dependency of readComputeDependencies(compute.read) ?? []) {
357
+ if (!storedScalar(model, resourceName, dependency)) {
358
+ 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
359
  }
388
360
  }
389
361
  }
390
- function applyBehaviors(projection, scope, config, diagnostics, allowRestrictedMissing = false) {
391
- if (config === undefined) {
362
+ function checkResourceTree(model, baseline, scope, tree, definitions, diagnostics) {
363
+ if (!isRecord(tree)) {
392
364
  return;
393
365
  }
394
- for (const [resourceName, rawResourceConfig] of Object.entries(config)) {
395
- if (rawResourceConfig === undefined) {
366
+ for (const [resourceName, entry] of Object.entries(tree)) {
367
+ const resourcePath = `${scope}.resources.${resourceName}`;
368
+ const resource = baseline.resources[resourceName];
369
+ if (resource === undefined) {
370
+ diagnostics.push(diagnostic("COMPILER_UNKNOWN_RESOURCE", resourcePath, `resource "${resourceName}" is not present in the canonical adapter model`));
396
371
  continue;
397
372
  }
398
- const resource = projection.resources[resourceName];
399
- if (resource === undefined) {
400
- if (allowRestrictedMissing) {
401
- continue;
373
+ if (!isRecord(entry)) {
374
+ continue;
375
+ }
376
+ if (isRecord(entry.operations)) {
377
+ for (const [family, level] of Object.entries(entry.operations)) {
378
+ const familyPath = `${resourcePath}.operations.${family}`;
379
+ const variants = resource.operations[family];
380
+ if (!isRecord(variants)) {
381
+ diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", familyPath, `operation family "${family}" is not available for resource "${resourceName}"`));
382
+ continue;
383
+ }
384
+ if (!isRecord(level)) {
385
+ continue;
386
+ }
387
+ for (const variant of Object.keys(level)) {
388
+ if (variant === "pipelines" || variant === "forceTransaction") {
389
+ continue;
390
+ }
391
+ if (!isOperationVariantAvailable(variants[variant])) {
392
+ diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", `${familyPath}.${variant}`, `operation "${family}.${variant}" is not available for resource "${resourceName}"`));
393
+ }
394
+ }
402
395
  }
403
- diagnostics.push(diagnostic("COMPILER_UNKNOWN_RESOURCE", `${scope}.resources.${resourceName}`, `resource "${resourceName}" is not present in the canonical adapter model`));
396
+ }
397
+ if (!isRecord(entry.fields)) {
404
398
  continue;
405
399
  }
406
- for (const [fieldName, behavior] of Object.entries(rawResourceConfig.fields)) {
407
- if (behavior === undefined) {
400
+ for (const [fieldName, fieldEntry] of Object.entries(entry.fields)) {
401
+ const field = resource.fields[fieldName];
402
+ if (field === undefined &&
403
+ definitions[resourceName]?.[fieldName] === undefined) {
404
+ diagnostics.push(diagnostic("COMPILER_UNKNOWN_FIELD", `${resourcePath}.fields.${fieldName}`, `field "${fieldName}" is neither discovered by the adapter nor defined at the root`));
408
405
  continue;
409
406
  }
410
- applyFieldBehavior(resource, fieldName, behavior, `${scope}.resources.${resourceName}.fields.${fieldName}.behaviors`, diagnostics, allowRestrictedMissing);
407
+ if (!isRecord(fieldEntry) || !isRecord(fieldEntry.pipelines)) {
408
+ continue;
409
+ }
410
+ const pipelinesPath = `${resourcePath}.fields.${fieldName}.pipelines`;
411
+ if (field?.kind === "relation" &&
412
+ fieldEntry.pipelines.pipes !== undefined) {
413
+ 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`));
414
+ }
415
+ checkFieldCompute(model, resourceName, fieldName, field, fieldEntry.pipelines.compute, `${pipelinesPath}.compute`, diagnostics);
416
+ }
417
+ }
418
+ }
419
+ function checkReservedResourceNames(model, diagnostics) {
420
+ for (const resourceName of Object.keys(model.resources)) {
421
+ if (resourceName.startsWith("$")) {
422
+ 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
423
  }
412
424
  }
413
425
  }
426
+ function isAdapterStated(axis) {
427
+ return axis?.kind === "limit" || axis?.kind === "adapterTokens";
428
+ }
414
429
  const SCALAR_CAPABILITY_AXES = {
415
430
  select: { kind: "flag" },
416
431
  filter: { kind: "tokens" },
432
+ filterModes: { kind: "adapterTokens", values: FILTER_MODES },
433
+ pathFilterModes: { kind: "adapterTokens", values: FILTER_MODES },
417
434
  order: { kind: "flag" },
418
435
  orderNulls: { kind: "flag" },
419
436
  create: { kind: "tokens" },
@@ -441,226 +458,366 @@ function capabilityAxisFor(kind, key) {
441
458
  function unavailableCapability(path, message, diagnostics) {
442
459
  diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_CAPABILITY", path, message));
443
460
  }
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];
461
+ function otherOverlay(layer) {
462
+ return layer === "client" ? "application" : "client";
451
463
  }
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" };
464
+ function widening(walk, code, path, what) {
465
+ walk.diagnostics.push(diagnostic(code, path, walk.layer === "agent"
466
+ ? `${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`
467
+ : `${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`));
468
+ }
469
+ function exemptsWhole(entry) {
470
+ return entry === true;
471
+ }
472
+ function exemptsAnything(entry) {
473
+ return (entry === true ||
474
+ (Array.isArray(entry) && entry.length > 0) ||
475
+ isRecord(entry));
476
+ }
477
+ function listedNames(entry, path) {
478
+ if (!Array.isArray(entry)) {
479
+ return [];
460
480
  }
461
- return { outcome: "narrowed", scopes: [...requested] };
481
+ return entry.flatMap((name, index) => typeof name === "string"
482
+ ? [[name, `${path}[${index}]`]]
483
+ : []);
462
484
  }
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;
485
+ function memberOffered(axis, value) {
486
+ if (value === undefined || value === false) {
487
+ return false;
468
488
  }
469
- if (narrowing.outcome === "denied") {
470
- delete current[key];
471
- return;
489
+ switch (axis.kind) {
490
+ case "tokens":
491
+ return Array.isArray(value);
492
+ case "scopedTokens":
493
+ return memberTokens(axis, value) !== undefined;
494
+ case "scopes":
495
+ return holdsInAnyScope(value);
496
+ default:
497
+ return true;
472
498
  }
473
- current[key] = narrowing.scopes;
474
499
  }
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;
500
+ function memberTokens(axis, value) {
501
+ if (axis.kind === "tokens") {
502
+ return Array.isArray(value) ? value : undefined;
485
503
  }
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;
504
+ if (axis.kind === "scopedTokens") {
505
+ return axis.tokenKey === "directives"
506
+ ? advertisedDirectives(value)
507
+ : advertisedReducers(value);
492
508
  }
493
- const tokens = requestedTokens === undefined
494
- ? [...baselineTokens]
495
- : narrowedTokens(baselineTokens, requestedTokens, key, path, diagnostics);
496
- if (tokens === undefined) {
497
- return true;
509
+ return undefined;
510
+ }
511
+ function withTokens(axis, parentValue, tokens) {
512
+ if (axis.kind === "scopedTokens" &&
513
+ isRecord(parentValue) &&
514
+ Array.isArray(parentValue.scopes)) {
515
+ return {
516
+ [axis.tokenKey]: [...tokens],
517
+ scopes: [...parentValue.scopes],
518
+ };
498
519
  }
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;
520
+ return [...tokens];
521
+ }
522
+ function fieldName(resource, field) {
523
+ return `"${resource}.${field}"`;
524
+ }
525
+ function exemptableAxis(walk, universeField, capability, label, path) {
526
+ const axis = capabilityAxisFor(universeField.kind, capability);
527
+ if (axis === undefined || isAdapterStated(axis)) {
528
+ const names = Object.entries(universeField.kind === "scalar"
529
+ ? SCALAR_CAPABILITY_AXES
530
+ : RELATION_CAPABILITY_AXES)
531
+ .filter(([, entry]) => !isAdapterStated(entry))
532
+ .map(([name]) => name);
533
+ unavailableCapability(path, `"${capability}" is not a capability a ${universeField.kind} field's exemption can name; it takes ${names.join(", ")}`, walk.diagnostics);
534
+ return undefined;
508
535
  }
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;
536
+ const capabilities = universeField.capabilities;
537
+ if (!memberOffered(axis, capabilities[capability])) {
538
+ unavailableCapability(path, `capability "${capability}" is not available for field ${label}: the adapter does not offer it`, walk.diagnostics);
539
+ return undefined;
514
540
  }
515
- if (narrowing.outcome === "denied") {
516
- delete current[key];
517
- return true;
541
+ return axis;
542
+ }
543
+ function offeredTokens(walk, axis, universeValue, listed, capability, label) {
544
+ const offered = memberTokens(axis, universeValue) ?? [];
545
+ const tokens = [];
546
+ for (const [token, tokenPath] of listed) {
547
+ if (!offered.includes(token)) {
548
+ unavailableCapability(tokenPath, `capability "${capability}" of field ${label} does not offer "${token}": the adapter does not offer it`, walk.diagnostics);
549
+ continue;
550
+ }
551
+ tokens.push(token);
518
552
  }
519
- current[key] = { [tokenKey]: tokens, scopes: narrowing.scopes };
520
- return true;
553
+ return tokens;
521
554
  }
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);
555
+ function exemptCapability(walk, universeField, parentCapabilities, result, capability, entry, label, path) {
556
+ if (entry === undefined) {
525
557
  return;
526
558
  }
527
- if (value === false) {
528
- delete current[key];
559
+ const axis = exemptableAxis(walk, universeField, capability, label, path);
560
+ if (axis === undefined || entry === false) {
529
561
  return;
530
562
  }
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);
563
+ const isTokenAxis = axis.kind === "tokens" || axis.kind === "scopedTokens";
564
+ if (entry !== true && !(isTokenAxis && Array.isArray(entry))) {
565
+ unavailableCapability(path, isTokenAxis
566
+ ? `capability "${capability}" takes true, false or a list of its tokens`
567
+ : `capability "${capability}" takes true or false`, walk.diagnostics);
537
568
  return;
538
569
  }
539
- if (value === true) {
540
- current[key] = cloneValue(baselineValue);
570
+ const universeValue = universeField.capabilities[capability];
571
+ const tokens = entry === true
572
+ ? undefined
573
+ : offeredTokens(walk, axis, universeValue, listedNames(entry, path), capability, label);
574
+ if (tokens !== undefined && tokens.length !== entry.length) {
541
575
  return;
542
576
  }
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;
577
+ const parentValue = parentCapabilities[capability];
578
+ const parentHas = memberOffered(axis, parentValue);
579
+ if (walk.style === "omit") {
580
+ if (!parentHas) {
581
+ return;
582
+ }
583
+ if (tokens === undefined) {
584
+ delete result[capability];
585
+ return;
549
586
  }
587
+ const kept = (memberTokens(axis, parentValue) ?? []).filter((token) => !tokens.includes(token));
588
+ result[capability] = withTokens(axis, parentValue, kept);
550
589
  return;
551
590
  }
552
- if (axis.kind === "scopes" && Array.isArray(value)) {
553
- applyScopeRestriction(axis.axis, baselineValue, current, key, value, path, diagnostics);
591
+ if (tokens !== undefined && tokens.length === 0) {
554
592
  return;
555
593
  }
556
- if (axis.kind === "scopedTokens") {
557
- if (applyScopedTokenRestriction(axis.tokenKey, axis.axis, baselineValue, current, key, value, path, diagnostics)) {
558
- return;
559
- }
594
+ if (!parentHas) {
595
+ widening(walk, "COMPILER_UNAVAILABLE_CAPABILITY", path, `capability "${capability}" of field ${label}`);
596
+ return;
560
597
  }
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) {
598
+ if (tokens === undefined) {
599
+ result[capability] = cloneValue(parentValue);
566
600
  return;
567
601
  }
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;
602
+ const parentTokens = memberTokens(axis, parentValue) ?? [];
603
+ const hidden = tokens.filter((token) => !parentTokens.includes(token));
604
+ if (hidden.length > 0) {
605
+ widening(walk, "COMPILER_UNAVAILABLE_CAPABILITY", path, `${hidden.map((token) => `"${token}"`).join(", ")} of capability "${capability}" of field ${label}`);
606
+ return;
607
+ }
608
+ result[capability] = withTokens(axis, parentValue, parentTokens.filter((token) => tokens.includes(token)));
609
+ }
610
+ function exemptField(walk, universeField, parentField, entry, label, path) {
611
+ const parentCapabilities = (parentField?.capabilities ?? {});
612
+ const result = walk.style === "omit" ? cloneValue(parentCapabilities) : {};
613
+ if (walk.style === "pick") {
614
+ for (const [key, value] of Object.entries(parentCapabilities)) {
615
+ if (isAdapterStated(capabilityAxisFor(universeField.kind, key))) {
616
+ result[key] = cloneValue(value);
617
+ }
618
+ }
619
+ }
620
+ if (Array.isArray(entry)) {
621
+ for (const [capability, capabilityPath] of listedNames(entry, path)) {
622
+ exemptCapability(walk, universeField, parentCapabilities, result, capability, true, label, capabilityPath);
623
+ }
624
+ }
625
+ else if (isRecord(entry)) {
626
+ for (const [capability, value] of Object.entries(entry)) {
627
+ exemptCapability(walk, universeField, parentCapabilities, result, capability, value, label, `${path}.${capability}`);
574
628
  }
575
- applyCapabilityRestriction(restorationField.kind, inherited, restoration, current, key, value, `${path}.capabilities.${key}`, diagnostics);
576
629
  }
630
+ if (parentField === undefined) {
631
+ return undefined;
632
+ }
633
+ if (walk.style === "pick" &&
634
+ !Object.keys(result).some((key) => !isAdapterStated(capabilityAxisFor(universeField.kind, key)))) {
635
+ return undefined;
636
+ }
637
+ return { ...cloneValue(parentField), capabilities: result };
577
638
  }
578
- function applyOperationRestrictions(restoration, current, restrictions, path, diagnostics) {
579
- if (restrictions === undefined) {
580
- return;
639
+ function exemptFields(walk, resourceName, universe, parent, entries, path) {
640
+ const named = isRecord(entries) ? entries : {};
641
+ for (const fieldName of Object.keys(named)) {
642
+ if (universe.fields[fieldName] === undefined) {
643
+ walk.diagnostics.push(diagnostic("COMPILER_UNKNOWN_FIELD", `${path}.${fieldName}`, `field "${fieldName}" is neither discovered by the adapter nor defined at the root`));
644
+ }
581
645
  }
582
- for (const [family, variants] of Object.entries(restrictions)) {
583
- if (!isRecord(variants)) {
646
+ const result = {};
647
+ for (const [name, entry] of Object.entries(named)) {
648
+ const universeField = universe.fields[name];
649
+ if (universeField === undefined) {
584
650
  continue;
585
651
  }
586
- const baselineFamily = restoration[family];
587
- let currentFamily = current[family];
588
- if (!isRecord(currentFamily)) {
589
- currentFamily = {};
590
- current[family] = currentFamily;
652
+ const parentField = parent.fields[name];
653
+ const label = fieldName(resourceName, name);
654
+ const fieldPath = `${path}.${name}`;
655
+ if (walk.style === "pick" &&
656
+ exemptsAnything(entry) &&
657
+ parentField === undefined) {
658
+ widening(walk, "COMPILER_UNAVAILABLE_CAPABILITY", fieldPath, `field ${label}`);
659
+ continue;
591
660
  }
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;
661
+ if (exemptsWhole(entry) || !exemptsAnything(entry)) {
662
+ continue;
663
+ }
664
+ const exempted = exemptField(walk, universeField, parentField, entry, label, fieldPath);
665
+ if (exempted !== undefined) {
666
+ result[name] = exempted;
667
+ }
668
+ }
669
+ for (const [name, parentField] of Object.entries(parent.fields)) {
670
+ if (Object.hasOwn(result, name)) {
671
+ continue;
672
+ }
673
+ const entry = Object.hasOwn(named, name) ? named[name] : undefined;
674
+ if (walk.style === "omit" ? !exemptsWhole(entry) : exemptsWhole(entry)) {
675
+ result[name] = cloneValue(parentField);
676
+ }
677
+ }
678
+ return result;
679
+ }
680
+ function exemptVariants(walk, resourceName, family, universeFamily, parentFamily, entry, path) {
681
+ if (entry === true) {
682
+ return "all";
683
+ }
684
+ const named = Array.isArray(entry)
685
+ ? [...listedNames(entry, path)]
686
+ : isRecord(entry)
687
+ ? Object.keys(entry).map((variant) => [variant, `${path}.${variant}`])
688
+ : [];
689
+ const variants = [];
690
+ for (const [variant, variantPath] of named) {
691
+ if (!isOperationVariantAvailable(universeFamily[variant])) {
692
+ walk.diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", variantPath, `operation "${family}.${variant}" is not available for resource "${resourceName}"`));
693
+ continue;
694
+ }
695
+ if (isRecord(entry)) {
696
+ const value = entry[variant];
697
+ if (value !== true && value !== false) {
698
+ walk.diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", variantPath, "an operation variant takes true or false"));
599
699
  }
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`));
700
+ if (value !== true) {
605
701
  continue;
606
702
  }
607
- const target = currentFamily;
608
- if (!isOperationVariantAvailable(target[variant])) {
609
- target[variant] = cloneValue(baselineAvailability);
610
- }
611
703
  }
704
+ if (walk.style === "pick" &&
705
+ !isOperationVariantAvailable(parentFamily?.[variant])) {
706
+ widening(walk, "COMPILER_UNAVAILABLE_OPERATION", variantPath, `operation "${resourceName}.${family}.${variant}"`);
707
+ continue;
708
+ }
709
+ variants.push(variant);
612
710
  }
711
+ return variants;
613
712
  }
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) {
713
+ function exemptOperations(walk, resourceName, universe, parent, entry, path) {
714
+ const omit = walk.style === "omit";
715
+ if (entry === true) {
716
+ return omit ? {} : cloneValue(parent);
717
+ }
718
+ const families = Array.isArray(entry)
719
+ ? listedNames(entry, path).map(([family, familyPath]) => [family, familyPath, true])
720
+ : isRecord(entry)
721
+ ? Object.entries(entry).map(([family, value]) => [family, `${path}.${family}`, value])
722
+ : [];
723
+ const result = omit ? cloneValue(parent) : {};
724
+ for (const [family, familyPath, value] of families) {
725
+ const universeFamily = universe[family];
726
+ if (!isRecord(universeFamily)) {
727
+ walk.diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", familyPath, `operation family "${family}" is not available for resource "${resourceName}"`));
620
728
  continue;
621
729
  }
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`));
730
+ if (value === false || value === undefined) {
626
731
  continue;
627
732
  }
628
- if (restrictions.hidden === true) {
629
- delete projection.resources[resourceName];
733
+ if (value !== true && !Array.isArray(value) && !isRecord(value)) {
734
+ 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
735
  continue;
631
736
  }
632
- if (restrictions.hidden === false &&
633
- projection.resources[resourceName] === undefined) {
634
- projection.resources[resourceName] = cloneValue(baselineResource);
737
+ const parentFamily = isRecord(parent[family])
738
+ ? parent[family]
739
+ : undefined;
740
+ if (value === true && !omit && parentFamily === undefined) {
741
+ widening(walk, "COMPILER_UNAVAILABLE_OPERATION", familyPath, `operation family "${resourceName}.${family}"`);
742
+ continue;
635
743
  }
636
- const resource = projection.resources[resourceName];
637
- if (resource === undefined) {
744
+ const variants = exemptVariants(walk, resourceName, family, universeFamily, parentFamily, value, familyPath);
745
+ if (parentFamily === undefined) {
638
746
  continue;
639
747
  }
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);
748
+ const named = variants === "all" ? Object.keys(parentFamily) : variants;
749
+ if (omit) {
750
+ const target = result[family];
751
+ for (const variant of named) {
752
+ if (target !== undefined) {
753
+ delete target[variant];
754
+ }
655
755
  }
656
- const field = resource.fields[fieldName];
657
- if (field === undefined) {
658
- continue;
756
+ continue;
757
+ }
758
+ const target = (isRecord(result[family]) ? result[family] : {});
759
+ for (const variant of named) {
760
+ if (isOperationVariantAvailable(parentFamily[variant])) {
761
+ target[variant] = cloneValue(parentFamily[variant]);
659
762
  }
660
- applyCapabilityRestrictions(baselines.narrowing.resources[resourceName]?.fields[fieldName], baselineField, field, fieldRestrictions, path, diagnostics);
661
763
  }
662
- applyOperationRestrictions(baselineResource.operations, resource.operations, restrictions.operations, `${scope}.resources.${resourceName}.operations`, diagnostics);
764
+ result[family] = target;
765
+ }
766
+ pruneEmptyOperationFamilies(result);
767
+ return result;
768
+ }
769
+ function exemptResource(walk, resourceName, universe, parent, entry, path) {
770
+ const resource = {
771
+ fields: exemptFields(walk, resourceName, universe, parent, entry.fields, `${path}.fields`),
772
+ identifiers: [],
773
+ operations: exemptOperations(walk, resourceName, universe.operations, parent.operations, entry.operations, `${path}.operations`),
774
+ };
775
+ reconcileIdentifiers(walk, resource, parent, path);
776
+ return resource;
777
+ }
778
+ function projectExposure(walk, universe, parent, exemptions) {
779
+ const path = `${walk.layer}.exemptions.resources`;
780
+ const entries = isRecord(exemptions) && isRecord(exemptions.resources)
781
+ ? exemptions.resources
782
+ : {};
783
+ const resources = {};
784
+ for (const [resourceName, entry] of Object.entries(entries)) {
785
+ const resourcePath = `${path}.${resourceName}`;
786
+ const universeResource = universe.resources[resourceName];
787
+ if (universeResource === undefined) {
788
+ walk.diagnostics.push(diagnostic("COMPILER_UNKNOWN_RESOURCE", resourcePath, `resource "${resourceName}" is not present in the canonical adapter model`));
789
+ continue;
790
+ }
791
+ const parentResource = parent.resources[resourceName];
792
+ if (walk.style === "pick" &&
793
+ exemptsAnything(entry) &&
794
+ parentResource === undefined) {
795
+ widening(walk, "COMPILER_UNAVAILABLE_CAPABILITY", resourcePath, `resource "${resourceName}"`);
796
+ continue;
797
+ }
798
+ if (!isRecord(entry)) {
799
+ continue;
800
+ }
801
+ const exempted = exemptResource(walk, resourceName, universeResource, parentResource ?? { fields: {}, identifiers: [], operations: {} }, entry, resourcePath);
802
+ if (parentResource !== undefined &&
803
+ (walk.style === "omit" ||
804
+ Object.keys(exempted.fields).length > 0 ||
805
+ Object.keys(exempted.operations).length > 0)) {
806
+ resources[resourceName] = exempted;
807
+ }
663
808
  }
809
+ for (const [resourceName, parentResource] of Object.entries(parent.resources)) {
810
+ if (Object.hasOwn(resources, resourceName)) {
811
+ continue;
812
+ }
813
+ const entry = Object.hasOwn(entries, resourceName)
814
+ ? entries[resourceName]
815
+ : undefined;
816
+ if (walk.style === "omit" ? !exemptsWhole(entry) : exemptsWhole(entry)) {
817
+ resources[resourceName] = cloneValue(parentResource);
818
+ }
819
+ }
820
+ return { ...cloneValue({ ...parent, resources: {} }), resources };
664
821
  }
665
822
  function pruneEmptyOperationFamilies(operations) {
666
823
  for (const [family, variants] of Object.entries(operations)) {
@@ -669,45 +826,28 @@ function pruneEmptyOperationFamilies(operations) {
669
826
  }
670
827
  }
671
828
  }
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;
829
+ function identifierLabel(identifier) {
830
+ return identifier.map((name) => `\`${name}\``).join(" and ");
678
831
  }
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
- }));
832
+ function reconcileIdentifiers(walk, resource, parent, path) {
833
+ resource.identifiers = parent.identifiers
834
+ .filter((identifier) => identifier.every((name) => resource.fields[name]?.kind === "scalar"))
835
+ .map((identifier) => [...identifier]);
836
+ const first = parent.identifiers[0];
837
+ if (resource.identifiers.length > 0 || first === undefined) {
838
+ return;
839
+ }
684
840
  for (const [family, variant] of UNIQUE_VARIANTS) {
685
841
  const currentFamily = resource.operations[family];
686
- if (resource.identifiers.length === 0) {
687
- if (isRecord(currentFamily)) {
688
- delete currentFamily[variant];
689
- }
690
- continue;
691
- }
692
- if (operationIsExplicitlyDisabled(restrictions, family, variant)) {
693
- if (isRecord(currentFamily)) {
694
- delete currentFamily[variant];
695
- }
842
+ if (!isRecord(currentFamily) ||
843
+ !isOperationVariantAvailable(currentFamily[variant])) {
696
844
  continue;
697
845
  }
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
- }
846
+ const variantPath = `${path}.operations.${family}.${variant}`;
847
+ walk.diagnostics.push(diagnostic("COMPILER_MISSING_IDENTIFIER", variantPath, walk.style === "pick"
848
+ ? `${variantPath} needs an identifier field; pick ${identifierLabel(first)} (or another complete identifier) too`
849
+ : `${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
850
  }
710
- pruneEmptyOperationFamilies(resource.operations);
711
851
  }
712
852
  function validateScalarReference(projection, field, path, diagnostics) {
713
853
  const type = field.type;
@@ -757,7 +897,7 @@ function validateFieldLifecycle(field, path, diagnostics) {
757
897
  if (lifecycle.includes("VIRTUAL") &&
758
898
  holdsInAnyScope(capabilities.select) &&
759
899
  !lifecycle.includes("COMPUTED_ON_READ")) {
760
- diagnostics.push(diagnostic("COMPILER_MISSING_READ_BEHAVIOR", path, "selectable VIRTUAL field requires an effective read behavior"));
900
+ diagnostics.push(diagnostic("COMPILER_MISSING_READ_COMPUTE", path, "selectable VIRTUAL field requires a compute.read stage (pipelines.compute.read)"));
761
901
  }
762
902
  }
763
903
  function isTokenList(value) {
@@ -778,6 +918,11 @@ function representationFault(axis, value) {
778
918
  return typeof value === "string" && axis.values.includes(value)
779
919
  ? undefined
780
920
  : `one of ${axis.values.join(", ")} is expected`;
921
+ case "adapterTokens":
922
+ return isTokenList(value) &&
923
+ value.every((token) => axis.values.includes(token))
924
+ ? undefined
925
+ : `a list of ${axis.values.join(", ")} is expected`;
781
926
  case "flag":
782
927
  return typeof value === "boolean" ? undefined : "a boolean is expected";
783
928
  case "tokens":
@@ -815,12 +960,8 @@ function validateCapabilityRepresentation(field, path, diagnostics) {
815
960
  }
816
961
  }
817
962
  }
818
- function validateProjection(projection, restoration, scope, restrictions, diagnostics) {
963
+ function validateProjection(projection, scope, diagnostics) {
819
964
  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
965
  for (const [fieldName, field] of Object.entries(resource.fields)) {
825
966
  const fieldPath = `${scope}.resources.${resourceName}.fields.${fieldName}`;
826
967
  validateCapabilityRepresentation(field, fieldPath, diagnostics);
@@ -841,49 +982,75 @@ function resolveLimits(scope) {
841
982
  return { ...DEFAULT_CONTRACT_LIMITS, ...(scope.limits ?? {}) };
842
983
  }
843
984
  function resolveTransactions(model, scopeName, scope, diagnostics) {
844
- const requested = scope.transactions ?? model.transactions;
985
+ const requested = scope.transactionsStyle ?? model.transactions;
845
986
  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'));
987
+ diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_CAPABILITY", `${scopeName}.transactionsStyle`, 'transaction capability "interactive" is not available in the canonical adapter model'));
847
988
  }
848
989
  return requested;
849
990
  }
850
991
  function semanticObject(contract) {
851
992
  return JSON.parse(canonicalizeContract(contract));
852
993
  }
853
- function projectCore(model, scope, diagnostics) {
994
+ function exposureStyle(layer) {
995
+ return layer.exposed === false ? "pick" : "omit";
996
+ }
997
+ function projectCore(model, config, diagnostics) {
998
+ const scope = config.root;
854
999
  const providerBaseline = createBaseline(model);
855
1000
  const baseline = cloneValue(providerBaseline);
856
1001
  baseline.limits = resolveLimits(scope);
857
1002
  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) {
1003
+ checkResourceTree(model, baseline, "root", config.trees.root, config.definitions, diagnostics);
1004
+ applyFieldDefinitions(model, baseline, config.definitions, diagnostics);
1005
+ applyCompute(baseline, scope.compute);
1006
+ checkLayerValidators(baseline.resources, "root", config.trees.root, diagnostics);
1007
+ const validators = compileValidatorTable(baseline.resources, scope.pipelines);
1008
+ const projection = projectExposure({ layer: "root", style: exposureStyle(scope), diagnostics }, baseline, baseline, scope.exemptions);
1009
+ qualifyFilterModes(projection);
1010
+ pruneHiddenPairings(projection);
1011
+ projectCreateRequirements(model, projection.resources);
1012
+ checkHiddenRequirements(model, "root", baseline.resources, baseline.resources, projection.resources, diagnostics);
1013
+ applyValidators(projection.resources, validators);
1014
+ validateProjection(projection, "root", diagnostics);
1015
+ return { baseline, projection, validators };
1016
+ }
1017
+ function projectDerivedScope(model, scopeName, core, parentView, config, diagnostics) {
1018
+ const scope = config[scopeName];
866
1019
  const baseline = cloneValue(core.baseline);
867
1020
  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) {
1021
+ baseline.transactions = resolveTransactions(model, scopeName, scope, scopeName === "agent" &&
1022
+ scope.transactionsStyle === config[config.agent.parent].transactionsStyle
1023
+ ? []
1024
+ : diagnostics);
1025
+ checkResourceTree(model, baseline, scopeName, config.trees[scopeName], config.definitions, diagnostics);
1026
+ applyCompute(baseline, scope.compute);
1027
+ checkLayerValidators(baseline.resources, scopeName, config.trees[scopeName], diagnostics);
1028
+ const validators = compileValidatorTable(baseline.resources, scope.pipelines);
1029
+ const parent = cloneValue(parentView);
1030
+ parent.limits = resolveLimits(scope);
1031
+ parent.transactions = baseline.transactions;
1032
+ applyCompute(parent, scope.compute);
1033
+ const projection = projectExposure({
1034
+ layer: scopeName,
1035
+ style: exposureStyle(scope),
1036
+ diagnostics,
1037
+ ...(scopeName === "agent" ? { parent: config.agent.parent } : {}),
1038
+ }, baseline, parent, scope.exemptions);
1039
+ qualifyFilterModes(projection);
1040
+ pruneHiddenPairings(projection);
1041
+ projectCreateRequirements(model, projection.resources);
1042
+ checkHiddenRequirements(model, scopeName, baseline.resources, parent.resources, projection.resources, diagnostics);
1043
+ applyValidators(projection.resources, validators);
1044
+ validateProjection(projection, scopeName, diagnostics);
1045
+ return { projection, validators };
1046
+ }
1047
+ function bindShapedFields(contract, definitions) {
881
1048
  for (const [resourceName, resource] of Object.entries(contract.resources)) {
882
1049
  for (const [fieldName, field] of Object.entries(resource.fields)) {
883
1050
  if (field.kind !== "scalar" || field.json === undefined) {
884
1051
  continue;
885
1052
  }
886
- const shape = jsonShapeOf(fields?.[resourceName]?.[fieldName]?.json);
1053
+ const shape = jsonShapeOf(definitions[resourceName]?.[fieldName]?.json);
887
1054
  if (shape === undefined) {
888
1055
  throw new Error(`the Contract compiler found no shape for shaped field "${resourceName}.${fieldName}"`);
889
1056
  }
@@ -894,37 +1061,65 @@ function bindShapedFields(contract, fields) {
894
1061
  export async function compileContracts(model, config) {
895
1062
  const compilerConfig = config;
896
1063
  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);
1064
+ checkReservedResourceNames(model, diagnostics);
1065
+ checkCreateRequirements(model, diagnostics);
1066
+ const coreCompilation = projectCore(model, compilerConfig, diagnostics);
1067
+ const applicationProjection = projectDerivedScope(model, "application", coreCompilation, coreCompilation.projection, compilerConfig, diagnostics);
1068
+ const clientProjection = projectDerivedScope(model, "client", coreCompilation, coreCompilation.projection, compilerConfig, diagnostics);
1069
+ const agentParent = compilerConfig.agent.parent;
1070
+ const agentProjection = projectDerivedScope(model, "agent", coreCompilation, agentParent === "root"
1071
+ ? coreCompilation.projection
1072
+ : agentParent === "application"
1073
+ ? applicationProjection.projection
1074
+ : clientProjection.projection, compilerConfig, diagnostics);
900
1075
  if (diagnostics.length > 0) {
901
1076
  return { ok: false, diagnostics };
902
1077
  }
903
1078
  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
- };
1079
+ const application = semanticObject(applicationProjection.projection);
1080
+ const client = await identifiedContract(clientProjection.projection);
1081
+ const agent = await identifiedContract(agentProjection.projection);
1082
+ for (const [layer, contract] of [
1083
+ ["client", client],
1084
+ ["agent", agent],
1085
+ ]) {
1086
+ const structure = validateClientContractStructure(contract);
1087
+ if (!structure.valid) {
1088
+ return {
1089
+ ok: false,
1090
+ diagnostics: [
1091
+ diagnostic("COMPILER_INVALID_CLIENT_CONTRACT", structure.path, layer === "client"
1092
+ ? structure.message
1093
+ : `Agent Contract: ${structure.message}`),
1094
+ ],
1095
+ };
1096
+ }
922
1097
  }
923
- bindShapedFields(core, compilerConfig.root.fields);
924
- bindShapedFields(application, compilerConfig.application.fields);
925
- bindShapedFields(client, compilerConfig.client.fields);
1098
+ bindShapedFields(core, compilerConfig.definitions);
1099
+ bindShapedFields(application, compilerConfig.definitions);
1100
+ bindShapedFields(client, compilerConfig.definitions);
1101
+ bindShapedFields(agent, compilerConfig.definitions);
1102
+ bindFieldValidators(core, coreCompilation.validators);
1103
+ bindFieldValidators(application, applicationProjection.validators);
1104
+ bindFieldValidators(client, clientProjection.validators);
1105
+ bindFieldValidators(agent, agentProjection.validators);
926
1106
  return {
927
1107
  ok: true,
928
- contracts: { core, application, client },
1108
+ contracts: { core, application, client, agent },
929
1109
  };
930
1110
  }
1111
+ async function identifiedContract(projection) {
1112
+ const members = canonicalizeContractMembers({
1113
+ ...projection,
1114
+ protocol: { version: AVENTARA_PROTOCOL_VERSION },
1115
+ });
1116
+ const hash = await hashCanonicalContractText(joinCanonicalContractMembers(members));
1117
+ const protocol = canonicalizeJson({
1118
+ version: AVENTARA_PROTOCOL_VERSION,
1119
+ hash,
1120
+ });
1121
+ const text = joinCanonicalContractMembers(members.map((member) => member.key === "protocol" ? { key: "protocol", text: protocol } : member));
1122
+ const contract = JSON.parse(text);
1123
+ recordCanonicalContractText(contract, text);
1124
+ return contract;
1125
+ }