@reventlessdev/reventless-spec 3.0.0-alpha.67 → 3.0.0-alpha.69

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
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.69 (2026-07-08)
7
+
8
+ ### Features
9
+
10
+ * **reventless-core:** classify plugins by kind in the lifecycle read model ([64e3f22](https://github.com/ReventlessDev/reventless-core/commit/64e3f22b8114a771886b7c8ec023e95971413c0b))
11
+
12
+
13
+ # 3.0.0-alpha.68 (2026-07-07)
14
+
15
+ ### Bug Fixes
16
+
17
+ * **reventless-aws:** thread inferred DCB scope into the deployed command handler ([4d8327f](https://github.com/ReventlessDev/reventless-core/commit/4d8327fad8659a1cde8c36098c72392737437af1))
18
+
19
+
6
20
  # 3.0.0-alpha.67 (2026-07-06)
7
21
 
8
22
  **Note:** Version bump only for package @reventlessdev/reventless-spec
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-spec",
3
- "version": "3.0.0-alpha.67",
3
+ "version": "3.0.0-alpha.69",
4
4
  "description": "Specifications for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -989,6 +989,74 @@ let sliceShapeFromSchemas = (
989
989
  }
990
990
  }
991
991
 
992
+ /** One slice's sury schemas — the input to `deriveEffectiveScope`. */
993
+ type sliceSchemas = {
994
+ name: string,
995
+ commandSchema: S.t<unknown>,
996
+ consumedEventSchema: S.t<unknown>,
997
+ eventSchema: S.t<unknown>,
998
+ }
999
+
1000
+ /**
1001
+ The DCB decision-read scope threaded into every StateChangeSlice callback:
1002
+ - `crossPartitionTagKeys` — keys whose scalar command tag must be fanned into its
1003
+ own single-tag clause (a cross-entity reference read), and
1004
+ - `tagKeysByEventType` — each produced event type's *indexed* tag keys, so a
1005
+ decision query drops vacuous (type, tag) clause combinations.
1006
+ */
1007
+ type effectiveScope = {
1008
+ crossPartitionTagKeys: array<string>,
1009
+ tagKeysByEventType: dict<array<string>>,
1010
+ }
1011
+
1012
+ /**
1013
+ Derives the effective DCB decision-read scope for one consistency boundary from
1014
+ its slices' schemas.
1015
+
1016
+ Prefers the slice-graph inference (`DcbScopeInference.infer`) and falls back to
1017
+ the `@crossPartition` / tag *annotations* only when a slice's partition is
1018
+ ambiguous — all-or-nothing, matching `Dcb_Builder`. This is the **single source of
1019
+ truth** shared by the deploy-time builder (`Dcb_Builder.res`) and the deployed
1020
+ command-handler entry point (`DcbCommandTopicEntryPoint.mjs`): both call this so
1021
+ the runtime decision query cannot diverge from the storage/GSI scope. Before this
1022
+ existed the entry point re-derived scope from annotations alone, silently dropping
1023
+ inferred cross-partition reference reads — see
1024
+ `docs/analysis/dcb-runtime-scope-annotation-drift.md`.
1025
+ */
1026
+ let deriveEffectiveScope = (slices: array<sliceSchemas>): effectiveScope => {
1027
+ let producedSchemas = slices->Array.map(s => s.eventSchema)
1028
+ let annotatedCross = {
1029
+ let seen = Set.make()
1030
+ producedSchemas
1031
+ ->Array.flatMap(extractCrossPartitionTagKeys)
1032
+ ->Array.filter(k =>
1033
+ if seen->Set.has(k) {
1034
+ false
1035
+ } else {
1036
+ seen->Set.add(k)
1037
+ true
1038
+ }
1039
+ )
1040
+ }
1041
+ let annotatedTagKeys =
1042
+ producedSchemas->Array.map(extractTagKeysByEventType)->mergeTagKeysByEventType
1043
+ let shapes =
1044
+ slices->Array.map(s =>
1045
+ sliceShapeFromSchemas(
1046
+ ~name=s.name,
1047
+ ~commandSchema=s.commandSchema,
1048
+ ~consumedEventSchema=s.consumedEventSchema,
1049
+ ~eventSchema=s.eventSchema,
1050
+ )
1051
+ )
1052
+ let inferred = DcbScopeInference.infer(shapes)
1053
+ let useInferred = inferred.ambiguities->Array.length == 0
1054
+ {
1055
+ crossPartitionTagKeys: useInferred ? inferred.crossPartitionTagKeys : annotatedCross,
1056
+ tagKeysByEventType: useInferred ? inferred.tagKeysByEventType : annotatedTagKeys,
1057
+ }
1058
+ }
1059
+
992
1060
  /**
993
1061
  Checks whether any single variant in a schema has multiple tagged fields.
994
1062
  If so, a partition tag annotation is needed to disambiguate.
@@ -8,6 +8,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
8
8
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
9
9
  import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
10
10
  import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
11
+ import * as DcbScopeInference$Reventless from "./DcbScopeInference.res.mjs";
11
12
 
12
13
  let tagSchema = S.schema(s => ({
13
14
  key: s.m(S.string),
@@ -618,6 +619,27 @@ function sliceShapeFromSchemas(name, commandSchema, consumedEventSchema, eventSc
618
619
  };
619
620
  }
620
621
 
622
+ function deriveEffectiveScope(slices) {
623
+ let producedSchemas = slices.map(s => s.eventSchema);
624
+ let seen = new Set();
625
+ let annotatedCross = producedSchemas.flatMap(extractCrossPartitionTagKeys).filter(k => {
626
+ if (seen.has(k)) {
627
+ return false;
628
+ } else {
629
+ seen.add(k);
630
+ return true;
631
+ }
632
+ });
633
+ let annotatedTagKeys = mergeTagKeysByEventType(producedSchemas.map(extractTagKeysByEventType));
634
+ let shapes = slices.map(s => sliceShapeFromSchemas(s.name, s.commandSchema, s.consumedEventSchema, s.eventSchema));
635
+ let inferred = DcbScopeInference$Reventless.infer(shapes);
636
+ let useInferred = inferred.ambiguities.length === 0;
637
+ return {
638
+ crossPartitionTagKeys: useInferred ? inferred.crossPartitionTagKeys : annotatedCross,
639
+ tagKeysByEventType: useInferred ? inferred.tagKeysByEventType : annotatedTagKeys
640
+ };
641
+ }
642
+
621
643
  function hasMultiTagVariant(schema) {
622
644
  switch (schema.type) {
623
645
  case "object" :
@@ -866,6 +888,7 @@ export {
866
888
  eventShapesOfSchema,
867
889
  extractPartitionTagFields,
868
890
  sliceShapeFromSchemas,
891
+ deriveEffectiveScope,
869
892
  hasMultiTagVariant,
870
893
  findMultiTagVariantNames,
871
894
  extractCompositePartitionFieldsFromProperties,
@@ -6,6 +6,23 @@ type name = string
6
6
  @schema
7
7
  type version = string
8
8
 
9
+ /**
10
+ Classifies the business role of a plugin, carried on `pluginDefinition` so the
11
+ plugin-lifecycle read model can segregate infrastructure/commercial/marketplace
12
+ plugins from domain plugins in the admin Plugins view. Absent (on definitions
13
+ persisted before this field existed) is read as `Domain`.
14
+
15
+ Canonical here in `reventless-spec` because `pluginDefinition` (a `@schema` type
16
+ nested in the lifecycle Message union) needs the sury schema; `ReventlessCore.Plugin_BuiltHook`
17
+ re-exports this same type for its deploy-time metadata registry.
18
+ */
19
+ @schema
20
+ type pluginKind =
21
+ | Domain
22
+ | PlatformInfrastructure
23
+ | Commercial
24
+ | Marketplace
25
+
9
26
  /**
10
27
  Describes an extension point exported by a plugin.
11
28
  Included in the plugin's `pluginDefinition` for use by the gateway / host.
@@ -373,5 +390,10 @@ type pluginDefinition = {
373
390
  // subscriptions from this plugin's DCB topic → peer EventCollectors.
374
391
  // None for plugins without a DCB EventLog.
375
392
  dcbEventLog: @s.matches(dcbEventLogOptionSchema) option<dcbEventLogDefinition>,
393
+ // Business role of this plugin. Mandatory: `Domain` is the default kind, resolved once
394
+ // at the deploy-metadata → definition boundary (Plugin_Builder). PlatformInfrastructure
395
+ // plugins are segregated out of the admin Plugins list. Payload-less variant → serialises
396
+ // as a bare JSON string, so it is JSON-safe inside the lifecycle Message union without js_nullable.
397
+ kind: pluginKind,
376
398
  }
377
399
 
@@ -3,6 +3,13 @@
3
3
  import * as S from "sury/src/S.res.mjs";
4
4
  import * as SuryResMjs from "sury/src/Sury.res.mjs";
5
5
 
6
+ let pluginKindSchema = S.union([
7
+ S.literal("Domain"),
8
+ S.literal("PlatformInfrastructure"),
9
+ S.literal("Commercial"),
10
+ S.literal("Marketplace")
11
+ ]);
12
+
6
13
  let extensionPointDefinitionSchema = S.schema(s => ({
7
14
  name: s.m(S.string),
8
15
  commandTopic: s.m(S.string),
@@ -204,7 +211,8 @@ let pluginDefinitionSchema = S.schema(s => ({
204
211
  apiTarget: s.m(stringOptionSchema),
205
212
  uiFragments: s.m(uiFragmentManifestOptionSchema),
206
213
  structure: s.m(pluginStructureOptionSchema),
207
- dcbEventLog: s.m(dcbEventLogOptionSchema)
214
+ dcbEventLog: s.m(dcbEventLogOptionSchema),
215
+ kind: s.m(pluginKindSchema)
208
216
  }));
209
217
 
210
218
  let nameSchema = S.string;
@@ -214,6 +222,7 @@ let versionSchema = S.string;
214
222
  export {
215
223
  nameSchema,
216
224
  versionSchema,
225
+ pluginKindSchema,
217
226
  extensionPointDefinitionSchema,
218
227
  extensionDefinitionSchema,
219
228
  dcbEventLogDefinitionSchema,
@@ -248,4 +257,4 @@ export {
248
257
  platformEventGraphSchema,
249
258
  pluginDefinitionSchema,
250
259
  }
251
- /* extensionPointDefinitionSchema Not a pure module */
260
+ /* pluginKindSchema Not a pure module */