@reventlessdev/reventless-core 3.0.0-alpha.238 → 3.0.0-alpha.240

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 (178) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/package.json +12 -12
  3. package/src/Message.res +11 -11
  4. package/src/Message.res.mjs +15 -14
  5. package/src/Projection.res +2 -2
  6. package/src/Projection.res.mjs +5 -4
  7. package/src/adapter/Monitoring/Monitoring.res +24 -2
  8. package/src/adapter/Monitoring/Monitoring.res.mjs +3 -3
  9. package/src/admin/Platform_AdminApi.res.mjs +3 -3
  10. package/src/admin/Platform_Admin_Structure.res +33 -112
  11. package/src/admin/Platform_Admin_Structure.res.mjs +8 -138
  12. package/src/admin/Platform_ComponentDefinitionsApi.res +2 -1
  13. package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +5 -1
  14. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs +12 -12
  15. package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments.res.mjs +14 -14
  16. package/src/components/Aggregate/Aggregate_Callback.res +2 -2
  17. package/src/components/Aggregate/Aggregate_Callback.res.mjs +4 -4
  18. package/src/components/Api/ApiAllowedStatesHelpers.res.mjs +3 -3
  19. package/src/components/Api/ApiNoApiHelpers.res.mjs +5 -5
  20. package/src/components/Api/ApiTargetStateHelpers.res.mjs +3 -3
  21. package/src/components/Api/GraphQL_FragmentGenerator.res +140 -24
  22. package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +48 -21
  23. package/src/components/Api/GraphQL_SchemaInspector.res +1 -1
  24. package/src/components/Api/GraphQL_SchemaInspector.res.mjs +1 -1
  25. package/src/components/Api/MCP_SchemaGenerator.res +1 -1
  26. package/src/components/Api/MCP_SchemaGenerator.res.mjs +1 -1
  27. package/src/components/Api/SchemaType.res +6 -10
  28. package/src/components/Api/SchemaType.res.mjs +4 -4
  29. package/src/components/Api/StorageRefFields.res +1 -1
  30. package/src/components/Api/StorageRefFields.res.mjs +1 -1
  31. package/src/components/Api/SuryToJsonSchema.res +19 -0
  32. package/src/components/Api/SuryToJsonSchema.res.mjs +10 -5
  33. package/src/components/AutomationSlice/AutomationSlice_Callback.res +3 -4
  34. package/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs +22 -23
  35. package/src/components/CommandTopic/CommandTopic_Callback.res +1 -2
  36. package/src/components/CommandTopic/CommandTopic_Callback.res.mjs +3 -4
  37. package/src/components/CommandTopic/CommandTopic_Helpers.res +1 -1
  38. package/src/components/CommandTopic/CommandTopic_Helpers.res.mjs +3 -3
  39. package/src/components/Counter/Counter.res.mjs +2 -2
  40. package/src/components/Counter/Counter_Callback.res.mjs +4 -4
  41. package/src/components/Counter/Counter_Operations.res.mjs +4 -4
  42. package/src/components/Dcb/Dcb_Builder.res +1 -1
  43. package/src/components/Dcb/Dcb_Builder.res.mjs +8 -6
  44. package/src/components/DcbEventLog/DcbEventLog_Builder.res.mjs +4 -3
  45. package/src/components/EventCollector/EventCollector_Builder.res +1 -1
  46. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res +1 -2
  47. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs +11 -12
  48. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res +3 -4
  49. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res.mjs +24 -25
  50. package/src/components/SideEffectHandler/SideEffectHandler_Callback.res +1 -1
  51. package/src/components/SideEffectHandler/SideEffectHandler_Callback.res.mjs +3 -3
  52. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +2 -2
  53. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +4 -4
  54. package/src/components/StateViewSlice/StateViewSlice_Builder.res +1 -1
  55. package/src/components/StateViewSlice/StateViewSlice_Builder.res.mjs +3 -3
  56. package/src/plugin/api/PluginBaseFragment.res +12 -29
  57. package/src/plugin/api/PluginBaseFragment.res.mjs +3 -22
  58. package/src/plugin/component/Capability_Inference.res +118 -1
  59. package/src/plugin/component/Capability_Inference.res.mjs +128 -1
  60. package/src/plugin/component/Plugin_Builder.res +9 -2
  61. package/src/plugin/component/Plugin_Builder.res.mjs +5 -2
  62. package/src/plugin/component/Plugin_Helpers.res +11 -11
  63. package/src/plugin/component/Plugin_Helpers.res.mjs +11 -10
  64. package/src/plugin/component/Plugin_Structure.res +458 -296
  65. package/src/plugin/component/Plugin_Structure.res.mjs +259 -182
  66. package/src/plugin/component/Plugin_SubscriptionSchema.res +1 -1
  67. package/src/plugin/component/Plugin_SubscriptionSchema.res.mjs +1 -1
  68. package/src/plugin/component/SchemaWalker.res +11 -11
  69. package/src/plugin/component/SchemaWalker.res.mjs +8 -9
  70. package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +15 -15
  71. package/src/plugin/lifecycle/PluginBehavior.res.mjs +55 -55
  72. package/src/plugin/lifecycle/PluginSpec.res +22 -7
  73. package/src/plugin/lifecycle/PluginSpec.res.mjs +91 -36
  74. package/src/plugin/lifecycle/PluginsReadModelSpec.res +36 -9
  75. package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +33 -22
  76. package/src/util/Interstack.res +3 -3
  77. package/src/util/LogFormat.res +1 -2
  78. package/src/util/LogFormat.res.mjs +2 -2
  79. package/tests/IdentityTest.res +6 -7
  80. package/tests/IdentityTest.res.mjs +7 -9
  81. package/tests/adapter/MonitoringTest.res +30 -3
  82. package/tests/adapter/MonitoringTest.res.mjs +31 -12
  83. package/tests/adapter/RuntimeExtensionHookReachTest.res +0 -1
  84. package/tests/adapter/RuntimeExtensionHookReachTest.res.mjs +5 -7
  85. package/tests/admin/Platform_Admin_StructureTest.res +254 -0
  86. package/tests/admin/Platform_Admin_StructureTest.res.mjs +219 -0
  87. package/tests/admin/Platform_BakedManifestTest.res +1 -0
  88. package/tests/admin/Platform_BakedManifestTest.res.mjs +1 -0
  89. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +3 -0
  90. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +6 -0
  91. package/tests/admin/Platform_PluginStructuresApiTest.res +1 -0
  92. package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +2 -0
  93. package/tests/aggregate/AggregateCacheTest.res +0 -1
  94. package/tests/aggregate/AggregateCacheTest.res.mjs +13 -15
  95. package/tests/aggregate/AggregateConflictTest.res +0 -1
  96. package/tests/aggregate/AggregateConflictTest.res.mjs +7 -9
  97. package/tests/aggregate/AggregateFixtures.res +0 -1
  98. package/tests/aggregate/AggregateFixtures.res.mjs +19 -21
  99. package/tests/aggregate/AggregateSnapshotTest.res +1 -2
  100. package/tests/aggregate/AggregateSnapshotTest.res.mjs +15 -16
  101. package/tests/api/ApiNoApiTest.res.mjs +17 -9
  102. package/tests/api/GraphQL_FragmentGeneratorTest.res +232 -0
  103. package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +207 -55
  104. package/tests/api/PsInternalFieldsReadModel.res +21 -0
  105. package/tests/api/PsInternalFieldsReadModel.res.mjs +74 -0
  106. package/tests/api/StorageRefFieldsTest.res.mjs +13 -13
  107. package/tests/api/SuryToJsonSchemaTest.res +73 -3
  108. package/tests/api/SuryToJsonSchemaTest.res.mjs +364 -816
  109. package/tests/commandgenerator/CommandGeneratorFixtures.res +0 -1
  110. package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +19 -21
  111. package/tests/commandgenerator/OwnerStampingTest.res +0 -1
  112. package/tests/commandgenerator/OwnerStampingTest.res.mjs +9 -11
  113. package/tests/commandtopic/CommandTopicCallbackFixtures.res +0 -1
  114. package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +7 -9
  115. package/tests/counter/CounterFixtures.res +0 -1
  116. package/tests/counter/CounterFixtures.res.mjs +1 -4
  117. package/tests/dcb/DcbDecodeTest.res.mjs +24 -24
  118. package/tests/dcb/DcbEventLogOperationsTest.res +2 -2
  119. package/tests/dcb/DcbEventLogOperationsTest.res.mjs +3 -3
  120. package/tests/dcb/DcbEventLogSchemaTest.res.mjs +7 -7
  121. package/tests/dcb/DcbFixtures.res +0 -1
  122. package/tests/dcb/DcbFixtures.res.mjs +87 -89
  123. package/tests/dcb/DcbTagTest.res.mjs +6 -6
  124. package/tests/dcb/DcbValidationTest.res.mjs +52 -52
  125. package/tests/eventlog/EventLogFixtures.res +0 -1
  126. package/tests/eventlog/EventLogFixtures.res.mjs +7 -9
  127. package/tests/eventmapper/EventMapperFixtures.res +0 -1
  128. package/tests/eventmapper/EventMapperFixtures.res.mjs +24 -26
  129. package/tests/eventtopic/EventTopicFixtures.res +0 -1
  130. package/tests/eventtopic/EventTopicFixtures.res.mjs +7 -9
  131. package/tests/extensionpoint/ExtensionPointFixtures.res +0 -1
  132. package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +10 -12
  133. package/tests/extensionpoint/ExtensionPointOperationsTest.res +1 -2
  134. package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +9 -10
  135. package/tests/logger/LogFormatTest.res +3 -2
  136. package/tests/logger/LogFormatTest.res.mjs +7 -9
  137. package/tests/message/MessageTest.res +2 -2
  138. package/tests/message/MessageTest.res.mjs +8 -7
  139. package/tests/message/MetaEnvelopeTest.res +5 -5
  140. package/tests/message/MetaEnvelopeTest.res.mjs +12 -11
  141. package/tests/plugin/ManifestVisibilityTest.res.mjs +7 -7
  142. package/tests/plugin/PluginDefinitionScalars.res +5 -3
  143. package/tests/plugin/PluginDefinitionScalars.res.mjs +2 -2
  144. package/tests/plugin/PluginLifecycleCorpusTest.res +4 -3
  145. package/tests/plugin/PluginLifecycleCorpusTest.res.mjs +3 -6
  146. package/tests/plugin/PluginSpecExperiment.res +0 -1
  147. package/tests/plugin/PluginSpecExperiment.res.mjs +12 -14
  148. package/tests/plugin/PluginStructureTest.res +292 -2
  149. package/tests/plugin/PluginStructureTest.res.mjs +391 -88
  150. package/tests/plugin/Plugin_EventQuerySchemaTest.res.mjs +3 -3
  151. package/tests/plugin/SchemaWalkerTest.res +0 -1
  152. package/tests/plugin/SchemaWalkerTest.res.mjs +17 -19
  153. package/tests/plugin/StateChangeSlice/PsAttachInvoice.res.mjs +5 -5
  154. package/tests/plugin/StateChangeSlice/PsChangePhoto.res.mjs +8 -8
  155. package/tests/plugin/StateChangeSlice/PsDispatchShipment.res.mjs +5 -5
  156. package/tests/plugin/StateChangeSlice/PsGatedCommands.res.mjs +9 -9
  157. package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +5 -5
  158. package/tests/plugin/StateChangeSlice/PsReserveStock.res.mjs +8 -8
  159. package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +15 -15
  160. package/tests/plugin/StateChangeSlice/PsTypoStore.res +31 -0
  161. package/tests/plugin/StateChangeSlice/PsTypoStore.res.mjs +62 -0
  162. package/tests/plugin/StateChangeSlice/PsUploadAvatar.res.mjs +8 -8
  163. package/tests/plugin/StateChangeSlice/PsUploadImages.res +40 -0
  164. package/tests/plugin/StateChangeSlice/PsUploadImages.res.mjs +66 -0
  165. package/tests/plugin/StateViewSlice/PsAnnotatedView.res.mjs +13 -12
  166. package/tests/plugin/StateViewSlice/PsAvailableProductsView.res.mjs +8 -8
  167. package/tests/plugin/StateViewSlice/PsCategoriesView.res.mjs +6 -6
  168. package/tests/plugin/StateViewSlice/PsCustomersView.res.mjs +10 -10
  169. package/tests/plugin/StateViewSlice/PsGatedView.res.mjs +6 -6
  170. package/tests/plugin/StateViewSlice/PsOrdersView.res.mjs +9 -9
  171. package/tests/plugin/StateViewSlice/PsShipmentsView.res.mjs +11 -10
  172. package/tests/plugin/pluginDefinitionRequiredScalars.txt +15 -0
  173. package/tests/querydb/QueryDbFixtures.res +0 -1
  174. package/tests/querydb/QueryDbFixtures.res.mjs +5 -7
  175. package/tests/sideeffecthandler/SideEffectHandlerFixtures.res +0 -1
  176. package/tests/sideeffecthandler/SideEffectHandlerFixtures.res.mjs +4 -6
  177. package/tests/util/CommandPublisherTest.res +0 -1
  178. package/tests/util/CommandPublisherTest.res.mjs +4 -6
package/CHANGELOG.md CHANGED
@@ -3,6 +3,46 @@
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.240 (2026-08-18)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **core:** derive the platform's own command defs from its spec ([599d0b6](https://github.com/ReventlessDev/reventless-core/commit/599d0b64e1ab1687841dbff9383f9bda5d2e629b))
11
+ * **core:** give the Plugin lifecycle its transitions back ([1dc2ab2](https://github.com/ReventlessDev/reventless-core/commit/1dc2ab28cf145f2e709359d2a5a67a50b46f5ce4))
12
+ * **core:** let the Plugins view say which field its lifecycle lives in ([45511c5](https://github.com/ReventlessDev/reventless-core/commit/45511c5476e45586b2a6ae8ebac67786360d0d85))
13
+ * **core:** let the Plugins view state its own read rule ([1fe9fcf](https://github.com/ReventlessDev/reventless-core/commit/1fe9fcfb198334923dd8342861315ac53cb78cac))
14
+ * **core:** order union constructors so every one decodes ([39c17ad](https://github.com/ReventlessDev/reventless-core/commit/39c17ad8cedd6f120e92d81b4a05b8c733b4617d)), closes [DZakh/sury#392](https://github.com/DZakh/sury/issues/392)
15
+ * **core:** report the real reason a decode failed ([233c835](https://github.com/ReventlessDev/reventless-core/commit/233c835be47cb83fbd247ab0d8a7073cad622fee)), closes [DZakh/sury#392](https://github.com/DZakh/sury/issues/392)
16
+ * feat(core)!: tell the Monitoring seam where a unit's logs live ([6cbd595](https://github.com/ReventlessDev/reventless-core/commit/6cbd595e2474fce3f87b7720a332e82583899bae))
17
+ * feat(sury)!: migrate to sury 11.0.0-rc.1 ([2cf8969](https://github.com/ReventlessDev/reventless-core/commit/2cf8969a222ce1b775563668a4126cb20611966c))
18
+ ### Features
19
+
20
+ * **core:** let a command declare the edge that creates the row ([a5b6dbd](https://github.com/ReventlessDev/reventless-core/commit/a5b6dbddd3f81ac23117f9ccb9ffdbdd07781dcc))
21
+ * **spec,ppx:** uploadable types that derive their store from the field name ([722b404](https://github.com/ReventlessDev/reventless-core/commit/722b4044a2e8dfb6a34231d6bfb529526de38b20))
22
+
23
+ ### BREAKING CHANGES
24
+
25
+ * Monitoring.Backend.onProvisioned gained a required
26
+ ~logLocator argument. An extension that packs its backend against the
27
+ module type will not compile until onProvisioned accepts it.
28
+ * sury is a direct dependency of the published packages and
29
+ its schema and serialization surface changed; consumers must migrate to
30
+ sury 11.
31
+
32
+
33
+
34
+ # 3.0.0-alpha.239 (2026-08-18)
35
+
36
+ ### Bug Fixes
37
+
38
+ * **api:** declare the reference door in the SDL every backend is built from ([5c1857e](https://github.com/ReventlessDev/reventless-core/commit/5c1857ea90ff40305a1c44a9e57043528e6a93aa))
39
+ * **api:** make the by-index door answer, and let an elevated caller widen it ([0fe0c6f](https://github.com/ReventlessDev/reventless-core/commit/0fe0c6f8dec6228ecaba39577e28d780b4f79c83))
40
+ * **core:** fail the build on a retired state the field's enum does not declare ([781935b](https://github.com/ReventlessDev/reventless-core/commit/781935bf8f3e9c418d1e7b25e7235717e4812d7c))
41
+ ### Features
42
+
43
+ * **core:** let a reference name a retired row, and let an elevated caller open one ([9e2623a](https://github.com/ReventlessDev/reventless-core/commit/9e2623a4b22487561607fcc0ca19d51726069ee4))
44
+
45
+
6
46
  # 3.0.0-alpha.238 (2026-08-16)
7
47
 
8
48
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-core",
3
- "version": "3.0.0-alpha.238",
3
+ "version": "3.0.0-alpha.240",
4
4
  "description": "Core package for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -26,24 +26,24 @@
26
26
  }
27
27
  },
28
28
  "dependencies": {
29
- "sury": "11.0.0-alpha.4",
29
+ "sury": "11.0.0-rc.1",
30
30
  "uuid": "^13.0.0",
31
31
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
32
- "@reventlessdev/rescript-fast-csv": "2.0.0-alpha.7",
33
- "@reventlessdev/rescript-hash-object": "1.2.0-alpha.14",
32
+ "@reventlessdev/rescript-fast-csv": "2.0.0-alpha.8",
33
+ "@reventlessdev/rescript-node": "2.0.0-alpha.8",
34
34
  "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
35
- "@reventlessdev/rescript-node": "2.0.0-alpha.7",
35
+ "@reventlessdev/rescript-hash-object": "1.2.0-alpha.14",
36
36
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
37
- "@reventlessdev/rescript-ssh2": "2.0.0-alpha.7",
38
- "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
39
- "@reventlessdev/reventless-interop": "3.0.0-alpha.31",
40
- "@reventlessdev/reventless-spec": "3.0.0-alpha.116",
41
- "@reventlessdev/reventless-infra": "3.0.0-alpha.144"
37
+ "@reventlessdev/rescript-ssh2": "2.0.0-alpha.8",
38
+ "@reventlessdev/reventless-interop": "3.0.0-alpha.32",
39
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.146",
40
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.118",
41
+ "@reventlessdev/rescript-uuid": "2.0.0-alpha.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "rescript": "12.3.0",
45
- "sury-ppx": "11.0.0-alpha.2",
46
- "@reventlessdev/reventless-ppx": "1.0.0-alpha.68"
45
+ "sury-ppx": "11.0.0-rc.1",
46
+ "@reventlessdev/reventless-ppx": "1.0.0-alpha.71"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "rescript": "12.3.0"
package/src/Message.res CHANGED
@@ -76,9 +76,9 @@ let decodeCommand' = (json, idSchema, commandSchema) =>
76
76
  json->parseJsonTolerant(toCommandSchema'(idSchema, commandSchema))
77
77
 
78
78
  let encodeEvent' = (event', idSchema, eventSchema) =>
79
- event'->S.reverseConvertToJsonOrThrow(toEventSchema'(idSchema, eventSchema))
79
+ event'->Reventless.Util_Sury.toJson(toEventSchema'(idSchema, eventSchema))
80
80
  let encodeCommand' = (command', idSchema, commandSchema) =>
81
- command'->S.reverseConvertToJsonOrThrow(toCommandSchema'(idSchema, commandSchema))
81
+ command'->Reventless.Util_Sury.toJson(toCommandSchema'(idSchema, commandSchema))
82
82
 
83
83
  let uuid = Uuid.v4
84
84
 
@@ -95,7 +95,7 @@ let toMessageBody = ({id, meta, commandJson}) => {
95
95
  let commandMeta: meta = {...meta, msgId: uuid(), time: nowAsISOString()}
96
96
  [
97
97
  ("id", id->JSON.Encode.string),
98
- ("meta", commandMeta->S.reverseConvertToJsonOrThrow(metaSchema)),
98
+ ("meta", commandMeta->Reventless.Util_Sury.toJson(metaSchema)),
99
99
  ("command", commandJson),
100
100
  ]
101
101
  ->Dict.fromArray
@@ -115,7 +115,7 @@ let serviceNameOfMsg = msgJson =>
115
115
  msgObj
116
116
  ->Dict.get("meta")
117
117
  ->Option.flatMap(meta =>
118
- switch meta->S.parseJsonOrThrow(metaSchema) {
118
+ switch meta->Reventless.Util_Sury.fromJson(metaSchema) {
119
119
  | msgMeta => Some(msgMeta.service)
120
120
  | exception err =>
121
121
  logger.warn(
@@ -252,7 +252,7 @@ let deriveMeta = (~parent: meta, ~service=?) => {
252
252
 
253
253
  let decomposeMeta = meta =>
254
254
  meta
255
- ->S.reverseConvertToJsonOrThrow(metaSchema)
255
+ ->Reventless.Util_Sury.toJson(metaSchema)
256
256
  ->JSON.Decode.object
257
257
  ->Option.getOrThrow
258
258
  ->Dict.toArray
@@ -260,7 +260,7 @@ let decomposeMeta = meta =>
260
260
  let composeEventJson' = (id, meta, ~recordedAt, eventJson) =>
261
261
  [
262
262
  ("id", id->JSON.Encode.string),
263
- ("meta", meta->S.reverseConvertToJsonOrThrow(metaSchema)),
263
+ ("meta", meta->Reventless.Util_Sury.toJson(metaSchema)),
264
264
  ("recordedAt", recordedAt->JSON.Encode.string),
265
265
  ("event", eventJson),
266
266
  ]
@@ -316,7 +316,7 @@ let storedEventToFlatJson = (
316
316
  idSchema: S.t<'id>,
317
317
  ): JSON.t => {
318
318
  let fields = [
319
- ("id", stored.id->S.reverseConvertToJsonOrThrow(idSchema)),
319
+ ("id", stored.id->Reventless.Util_Sury.toJson(idSchema)),
320
320
  ("position", JSON.String(stored.position)),
321
321
  ("event", JSON.String(stored.event)),
322
322
  ("data", stored.data),
@@ -324,7 +324,7 @@ let storedEventToFlatJson = (
324
324
  ]
325
325
  let withTags = switch stored.tags {
326
326
  | Some(tags) =>
327
- let tagsJson = tags->S.reverseConvertToJsonOrThrow(S.array(Reventless.DcbTag.tagSchema))
327
+ let tagsJson = tags->Reventless.Util_Sury.toJson(S.array(Reventless.DcbTag.tagSchema))
328
328
  fields->Array.concat([("tags", tagsJson)])
329
329
  | None => fields
330
330
  }
@@ -337,17 +337,17 @@ let flatJsonToStoredEvent = (
337
337
  idSchema: S.t<'id>,
338
338
  ): Reventless.StoredEvent.storedEvent<'id> => {
339
339
  let dict = json->JSON.Decode.object->Option.getOrThrow
340
- let id = dict->Dict.get("id")->Option.getOrThrow->S.parseJsonOrThrow(idSchema)
340
+ let id = dict->Dict.get("id")->Option.getOrThrow->Reventless.Util_Sury.fromJson(idSchema)
341
341
  let position = dict->Dict.get("position")->Option.getOrThrow->JSON.Decode.string->Option.getOrThrow
342
342
  let event = dict->Dict.get("event")->Option.getOrThrow->JSON.Decode.string->Option.getOrThrow
343
343
  let data = dict->Dict.get("data")->Option.getOrThrow
344
344
  let recordedAt =
345
345
  dict->Dict.get("recordedAt")->Option.getOrThrow->JSON.Decode.string->Option.getOrThrow
346
- let meta = composeMeta(dict)->S.parseJsonOrThrow(metaSchema)
346
+ let meta = composeMeta(dict)->Reventless.Util_Sury.fromJson(metaSchema)
347
347
  let tags =
348
348
  dict
349
349
  ->Dict.get("tags")
350
- ->Option.map(t => t->S.parseJsonOrThrow(S.array(Reventless.DcbTag.tagSchema)))
350
+ ->Option.map(t => t->Reventless.Util_Sury.fromJson(S.array(Reventless.DcbTag.tagSchema)))
351
351
  {id, position, event, data, recordedAt, meta, tags: ?tags}
352
352
  }
353
353
 
@@ -1,6 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as S from "sury/src/S.res.mjs";
3
+ import * as Sury from "sury";
4
4
  import * as Uuid from "uuid";
5
5
  import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
6
6
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
@@ -8,6 +8,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
8
8
  import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
9
9
  import * as Message$Reventless from "@reventlessdev/reventless-spec/src/types/Message.res.mjs";
10
10
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
11
+ import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
11
12
  import * as Logger$ReventlessCore from "./util/Logger.res.mjs";
12
13
 
13
14
  let logger = Logger$ReventlessCore.fromEnv();
@@ -28,7 +29,7 @@ function memoizeBySchemaPair(a, b, build) {
28
29
  }
29
30
 
30
31
  function toEventSchema$p(idSchema, eventSchema) {
31
- return _rawMemoize(idSchema, eventSchema, (idSchema, eventSchema) => S.object(s => ({
32
+ return _rawMemoize(idSchema, eventSchema, (idSchema, eventSchema) => Sury.object(s => ({
32
33
  id: s.f("id", idSchema),
33
34
  meta: s.f("meta", Message$Reventless.metaSchema),
34
35
  event: s.f("event", eventSchema)
@@ -36,7 +37,7 @@ function toEventSchema$p(idSchema, eventSchema) {
36
37
  }
37
38
 
38
39
  function toCommandSchema$p(idSchema, commandSchema) {
39
- return _rawMemoize(idSchema, commandSchema, (idSchema, commandSchema) => S.object(s => ({
40
+ return _rawMemoize(idSchema, commandSchema, (idSchema, commandSchema) => Sury.object(s => ({
40
41
  id: s.f("id", idSchema),
41
42
  meta: s.f("meta", Message$Reventless.metaSchema),
42
43
  command: s.f("command", commandSchema)
@@ -52,11 +53,11 @@ function decodeCommand$p(json, idSchema, commandSchema) {
52
53
  }
53
54
 
54
55
  function encodeEvent$p(event$p, idSchema, eventSchema) {
55
- return S.reverseConvertToJsonOrThrow(event$p, toEventSchema$p(idSchema, eventSchema));
56
+ return Util_Sury$Reventless.toJson(event$p, toEventSchema$p(idSchema, eventSchema));
56
57
  }
57
58
 
58
59
  function encodeCommand$p(command$p, idSchema, commandSchema) {
59
- return S.reverseConvertToJsonOrThrow(command$p, toCommandSchema$p(idSchema, commandSchema));
60
+ return Util_Sury$Reventless.toJson(command$p, toCommandSchema$p(idSchema, commandSchema));
60
61
  }
61
62
 
62
63
  function uuid(prim) {
@@ -87,7 +88,7 @@ function toMessageBody(param) {
87
88
  ],
88
89
  [
89
90
  "meta",
90
- S.reverseConvertToJsonOrThrow(newrecord, Message$Reventless.metaSchema)
91
+ Util_Sury$Reventless.toJson(newrecord, Message$Reventless.metaSchema)
91
92
  ],
92
93
  [
93
94
  "command",
@@ -102,7 +103,7 @@ function serviceNameOfMsg(msgJson) {
102
103
  return Stdlib_Option.flatMap(msgObj["meta"], meta => {
103
104
  let msgMeta;
104
105
  try {
105
- msgMeta = S.parseJsonOrThrow(meta, Message$Reventless.metaSchema);
106
+ msgMeta = Util_Sury$Reventless.fromJson(meta, Message$Reventless.metaSchema);
106
107
  } catch (raw_err) {
107
108
  let err = Primitive_exceptions.internalToException(raw_err);
108
109
  logger.warn("Message", Stdlib_Option.getOr(JSON.stringify(err), ""), "serviceNameOfMsg: Couldn't parse meta");
@@ -200,7 +201,7 @@ function deriveMeta(parent, service) {
200
201
  }
201
202
 
202
203
  function decomposeMeta(meta) {
203
- return Object.entries(Stdlib_Option.getOrThrow(Stdlib_JSON.Decode.object(S.reverseConvertToJsonOrThrow(meta, Message$Reventless.metaSchema)), undefined));
204
+ return Object.entries(Stdlib_Option.getOrThrow(Stdlib_JSON.Decode.object(Util_Sury$Reventless.toJson(meta, Message$Reventless.metaSchema)), undefined));
204
205
  }
205
206
 
206
207
  function composeEventJson$p(id, meta, recordedAt, eventJson) {
@@ -211,7 +212,7 @@ function composeEventJson$p(id, meta, recordedAt, eventJson) {
211
212
  ],
212
213
  [
213
214
  "meta",
214
- S.reverseConvertToJsonOrThrow(meta, Message$Reventless.metaSchema)
215
+ Util_Sury$Reventless.toJson(meta, Message$Reventless.metaSchema)
215
216
  ],
216
217
  [
217
218
  "recordedAt",
@@ -275,7 +276,7 @@ function storedEventToFlatJson(stored, idSchema) {
275
276
  let fields = [
276
277
  [
277
278
  "id",
278
- S.reverseConvertToJsonOrThrow(stored.id, idSchema)
279
+ Util_Sury$Reventless.toJson(stored.id, idSchema)
279
280
  ],
280
281
  [
281
282
  "position",
@@ -297,7 +298,7 @@ function storedEventToFlatJson(stored, idSchema) {
297
298
  let tags = stored.tags;
298
299
  let withTags;
299
300
  if (tags !== undefined) {
300
- let tagsJson = S.reverseConvertToJsonOrThrow(tags, S.array(DcbTag$Reventless.tagSchema));
301
+ let tagsJson = Util_Sury$Reventless.toJson(tags, Sury.array(DcbTag$Reventless.tagSchema));
301
302
  withTags = fields.concat([[
302
303
  "tags",
303
304
  tagsJson
@@ -310,13 +311,13 @@ function storedEventToFlatJson(stored, idSchema) {
310
311
 
311
312
  function flatJsonToStoredEvent(json, idSchema) {
312
313
  let dict = Stdlib_Option.getOrThrow(Stdlib_JSON.Decode.object(json), undefined);
313
- let id = S.parseJsonOrThrow(Stdlib_Option.getOrThrow(dict["id"], undefined), idSchema);
314
+ let id = Util_Sury$Reventless.fromJson(Stdlib_Option.getOrThrow(dict["id"], undefined), idSchema);
314
315
  let position = Stdlib_Option.getOrThrow(Stdlib_JSON.Decode.string(Stdlib_Option.getOrThrow(dict["position"], undefined)), undefined);
315
316
  let event = Stdlib_Option.getOrThrow(Stdlib_JSON.Decode.string(Stdlib_Option.getOrThrow(dict["event"], undefined)), undefined);
316
317
  let data = Stdlib_Option.getOrThrow(dict["data"], undefined);
317
318
  let recordedAt = Stdlib_Option.getOrThrow(Stdlib_JSON.Decode.string(Stdlib_Option.getOrThrow(dict["recordedAt"], undefined)), undefined);
318
- let meta = S.parseJsonOrThrow(composeMeta(dict), Message$Reventless.metaSchema);
319
- let tags = Stdlib_Option.map(dict["tags"], t => S.parseJsonOrThrow(t, S.array(DcbTag$Reventless.tagSchema)));
319
+ let meta = Util_Sury$Reventless.fromJson(composeMeta(dict), Message$Reventless.metaSchema);
320
+ let tags = Stdlib_Option.map(dict["tags"], t => Util_Sury$Reventless.fromJson(t, Sury.array(DcbTag$Reventless.tagSchema)));
320
321
  return {
321
322
  id: id,
322
323
  position: position,
@@ -33,13 +33,13 @@ let overlayDisplayName = (
33
33
  stateSchema: S.t<'state>,
34
34
  spec: Reventless.DisplayName.displayNameSpec,
35
35
  ): 'state => {
36
- let json = state->S.reverseConvertToJsonOrThrow(stateSchema)
36
+ let json = state->Reventless.Util_Sury.toJson(stateSchema)
37
37
  switch json->JSON.Decode.object {
38
38
  | None => state
39
39
  | Some(stateDict) =>
40
40
  let label = Reventless.DisplayName.computeLabel(spec, stateDict)
41
41
  stateDict->Dict.set("displayName", JSON.Encode.string(label))
42
- stateDict->JSON.Encode.object->S.parseJsonOrThrow(stateSchema)
42
+ stateDict->JSON.Encode.object->Reventless.Util_Sury.fromJson(stateSchema)
43
43
  }
44
44
  }
45
45
 
@@ -1,6 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as S from "sury/src/S.res.mjs";
3
+ import * as Sury from "sury";
4
4
  import * as Stream from "@reventlessdev/rescript-effect/src/Stream.res.mjs";
5
5
  import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
6
6
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
@@ -11,6 +11,7 @@ import * as Belt_SetString from "@rescript/runtime/lib/es6/Belt_SetString.js";
11
11
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
12
12
  import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
13
13
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
14
+ import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
14
15
  import * as Logger$ReventlessCore from "./util/Logger.res.mjs";
15
16
  import * as DisplayName$Reventless from "@reventlessdev/reventless-spec/src/components/DisplayName.res.mjs";
16
17
  import * as Message$ReventlessCore from "./Message.res.mjs";
@@ -21,7 +22,7 @@ import * as LogFormat$ReventlessCore from "./util/LogFormat.res.mjs";
21
22
  let log = Logger$ReventlessCore.fromEnv();
22
23
 
23
24
  function MakeGenericSource(Mapping) {
24
- let decode$p = json => Message$ReventlessCore.decodeEvent$p(json, S.string, Mapping.sourceEventSchema);
25
+ let decode$p = json => Message$ReventlessCore.decodeEvent$p(json, Sury.string, Mapping.sourceEventSchema);
25
26
  return {
26
27
  name: Mapping.sourceName,
27
28
  decode$p: decode$p
@@ -37,14 +38,14 @@ function logAction(makeStr) {
37
38
  }
38
39
 
39
40
  function overlayDisplayName(state, stateSchema, spec) {
40
- let json = S.reverseConvertToJsonOrThrow(state, stateSchema);
41
+ let json = Util_Sury$Reventless.toJson(state, stateSchema);
41
42
  let stateDict = Stdlib_JSON.Decode.object(json);
42
43
  if (stateDict === undefined) {
43
44
  return state;
44
45
  }
45
46
  let label = DisplayName$Reventless.computeLabel(spec, stateDict);
46
47
  stateDict["displayName"] = label;
47
- return S.parseJsonOrThrow(stateDict, stateSchema);
48
+ return Util_Sury$Reventless.fromJson(stateDict, stateSchema);
48
49
  }
49
50
 
50
51
  function rewriteAction(action, stateSchema) {
@@ -64,6 +64,16 @@ same stack, parented naturally.
64
64
  `AWS_Tags` uses). Both are `None` for substrate provisioned outside any plugin construct — the
65
65
  correct answer for platform-scope resources. A backend that names or routes its monitoring resources
66
66
  per plugin needs this: `~name` is only the component, and two plugins can own like-named components.
67
+
68
+ `~logLocator` is the provider-native address of the unit's logs — an OPAQUE string core assigns no
69
+ further meaning to (a CloudWatch log group name on one runtime, a namespace/selector on another),
70
+ and `None` for a unit with no logs of its own. It exists because an alert is only half of what an
71
+ operator needs: the notification a provider sends names the unit and the metric, never where to
72
+ read what happened. A backend cannot reconstruct the address either — where a unit's logs land can
73
+ depend on stack configuration the runtime backend resolved and did not record, so a derived guess is
74
+ wrong exactly where it differs from the default. Like `~component`'s fields this is an `Output`, so
75
+ it can only be used inside a resolution (a description, an annotation) and never as a synchronous
76
+ logical resource name — which is no limitation for an address a human is meant to follow.
67
77
  */
68
78
  module type Backend = {
69
79
  let onProvisioned: (
@@ -72,12 +82,20 @@ module type Backend = {
72
82
  ~component: ReventlessInfra.Adapter.resource,
73
83
  ~plugin: option<string>,
74
84
  ~platform: option<string>,
85
+ ~logLocator: option<Pulumi.Output.t<string>>,
75
86
  ) => unit
76
87
  }
77
88
 
78
89
  /** Default backend: does nothing. Active unless an extension calls `use`. */
79
90
  module Noop: Backend = {
80
- let onProvisioned = (~kind as _, ~name as _, ~component as _, ~plugin as _, ~platform as _) => ()
91
+ let onProvisioned = (
92
+ ~kind as _,
93
+ ~name as _,
94
+ ~component as _,
95
+ ~plugin as _,
96
+ ~platform as _,
97
+ ~logLocator as _,
98
+ ) => ()
81
99
  }
82
100
 
83
101
  let backend: ref<module(Backend)> = ref(module(Noop: Backend))
@@ -98,11 +116,15 @@ let notify = (
98
116
  ~kind: unitKind,
99
117
  ~name: string,
100
118
  ~component: ReventlessInfra.Adapter.resource,
119
+ ~logLocator: option<Pulumi.Output.t<string>>=?,
101
120
  ) => {
102
121
  module B = unpack(backend.contents)
103
122
  // The owning plugin/platform come from the ambient context the plugin builder publishes
104
123
  // (ResourceAttribution.enter) — populated exactly when this fires, so the call sites need not
105
124
  // thread the names. `None` outside a plugin construct (platform substrate).
106
125
  let {ResourceAttribution.plugin: plugin, platform} = ResourceAttribution.current.contents
107
- B.onProvisioned(~kind, ~name, ~component, ~plugin, ~platform)
126
+ // `~logLocator`, unlike the owner, is NOT ambient: only the provisioning site knows where it
127
+ // pointed the unit's logs. So it is passed, and optional — a call site with nothing to say omits
128
+ // it rather than being forced to invent an address.
129
+ B.onProvisioned(~kind, ~name, ~component, ~plugin, ~platform, ~logLocator)
108
130
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as ResourceAttribution$ReventlessCore from "../../ResourceAttribution.res.mjs";
4
4
 
5
- function onProvisioned(param, param$1, param$2, param$3, param$4) {
5
+ function onProvisioned(param, param$1, param$2, param$3, param$4, param$5) {
6
6
 
7
7
  }
8
8
 
@@ -18,10 +18,10 @@ function use(b) {
18
18
  backend.contents = b;
19
19
  }
20
20
 
21
- function notify(kind, name, component) {
21
+ function notify(kind, name, component, logLocator) {
22
22
  let B = backend.contents;
23
23
  let match = ResourceAttribution$ReventlessCore.current.contents;
24
- B.onProvisioned(kind, name, component, match.plugin, match.platform);
24
+ B.onProvisioned(kind, name, component, match.plugin, match.platform, logLocator);
25
25
  }
26
26
 
27
27
  export {
@@ -1,6 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as S from "sury/src/S.res.mjs";
3
+ import * as Sury from "sury";
4
4
  import * as Api_Naming$ReventlessCore from "../components/Api/Api_Naming.res.mjs";
5
5
  import * as GraphQL_Stitcher$ReventlessCore from "../components/Api/GraphQL_Stitcher.res.mjs";
6
6
  import * as PluginBaseFragment$ReventlessCore from "../plugin/api/PluginBaseFragment.res.mjs";
@@ -10,8 +10,8 @@ import * as Plugin_SubscriptionSchema$ReventlessCore from "../plugin/component/P
10
10
  import * as Platform_PluginStructuresApi$ReventlessCore from "./Platform_PluginStructuresApi.res.mjs";
11
11
  import * as Platform_ComponentDefinitionsApi$ReventlessCore from "./Platform_ComponentDefinitionsApi.res.mjs";
12
12
 
13
- let cloneArgsSchema = S.schema(s => ({
14
- restoreDateTime: s.m(S.option(S.string))
13
+ let cloneArgsSchema = Sury.$schema(s => ({
14
+ restoreDateTime: s.m(Sury.$option(Sury.string))
15
15
  }));
16
16
 
17
17
  let cloneMutationEntry_fieldNames = [Api_Naming$ReventlessCore.adminField("Clone")];
@@ -7,9 +7,11 @@
7
7
  // never seeds them into pluginStructuresStore — so the host shell's Auto UI
8
8
  // has no metadata for the admin plugin and renders nothing for it.
9
9
  //
10
- // This module produces an equivalent structure by hand using the same field
11
- // naming conventions (Api_Naming.adminField) and the mutation arg schemas
12
- // from PluginBaseFragment.
10
+ // This module assembles the equivalent structure from `PluginSpec` /
11
+ // `PluginsReadModelSpec` directly, through the same module-level extractors
12
+ // `make` uses for every ordinary plugin. It cannot call `make` itself — the
13
+ // platform has no `module(Aggregate.T)` to hand it at structure-assembly time —
14
+ // but the extractors take schemas, which is all this needs.
13
15
 
14
16
  open Reventless.Plugin
15
17
 
@@ -17,86 +19,31 @@ let pluginId = "Platform"
17
19
 
18
20
  let pluginName = pluginId
19
21
 
20
- let encodeSchema = (schema: S.t<unknown>): string =>
21
- schema->SuryToJsonSchema.deriveObjectSchema->JSON.stringify
22
+ let commandSchema = PluginSpec.commandSchema->S.castToUnknown
22
23
 
23
- // Strip the named fields from a derived JSON schema's `properties` map and
24
- // `required` list. Used so Plugin read model metadata announced via
25
- // `Platform_ComponentDefinitions` matches the SDL — fields excluded from the GraphQL
26
- // schema in `PluginBaseFragment` must also disappear from the schema string
27
- // AutoUI consumes, or list-view queries will reference non-existent fields.
28
- let encodeSchemaExcluding = (schema: S.t<unknown>, ~excludeFields: array<string>): string => {
29
- let derived = schema->SuryToJsonSchema.deriveObjectSchema
30
- switch derived->JSON.Decode.object {
31
- | None => derived->JSON.stringify
32
- | Some(obj) =>
33
- switch obj->Dict.get("properties")->Option.flatMap(JSON.Decode.object) {
34
- | None => derived->JSON.stringify
35
- | Some(props) =>
36
- excludeFields->Array.forEach(f => props->Dict.delete(f))
37
- obj->Dict.set("properties", props->JSON.Encode.object)
38
- switch obj->Dict.get("required")->Option.flatMap(JSON.Decode.array) {
39
- | None => ()
40
- | Some(req) =>
41
- let filtered =
42
- req
43
- ->Array.filterMap(JSON.Decode.string)
44
- ->Array.filter(name => !(excludeFields->Array.includes(name)))
45
- ->Array.map(JSON.Encode.string)
46
- ->JSON.Encode.array
47
- obj->Dict.set("required", filtered)
48
- }
49
- obj->JSON.Encode.object->JSON.stringify
50
- }
51
- }
52
- }
53
-
54
- // Args schema for the synthetic Auto UI metadata. Activate/Deactivate are payload-less
55
- // `PluginSpec.command` variants on the wire — the auto-resolver flow injects `id: ID!`.
56
- // Keep a local schema here so Auto UI's command-form metadata stays accurate.
57
- @schema
58
- type idArgs = {id: @s.matches(Reventless.DcbTag.string) string}
59
-
60
- // `aggregateIdField: Some("id")` pre-fills the form's `id` input from the
61
- // selected list row. The auto-codegen path would emit this for any
62
- // Instance-level command whose schema has a DCB-tagged id field; the
63
- // hand-rolled definitions below have to mirror that.
64
-
65
- // `allowedStates` mirrors the PluginBehavior accept/reject branches: Activate
66
- // is meaningful only on Inactive plugins; Deactivate is meaningful on
67
- // Connected or Disconnected ones. Strings here must match the variant
68
- // constructor names that PluginsReadModelSpec.status serialises to.
69
- let activateCommand: commandDef = {
70
- name: "Activate",
71
- schema: idArgsSchema->S.castToUnknown->encodeSchema,
72
- level: Instance,
73
- aggregateIdField: Some("id"),
74
- mutationField: Api_Naming.adminField(~name="Plugin_Activate"),
75
- references: [],
76
- allowedStates: Some(["Inactive"]),
77
- targetState: None,
78
- apiExposed: Some(true),
79
- requiredAccess: None,
80
- ownerField: None,
81
- }
82
-
83
- let deactivateCommand: commandDef = {
84
- name: "Deactivate",
85
- schema: idArgsSchema->S.castToUnknown->encodeSchema,
86
- level: Instance,
87
- aggregateIdField: Some("id"),
88
- mutationField: Api_Naming.adminField(~name="Plugin_Deactivate"),
89
- references: [],
90
- allowedStates: Some(["Connected", "Disconnected"]),
91
- targetState: None,
92
- apiExposed: Some(true),
93
- requiredAccess: None,
94
- ownerField: None,
95
- }
24
+ // Every variant of `PluginSpec.command`, walked by the same `extractCommandDefs`
25
+ // an ordinary aggregate goes through. The hand-written alternative described the
26
+ // arguments with a local `{id}` schema while the generated SDL declared
27
+ // `Platform_Plugin_Activate(_0: String!, id: ID!)`, so AutoUI built a mutation
28
+ // missing a required argument and every admin row action was rejected before it
29
+ // reached the aggregate. Deriving from `commandSchema` — the same schema the SDL
30
+ // is generated from — cannot reproduce that class of fault.
31
+ //
32
+ // `mutationFieldFor` is composed the way `PluginBaseFragment` composes the field
33
+ // it generates, so the two cannot name it differently. The `@noApi` protocol
34
+ // variants come back too, carrying `apiExposed: false` and the empty
35
+ // `mutationField` sentinel: metadata about an edge, not a way to invoke it.
36
+ let pluginCommands: array<commandDef> = Plugin_Structure.extractCommandDefs(
37
+ ~isAggregate=true,
38
+ ~mutationFieldFor=variantName =>
39
+ Api_Naming.adminField(~name=PluginSpec.name ++ "_" ++ variantName),
40
+ ~commandAuthorization=PluginSpec.commandAuthorization->Obj.magic,
41
+ commandSchema,
42
+ )
96
43
 
97
44
  let pluginAggregate: writableDef = {
98
45
  name: "Plugin",
99
- commands: [activateCommand, deactivateCommand],
46
+ commands: pluginCommands,
100
47
  producedEventTypes: [],
101
48
  consumedEventTypes: [],
102
49
  linkedViews: ["Plugins"],
@@ -109,39 +56,13 @@ let pluginAggregate: writableDef = {
109
56
  chapter: None,
110
57
  }
111
58
 
112
- let pluginReadModel: queryableDef = {
113
- name: "Plugins",
114
- queryField: Api_Naming.adminField(~name="Plugins"),
115
- schema: PluginsReadModelSpec.stateSchema
116
- ->S.castToUnknown
117
- ->encodeSchemaExcluding(~excludeFields=PluginBaseFragment.pluginUIOnlyExcludeFields),
118
- consumedEventTypes: [],
119
- linkedWriteSide: ["Plugin"],
120
- labelField: "name",
121
- searchableFields: ["name"],
122
- // Hand-rolled rather than resolved, but the rung is the same one
123
- // `labelFieldsFromStateSchema` would report for a field literally named `name`.
124
- labelFieldSource: Some("convention"),
125
- // The published wire name stays `status` — it is what the SDL declares and what
126
- // stored rows carry. Only the key naming it moved.
127
- lifecycleField: Some("status"),
128
- visibility: None,
129
- chapter: None,
130
- // The admin fragment hand-declares its query names rather than deriving them from
131
- // the read-model name, so the singular is taken from the same call
132
- // `PluginBaseFragment.queryNames.singleFieldName` makes — not singularised here.
133
- singleQueryField: Some(Api_Naming.adminField(~name="Plugin")),
134
- // No key field to name: the row id is `name@version` (`Plugin.makeId`), and the
135
- // state carries the two halves separately rather than the composed key. `None`
136
- // is the honest answer — the same one the resolver ladder reaches for a state
137
- // with no `*Id` field.
138
- idField: None,
139
- idFieldSource: None,
140
- requiredAccess: None,
141
- ownerField: None,
142
- retiredField: None,
143
- retiredValues: None,
144
- }
59
+ let pluginReadModel: queryableDef = Plugin_Structure.queryableDefFromSpec(
60
+ ~plugin=pluginId,
61
+ ~name=PluginsReadModelSpec.name,
62
+ ~stateSchema=PluginsReadModelSpec.stateSchema->S.castToUnknown,
63
+ ~authorization=PluginsReadModelSpec.authorization,
64
+ ~linkedWriteSide=["Plugin"],
65
+ )
145
66
 
146
67
  let structure: pluginStructure = {
147
68
  readModels: [pluginReadModel],