@reventlessdev/reventless-core 3.0.0-alpha.152 → 3.0.0-alpha.154

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,24 @@
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.154 (2026-07-11)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **plugin-lifecycle:** heal message decode of definitions persisted before a schema field existed ([6bb3e72](https://github.com/ReventlessDev/reventless-core/commit/6bb3e7259ad606a0f77fb670bcfc680256592003))
11
+ ### Features
12
+
13
+ * **monitoring:** deploy-time Monitoring hook seam for provisioned execution units ([30f1c23](https://github.com/ReventlessDev/reventless-core/commit/30f1c23ba118805dae83af9115341f4aff6db92b))
14
+
15
+
16
+ # 3.0.0-alpha.153 (2026-07-10)
17
+
18
+ ### Features
19
+
20
+ * **plugin-structure:** capture per-component chapter grouping for the deployed graph ([f9c88a9](https://github.com/ReventlessDev/reventless-core/commit/f9c88a9a48d8c032ffe23f9e5277caf12c29e85c))
21
+ * **plugin-structure:** carry translation-slice externalSystem into the deployed structure ([80c0cda](https://github.com/ReventlessDev/reventless-core/commit/80c0cda3b6d5fc6625d4b285f41505028f1dd06f))
22
+
23
+
6
24
  # 3.0.0-alpha.152 (2026-07-10)
7
25
 
8
26
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-core",
3
- "version": "3.0.0-alpha.152",
3
+ "version": "3.0.0-alpha.154",
4
4
  "description": "Core package for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -29,16 +29,16 @@
29
29
  "sury": "11.0.0-alpha.4",
30
30
  "uuid": "^13.0.0",
31
31
  "@reventlessdev/rescript-effect": "0.1.0-alpha.25",
32
- "@reventlessdev/rescript-fast-csv": "1.2.0-alpha.11",
33
32
  "@reventlessdev/rescript-hash-object": "1.2.0-alpha.10",
34
33
  "@reventlessdev/rescript-jest": "1.0.0-alpha.6",
35
- "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
36
34
  "@reventlessdev/rescript-node-streams": "1.1.0-alpha.10",
35
+ "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
36
+ "@reventlessdev/rescript-fast-csv": "1.2.0-alpha.11",
37
37
  "@reventlessdev/rescript-ssh2": "1.1.0-alpha.11",
38
38
  "@reventlessdev/rescript-uuid": "1.1.0-alpha.14",
39
- "@reventlessdev/reventless-infra": "3.0.0-alpha.93",
40
- "@reventlessdev/reventless-spec": "3.0.0-alpha.71",
41
- "@reventlessdev/reventless-interop": "3.0.0-alpha.24"
39
+ "@reventlessdev/reventless-interop": "3.0.0-alpha.24",
40
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.95",
41
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.73"
42
42
  },
43
43
  "devDependencies": {
44
44
  "rescript": "^12.3.0",
package/src/Message.res CHANGED
@@ -64,10 +64,16 @@ let toCommandSchema' = (idSchema, commandSchema) =>
64
64
  })
65
65
  )
66
66
 
67
+ // Schema-migration-on-read: reuse the tolerant parse from `Reventless.Message`
68
+ // (`parseJsonTolerant` / `fillMissingDefaults`, included above) so a message persisted
69
+ // before a nested `@schema` field existed still decodes instead of bricking the
70
+ // aggregate's log replay. These two entry points thread the memoized envelope schema;
71
+ // `Reventless.Message.decode` (the other central decoder) is tolerant at the source.
72
+ // See docs/plans/platform-infrastructure-in-plugin-list.md (durable fix option 2).
67
73
  let decodeEvent' = (json, idSchema, eventSchema) =>
68
- json->S.parseJsonOrThrow(toEventSchema'(idSchema, eventSchema))
74
+ json->parseJsonTolerant(toEventSchema'(idSchema, eventSchema))
69
75
  let decodeCommand' = (json, idSchema, commandSchema) =>
70
- json->S.parseJsonOrThrow(toCommandSchema'(idSchema, commandSchema))
76
+ json->parseJsonTolerant(toCommandSchema'(idSchema, commandSchema))
71
77
 
72
78
  let encodeEvent' = (event', idSchema, eventSchema) =>
73
79
  event'->S.reverseConvertToJsonOrThrow(toEventSchema'(idSchema, eventSchema))
@@ -44,11 +44,11 @@ function toCommandSchema$p(idSchema, commandSchema) {
44
44
  }
45
45
 
46
46
  function decodeEvent$p(json, idSchema, eventSchema) {
47
- return S.parseJsonOrThrow(json, toEventSchema$p(idSchema, eventSchema));
47
+ return Message$Reventless.parseJsonTolerant(json, toEventSchema$p(idSchema, eventSchema));
48
48
  }
49
49
 
50
50
  function decodeCommand$p(json, idSchema, commandSchema) {
51
- return S.parseJsonOrThrow(json, toCommandSchema$p(idSchema, commandSchema));
51
+ return Message$Reventless.parseJsonTolerant(json, toCommandSchema$p(idSchema, commandSchema));
52
52
  }
53
53
 
54
54
  function encodeEvent$p(event$p, idSchema, eventSchema) {
@@ -384,6 +384,10 @@ let statusChangeSchema = Message$Reventless.statusChangeSchema;
384
384
 
385
385
  let commandJsonSchema = Message$Reventless.commandJsonSchema;
386
386
 
387
+ let fillMissingDefaults = Message$Reventless.fillMissingDefaults;
388
+
389
+ let parseJsonTolerant = Message$Reventless.parseJsonTolerant;
390
+
387
391
  let decode = Message$Reventless.decode;
388
392
 
389
393
  let encode = Message$Reventless.encode;
@@ -397,6 +401,8 @@ export {
397
401
  contextSchema,
398
402
  statusChangeSchema,
399
403
  commandJsonSchema,
404
+ fillMissingDefaults,
405
+ parseJsonTolerant,
400
406
  decode,
401
407
  encode,
402
408
  InvalidEvent,
@@ -0,0 +1,96 @@
1
+ /**
2
+ Deploy-time inventory hook: every runtime backend announces the execution units
3
+ it provisions so an extension can attach provider-native monitoring to each.
4
+
5
+ No-op by default. Zero behavioral change unless an extension registers a backend
6
+ via `use` before the platform/plugin build. The seam fires once per unit at
7
+ provisioning time (inside the deploy program) — never at invocation, and it runs
8
+ no code in production. Monitoring/alerting itself is deliberately NOT a framework
9
+ concern; this only exposes the choke point through which the framework says
10
+ "I just provisioned an execution unit" and leaves what to do about it to the
11
+ listener.
12
+
13
+ See `docs/plans/monitoring-hook-seam.md`.
14
+ */
15
+
16
+ /**
17
+ The ROLE of a provisioned execution unit, not its mechanism. What "failure"
18
+ means for a unit is provider-specific (a Lambda `Errors` metric vs. container
19
+ restarts vs. NATS redeliveries) and belongs in the extension's per-provider
20
+ `Backend`; core only says *that* a unit exists and *what role* it plays, which is
21
+ what keeps the contract stable across runtimes. `Other(string)` is the escape
22
+ hatch for support units and future roles (same pattern as the protocol's
23
+ `OtherKind`).
24
+ */
25
+ type unitKind =
26
+ | /** aggregates, state-change slices, extension points — failure = writes
27
+ rejected/dropped (the silent-freeze class) */
28
+ CommandHandler
29
+ | /** state-view slices, read models — failure = silently stale reads */
30
+ Projection
31
+ | /** automation slices, side-effect handlers, translations — failure = missed
32
+ side effects at boundaries */
33
+ Reactor
34
+ | /** cross-plugin/admin event ingestion — failure = event flow between plugins
35
+ stops */
36
+ EventCollector
37
+ | /** scheduled task runners */
38
+ Task
39
+ | /** heartbeat/keep-alive — failure = lifecycle detection AND any staleness
40
+ watchdog go blind (monitoring's own pulse) */
41
+ Scheduler
42
+ | /** receives messages that exhausted processing */
43
+ DeadLetterSink
44
+ | /** support units (counters, change-feed relays, migration runners, query
45
+ resolvers, …) — providers pass their name */
46
+ Other(string)
47
+
48
+ /**
49
+ A monitoring backend, registered by an extension (a deploy program) before the
50
+ platform builds. `onProvisioned` is called once per execution unit.
51
+
52
+ `~name` is the unit's STATIC logical name — the string the builder passed to its
53
+ provisioning call. A Pulumi-based backend derives logical resource names from it
54
+ (alarm/rule resource names must be stable and known at graph-construction time).
55
+ `~component` is the deploy-time `Adapter.resource`; its fields are `Output`s
56
+ (physical, suffix-bearing — right for a metric dimension like a CloudWatch
57
+ FunctionName or a PrometheusRule deployment selector, but unusable as a logical
58
+ resource name). Because `onProvisioned` fires during resource creation inside the
59
+ deploy program, a Pulumi-based backend can create monitoring resources in the
60
+ same stack, parented naturally.
61
+ */
62
+ module type Backend = {
63
+ let onProvisioned: (
64
+ ~kind: unitKind,
65
+ ~name: string,
66
+ ~component: ReventlessInfra.Adapter.resource,
67
+ ) => unit
68
+ }
69
+
70
+ /** Default backend: does nothing. Active unless an extension calls `use`. */
71
+ module Noop: Backend = {
72
+ let onProvisioned = (~kind as _, ~name as _, ~component as _) => ()
73
+ }
74
+
75
+ let backend: ref<module(Backend)> = ref(module(Noop: Backend))
76
+
77
+ /**
78
+ Register the monitoring backend. Must run before the platform/plugin build in the
79
+ deploy program (plain statement order — the registry is consulted lazily at each
80
+ provisioning site). Called once, by the extension, first.
81
+ */
82
+ let use = (b: module(Backend)) => backend := b
83
+
84
+ /**
85
+ Announce a provisioned execution unit to the registered backend. Called by
86
+ runtime backends at their provisioning sites (once per unit). No-op until an
87
+ extension registers a backend.
88
+ */
89
+ let notify = (
90
+ ~kind: unitKind,
91
+ ~name: string,
92
+ ~component: ReventlessInfra.Adapter.resource,
93
+ ) => {
94
+ module B = unpack(backend.contents)
95
+ B.onProvisioned(~kind, ~name, ~component)
96
+ }
@@ -0,0 +1,31 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ function onProvisioned(param, param$1, param$2) {
5
+
6
+ }
7
+
8
+ let Noop = {
9
+ onProvisioned: onProvisioned
10
+ };
11
+
12
+ let backend = {
13
+ contents: Noop
14
+ };
15
+
16
+ function use(b) {
17
+ backend.contents = b;
18
+ }
19
+
20
+ function notify(kind, name, component) {
21
+ let B = backend.contents;
22
+ B.onProvisioned(kind, name, component);
23
+ }
24
+
25
+ export {
26
+ Noop,
27
+ backend,
28
+ use,
29
+ notify,
30
+ }
31
+ /* No side effect */
@@ -96,6 +96,7 @@ let pluginAggregate: writableDef = {
96
96
  linkedViews: ["Plugins"],
97
97
  consistencyRead: None,
98
98
  events: [],
99
+ chapter: None,
99
100
  }
100
101
 
101
102
  let pluginReadModel: queryableDef = {
@@ -110,6 +111,7 @@ let pluginReadModel: queryableDef = {
110
111
  searchableFields: ["name"],
111
112
  statusField: Some("status"),
112
113
  visibility: None,
114
+ chapter: None,
113
115
  }
114
116
 
115
117
  let structure: pluginStructure = {
@@ -110,7 +110,8 @@ let pluginAggregate = {
110
110
  consumedEventTypes: pluginAggregate_consumedEventTypes,
111
111
  linkedViews: pluginAggregate_linkedViews,
112
112
  consistencyRead: undefined,
113
- events: pluginAggregate_events
113
+ events: pluginAggregate_events,
114
+ chapter: undefined
114
115
  };
115
116
 
116
117
  let pluginReadModel_queryField = Api_Naming$ReventlessCore.adminField("Plugins");
@@ -134,7 +135,8 @@ let pluginReadModel = {
134
135
  labelField: "name",
135
136
  searchableFields: pluginReadModel_searchableFields,
136
137
  statusField: pluginReadModel_statusField,
137
- visibility: undefined
138
+ visibility: undefined,
139
+ chapter: undefined
138
140
  };
139
141
 
140
142
  let structure_readModels = [pluginReadModel];
@@ -72,6 +72,7 @@ let encodeQueryableDef = (r: queryableDef): JSON.t =>
72
72
  ("labelField", JSON.Encode.string(r.labelField)),
73
73
  ("searchableFields", encodeStrings(r.searchableFields)),
74
74
  ("statusField", r.statusField->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
75
+ ("chapter", r.chapter->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
75
76
  ])->JSON.Encode.object
76
77
 
77
78
  let encodeEventDef = (e: eventDef): JSON.t =>
@@ -94,6 +95,7 @@ let encodeWritableDef = (w: writableDef): JSON.t =>
94
95
  ("consumedEventTypes", encodeStrings(w.consumedEventTypes)),
95
96
  // Phase 6.3: emitted-event field schemas (None → [] on the wire).
96
97
  ("events", w.events->Array.map(encodeEventDef)->JSON.Encode.array),
98
+ ("chapter", w.chapter->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
97
99
  ])->JSON.Encode.object
98
100
 
99
101
  let encodeAutomationSliceDef = (a: automationSliceDef): JSON.t =>
@@ -101,6 +103,7 @@ let encodeAutomationSliceDef = (a: automationSliceDef): JSON.t =>
101
103
  ("name", JSON.Encode.string(a.name)),
102
104
  ("consumedEventTypes", encodeStrings(a.consumedEventTypes)),
103
105
  ("producedCommandTypes", encodeStrings(a.producedCommandTypes)),
106
+ ("chapter", a.chapter->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
104
107
  ])->JSON.Encode.object
105
108
 
106
109
  let encodeOutboundTranslationSliceDef = (o: outboundTranslationSliceDef): JSON.t =>
@@ -108,12 +111,20 @@ let encodeOutboundTranslationSliceDef = (o: outboundTranslationSliceDef): JSON.t
108
111
  ("name", JSON.Encode.string(o.name)),
109
112
  ("consumedEventTypes", encodeStrings(o.consumedEventTypes)),
110
113
  ("inboundCommandTypes", encodeStrings(o.inboundCommandTypes)),
114
+ // Foreign system this slice publishes to — lets a deployed-graph consumer draw
115
+ // the external-system boundary box without workspace access. None → null.
116
+ ("externalSystem", o.externalSystem->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
117
+ ("chapter", o.chapter->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
111
118
  ])->JSON.Encode.object
112
119
 
113
120
  let encodeInboundTranslationSliceDef = (i: inboundTranslationSliceDef): JSON.t =>
114
121
  Dict.fromArray([
115
122
  ("name", JSON.Encode.string(i.name)),
116
123
  ("commandTypes", encodeStrings(i.commandTypes)),
124
+ // Foreign system this slice receives from — lets a deployed-graph consumer draw
125
+ // the external-system boundary box without workspace access. None → null.
126
+ ("externalSystem", i.externalSystem->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
127
+ ("chapter", i.chapter->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
117
128
  ])->JSON.Encode.object
118
129
 
119
130
  let encodeExtensionDef = (e: extensionDef): JSON.t =>
@@ -115,6 +115,10 @@ function encodeQueryableDef(r) {
115
115
  [
116
116
  "statusField",
117
117
  Stdlib_Option.mapOr(r.statusField, null, prim => prim)
118
+ ],
119
+ [
120
+ "chapter",
121
+ Stdlib_Option.mapOr(r.chapter, null, prim => prim)
118
122
  ]
119
123
  ]);
120
124
  }
@@ -165,6 +169,10 @@ function encodeWritableDef(w) {
165
169
  [
166
170
  "events",
167
171
  w.events.map(encodeEventDef)
172
+ ],
173
+ [
174
+ "chapter",
175
+ Stdlib_Option.mapOr(w.chapter, null, prim => prim)
168
176
  ]
169
177
  ]);
170
178
  }
@@ -182,6 +190,10 @@ function encodeAutomationSliceDef(a) {
182
190
  [
183
191
  "producedCommandTypes",
184
192
  a.producedCommandTypes.map(prim => prim)
193
+ ],
194
+ [
195
+ "chapter",
196
+ Stdlib_Option.mapOr(a.chapter, null, prim => prim)
185
197
  ]
186
198
  ]);
187
199
  }
@@ -199,6 +211,14 @@ function encodeOutboundTranslationSliceDef(o) {
199
211
  [
200
212
  "inboundCommandTypes",
201
213
  o.inboundCommandTypes.map(prim => prim)
214
+ ],
215
+ [
216
+ "externalSystem",
217
+ Stdlib_Option.mapOr(o.externalSystem, null, prim => prim)
218
+ ],
219
+ [
220
+ "chapter",
221
+ Stdlib_Option.mapOr(o.chapter, null, prim => prim)
202
222
  ]
203
223
  ]);
204
224
  }
@@ -212,6 +232,14 @@ function encodeInboundTranslationSliceDef(i) {
212
232
  [
213
233
  "commandTypes",
214
234
  i.commandTypes.map(prim => prim)
235
+ ],
236
+ [
237
+ "externalSystem",
238
+ Stdlib_Option.mapOr(i.externalSystem, null, prim => prim)
239
+ ],
240
+ [
241
+ "chapter",
242
+ Stdlib_Option.mapOr(i.chapter, null, prim => prim)
215
243
  ]
216
244
  ]);
217
245
  }
@@ -46,6 +46,7 @@ module type T = {
46
46
  ~inboundTranslationSlices: array<module(ReventlessInfra.InboundTranslationSlice.T)>=?,
47
47
  ~extensions: array<module(ReventlessInfra.Extension.Blueprint)>=?,
48
48
  ~extensionPoints: array<module(ReventlessInfra.ExtensionPointMapping.Mapping)>=?,
49
+ ~componentChapters: dict<string>=?,
49
50
  ) => Reventless.Plugin.pluginStructure
50
51
  }
51
52
 
@@ -368,6 +368,30 @@ module Make = (
368
368
  // so only aggregates, read models, and DCB slice names are included.
369
369
 
370
370
  let extractTypes = schema => Reventless.DcbTag.extractAllVariantNames(schema)
371
+
372
+ // Chapter (intra-plugin grouping band) per component name, mirrored from the
373
+ // static pluginStructure so the hook surface reaches parity with it. The
374
+ // structure is the single source — it was populated by the generator's
375
+ // componentChapters map at makePluginDefinition time.
376
+ let chapterByName: Dict.t<string> = Dict.make()
377
+ switch pluginStructure {
378
+ | Some(ps) =>
379
+ let put = (n, c) =>
380
+ switch c {
381
+ | Some(ch) => chapterByName->Dict.set(n, ch)
382
+ | None => ()
383
+ }
384
+ ps.aggregates->Array.forEach(d => put(d.name, d.chapter))
385
+ ps.readModels->Array.forEach(d => put(d.name, d.chapter))
386
+ ps.stateChangeSlices->Array.forEach(d => put(d.name, d.chapter))
387
+ ps.stateViewSlices->Array.forEach(d => put(d.name, d.chapter))
388
+ ps.automationSlices->Array.forEach(d => put(d.name, d.chapter))
389
+ ps.outboundTranslationSlices->Array.forEach(d => put(d.name, d.chapter))
390
+ ps.inboundTranslationSlices->Array.forEach(d => put(d.name, d.chapter))
391
+ | None => ()
392
+ }
393
+ let chapterFor = (n: string): option<string> => chapterByName->Dict.get(n)
394
+
371
395
  // Build per-component schema data and register it for the deployed hook.
372
396
  let aggregateComponents = aggregates->Array.map((
373
397
  module(M: ReventlessInfra.Aggregate.T with type api = api),
@@ -382,6 +406,7 @@ module Make = (
382
406
  eventSchemas: [
383
407
  SchemaWalker.walk(M.Spec.name ++ ".event", M.Spec.eventSchema),
384
408
  ],
409
+ chapter: ?chapterFor(M.Spec.name),
385
410
  }
386
411
  Plugin_Helpers.componentSchemaRegistry->Dict.set(M.Spec.name, schema)
387
412
  ({name: M.Spec.name, kind: "Aggregate", schema}: Plugin_Helpers.pluginBuiltComponent)
@@ -393,6 +418,7 @@ module Make = (
393
418
  let qn = Api_Naming.queryFieldNamesForReadModel(~plugin=name, ~name=R.Spec.name)
394
419
  let schema: Plugin_Helpers.pluginDeployedSchema = {
395
420
  queryFields: [qn.singleFieldName, qn.listFieldName],
421
+ chapter: ?chapterFor(R.Spec.name),
396
422
  }
397
423
  Plugin_Helpers.componentSchemaRegistry->Dict.set(R.Spec.name, schema)
398
424
  ({name: R.Spec.name, kind: "ReadModel", schema}: Plugin_Helpers.pluginBuiltComponent)
@@ -421,6 +447,7 @@ module Make = (
421
447
  consumedEventTypes,
422
448
  producedCommandTypes: extractTypes(As.Spec.commandSchema),
423
449
  targetName: As.Spec.targetName,
450
+ chapter: ?chapterFor(As.Spec.name),
424
451
  },
425
452
  )
426
453
  })
@@ -434,6 +461,9 @@ module Make = (
434
461
  commandTypes: extractTypes(Ots.Spec.inboundCommandSchema),
435
462
  // Outbound slices may have no target (option); pass it through.
436
463
  targetName: ?Ots.Spec.targetName,
464
+ // Foreign system this slice publishes to (option); drives the external box.
465
+ externalSystem: ?Ots.Spec.externalSystem,
466
+ chapter: ?chapterFor(Ots.Spec.name),
437
467
  },
438
468
  )
439
469
  )
@@ -445,6 +475,9 @@ module Make = (
445
475
  {
446
476
  commandTypes: extractTypes(Its.Spec.commandSchema),
447
477
  targetName: Its.Spec.targetName,
478
+ // Foreign system this slice receives from (option); drives the external box.
479
+ externalSystem: ?Its.Spec.externalSystem,
480
+ chapter: ?chapterFor(Its.Spec.name),
448
481
  },
449
482
  )
450
483
  )
@@ -464,13 +497,17 @@ module Make = (
464
497
  commandTypes: extractTypes(Scs.Spec.commandSchema),
465
498
  eventTypes: extractEventTypes(Scs.Spec.eventSchema->S.castToUnknown),
466
499
  consumedEventTypes: extractEventTypes(Scs.Spec.consumedEventSchema->S.castToUnknown),
500
+ chapter: ?chapterFor(Scs.Spec.name),
467
501
  },
468
502
  )
469
503
  )
470
504
  stateViewSlices->Array.forEach((module(Svs: ReventlessInfra.StateViewSlice.T)) =>
471
505
  stateSchemas->Dict.set(
472
506
  Svs.Spec.name,
473
- {consumedEventTypes: extractEventTypes(Svs.Spec.consumedEventSchema->S.castToUnknown)},
507
+ {
508
+ consumedEventTypes: extractEventTypes(Svs.Spec.consumedEventSchema->S.castToUnknown),
509
+ chapter: ?chapterFor(Svs.Spec.name),
510
+ },
474
511
  )
475
512
  )
476
513
 
@@ -993,6 +1030,7 @@ module Make = (
993
1030
  ~inboundTranslationSlices: array<module(ReventlessInfra.InboundTranslationSlice.T)>=[],
994
1031
  ~extensions: array<module(ReventlessInfra.Extension.Blueprint)>=[],
995
1032
  ~extensionPoints: array<module(ReventlessInfra.ExtensionPointMapping.Mapping)>=[],
1033
+ ~componentChapters: dict<string>=Dict.make(),
996
1034
  ): Reventless.Plugin.pluginStructure =>
997
1035
  Plugin_Structure.make(
998
1036
  ~name,
@@ -1005,6 +1043,7 @@ module Make = (
1005
1043
  ~inboundTranslationSlices,
1006
1044
  ~extensions,
1007
1045
  ~extensionPoints,
1046
+ ~componentChapters,
1008
1047
  )
1009
1048
 
1010
1049
  let make = (
@@ -89,7 +89,7 @@ function Make(Spec) {
89
89
  pages: pages
90
90
  };
91
91
  };
92
- let makePluginDefinition = (name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChangeSlicesOpt, automationSlicesOpt, outboundTranslationSlicesOpt, inboundTranslationSlicesOpt, extensionsOpt, extensionPointsOpt) => {
92
+ let makePluginDefinition = (name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChangeSlicesOpt, automationSlicesOpt, outboundTranslationSlicesOpt, inboundTranslationSlicesOpt, extensionsOpt, extensionPointsOpt, componentChaptersOpt) => {
93
93
  let aggregates = aggregatesOpt !== undefined ? aggregatesOpt : [];
94
94
  let readModels = readModelsOpt !== undefined ? readModelsOpt : [];
95
95
  let stateViewSlices = stateViewSlicesOpt !== undefined ? stateViewSlicesOpt : [];
@@ -99,7 +99,8 @@ function Make(Spec) {
99
99
  let inboundTranslationSlices = inboundTranslationSlicesOpt !== undefined ? inboundTranslationSlicesOpt : [];
100
100
  let extensions = extensionsOpt !== undefined ? extensionsOpt : [];
101
101
  let extensionPoints = extensionPointsOpt !== undefined ? extensionPointsOpt : [];
102
- return Plugin_Structure$ReventlessCore.make(name, aggregates, readModels, stateViewSlices, stateChangeSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, extensions, extensionPoints);
102
+ let componentChapters = componentChaptersOpt !== undefined ? componentChaptersOpt : ({});
103
+ return Plugin_Structure$ReventlessCore.make(name, aggregates, readModels, stateViewSlices, stateChangeSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, extensions, extensionPoints, componentChapters);
103
104
  };
104
105
  let make = (name, heartbeatInterval, extensionPointsOpt, extensionsOpt, aggregatesOpt, readModelsOpt, tasksOpt, stateChangeSlicesOpt, stateViewSlicesOpt, automationSlicesOpt, outboundTranslationSlicesOpt, inboundTranslationSlicesOpt, systemCallableComponentsOpt, uiFragments, pluginStructure, opts) => {
105
106
  let extensionPoints = extensionPointsOpt !== undefined ? extensionPointsOpt : [];
@@ -264,18 +265,36 @@ function Make(Spec) {
264
265
  allQueryDbs[param[0] + "Todo"] = param[1].queryDb;
265
266
  });
266
267
  let queryEngine = QueryEngineAdapter.make(allQueryDbs);
268
+ let chapterByName = {};
269
+ if (pluginStructure !== undefined) {
270
+ let put = (n, c) => {
271
+ if (c !== undefined) {
272
+ chapterByName[n] = c;
273
+ return;
274
+ }
275
+ };
276
+ pluginStructure.aggregates.forEach(d => put(d.name, d.chapter));
277
+ pluginStructure.readModels.forEach(d => put(d.name, d.chapter));
278
+ pluginStructure.stateChangeSlices.forEach(d => put(d.name, d.chapter));
279
+ pluginStructure.stateViewSlices.forEach(d => put(d.name, d.chapter));
280
+ pluginStructure.automationSlices.forEach(d => put(d.name, d.chapter));
281
+ pluginStructure.outboundTranslationSlices.forEach(d => put(d.name, d.chapter));
282
+ pluginStructure.inboundTranslationSlices.forEach(d => put(d.name, d.chapter));
283
+ }
267
284
  let aggregateComponents = aggregates.map(M => {
268
285
  let schema_commandTypes = DcbTag$Reventless.extractAllVariantNames(M.Spec.commandSchema);
269
286
  let schema_eventTypes = DcbTag$Reventless.extractAllVariantNames(M.Spec.eventSchema);
270
287
  let schema_errorTypes = DcbTag$Reventless.extractAllVariantNames(M.Spec.errorSchema);
271
288
  let schema_commandSchemas = [SchemaWalker$ReventlessCore.walk(M.Spec.name + ".command", M.Spec.commandSchema)];
272
289
  let schema_eventSchemas = [SchemaWalker$ReventlessCore.walk(M.Spec.name + ".event", M.Spec.eventSchema)];
290
+ let schema_chapter = chapterByName[M.Spec.name];
273
291
  let schema = {
274
292
  commandTypes: schema_commandTypes,
275
293
  eventTypes: schema_eventTypes,
276
294
  errorTypes: schema_errorTypes,
277
295
  commandSchemas: schema_commandSchemas,
278
- eventSchemas: schema_eventSchemas
296
+ eventSchemas: schema_eventSchemas,
297
+ chapter: schema_chapter
279
298
  };
280
299
  Plugin_Helpers$ReventlessCore.componentSchemaRegistry[M.Spec.name] = schema;
281
300
  return {
@@ -290,8 +309,10 @@ function Make(Spec) {
290
309
  qn.singleFieldName,
291
310
  qn.listFieldName
292
311
  ];
312
+ let schema_chapter = chapterByName[R.Spec.name];
293
313
  let schema = {
294
- queryFields: schema_queryFields
314
+ queryFields: schema_queryFields,
315
+ chapter: schema_chapter
295
316
  };
296
317
  Plugin_Helpers$ReventlessCore.componentSchemaRegistry[R.Spec.name] = schema;
297
318
  return {
@@ -306,20 +327,25 @@ function Make(Spec) {
306
327
  routingSchemas[As.Spec.name] = {
307
328
  consumedEventTypes: consumedEventTypes,
308
329
  producedCommandTypes: DcbTag$Reventless.extractAllVariantNames(As.Spec.commandSchema),
309
- targetName: As.Spec.targetName
330
+ targetName: As.Spec.targetName,
331
+ chapter: chapterByName[As.Spec.name]
310
332
  };
311
333
  });
312
334
  outboundTranslationSlices.forEach(Ots => {
313
335
  routingSchemas[Ots.Spec.name] = {
314
336
  commandTypes: DcbTag$Reventless.extractAllVariantNames(Ots.Spec.inboundCommandSchema),
315
337
  consumedEventTypes: DcbTag$Reventless.extractVariantNames(Ots.Spec.consumedEventSchema),
316
- targetName: Ots.Spec.targetName
338
+ targetName: Ots.Spec.targetName,
339
+ externalSystem: Ots.Spec.externalSystem,
340
+ chapter: chapterByName[Ots.Spec.name]
317
341
  };
318
342
  });
319
343
  inboundTranslationSlices.forEach(Its => {
320
344
  routingSchemas[Its.Spec.name] = {
321
345
  commandTypes: DcbTag$Reventless.extractAllVariantNames(Its.Spec.commandSchema),
322
- targetName: Its.Spec.targetName
346
+ targetName: Its.Spec.targetName,
347
+ externalSystem: Its.Spec.externalSystem,
348
+ chapter: chapterByName[Its.Spec.name]
323
349
  };
324
350
  });
325
351
  let stateSchemas = {};
@@ -327,12 +353,14 @@ function Make(Spec) {
327
353
  stateSchemas[Scs.Spec.name] = {
328
354
  commandTypes: DcbTag$Reventless.extractAllVariantNames(Scs.Spec.commandSchema),
329
355
  eventTypes: DcbTag$Reventless.extractVariantNames(Scs.Spec.eventSchema),
330
- consumedEventTypes: DcbTag$Reventless.extractVariantNames(Scs.Spec.consumedEventSchema)
356
+ consumedEventTypes: DcbTag$Reventless.extractVariantNames(Scs.Spec.consumedEventSchema),
357
+ chapter: chapterByName[Scs.Spec.name]
331
358
  };
332
359
  });
333
360
  stateViewSlices.forEach(Svs => {
334
361
  stateSchemas[Svs.Spec.name] = {
335
- consumedEventTypes: DcbTag$Reventless.extractVariantNames(Svs.Spec.consumedEventSchema)
362
+ consumedEventTypes: DcbTag$Reventless.extractVariantNames(Svs.Spec.consumedEventSchema),
363
+ chapter: chapterByName[Svs.Spec.name]
336
364
  };
337
365
  });
338
366
  let enrichedComponents = (d, kind, schemas) => Object.keys(d).map(name => {
@@ -78,6 +78,12 @@ type pluginDeployedSchema = {
78
78
  /** For routing slices (automation / inbound + outbound translation): the name
79
79
  of the writable component this slice routes its commands to. */
80
80
  targetName?: string,
81
+ /** For inbound / outbound translation slices: the foreign system this slice
82
+ integrates with, mirrored from the static `pluginStructure` for hook parity.
83
+ Drives the deployed graph's external-system boundary box. Absent when the slice
84
+ names no external system. See the translation-slice defs on
85
+ `Reventless.Plugin.pluginStructure`. */
86
+ externalSystem?: string,
81
87
  sharedBy?: array<string>,
82
88
  extensionPointName?: string,
83
89
  providerPlugin?: string,
@@ -88,6 +94,11 @@ type pluginDeployedSchema = {
88
94
  eventSchemas?: array<typeSchema>,
89
95
  /** Field-level schema for the state type. */
90
96
  stateSchema?: typeSchema,
97
+ /** Intra-plugin grouping band (the "chapter") this component belongs to, mirrored
98
+ from the static `pluginStructure` for parity on the hook surface. Absent when the
99
+ component carries no chapter (lives directly under a kind-folder). See
100
+ `Reventless.Plugin.queryableDef.chapter`. */
101
+ chapter?: string,
91
102
  }
92
103
 
93
104
  // ---------------------------------------------------------------------------