@reventlessdev/reventless-core 3.0.0-alpha.292 → 3.0.0-alpha.293

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 (49) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/package.json +9 -9
  3. package/src/admin/Platform_Admin_Structure.res +1 -2
  4. package/src/admin/Platform_Admin_Structure.res.mjs +1 -1
  5. package/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res.mjs +2 -2
  6. package/src/components/Dcb/Dcb_Builder.res +11 -20
  7. package/src/components/Dcb/Dcb_Builder.res.mjs +7 -12
  8. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res +27 -24
  9. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs +4 -4
  10. package/src/plugin/component/Plugin_Builder.res +8 -18
  11. package/src/plugin/component/Plugin_Builder.res.mjs +2 -8
  12. package/src/plugin/component/Plugin_Helpers.res +5 -6
  13. package/src/plugin/component/Plugin_Helpers.res.mjs +1 -1
  14. package/src/plugin/component/Plugin_Structure.res +22 -28
  15. package/src/plugin/component/Plugin_Structure.res.mjs +12 -12
  16. package/src/plugin/connect/PluginConnectExtension_Mapping.res.mjs +1 -1
  17. package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +1 -1
  18. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res +1 -1
  19. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res.mjs +1 -1
  20. package/src/plugin/lifecycle/PluginSpec.res.mjs +2 -2
  21. package/tests/aggregate/AggregateCacheTest.res.mjs +4 -4
  22. package/tests/aggregate/AggregateConflictTest.res.mjs +4 -4
  23. package/tests/aggregate/AggregateFixtures.res.mjs +4 -4
  24. package/tests/aggregate/AggregateSnapshotTest.res.mjs +6 -6
  25. package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +6 -6
  26. package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +3 -3
  27. package/tests/commandgenerator/OwnerActingTest.res.mjs +7 -7
  28. package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +1 -1
  29. package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +3 -3
  30. package/tests/dcb/DcbFixtures.res.mjs +2 -2
  31. package/tests/dcb/DcbStateChangeSliceTest.res.mjs +7 -7
  32. package/tests/eventmapper/EventMapperFixtures.res.mjs +4 -4
  33. package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +2 -2
  34. package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +2 -2
  35. package/tests/plugin/PluginStructureAccessTest.res +6 -5
  36. package/tests/plugin/PluginStructureAccessTest.res.mjs +17 -14
  37. package/tests/plugin/PluginStructureTest.res.mjs +10 -10
  38. package/tests/plugin/StateChange/PsAttachInvoice.res.mjs +2 -2
  39. package/tests/plugin/StateChange/PsChangePhoto.res.mjs +2 -2
  40. package/tests/plugin/StateChange/PsDispatchShipment.res.mjs +2 -2
  41. package/tests/plugin/StateChange/PsGatedCommands.res.mjs +3 -3
  42. package/tests/plugin/StateChange/PsPlaceOrder.res.mjs +2 -2
  43. package/tests/plugin/StateChange/PsReserveStock.res.mjs +2 -2
  44. package/tests/plugin/StateChange/PsShipOrder.res.mjs +2 -2
  45. package/tests/plugin/StateChange/PsTransitionSwitch.res.mjs +2 -2
  46. package/tests/plugin/StateChange/PsTypoStore.res.mjs +2 -2
  47. package/tests/plugin/StateChange/PsUploadAvatar.res.mjs +2 -2
  48. package/tests/plugin/StateChange/PsUploadImages.res.mjs +2 -2
  49. package/tests/util/CommandPublisherTest.res.mjs +2 -2
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.293 (2026-10-10)
7
+
8
+ * feat!: a command's rule is looked up by its name, without a cast ([5f02c40](https://github.com/ReventlessDev/reventless-core/commit/5f02c4081d077bf4a6951d8c9b2b16553a8ca87b))
9
+
10
+ ### BREAKING CHANGES
11
+
12
+ * a spec that writes `commandAuthorization` by hand writes
13
+ `authorizationOf = (name: string) => …` instead; the PPX refuses the old name.
14
+
15
+
16
+
6
17
  # 3.0.0-alpha.292 (2026-10-10)
7
18
 
8
19
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-core",
3
- "version": "3.0.0-alpha.292",
3
+ "version": "3.0.0-alpha.293",
4
4
  "description": "Core package for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -28,22 +28,22 @@
28
28
  "dependencies": {
29
29
  "sury": "11.0.0",
30
30
  "uuid": "^13.0.0",
31
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.11",
31
32
  "@reventlessdev/rescript-effect": "0.1.0-alpha.34",
32
- "@reventlessdev/rescript-fast-csv": "2.0.0-alpha.22",
33
- "@reventlessdev/rescript-hash-object": "1.2.0-alpha.15",
34
33
  "@reventlessdev/rescript-node": "2.0.0-alpha.22",
35
- "@reventlessdev/rescript-jest": "1.0.0-alpha.11",
36
- "@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
34
+ "@reventlessdev/rescript-hash-object": "1.2.0-alpha.15",
35
+ "@reventlessdev/rescript-fast-csv": "2.0.0-alpha.22",
37
36
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.23",
38
- "@reventlessdev/reventless-infra": "3.0.0-alpha.189",
37
+ "@reventlessdev/rescript-ssh2": "2.0.0-alpha.23",
38
+ "@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
39
39
  "@reventlessdev/reventless-interop": "3.0.0-alpha.39",
40
- "@reventlessdev/reventless-spec": "3.0.0-alpha.161",
41
- "@reventlessdev/rescript-ssh2": "2.0.0-alpha.23"
40
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.162",
41
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.190"
42
42
  },
43
43
  "devDependencies": {
44
44
  "rescript": "12.3.1",
45
45
  "sury-ppx": "11.0.0",
46
- "@reventlessdev/reventless-ppx": "1.0.0-alpha.98"
46
+ "@reventlessdev/reventless-ppx": "1.0.0-alpha.99"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "rescript": "^12.3.1"
@@ -37,8 +37,7 @@ let pluginCommands: array<commandDef> = Plugin_Structure.extractCommandDefs(
37
37
  ~isAggregate=true,
38
38
  ~mutationFieldFor=variantName =>
39
39
  Api_Naming.adminField(~name=PluginSpec.name ++ "_" ++ variantName),
40
- ~commandAuthorization=command =>
41
- PluginSpec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
40
+ ~authorizationOf=name => PluginSpec.authorizationOf(name)->Reventless.Authorization.named,
42
41
  ~commandTransition=PluginSpec.commandTransition->Obj.magic,
43
42
  commandSchema,
44
43
  )
@@ -8,7 +8,7 @@ import * as PluginsReadModelSpec$ReventlessCore from "../plugin/lifecycle/Plugin
8
8
 
9
9
  let pluginId = "Platform";
10
10
 
11
- let pluginCommands = Plugin_Structure$ReventlessCore.extractCommandDefs(undefined, true, undefined, variantName => Api_Naming$ReventlessCore.adminField(PluginSpec$ReventlessCore.name + "_" + variantName), command => Authorization$Reventless.named(PluginSpec$ReventlessCore.commandAuthorization(command)), PluginSpec$ReventlessCore.commandTransition, undefined, PluginSpec$ReventlessCore.commandSchema);
11
+ let pluginCommands = Plugin_Structure$ReventlessCore.extractCommandDefs(undefined, true, undefined, variantName => Api_Naming$ReventlessCore.adminField(PluginSpec$ReventlessCore.name + "_" + variantName), name => Authorization$Reventless.named(PluginSpec$ReventlessCore.authorizationOf(name)), PluginSpec$ReventlessCore.commandTransition, undefined, PluginSpec$ReventlessCore.commandSchema);
12
12
 
13
13
  let pluginAggregate_producedEventTypes = [];
14
14
 
@@ -42,7 +42,7 @@ let eventSchema = Sury.union([
42
42
 
43
43
  let commandSchema$1 = Api$ReventlessInfra.markNoApi(commandSchema);
44
44
 
45
- function commandAuthorization(param) {
45
+ function authorizationOf(param) {
46
46
  return "AllowAuthenticated";
47
47
  }
48
48
 
@@ -70,7 +70,7 @@ export {
70
70
  consumedEventSchema,
71
71
  commandSchema$1 as commandSchema,
72
72
  moduleUrl,
73
- commandAuthorization,
73
+ authorizationOf,
74
74
  readConsistency,
75
75
  commandTransition,
76
76
  traits,
@@ -92,11 +92,11 @@ let emptyResult: dcbResult = {
92
92
  // An inbound slice's door rule, read off its spec.
93
93
  let inboundDoorPermission = (
94
94
  commandSchema: S.t<'command>,
95
- commandAuthorization: 'command => Reventless.Authorization.rule<'role>,
95
+ authorizationOf: string => Reventless.Authorization.rule<'role>,
96
96
  ) =>
97
97
  Plugin_Structure.inboundDoorPermission(
98
98
  ~commandSchema=commandSchema->S.castToUnknown,
99
- ~commandAuthorization=cmd => cmd->commandAuthorization->Reventless.Authorization.named,
99
+ ~authorizationOf=name => name->authorizationOf->Reventless.Authorization.named,
100
100
  )
101
101
 
102
102
  module Make = (
@@ -544,8 +544,7 @@ module Make = (
544
544
  ~commandSchema,
545
545
  )->Array.map(((f, _)) => f),
546
546
  ~commandSchema,
547
- ~commandAuthorization=command =>
548
- S.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
547
+ ~authorizationOf=name => S.Spec.authorizationOf(name)->Reventless.Authorization.named,
549
548
  )
550
549
  }
551
550
  })
@@ -560,8 +559,7 @@ module Make = (
560
559
  ~commandSchema,
561
560
  )->Array.map(((f, _)) => f),
562
561
  ~commandSchema,
563
- ~commandAuthorization=command =>
564
- S.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
562
+ ~authorizationOf=name => S.Spec.authorizationOf(name)->Reventless.Authorization.named,
565
563
  )
566
564
  }
567
565
  })
@@ -759,10 +757,7 @@ module Make = (
759
757
  registerResolver(
760
758
  ~fieldName,
761
759
  ~externalInputSchema=ITS.Spec.externalInputSchema->S.castToUnknown,
762
- ~permission=?inboundDoorPermission(
763
- ITS.Spec.commandSchema,
764
- ITS.Spec.commandAuthorization,
765
- ),
760
+ ~permission=?inboundDoorPermission(ITS.Spec.commandSchema, ITS.Spec.authorizationOf),
766
761
  )
767
762
  | None => ()
768
763
  }
@@ -1066,17 +1061,13 @@ module Make = (
1066
1061
  )
1067
1062
  // One SDL field per constructor, each with its own constructor's args and
1068
1063
  // its own per-constructor authorization rule.
1069
- let commandAuthorization: unknown => Reventless.Authorization.permission = command =>
1070
- S.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named
1071
1064
  let fieldPermissions = Dict.make()
1072
- fieldSpecs->Array.forEach(((fieldName, ctor)) => {
1073
- let hasPayload = Reventless.DcbTag.isVariantPayloadBearing(
1074
- commandSchema->Obj.magic,
1075
- ctor,
1065
+ fieldSpecs->Array.forEach(((fieldName, ctor)) =>
1066
+ fieldPermissions->Dict.set(
1067
+ fieldName,
1068
+ S.Spec.authorizationOf(ctor)->Reventless.Authorization.named,
1076
1069
  )
1077
- let syntheticCmd: unknown = hasPayload ? {"TAG": ctor}->Obj.magic : ctor->Obj.magic
1078
- fieldPermissions->Dict.set(fieldName, commandAuthorization(syntheticCmd))
1079
- })
1070
+ )
1080
1071
  Some({
1081
1072
  ReventlessInfra.Api.fieldNames: fieldSpecs->Array.map(((f, _)) => f),
1082
1073
  commandSchema,
@@ -1098,7 +1089,7 @@ module Make = (
1098
1089
  let fieldPermissions = Dict.make()
1099
1090
  inboundDoorPermission(
1100
1091
  ITS.Spec.commandSchema,
1101
- ITS.Spec.commandAuthorization,
1092
+ ITS.Spec.authorizationOf,
1102
1093
  )->Option.forEach(rule => fieldPermissions->Dict.set(fieldName, rule))
1103
1094
  {
1104
1095
  ReventlessInfra.Api.fieldNames: [fieldName],
@@ -118,8 +118,8 @@ let emptyResult = {
118
118
  eventLogEntries: emptyResult_eventLogEntries
119
119
  };
120
120
 
121
- function inboundDoorPermission(commandSchema, commandAuthorization) {
122
- return Plugin_Structure$ReventlessCore.inboundDoorPermission(commandSchema, cmd => Authorization$Reventless.named(commandAuthorization(cmd)));
121
+ function inboundDoorPermission(commandSchema, authorizationOf) {
122
+ return Plugin_Structure$ReventlessCore.inboundDoorPermission(commandSchema, name => Authorization$Reventless.named(authorizationOf(name)));
123
123
  }
124
124
 
125
125
  function Make(DcbEventLogStorage) {
@@ -295,13 +295,13 @@ function Make(DcbEventLogStorage) {
295
295
  syncSlices.forEach(S => {
296
296
  let commandSchema = S.Spec.commandSchema;
297
297
  if (!ApiNoApiHelpers$ReventlessCore.isNoApi(commandSchema)) {
298
- return registerResolver("Dcb", Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema).map(param => param[0]), commandSchema, command => Authorization$Reventless.named(S.Spec.commandAuthorization(command)));
298
+ return registerResolver("Dcb", Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema).map(param => param[0]), commandSchema, name => Authorization$Reventless.named(S.Spec.authorizationOf(name)));
299
299
  }
300
300
  });
301
301
  asyncSlices.forEach(S => {
302
302
  let commandSchema = S.Spec.commandSchema;
303
303
  if (!ApiNoApiHelpers$ReventlessCore.isNoApi(commandSchema)) {
304
- return registerResolver("Dcb", Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema).map(param => param[0]), commandSchema, command => Authorization$Reventless.named(S.Spec.commandAuthorization(command)));
304
+ return registerResolver("Dcb", Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema).map(param => param[0]), commandSchema, name => Authorization$Reventless.named(S.Spec.authorizationOf(name)));
305
305
  }
306
306
  });
307
307
  }
@@ -397,7 +397,7 @@ function Make(DcbEventLogStorage) {
397
397
  let fieldName = Api_Naming$ReventlessCore.sliceMutationField(name, ITS.Spec.name);
398
398
  let registerResolver = HooksConfig.hooks.inboundMutationResolverHook;
399
399
  if (registerResolver !== undefined) {
400
- registerResolver(fieldName, ITS.Spec.externalInputSchema, inboundDoorPermission(ITS.Spec.commandSchema, ITS.Spec.commandAuthorization));
400
+ registerResolver(fieldName, ITS.Spec.externalInputSchema, inboundDoorPermission(ITS.Spec.commandSchema, ITS.Spec.authorizationOf));
401
401
  }
402
402
  let bindReceive = HooksConfig.hooks.inboundMutationBindReceiveHook;
403
403
  if (bindReceive !== undefined) {
@@ -554,12 +554,7 @@ function Make(DcbEventLogStorage) {
554
554
  let sliceDef = Stdlib_Option.flatMap(pluginStructure, s => s.stateChangeSlices.find(d => d.name === S.Spec.name));
555
555
  let fieldPermissions = {};
556
556
  fieldSpecs.forEach(param => {
557
- let ctor = param[1];
558
- let hasPayload = DcbTag$Reventless.isVariantPayloadBearing(commandSchema, ctor);
559
- let syntheticCmd = hasPayload ? ({
560
- TAG: ctor
561
- }) : ctor;
562
- fieldPermissions[param[0]] = Authorization$Reventless.named(S.Spec.commandAuthorization(syntheticCmd));
557
+ fieldPermissions[param[0]] = Authorization$Reventless.named(S.Spec.authorizationOf(param[1]));
563
558
  });
564
559
  return {
565
560
  fieldNames: fieldSpecs.map(param => param[0]),
@@ -574,7 +569,7 @@ function Make(DcbEventLogStorage) {
574
569
  let mutationEntriesFromInboundSlices = inboundTranslationSlices.map(ITS => {
575
570
  let fieldName = Api_Naming$ReventlessCore.sliceMutationField(name, ITS.Spec.name);
576
571
  let fieldPermissions = {};
577
- Stdlib_Option.forEach(inboundDoorPermission(ITS.Spec.commandSchema, ITS.Spec.commandAuthorization), rule => {
572
+ Stdlib_Option.forEach(inboundDoorPermission(ITS.Spec.commandSchema, ITS.Spec.authorizationOf), rule => {
578
573
  fieldPermissions[fieldName] = rule;
579
574
  });
580
575
  return {
@@ -168,34 +168,37 @@ module Make = (
168
168
  switch Translation.translate(input) {
169
169
  | Error(msg) => fail(msg)
170
170
  | Ok(pairs) =>
171
- // Each command against its own rule; one refused refuses the message.
172
- let refused =
173
- pairs->Array.find(((_, cmd)) =>
174
- !callerAdmits(Spec.commandAuthorization(cmd)->Reventless.Authorization.named, caller)
175
- )
176
- switch refused {
177
- | Some(_) =>
171
+ switch pairs->Array.reduce(Ok([]), (acc, pair) =>
172
+ switch (acc, encode(pair)) {
173
+ | (Ok(msgs), Ok(msg)) => Ok(msgs->Array.concat([msg]))
174
+ | (Error(_) as failed, _) => failed
175
+ | (_, Error(msg)) => Error(msg)
176
+ }
177
+ ) {
178
+ | Error(msg) => fail(msg)
179
+ // Each command against its own rule, read by the name it encodes to; one
180
+ // refused refuses the message.
181
+ | Ok(msgs)
182
+ if msgs->Array.some(({commandJson}) =>
183
+ !callerAdmits(
184
+ commandJson
185
+ ->Reventless.Message.variantNameOfJson
186
+ ->Spec.authorizationOf
187
+ ->Reventless.Authorization.named,
188
+ caller,
189
+ )
190
+ ) =>
178
191
  fail(
179
192
  ~errorCode="Forbidden",
180
193
  `${Spec.name}: the caller is not authorized for every command this input translates into`,
181
194
  )
182
- | None =>
183
- switch pairs->Array.reduce(Ok([]), (acc, pair) =>
184
- switch (acc, encode(pair)) {
185
- | (Ok(msgs), Ok(msg)) => Ok(msgs->Array.concat([msg]))
186
- | (Error(_) as failed, _) => failed
187
- | (_, Error(msg)) => Error(msg)
188
- }
189
- ) {
190
- | Error(msg) => fail(msg)
191
- | Ok([]) => succeed(~requestId, ~inputJson, [])
192
- | Ok(msgs) =>
193
- try {
194
- await publishJsons(msgs)
195
- succeed(~requestId, ~inputJson, pairs->Array.map(((targetId, _)) => targetId))
196
- } catch {
197
- | exn => fail(messageOf(exn, "publish failed"))
198
- }
195
+ | Ok([]) => succeed(~requestId, ~inputJson, [])
196
+ | Ok(msgs) =>
197
+ try {
198
+ await publishJsons(msgs)
199
+ succeed(~requestId, ~inputJson, pairs->Array.map(((targetId, _)) => targetId))
200
+ } catch {
201
+ | exn => fail(messageOf(exn, "publish failed"))
199
202
  }
200
203
  }
201
204
  }
@@ -8,6 +8,7 @@ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
8
8
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
9
9
  import * as Effect from "effect/Effect";
10
10
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
11
+ import * as Message$Reventless from "@reventlessdev/reventless-spec/src/types/Message.res.mjs";
11
12
  import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
12
13
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
13
14
  import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
@@ -167,10 +168,6 @@ function Make(Spec) {
167
168
  return fail(undefined, msg._0);
168
169
  }
169
170
  let pairs = msg._0;
170
- let refused = pairs.find(param => !callerAdmits(Authorization$Reventless.named(Spec.commandAuthorization(param[1])), caller));
171
- if (refused !== undefined) {
172
- return fail("Forbidden", Spec.name + `: the caller is not authorized for every command this input translates into`);
173
- }
174
171
  let msg$1 = Stdlib_Array.reduce(pairs, {
175
172
  TAG: "Ok",
176
173
  _0: []
@@ -196,6 +193,9 @@ function Make(Spec) {
196
193
  return fail(undefined, msg$1._0);
197
194
  }
198
195
  let msgs = msg$1._0;
196
+ if (msgs.some(param => !callerAdmits(Authorization$Reventless.named(Spec.authorizationOf(Message$Reventless.variantNameOfJson(param.commandJson))), caller))) {
197
+ return fail("Forbidden", Spec.name + `: the caller is not authorized for every command this input translates into`);
198
+ }
199
199
  if (msgs.length === 0) {
200
200
  return succeed(requestId, inputJson, []);
201
201
  }
@@ -214,32 +214,22 @@ module Make = (
214
214
  ~kind=Aggregate,
215
215
  ~fields=fieldNames,
216
216
  ~commandSchema,
217
- ~commandAuthorization=command =>
218
- M.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
217
+ ~authorizationOf=name =>
218
+ M.Spec.authorizationOf(name)->Reventless.Authorization.named,
219
219
  )
220
220
  )
221
221
  let aggDef =
222
222
  pluginStructure->Option.flatMap(s =>
223
223
  s.aggregates->Array.find(d => d.name == M.Spec.name)
224
224
  )
225
- // Stage E2 (host-ui-login-core): derive per-field permissions by
226
- // evaluating the PPX-generated `commandAuthorization` against a
227
- // synthetic command value per constructor. Mirrors the resolver-time
228
- // shape from `CommandGeneratorResolvers_GraphQL.syntheticCommand` —
229
- // payload-bearing variants compile to `{TAG, ...}`, payload-less to
230
- // bare strings.
225
+ // Each field gated by its own constructor's rule.
231
226
  let fieldPermissions = Dict.make()
232
- filteredConstructorNames->Array.forEachWithIndex((cname, idx) => {
233
- let fieldName = fieldNames->Array.getUnsafe(idx)
234
- let hasPayload = Reventless.DcbTag.isVariantPayloadBearing(
235
- M.Spec.commandSchema->Obj.magic,
236
- cname,
227
+ filteredConstructorNames->Array.forEachWithIndex((cname, idx) =>
228
+ fieldPermissions->Dict.set(
229
+ fieldNames->Array.getUnsafe(idx),
230
+ M.Spec.authorizationOf(cname)->Reventless.Authorization.named,
237
231
  )
238
- let syntheticCmd: unknown = hasPayload ? {"TAG": cname}->Obj.magic : cname->Obj.magic
239
- let rule =
240
- M.Spec.commandAuthorization(syntheticCmd->Obj.magic)->Reventless.Authorization.named
241
- fieldPermissions->Dict.set(fieldName, rule)
242
- })
232
+ )
243
233
  [
244
234
  {
245
235
  ReventlessInfra.Api.fieldNames,
@@ -176,17 +176,11 @@ function Make(Spec) {
176
176
  if (fieldNames.length === 0) {
177
177
  return [];
178
178
  }
179
- Stdlib_Option.forEach(Spec.hooks.mutationResolverHook, registerResolver => registerResolver("Aggregate", fieldNames, commandSchema, command => Authorization$Reventless.named(M.Spec.commandAuthorization(command))));
179
+ Stdlib_Option.forEach(Spec.hooks.mutationResolverHook, registerResolver => registerResolver("Aggregate", fieldNames, commandSchema, name => Authorization$Reventless.named(M.Spec.authorizationOf(name))));
180
180
  let aggDef = Stdlib_Option.flatMap(pluginStructure, s => s.aggregates.find(d => d.name === M.Spec.name));
181
181
  let fieldPermissions = {};
182
182
  filteredConstructorNames.forEach((cname, idx) => {
183
- let fieldName = fieldNames[idx];
184
- let hasPayload = DcbTag$Reventless.isVariantPayloadBearing(M.Spec.commandSchema, cname);
185
- let syntheticCmd = hasPayload ? ({
186
- TAG: cname
187
- }) : cname;
188
- let rule = Authorization$Reventless.named(M.Spec.commandAuthorization(syntheticCmd));
189
- fieldPermissions[fieldName] = rule;
183
+ fieldPermissions[fieldNames[idx]] = Authorization$Reventless.named(M.Spec.authorizationOf(cname));
190
184
  });
191
185
  return [{
192
186
  fieldNames: fieldNames,
@@ -1006,14 +1006,14 @@ type subscriptionInfraParams = {
1006
1006
  type platformHooks = {
1007
1007
  // ── In-memory GraphQL mutation registration ────────────────────────────
1008
1008
  // Phase 1: register SDL + resolver stub synchronously.
1009
- // `commandAuthorization` is the `command => permission` function from the
1010
- // aggregate / slice Spec; the resolver evaluates it per-request against
1011
- // `ctx.identity` to enforce per-constructor authorization rules.
1009
+ // `authorizationOf` is the aggregate / slice Spec's rule by constructor name;
1010
+ // the resolver evaluates it per-request against `ctx.identity` to enforce
1011
+ // per-constructor authorization rules.
1012
1012
  mutationResolverHook?: (
1013
1013
  ~kind: mutationKind,
1014
1014
  ~fields: array<string>,
1015
1015
  ~commandSchema: S.t<unknown>,
1016
- ~commandAuthorization: unknown => Reventless.Authorization.permission,
1016
+ ~authorizationOf: string => Reventless.Authorization.permission,
1017
1017
  ) => unit,
1018
1018
  // Phase 2: bind generateCommand inside Output.apply.
1019
1019
  mutationBindHook?: (~field: string, ~generateCommand: CommandGenerator.commandGenerator) => unit,
@@ -1158,8 +1158,7 @@ let registerAdminAggregateMutations = (
1158
1158
  ~kind=Aggregate,
1159
1159
  ~fields=fieldNames,
1160
1160
  ~commandSchema,
1161
- ~commandAuthorization=command =>
1162
- M.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
1161
+ ~authorizationOf=name => M.Spec.authorizationOf(name)->Reventless.Authorization.named,
1163
1162
  )
1164
1163
  )
1165
1164
  }
@@ -541,7 +541,7 @@ function registerAdminAggregateMutations(aggregates, hooks) {
541
541
  let fieldNames = filteredConstructorNames.map(cname => Api_Naming$ReventlessCore.adminField(M.Spec.name + "_" + cname));
542
542
  aggregateMutationFieldsRegistry[M.Spec.name] = fieldNames;
543
543
  if (fieldNames.length !== 0) {
544
- return Stdlib_Option.forEach(hooks.mutationResolverHook, registerResolver => registerResolver("Aggregate", fieldNames, commandSchema, command => Authorization$Reventless.named(M.Spec.commandAuthorization(command))));
544
+ return Stdlib_Option.forEach(hooks.mutationResolverHook, registerResolver => registerResolver("Aggregate", fieldNames, commandSchema, name => Authorization$Reventless.named(M.Spec.authorizationOf(name))));
545
545
  }
546
546
  });
547
547
  }
@@ -810,28 +810,25 @@ let nonEmpty = (xs: array<string>): option<array<string>> => xs->Array.length >
810
810
  let rolesFor = (rule: Reventless.Authorization.permission): option<array<string>> =>
811
811
  rule->Reventless.Authorization.rolesOf->Array.map(role => (role :> string))->nonEmpty
812
812
 
813
- // A stand-in for one constructor, the same shape the resolver builds at call time:
814
- // a payload-bearing variant compiles to `{TAG, ...}`, a payload-less one to a bare
815
- // string. Enough for the per-command switches the PPX generates, which match on
816
- // the constructor alone.
813
+ // A stand-in for one constructor: a payload-bearing variant compiles to
814
+ // `{TAG, ...}`, a payload-less one to a bare string. Enough for a spec's
815
+ // `commandTransition`, whose switch matches on the constructor alone.
817
816
  let syntheticCommand = (~schema: S.t<unknown>, variantName: string): 'command =>
818
817
  Reventless.DcbTag.isVariantPayloadBearing(schema, variantName)
819
818
  ? {"TAG": variantName}->Obj.magic
820
819
  : variantName->Obj.magic
821
820
 
822
- // Each constructor's rule, read off its stand-in.
821
+ // Each constructor's rule.
823
822
  let constructorPermissions = (
824
823
  ~commandSchema: S.t<unknown>,
825
- ~commandAuthorization: 'command => Reventless.Authorization.permission,
824
+ ~authorizationOf: string => Reventless.Authorization.permission,
826
825
  ): array<Reventless.Authorization.permission> =>
827
- Reventless.DcbTag.extractAllVariantNames(commandSchema)->Array.map(variantName =>
828
- syntheticCommand(~schema=commandSchema, variantName)->commandAuthorization
829
- )
826
+ Reventless.DcbTag.extractAllVariantNames(commandSchema)->Array.map(authorizationOf)
830
827
 
831
828
  // An inbound slice's gate where no command exists yet: any constructor's rule.
832
829
  // Both the deployed gate and the local resolver apply this one.
833
- let inboundDoorPermission = (~commandSchema, ~commandAuthorization) =>
834
- constructorPermissions(~commandSchema, ~commandAuthorization)->Reventless.Authorization.anyOf
830
+ let inboundDoorPermission = (~commandSchema, ~authorizationOf) =>
831
+ constructorPermissions(~commandSchema, ~authorizationOf)->Reventless.Authorization.anyOf
835
832
 
836
833
  // Each mutation argument's GraphQL type onto its property, so a consumer declares
837
834
  // the variable the server expects. Mutates the freshly derived schema in place.
@@ -860,13 +857,13 @@ let toCommandDef = (
860
857
  ~partitionKey: option<string>=?,
861
858
  ~mutationFieldFor: string => string,
862
859
  ~parentSchema: S.t<unknown>,
863
- // The PPX-generated `command => permission`. Per VARIANT, not per component:
864
- // one aggregate carries commands with very different audiences, and a
865
- // component-level shortcut would gate `AddProduct` and `PlaceOrder` alike.
866
- ~commandAuthorization: unknown => Reventless.Authorization.permission,
867
- // The spec's `command => Transition.t<_>`, evaluated per variant the same way
868
- // `commandAuthorization` is, and the only declaration of an edge there is —
869
- // the PPX injects `Unrestricted` for a spec that writes no switch.
860
+ // The spec's `authorizationOf`, by constructor name. Per VARIANT, not per
861
+ // component: one aggregate carries commands with very different audiences,
862
+ // and a component-level shortcut would gate `AddProduct` and `PlaceOrder` alike.
863
+ ~authorizationOf: string => Reventless.Authorization.permission,
864
+ // The spec's `command => Transition.t<_>`, evaluated per variant against a
865
+ // stand-in, and the only declaration of an edge there is — the PPX injects
866
+ // `Undeclared` for a spec that writes no switch.
870
867
  //
871
868
  // Read at `t<string>` because this is the erasure boundary: the spec declares
872
869
  // its edges over the linked view's own lifecycle enum, whose arms are
@@ -961,7 +958,7 @@ let toCommandDef = (
961
958
  | Some(excluded) => !(excluded->Set.has(variantName))
962
959
  | None => true
963
960
  }
964
- let rule = commandAuthorization(syntheticCommand)
961
+ let rule = authorizationOf(variantName)
965
962
  let requiredAccess = accessKeysFor(rule)
966
963
  // See the note on the record's `mutationField` for why a non-exposed
967
964
  // variant gets the empty sentinel. It has no callable field, and the
@@ -1032,7 +1029,7 @@ let extractCommandDefs = (
1032
1029
  ~isAggregate,
1033
1030
  ~partitionKey: option<string>=?,
1034
1031
  ~mutationFieldFor: string => string,
1035
- ~commandAuthorization: unknown => Reventless.Authorization.permission,
1032
+ ~authorizationOf: string => Reventless.Authorization.permission,
1036
1033
  ~commandTransition: unknown => Reventless.Transition.t<string>,
1037
1034
  ~derivedEdgeFor: string => option<Reventless.Plugin.derivedEdge>=_ => None,
1038
1035
  commandSchema: S.t<unknown>,
@@ -1046,7 +1043,7 @@ let extractCommandDefs = (
1046
1043
  ~partitionKey?,
1047
1044
  ~mutationFieldFor,
1048
1045
  ~parentSchema=commandSchema,
1049
- ~commandAuthorization,
1046
+ ~authorizationOf,
1050
1047
  ~commandTransition,
1051
1048
  ~derivedEdgeFor,
1052
1049
  v,
@@ -1060,7 +1057,7 @@ let extractCommandDefs = (
1060
1057
  ~partitionKey?,
1061
1058
  ~mutationFieldFor,
1062
1059
  ~parentSchema=commandSchema,
1063
- ~commandAuthorization,
1060
+ ~authorizationOf,
1064
1061
  ~commandTransition,
1065
1062
  ~derivedEdgeFor,
1066
1063
  commandSchema,
@@ -1719,8 +1716,7 @@ let make = (
1719
1716
  ~commandSchema=SCS.Spec.commandSchema->S.castToUnknown,
1720
1717
  ~variant=variantName,
1721
1718
  ),
1722
- ~commandAuthorization=command =>
1723
- SCS.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
1719
+ ~authorizationOf=name => SCS.Spec.authorizationOf(name)->Reventless.Authorization.named,
1724
1720
  ~commandTransition=SCS.Spec.commandTransition->Obj.magic,
1725
1721
  ~derivedEdgeFor=derivedEdgeFor(~component=SCS.Spec.name, ...),
1726
1722
  SCS.Spec.commandSchema->S.castToUnknown,
@@ -1754,8 +1750,7 @@ let make = (
1754
1750
  ~aggregate=A.Spec.name,
1755
1751
  ~command=variantName,
1756
1752
  ),
1757
- ~commandAuthorization=command =>
1758
- A.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
1753
+ ~authorizationOf=name => A.Spec.authorizationOf(name)->Reventless.Authorization.named,
1759
1754
  ~commandTransition=A.Spec.commandTransition->Obj.magic,
1760
1755
  ~derivedEdgeFor=derivedEdgeFor(~component=A.Spec.name, ...),
1761
1756
  A.Spec.commandSchema->S.castToUnknown,
@@ -1834,8 +1829,7 @@ let make = (
1834
1829
  requiredRoles: ?(
1835
1830
  constructorPermissions(
1836
1831
  ~commandSchema=ITS.Spec.commandSchema->S.castToUnknown,
1837
- ~commandAuthorization=cmd =>
1838
- ITS.Spec.commandAuthorization(cmd)->Reventless.Authorization.named,
1832
+ ~authorizationOf=name => ITS.Spec.authorizationOf(name)->Reventless.Authorization.named,
1839
1833
  )
1840
1834
  ->Array.flatMap(Reventless.Authorization.rolesOf)
1841
1835
  ->Array.map(role => (role :> string))
@@ -644,12 +644,12 @@ function syntheticCommand(schema, variantName) {
644
644
  }
645
645
  }
646
646
 
647
- function constructorPermissions(commandSchema, commandAuthorization) {
648
- return DcbTag$Reventless.extractAllVariantNames(commandSchema).map(variantName => commandAuthorization(syntheticCommand(commandSchema, variantName)));
647
+ function constructorPermissions(commandSchema, authorizationOf) {
648
+ return DcbTag$Reventless.extractAllVariantNames(commandSchema).map(authorizationOf);
649
649
  }
650
650
 
651
- function inboundDoorPermission(commandSchema, commandAuthorization) {
652
- return Authorization$Reventless.anyOf(constructorPermissions(commandSchema, commandAuthorization));
651
+ function inboundDoorPermission(commandSchema, authorizationOf) {
652
+ return Authorization$Reventless.anyOf(DcbTag$Reventless.extractAllVariantNames(commandSchema).map(authorizationOf));
653
653
  }
654
654
 
655
655
  function annotateArgTypes(schema, argTypes) {
@@ -666,7 +666,7 @@ function annotateArgTypes(schema, argTypes) {
666
666
  return schema;
667
667
  }
668
668
 
669
- function toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, parentSchema, commandAuthorization, commandTransition, derivedEdgeFor, v) {
669
+ function toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, parentSchema, authorizationOf, commandTransition, derivedEdgeFor, v) {
670
670
  let mkDef = (variantName, properties) => {
671
671
  let match = commandLevelAndId(isAggregate, partitionKey, variantName, properties);
672
672
  let aggregateIdField = match[1];
@@ -708,7 +708,7 @@ function toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor
708
708
  let excluded = ApiNoApiHelpers$ReventlessCore.getExcludedVariants(parentSchema);
709
709
  apiExposed = excluded !== undefined ? !excluded.has(variantName) : true;
710
710
  }
711
- let rule = commandAuthorization(syntheticCommand$1);
711
+ let rule = authorizationOf(variantName);
712
712
  let requiredAccess = accessKeysFor(rule);
713
713
  let mutationField = apiExposed ? mutationFieldFor(variantName) : "";
714
714
  let jsonSchema = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(apiExposed, mutationField, v);
@@ -753,12 +753,12 @@ function toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor
753
753
  }
754
754
  }
755
755
 
756
- function extractCommandDefs(identityViews, isAggregate, partitionKey, mutationFieldFor, commandAuthorization, commandTransition, derivedEdgeForOpt, commandSchema) {
756
+ function extractCommandDefs(identityViews, isAggregate, partitionKey, mutationFieldFor, authorizationOf, commandTransition, derivedEdgeForOpt, commandSchema) {
757
757
  let derivedEdgeFor = derivedEdgeForOpt !== undefined ? derivedEdgeForOpt : param => {};
758
758
  if (commandSchema.type === "anyOf") {
759
- return Stdlib_Array.filterMap(commandSchema.anyOf, v => toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, derivedEdgeFor, v));
759
+ return Stdlib_Array.filterMap(commandSchema.anyOf, v => toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, commandSchema, authorizationOf, commandTransition, derivedEdgeFor, v));
760
760
  } else {
761
- return Stdlib_Option.mapOr(toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, derivedEdgeFor, commandSchema), [], def => [def]);
761
+ return Stdlib_Option.mapOr(toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, commandSchema, authorizationOf, commandTransition, derivedEdgeFor, commandSchema), [], def => [def]);
762
762
  }
763
763
  }
764
764
 
@@ -1226,7 +1226,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
1226
1226
  let consumed = match$1[1];
1227
1227
  return {
1228
1228
  name: SCS.Spec.name,
1229
- commands: extractCommandDefs(identityViews, false, partitionBySlice[SCS.Spec.name], variantName => Api_Naming$ReventlessCore.sliceMutationFieldFor(name, SCS.Spec.name, SCS.Spec.commandSchema, variantName), command => Authorization$Reventless.named(SCS.Spec.commandAuthorization(command)), SCS.Spec.commandTransition, extra => derivedEdgeFor(SCS.Spec.name, extra), SCS.Spec.commandSchema),
1229
+ commands: extractCommandDefs(identityViews, false, partitionBySlice[SCS.Spec.name], variantName => Api_Naming$ReventlessCore.sliceMutationFieldFor(name, SCS.Spec.name, SCS.Spec.commandSchema, variantName), name => Authorization$Reventless.named(SCS.Spec.authorizationOf(name)), SCS.Spec.commandTransition, extra => derivedEdgeFor(SCS.Spec.name, extra), SCS.Spec.commandSchema),
1230
1230
  producedEventTypes: produced,
1231
1231
  consumedEventTypes: consumed,
1232
1232
  linkedViews: linkedSvsFor(produced),
@@ -1241,7 +1241,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
1241
1241
  let produced = match[1];
1242
1242
  return {
1243
1243
  name: A.Spec.name,
1244
- commands: extractCommandDefs(identityViews, true, undefined, variantName => Api_Naming$ReventlessCore.aggregateMutationField(name, A.Spec.name, variantName), command => Authorization$Reventless.named(A.Spec.commandAuthorization(command)), A.Spec.commandTransition, extra => derivedEdgeFor(A.Spec.name, extra), A.Spec.commandSchema),
1244
+ commands: extractCommandDefs(identityViews, true, undefined, variantName => Api_Naming$ReventlessCore.aggregateMutationField(name, A.Spec.name, variantName), name => Authorization$Reventless.named(A.Spec.authorizationOf(name)), A.Spec.commandTransition, extra => derivedEdgeFor(A.Spec.name, extra), A.Spec.commandSchema),
1245
1245
  producedEventTypes: produced,
1246
1246
  consumedEventTypes: [],
1247
1247
  linkedViews: linkedSvsFor(produced).concat(linkedReadModelsFor(A.Spec.name)),
@@ -1276,7 +1276,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
1276
1276
  targetName: ITS.Spec.targetName,
1277
1277
  externalSystem: ITS.Spec.externalSystem,
1278
1278
  chapter: componentChapters[ITS.Spec.name],
1279
- requiredRoles: nonEmpty(Array.from(new Set(constructorPermissions(ITS.Spec.commandSchema, cmd => Authorization$Reventless.named(ITS.Spec.commandAuthorization(cmd))).flatMap(Authorization$Reventless.rolesOf).map(role => role)).values()))
1279
+ requiredRoles: nonEmpty(Array.from(new Set(DcbTag$Reventless.extractAllVariantNames(ITS.Spec.commandSchema).map(name => Authorization$Reventless.named(ITS.Spec.authorizationOf(name))).flatMap(Authorization$Reventless.rolesOf).map(role => role)).values()))
1280
1280
  }));
1281
1281
  let tableFailures = [];
1282
1282
  let tableWarnings = [];