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

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 (53) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/package.json +6 -6
  3. package/src/Message.res +1 -1
  4. package/src/adapter/Monitoring/Monitoring.res +1 -1
  5. package/src/admin/Platform_Admin_Structure.res +5 -1
  6. package/src/admin/Platform_Admin_Structure.res.mjs +4 -2
  7. package/src/admin/Platform_ComponentDefinitionsApi.res +4 -2
  8. package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +11 -3
  9. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res +1 -1
  10. package/src/components/Aggregate/Aggregate_Callback.res +2 -2
  11. package/src/components/Api/ApiAllowedStatesHelpers.res +2 -3
  12. package/src/components/Api/ApiTargetStateHelpers.res +4 -3
  13. package/src/components/Api/GraphQL_FragmentGenerator.res +14 -1
  14. package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +1 -1
  15. package/src/components/Api/QueryDbListQuery.res +25 -1
  16. package/src/components/Api/QueryDbListQuery.res.mjs +11 -4
  17. package/src/components/Api/SuryToJsonSchema.res +39 -0
  18. package/src/components/Api/SuryToJsonSchema.res.mjs +34 -3
  19. package/src/components/Dcb/Dcb_Builder.res +79 -16
  20. package/src/components/Dcb/Dcb_Builder.res.mjs +41 -5
  21. package/src/components/EventLog/EventLog.res +1 -1
  22. package/src/plugin/component/Plugin_Structure.res +377 -18
  23. package/src/plugin/component/Plugin_Structure.res.mjs +204 -9
  24. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res +1 -1
  25. package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +3 -2
  26. package/tests/admin/Platform_Admin_StructureTest.res +49 -0
  27. package/tests/admin/Platform_Admin_StructureTest.res.mjs +24 -0
  28. package/tests/admin/Platform_BakedManifestTest.res +3 -1
  29. package/tests/admin/Platform_BakedManifestTest.res.mjs +3 -1
  30. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +14 -8
  31. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +24 -12
  32. package/tests/admin/Platform_PluginStructuresApiTest.res +3 -1
  33. package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +6 -2
  34. package/tests/aggregate/AggregateCacheTest.res +1 -1
  35. package/tests/aggregate/AggregateSnapshotTest.res +1 -1
  36. package/tests/api/Api_IdsTest.res.mjs +1 -1
  37. package/tests/api/SuryToJsonSchemaTest.res +141 -1
  38. package/tests/api/SuryToJsonSchemaTest.res.mjs +269 -54
  39. package/tests/commandgenerator/OwnerStampingTest.res +57 -0
  40. package/tests/commandgenerator/OwnerStampingTest.res.mjs +19 -0
  41. package/tests/message/MessageTest.res +1 -1
  42. package/tests/plugin/HeartbeatDisconnectGraceTest.res +1 -1
  43. package/tests/plugin/PluginStructureTest.res +332 -15
  44. package/tests/plugin/PluginStructureTest.res.mjs +316 -10
  45. package/tests/plugin/StateChangeSlice/PsDispatchShipment.res +31 -0
  46. package/tests/plugin/StateChangeSlice/PsDispatchShipment.res.mjs +73 -0
  47. package/tests/plugin/StateChangeSlice/PsShipOrder.res +16 -1
  48. package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +7 -2
  49. package/tests/plugin/StateViewSlice/PsAnnotatedView.res.mjs +3 -2
  50. package/tests/plugin/StateViewSlice/PsShipmentsView.res +26 -0
  51. package/tests/plugin/StateViewSlice/PsShipmentsView.res.mjs +101 -0
  52. package/tests/plugin/pluginDefinitionRequiredScalars.txt +2 -0
  53. package/tests/util/CsvStreamTest.res +1 -1
@@ -5,6 +5,7 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
5
5
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
6
6
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
7
  import * as Belt_SetString from "@rescript/runtime/lib/es6/Belt_SetString.js";
8
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
8
9
  import * as Owner$Reventless from "@reventlessdev/reventless-spec/src/components/Owner.res.mjs";
9
10
  import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
10
11
  import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
@@ -39,7 +40,7 @@ function isLabelShape(_t) {
39
40
  };
40
41
  }
41
42
 
42
- function isStatusShape(_t) {
43
+ function isLifecycleShape(_t) {
43
44
  while (true) {
44
45
  let t = _t;
45
46
  if (typeof t !== "object") {
@@ -68,15 +69,15 @@ function shapeOfItem(entityName, item) {
68
69
  return SchemaType$ReventlessCore.fromSury(entityName, item.location, item.schema);
69
70
  }
70
71
 
71
- function statusFieldFromStateSchema(entityName, stateSchema) {
72
+ function lifecycleFieldFromStateSchema(entityName, stateSchema) {
72
73
  let spec = StateAnnotations$Reventless.getSpec(stateSchema);
73
- let annotated = spec !== undefined ? spec.status : undefined;
74
+ let annotated = spec !== undefined ? spec.lifecycle : undefined;
74
75
  if (annotated !== undefined) {
75
76
  return annotated;
76
77
  } else if (stateSchema.type === "object") {
77
78
  return Stdlib_Option.map(stateSchema.items.find(item => {
78
- if (item.location === "status") {
79
- return isStatusShape(shapeOfItem(entityName, item));
79
+ if (item.location === "lifecycle") {
80
+ return isLifecycleShape(shapeOfItem(entityName, item));
80
81
  } else {
81
82
  return false;
82
83
  }
@@ -86,6 +87,174 @@ function statusFieldFromStateSchema(entityName, stateSchema) {
86
87
  }
87
88
  }
88
89
 
90
+ function retiredFromStateSchema(stateSchema) {
91
+ let spec = StateAnnotations$Reventless.getSpec(stateSchema);
92
+ if (spec !== undefined) {
93
+ return spec.retired;
94
+ }
95
+ }
96
+
97
+ function retiredFieldFromStateSchema(stateSchema) {
98
+ return Stdlib_Option.map(retiredFromStateSchema(stateSchema), r => r.field);
99
+ }
100
+
101
+ function retiredValuesFromStateSchema(stateSchema) {
102
+ return Stdlib_Option.flatMap(retiredFromStateSchema(stateSchema), r => r.values);
103
+ }
104
+
105
+ function checkRetiredValue(entityName, stateSchema) {
106
+ let match = retiredFromStateSchema(stateSchema);
107
+ if (match === undefined) {
108
+ return;
109
+ }
110
+ let values = match.values;
111
+ if (values === undefined) {
112
+ return;
113
+ }
114
+ let field = match.field;
115
+ let named = values.join(", ");
116
+ let lifecycle = lifecycleFieldFromStateSchema(entityName, stateSchema);
117
+ if (Primitive_object.notequal(lifecycle, field)) {
118
+ log.warn("Plugin_Structure", undefined, entityName + `: @retired(` + named + `) is on "` + field + `", which is not this record's lifecycle field` + Stdlib_Option.getOr(Stdlib_Option.map(lifecycle, f => ` (that is "` + f + `")`), " (it declares none)") + `. A retirement state no command's @transition can name loses the command filtering the state form exists for.`);
119
+ }
120
+ let declared;
121
+ declared = stateSchema.type === "object" ? Stdlib_Option.getOr(Stdlib_Option.map(stateSchema.items.find(item => item.location === field), item => {
122
+ let match = shapeOfItem(entityName, item);
123
+ if (typeof match !== "object") {
124
+ return [];
125
+ }
126
+ switch (match.TAG) {
127
+ case "Nullable" :
128
+ let match$1 = match._0;
129
+ if (typeof match$1 !== "object") {
130
+ return [];
131
+ } else if (match$1.TAG === "Enum") {
132
+ return match$1._1;
133
+ } else {
134
+ return [];
135
+ }
136
+ case "Enum" :
137
+ return match._1;
138
+ default:
139
+ return [];
140
+ }
141
+ }), []) : [];
142
+ if (declared.length !== 0) {
143
+ values.filter(v => !declared.includes(v)).forEach(v => log.warn("Plugin_Structure", undefined, entityName + `: @retired(` + v + `) names a state "` + field + `" does not declare — known values: ` + declared.join(", ") + `.`));
144
+ return;
145
+ }
146
+ }
147
+
148
+ function lifecycleStatesFromStateSchema(entityName, stateSchema) {
149
+ return Stdlib_Option.flatMap(lifecycleFieldFromStateSchema(entityName, stateSchema), field => {
150
+ if (stateSchema.type === "object") {
151
+ return Stdlib_Option.map(stateSchema.items.find(item => item.location === field), item => {
152
+ let match = shapeOfItem(entityName, item);
153
+ if (typeof match !== "object") {
154
+ return [];
155
+ }
156
+ switch (match.TAG) {
157
+ case "Nullable" :
158
+ let match$1 = match._0;
159
+ if (typeof match$1 !== "object") {
160
+ return [];
161
+ } else if (match$1.TAG === "Enum") {
162
+ return match$1._1;
163
+ } else {
164
+ return [];
165
+ }
166
+ case "Enum" :
167
+ return match._1;
168
+ default:
169
+ return [];
170
+ }
171
+ });
172
+ }
173
+ });
174
+ }
175
+
176
+ function checkDeclaredTransitions(pluginName, writables, lifecycleStatesByView) {
177
+ let unvalidated = {
178
+ contents: 0
179
+ };
180
+ let failures = [];
181
+ writables.forEach(w => {
182
+ w.commands.forEach(cmd => {
183
+ let t = cmd.targetState;
184
+ let declared = Stdlib_Option.getOr(cmd.allowedStates, []).concat(t !== undefined ? [t] : []);
185
+ if (declared.length === 0) {
186
+ return;
187
+ }
188
+ let known = Stdlib_Array.reduce(w.linkedViews, [], (acc, view) => {
189
+ let states = lifecycleStatesByView[view];
190
+ if (states !== undefined) {
191
+ return acc.concat(states);
192
+ } else {
193
+ return acc;
194
+ }
195
+ });
196
+ if (known.length === 0) {
197
+ unvalidated.contents = unvalidated.contents + 1 | 0;
198
+ } else {
199
+ declared.filter(state => !known.includes(state)).forEach(state => {
200
+ failures.push(w.name + `.` + cmd.name + `: @transition names "` + state + `", which none of its ` + (`linked views declare — ` + w.linkedViews.join(", ") + ` know ` + known.join(", ") + `.`));
201
+ });
202
+ }
203
+ });
204
+ });
205
+ if (unvalidated.contents > 0) {
206
+ log.warn("Plugin_Structure", undefined, pluginName + `: ` + unvalidated.contents.toString() + ` command(s) declare a @transition but no linked view declares a lifecycle to check it against.`);
207
+ }
208
+ if (failures.length !== 0) {
209
+ return Stdlib_JsError.throwWithMessage(pluginName + `: @transition names states that do not exist.\n` + failures.join("\n"));
210
+ }
211
+ }
212
+
213
+ function lifecycleTopologyFindings(writables, lifecycleStatesByView) {
214
+ let findings = [];
215
+ Object.entries(lifecycleStatesByView).forEach(param => {
216
+ let states = param[1];
217
+ let view = param[0];
218
+ let edges = Stdlib_Array.reduce(writables, [], (acc, w) => {
219
+ if (w.linkedViews.includes(view)) {
220
+ return acc.concat(Stdlib_Array.reduce(w.commands, [], (inner, cmd) => {
221
+ let match = cmd.allowedStates;
222
+ let match$1 = cmd.targetState;
223
+ if (match !== undefined && match$1 !== undefined) {
224
+ return inner.concat(match.map(from => [
225
+ from,
226
+ match$1
227
+ ]));
228
+ } else {
229
+ return inner;
230
+ }
231
+ }));
232
+ } else {
233
+ return acc;
234
+ }
235
+ });
236
+ if (edges.length === 0) {
237
+ return;
238
+ }
239
+ let initial = states[0];
240
+ let reachable = edges.map(param => param[1]);
241
+ states.forEach(state => {
242
+ if (!reachable.includes(state) && Primitive_object.notequal(state, initial)) {
243
+ findings.push([
244
+ view,
245
+ `no command declares a transition INTO "` + state + `" — it is unreachable unless something outside this plugin's declarations puts a row there.`
246
+ ]);
247
+ return;
248
+ }
249
+ });
250
+ });
251
+ return findings;
252
+ }
253
+
254
+ function checkLifecycleTopology(pluginName, writables, lifecycleStatesByView) {
255
+ lifecycleTopologyFindings(writables, lifecycleStatesByView).forEach(param => log.warn("Plugin_Structure", undefined, pluginName + `/` + param[0] + `: ` + param[1]));
256
+ }
257
+
89
258
  function labelFieldSourceToString(s) {
90
259
  switch (s) {
91
260
  case "Annotation" :
@@ -529,12 +698,22 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
529
698
  return "Internal";
530
699
  }
531
700
  };
701
+ let lifecycleStatesByView = {};
702
+ let recordLifecycle = (entityName, stateSchema) => {
703
+ let states = lifecycleStatesFromStateSchema(entityName, stateSchema);
704
+ if (states !== undefined && states.length !== 0) {
705
+ lifecycleStatesByView[entityName] = states;
706
+ return;
707
+ }
708
+ };
532
709
  let readModelDefs = readModels.map(R => {
533
710
  let qf = Api_Naming$ReventlessCore.queryFieldNamesForReadModel(name, R.Spec.name, undefined);
534
711
  let stateSchema = R.Spec.stateSchema;
535
712
  let label = labelFieldsFromStateSchema(R.Spec.name, stateSchema);
536
713
  let keyField = GraphQL_FragmentGenerator$ReventlessCore.resolveKeyField(R.Spec.name, stateSchema);
537
714
  let consumed = qualify(name, R.consumedEventNames);
715
+ checkRetiredValue(R.Spec.name, stateSchema);
716
+ recordLifecycle(R.Spec.name, stateSchema);
538
717
  return {
539
718
  name: R.Spec.name,
540
719
  queryField: qf.listFieldName,
@@ -544,8 +723,10 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
544
723
  labelField: label.field,
545
724
  searchableFields: label.searchableFields,
546
725
  labelFieldSource: labelFieldSourceToString(label.source),
547
- statusField: statusFieldFromStateSchema(R.Spec.name, stateSchema),
726
+ lifecycleField: lifecycleFieldFromStateSchema(R.Spec.name, stateSchema),
548
727
  ownerField: Owner$Reventless.fieldNames(stateSchema)[0],
728
+ retiredField: retiredFieldFromStateSchema(stateSchema),
729
+ retiredValues: retiredValuesFromStateSchema(stateSchema),
549
730
  visibility: visibilityTag(R.Spec.visibility),
550
731
  chapter: componentChapters[R.Spec.name],
551
732
  singleQueryField: qf.singleFieldName,
@@ -561,6 +742,8 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
561
742
  let stateSchema = SVS.Spec.stateSchema;
562
743
  let label = labelFieldsFromStateSchema(SVS.Spec.name, stateSchema);
563
744
  let keyField = GraphQL_FragmentGenerator$ReventlessCore.resolveKeyField(SVS.Spec.name, stateSchema);
745
+ checkRetiredValue(SVS.Spec.name, stateSchema);
746
+ recordLifecycle(SVS.Spec.name, stateSchema);
564
747
  return {
565
748
  name: SVS.Spec.name,
566
749
  queryField: qf.listFieldName,
@@ -570,8 +753,10 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
570
753
  labelField: label.field,
571
754
  searchableFields: label.searchableFields,
572
755
  labelFieldSource: labelFieldSourceToString(label.source),
573
- statusField: statusFieldFromStateSchema(SVS.Spec.name, stateSchema),
756
+ lifecycleField: lifecycleFieldFromStateSchema(SVS.Spec.name, stateSchema),
574
757
  ownerField: Owner$Reventless.fieldNames(stateSchema)[0],
758
+ retiredField: retiredFieldFromStateSchema(stateSchema),
759
+ retiredValues: retiredValuesFromStateSchema(stateSchema),
575
760
  visibility: visibilityTag(SVS.Spec.visibility),
576
761
  chapter: componentChapters[SVS.Spec.name],
577
762
  singleQueryField: qf.singleFieldName,
@@ -671,6 +856,8 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
671
856
  commandTypes: Belt_SetString.toArray(Belt_SetString.fromArray(match[2]))
672
857
  };
673
858
  });
859
+ checkDeclaredTransitions(name, stateChangeDefs.concat(aggregateDefs), lifecycleStatesByView);
860
+ checkLifecycleTopology(name, stateChangeDefs.concat(aggregateDefs), lifecycleStatesByView);
674
861
  return {
675
862
  readModels: readModelDefs,
676
863
  stateViewSlices: stateViewDefs,
@@ -689,10 +876,18 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
689
876
  export {
690
877
  log,
691
878
  isLabelShape,
692
- isStatusShape,
879
+ isLifecycleShape,
693
880
  conventionalLabelNames,
694
881
  shapeOfItem,
695
- statusFieldFromStateSchema,
882
+ lifecycleFieldFromStateSchema,
883
+ retiredFromStateSchema,
884
+ retiredFieldFromStateSchema,
885
+ retiredValuesFromStateSchema,
886
+ checkRetiredValue,
887
+ lifecycleStatesFromStateSchema,
888
+ checkDeclaredTransitions,
889
+ lifecycleTopologyFindings,
890
+ checkLifecycleTopology,
696
891
  labelFieldSourceToString,
697
892
  labelFieldsFromStateSchema,
698
893
  extractReferences,
@@ -1,5 +1,5 @@
1
1
  // Second mapping on the admin PluginExtensionPoint, routing UI-fragment lifecycle to the
2
- // admin UiFragmentRegistry StateChangeSlice (docs/plans/event-sourced-fragment-registries.md).
2
+ // admin UiFragmentRegistry StateChangeSlice (docs/plans/done/event-sourced-fragment-registries.md).
3
3
  //
4
4
  // The EP runtime fans every incoming command through ALL mappings and flattens the results
5
5
  // (ExtensionPoint_Callback.mapIncomingCommands). This mapping's Delegate is the slice, so its
@@ -48,10 +48,11 @@ let stateSchema$1 = S.Metadata.set(stateSchema, StateAnnotations$Reventless.stat
48
48
  scanSort: [],
49
49
  semantic: [],
50
50
  metric: [],
51
- status: undefined,
51
+ lifecycle: undefined,
52
52
  groupBy: "kind",
53
53
  visibility: undefined,
54
- live: undefined
54
+ live: undefined,
55
+ retired: undefined
55
56
  });
56
57
 
57
58
  let name = "Plugins";
@@ -0,0 +1,49 @@
1
+ // The platform's own admin components, as `Platform_ComponentDefinitions` serves
2
+ // them. The Plugins read model is a framework-generated queryable: its field names
3
+ // are chosen here and read by every client, so the rule that applies to it is the
4
+ // opposite of the one that applies to an authored record.
5
+ //
6
+ // An authored record may follow the convention — name the field `lifecycle` and
7
+ // write no annotation. A generated row cannot, because its field name is a
8
+ // published wire name: `status` in the SDL, in stored QueryDb rows, and in any
9
+ // client selecting it. Renaming it to satisfy a convention would move the
10
+ // contract, break selecting clients and require re-projection. So it declares
11
+ // instead, and the declaration is what this asserts — the failure mode otherwise
12
+ // is silent (a board loses its columns, a command menu stops filtering, and
13
+ // nothing fails to compile).
14
+
15
+ open JestGlobals
16
+ open Reventless.Plugin
17
+
18
+ describe("the Plugins read model declares its lifecycle", () => {
19
+ let rm: queryableDef = Platform_Admin_Structure.pluginReadModel
20
+
21
+ testSync("names the field commands branch on", () =>
22
+ expect(rm.lifecycleField)->toEqual(Some("status"))
23
+ )
24
+
25
+ testSync("and the published wire name is unchanged by the rename", () => {
26
+ // Stated as its own assertion rather than folded into the one above: the two
27
+ // say different things. That the field is declared is the fix; that it is
28
+ // still called `status` is the constraint the fix had to respect.
29
+ let sdlDeclaresStatus =
30
+ rm.lifecycleField->Option.mapOr(false, f => f == "status") &&
31
+ rm.queryField->String.startsWith("Platform_")
32
+ expect(sdlDeclaresStatus)->toEqual(true)
33
+ })
34
+
35
+ testSync("is served on the encoded def under the new key", () => {
36
+ let json =
37
+ rm->Platform_ComponentDefinitionsApi.encodeQueryableDef->JSON.stringify
38
+ expect(json->String.includes(`"lifecycleField":"status"`))->toEqual(true)
39
+ })
40
+
41
+ testSync("and never under the old one", () =>
42
+ expect(
43
+ rm
44
+ ->Platform_ComponentDefinitionsApi.encodeQueryableDef
45
+ ->JSON.stringify
46
+ ->String.includes(`"statusField"`),
47
+ )->toEqual(false)
48
+ )
49
+ })
@@ -0,0 +1,24 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+ import * as Platform_Admin_Structure$ReventlessCore from "../../src/admin/Platform_Admin_Structure.res.mjs";
5
+ import * as Platform_ComponentDefinitionsApi$ReventlessCore from "../../src/admin/Platform_ComponentDefinitionsApi.res.mjs";
6
+
7
+ globalThis.describe("the Plugins read model declares its lifecycle", () => {
8
+ globalThis.test("names the field commands branch on", () => {
9
+ globalThis.expect(Platform_Admin_Structure$ReventlessCore.pluginReadModel.lifecycleField).toEqual("status");
10
+ });
11
+ globalThis.test("and the published wire name is unchanged by the rename", () => {
12
+ let sdlDeclaresStatus = Stdlib_Option.mapOr(Platform_Admin_Structure$ReventlessCore.pluginReadModel.lifecycleField, false, f => f === "status") && Platform_Admin_Structure$ReventlessCore.pluginReadModel.queryField.startsWith("Platform_");
13
+ globalThis.expect(sdlDeclaresStatus).toEqual(true);
14
+ });
15
+ globalThis.test("is served on the encoded def under the new key", () => {
16
+ let json = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodeQueryableDef(Platform_Admin_Structure$ReventlessCore.pluginReadModel));
17
+ globalThis.expect(json.includes(`"lifecycleField":"status"`)).toEqual(true);
18
+ });
19
+ globalThis.test("and never under the old one", () => {
20
+ globalThis.expect(JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodeQueryableDef(Platform_Admin_Structure$ReventlessCore.pluginReadModel)).includes(`"statusField"`)).toEqual(false);
21
+ });
22
+ });
23
+
24
+ /* Not a pure module */
@@ -15,7 +15,7 @@ let queryable = (~name, ~visibility=?, ~ownerField=?, ()): queryableDef => {
15
15
  labelField: "displayName",
16
16
  searchableFields: ["displayName"],
17
17
  labelFieldSource: None,
18
- statusField: None,
18
+ lifecycleField: None,
19
19
  visibility,
20
20
  chapter: None,
21
21
  singleQueryField: Some(`Ordering_${name}Single`),
@@ -23,6 +23,8 @@ let queryable = (~name, ~visibility=?, ~ownerField=?, ()): queryableDef => {
23
23
  idFieldSource: Some("convention"),
24
24
  requiredAccess: None,
25
25
  ownerField,
26
+ retiredField: None,
27
+ retiredValues: None,
26
28
  }
27
29
 
28
30
  let command = (~name, ~references=[], ~ownerField=?, ()): commandDef => {
@@ -18,8 +18,10 @@ function queryable(name, visibility, ownerField, param) {
18
18
  labelField: "displayName",
19
19
  searchableFields: ["displayName"],
20
20
  labelFieldSource: undefined,
21
- statusField: undefined,
21
+ lifecycleField: undefined,
22
22
  ownerField: ownerField,
23
+ retiredField: undefined,
24
+ retiredValues: undefined,
23
25
  visibility: visibility,
24
26
  chapter: undefined,
25
27
  singleQueryField: `Ordering_` + name + `Single`,
@@ -30,7 +30,7 @@ let qbl: queryableDef = {
30
30
  labelField: "displayName",
31
31
  searchableFields: ["displayName"],
32
32
  labelFieldSource: Some("annotation"),
33
- statusField: None,
33
+ lifecycleField: None,
34
34
  visibility: None,
35
35
  chapter: None,
36
36
  singleQueryField: Some("Catalog_Product"),
@@ -38,6 +38,8 @@ let qbl: queryableDef = {
38
38
  idFieldSource: Some("convention"),
39
39
  requiredAccess: None,
40
40
  ownerField: None,
41
+ retiredField: None,
42
+ retiredValues: None,
41
43
  }
42
44
 
43
45
  let wbl: writableDef = {
@@ -162,8 +164,8 @@ describe("encodePluginStructureEntry", () => {
162
164
  expect(json->String.includes("\"apiExposed\":true"))->toEqual(true)
163
165
  )
164
166
 
165
- testSync("encodes None statusField as null", () =>
166
- expect(json->String.includes("\"statusField\":null"))->toEqual(true)
167
+ testSync("encodes None lifecycleField as null", () =>
168
+ expect(json->String.includes("\"lifecycleField\":null"))->toEqual(true)
167
169
  )
168
170
 
169
171
  // Phase 6.3: write-side emitted-event field schemas.
@@ -256,7 +258,7 @@ describe("visibility filtering (deployed AutoUI hides Internal)", () => {
256
258
  labelField: "name",
257
259
  searchableFields: ["name"],
258
260
  labelFieldSource: Some("convention"),
259
- statusField: None,
261
+ lifecycleField: None,
260
262
  visibility: Some("Internal"),
261
263
  chapter: None,
262
264
  singleQueryField: Some("Ordering_AvailableProduct"),
@@ -264,6 +266,8 @@ describe("visibility filtering (deployed AutoUI hides Internal)", () => {
264
266
  idFieldSource: Some("sole"),
265
267
  requiredAccess: None,
266
268
  ownerField: None,
269
+ retiredField: None,
270
+ retiredValues: None,
267
271
  }
268
272
  // A distinct name per source array: the complement has to be fed by both, and
269
273
  // reusing one def would hide a version that only walks `readModels`.
@@ -329,7 +333,7 @@ describe("visibility filtering (deployed AutoUI hides Internal)", () => {
329
333
  })
330
334
  })
331
335
 
332
- describe("allowedStates + statusField populated", () => {
336
+ describe("allowedStates + lifecycleField populated", () => {
333
337
  let cmdWithStates: commandDef = {
334
338
  name: "Activate",
335
339
  schema: "{}",
@@ -353,7 +357,7 @@ describe("allowedStates + statusField populated", () => {
353
357
  labelField: "name",
354
358
  searchableFields: ["name"],
355
359
  labelFieldSource: Some("convention"),
356
- statusField: Some("status"),
360
+ lifecycleField: Some("status"),
357
361
  visibility: None,
358
362
  chapter: None,
359
363
  singleQueryField: Some("Platform_Plugin"),
@@ -361,6 +365,8 @@ describe("allowedStates + statusField populated", () => {
361
365
  idFieldSource: None,
362
366
  requiredAccess: None,
363
367
  ownerField: None,
368
+ retiredField: None,
369
+ retiredValues: None,
364
370
  }
365
371
 
366
372
  let wblWithStates: writableDef = {
@@ -402,8 +408,8 @@ describe("allowedStates + statusField populated", () => {
402
408
  expect(json->String.includes("\"targetState\":\"Active\""))->toEqual(true)
403
409
  )
404
410
 
405
- testSync("encodes populated statusField as the field name string", () =>
406
- expect(json->String.includes("\"statusField\":\"status\""))->toEqual(true)
411
+ testSync("encodes populated lifecycleField as the field name string", () =>
412
+ expect(json->String.includes("\"lifecycleField\":\"status\""))->toEqual(true)
407
413
  )
408
414
 
409
415
  // "declares no errors" is an empty list, never null — the SDL types `errors` as
@@ -51,8 +51,10 @@ let qbl = {
51
51
  labelField: "displayName",
52
52
  searchableFields: qbl_searchableFields,
53
53
  labelFieldSource: qbl_labelFieldSource,
54
- statusField: undefined,
54
+ lifecycleField: undefined,
55
55
  ownerField: undefined,
56
+ retiredField: undefined,
57
+ retiredValues: undefined,
56
58
  visibility: undefined,
57
59
  chapter: undefined,
58
60
  singleQueryField: qbl_singleQueryField,
@@ -198,8 +200,8 @@ globalThis.describe("encodePluginStructureEntry", () => {
198
200
  globalThis.test("encodes the command's apiExposed flag (drives the event-graph API badge)", () => {
199
201
  globalThis.expect(json.includes("\"apiExposed\":true")).toEqual(true);
200
202
  });
201
- globalThis.test("encodes None statusField as null", () => {
202
- globalThis.expect(json.includes("\"statusField\":null")).toEqual(true);
203
+ globalThis.test("encodes None lifecycleField as null", () => {
204
+ globalThis.expect(json.includes("\"lifecycleField\":null")).toEqual(true);
203
205
  });
204
206
  globalThis.test("encodes writableDef events", () => {
205
207
  globalThis.expect(json.includes("\"events\":[{\"name\":\"ProductAdded\"")).toEqual(true);
@@ -282,8 +284,10 @@ globalThis.describe("visibility filtering (deployed AutoUI hides Internal)", ()
282
284
  labelField: "name",
283
285
  searchableFields: internalQbl_searchableFields,
284
286
  labelFieldSource: internalQbl_labelFieldSource,
285
- statusField: undefined,
287
+ lifecycleField: undefined,
286
288
  ownerField: undefined,
289
+ retiredField: undefined,
290
+ retiredValues: undefined,
287
291
  visibility: internalQbl_visibility,
288
292
  chapter: undefined,
289
293
  singleQueryField: internalQbl_singleQueryField,
@@ -308,8 +312,10 @@ globalThis.describe("visibility filtering (deployed AutoUI hides Internal)", ()
308
312
  labelField: "name",
309
313
  searchableFields: internalSlice_searchableFields,
310
314
  labelFieldSource: internalSlice_labelFieldSource,
311
- statusField: undefined,
315
+ lifecycleField: undefined,
312
316
  ownerField: undefined,
317
+ retiredField: undefined,
318
+ retiredValues: undefined,
313
319
  visibility: internalSlice_visibility,
314
320
  chapter: undefined,
315
321
  singleQueryField: internalSlice_singleQueryField,
@@ -378,7 +384,7 @@ globalThis.describe("visibility filtering (deployed AutoUI hides Internal)", ()
378
384
  });
379
385
  });
380
386
 
381
- globalThis.describe("allowedStates + statusField populated", () => {
387
+ globalThis.describe("allowedStates + lifecycleField populated", () => {
382
388
  let cmdWithStates_aggregateIdField = "id";
383
389
  let cmdWithStates_references = [];
384
390
  let cmdWithStates_allowedStates = ["Inactive"];
@@ -401,7 +407,7 @@ globalThis.describe("allowedStates + statusField populated", () => {
401
407
  let qblWithStatus_linkedWriteSide = ["Plugin"];
402
408
  let qblWithStatus_searchableFields = ["name"];
403
409
  let qblWithStatus_labelFieldSource = "convention";
404
- let qblWithStatus_statusField = "status";
410
+ let qblWithStatus_lifecycleField = "status";
405
411
  let qblWithStatus_singleQueryField = "Platform_Plugin";
406
412
  let qblWithStatus = {
407
413
  name: "Plugin",
@@ -412,8 +418,10 @@ globalThis.describe("allowedStates + statusField populated", () => {
412
418
  labelField: "name",
413
419
  searchableFields: qblWithStatus_searchableFields,
414
420
  labelFieldSource: qblWithStatus_labelFieldSource,
415
- statusField: qblWithStatus_statusField,
421
+ lifecycleField: qblWithStatus_lifecycleField,
416
422
  ownerField: undefined,
423
+ retiredField: undefined,
424
+ retiredValues: undefined,
417
425
  visibility: undefined,
418
426
  chapter: undefined,
419
427
  singleQueryField: qblWithStatus_singleQueryField,
@@ -466,8 +474,8 @@ globalThis.describe("allowedStates + statusField populated", () => {
466
474
  globalThis.test("round-trips a declared targetState as the status string", () => {
467
475
  globalThis.expect(json.includes("\"targetState\":\"Active\"")).toEqual(true);
468
476
  });
469
- globalThis.test("encodes populated statusField as the field name string", () => {
470
- globalThis.expect(json.includes("\"statusField\":\"status\"")).toEqual(true);
477
+ globalThis.test("encodes populated lifecycleField as the field name string", () => {
478
+ globalThis.expect(json.includes("\"lifecycleField\":\"status\"")).toEqual(true);
471
479
  });
472
480
  globalThis.test("encodes a component with no declared errors as an empty array, not null", () => {
473
481
  globalThis.expect(json.includes("\"errors\":[]")).toEqual(true);
@@ -491,8 +499,10 @@ globalThis.describe("singleQueryField reaches the caller", () => {
491
499
  labelField: "displayName",
492
500
  searchableFields: qbl_searchableFields,
493
501
  labelFieldSource: "annotation",
494
- statusField: undefined,
502
+ lifecycleField: undefined,
495
503
  ownerField: undefined,
504
+ retiredField: undefined,
505
+ retiredValues: undefined,
496
506
  visibility: undefined,
497
507
  chapter: undefined,
498
508
  singleQueryField: undefined,
@@ -536,8 +546,10 @@ globalThis.describe("idField / idFieldSource reach the caller", () => {
536
546
  labelField: "displayName",
537
547
  searchableFields: qbl_searchableFields,
538
548
  labelFieldSource: "annotation",
539
- statusField: undefined,
549
+ lifecycleField: undefined,
540
550
  ownerField: undefined,
551
+ retiredField: undefined,
552
+ retiredValues: undefined,
541
553
  visibility: undefined,
542
554
  chapter: undefined,
543
555
  singleQueryField: "Catalog_Product",
@@ -16,7 +16,7 @@ let publicRm: queryableDef = {
16
16
  labelField: "displayName",
17
17
  searchableFields: ["displayName"],
18
18
  labelFieldSource: Some("annotation"),
19
- statusField: None,
19
+ lifecycleField: None,
20
20
  visibility: None,
21
21
  chapter: Some("Inventory"),
22
22
  singleQueryField: Some("Catalog_Product"),
@@ -24,6 +24,8 @@ let publicRm: queryableDef = {
24
24
  idFieldSource: Some("convention"),
25
25
  requiredAccess: None,
26
26
  ownerField: None,
27
+ retiredField: None,
28
+ retiredValues: None,
27
29
  }
28
30
 
29
31
  // The component `Platform_ComponentDefinitions` drops and this query must keep.
@@ -31,8 +31,10 @@ let publicRm = {
31
31
  labelField: "displayName",
32
32
  searchableFields: publicRm_searchableFields,
33
33
  labelFieldSource: publicRm_labelFieldSource,
34
- statusField: undefined,
34
+ lifecycleField: undefined,
35
35
  ownerField: undefined,
36
+ retiredField: undefined,
37
+ retiredValues: undefined,
36
38
  visibility: undefined,
37
39
  chapter: publicRm_chapter,
38
40
  singleQueryField: publicRm_singleQueryField,
@@ -68,8 +70,10 @@ let internalRm = {
68
70
  labelField: "displayName",
69
71
  searchableFields: internalRm_searchableFields,
70
72
  labelFieldSource: internalRm_labelFieldSource,
71
- statusField: undefined,
73
+ lifecycleField: undefined,
72
74
  ownerField: undefined,
75
+ retiredField: undefined,
76
+ retiredValues: undefined,
73
77
  visibility: internalRm_visibility,
74
78
  chapter: internalRm_chapter,
75
79
  singleQueryField: internalRm_singleQueryField,
@@ -1,5 +1,5 @@
1
1
  // Tests for the Aggregate_Callback in-process replay cache (Phase 1 of
2
- // docs/plans/aggregate-snapshotting.md): a warm same-id command skips the
2
+ // docs/plans/done/aggregate-snapshotting.md): a warm same-id command skips the
3
3
  // event-log replay and decides on the cached (state, sequenceNr); the OCC
4
4
  // append fences staleness — a stale cache conflicts, invalidates, and the
5
5
  // retry replays cold.