@reventlessdev/reventless-core 3.0.0-alpha.274 → 3.0.0-alpha.275

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,13 @@
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.275 (2026-09-25)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **spec,core:** a partition tag its chapter backs overrides inference ([4244a4a](https://github.com/ReventlessDev/reventless-core/commit/4244a4af9e7aad4378d9b6276ddeb44878e125b2))
11
+
12
+
6
13
  # 3.0.0-alpha.274 (2026-09-24)
7
14
 
8
15
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-core",
3
- "version": "3.0.0-alpha.274",
3
+ "version": "3.0.0-alpha.275",
4
4
  "description": "Core package for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -36,14 +36,14 @@
36
36
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.22",
37
37
  "@reventlessdev/rescript-ssh2": "2.0.0-alpha.15",
38
38
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
39
- "@reventlessdev/reventless-infra": "3.0.0-alpha.174",
40
- "@reventlessdev/reventless-spec": "3.0.0-alpha.146",
41
- "@reventlessdev/reventless-interop": "3.0.0-alpha.38"
39
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.175",
40
+ "@reventlessdev/reventless-interop": "3.0.0-alpha.38",
41
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.147"
42
42
  },
43
43
  "devDependencies": {
44
44
  "rescript": "12.3.1",
45
45
  "sury-ppx": "11.0.0",
46
- "@reventlessdev/reventless-ppx": "1.0.0-alpha.92"
46
+ "@reventlessdev/reventless-ppx": "1.0.0-alpha.94"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "rescript": "^12.3.1"
@@ -237,11 +237,14 @@ module Make = (
237
237
 
238
238
  // A `@partitionTag` inference contradicts would steer storage, fence and read
239
239
  // scope to a key the slice only references, so it stops the build. One that
240
- // inference agrees with is noise, and says so.
240
+ // inference agrees with is noise, and says so. One its chapter backs against
241
+ // inference is the author deciding what inference cannot see, and is kept.
241
242
  let hintIssues = Reventless.DcbValidation.validatePartitionHintsVsInference(
242
243
  ~shapes=inferenceShapes,
243
244
  )
244
- hintIssues.redundancies->Array.forEach(e =>
245
+ hintIssues.redundancies
246
+ ->Array.concat(hintIssues.overrides)
247
+ ->Array.forEach(e =>
245
248
  log.info(~comp="Dcb_Builder", `DCB partition (${e.sliceName}): ${e.message}`)
246
249
  )
247
250
  if hintIssues.contradictions->Array.length > 0 {
@@ -178,7 +178,7 @@ function Make(DcbEventLogStorage) {
178
178
  }));
179
179
  let inferenceShapes = sliceSchemas.map(DcbTag$Reventless.sliceShape);
180
180
  let hintIssues = DcbValidation$Reventless.validatePartitionHintsVsInference(inferenceShapes);
181
- hintIssues.redundancies.forEach(e => log.info("Dcb_Builder", undefined, `DCB partition (` + e.sliceName + `): ` + e.message));
181
+ hintIssues.redundancies.concat(hintIssues.overrides).forEach(e => log.info("Dcb_Builder", undefined, `DCB partition (` + e.sliceName + `): ` + e.message));
182
182
  if (hintIssues.contradictions.length !== 0) {
183
183
  let detail = hintIssues.contradictions.map(e => e.sliceName + `: ` + e.message).join(" | ");
184
184
  log.error("Dcb_Builder", undefined, `DCB partition contradiction (` + name + `): ` + detail);
@@ -503,6 +503,80 @@ describe("DcbScopeInference:", () => {
503
503
  expect(issues.redundancies)->toEqual([])
504
504
  },
505
505
  )
506
+
507
+ // `ShipOrder` records the carrier's booking, which nothing else in the boundary
508
+ // writes or reads. Rule 1 subtracts `orderId` (read off `OrderPlaced`) and is
509
+ // left with the booking: `AddProduct`'s shape exactly, with the other answer.
510
+ let placeOrder = slice(
511
+ "PlaceOrder",
512
+ ~chapter="Order",
513
+ ~produced=[ev("OrderPlaced", [scal("orderId")])],
514
+ )
515
+ let shipOrder = slice(
516
+ "ShipOrder",
517
+ ~chapter="Order",
518
+ ~partitionHint="orderId",
519
+ ~command=[scal("orderId"), scal("carrierBookingId")],
520
+ ~consumed=[ev("OrderPlaced", [scal("orderId")]), ev("OrderShipped", [scal("orderId")])],
521
+ ~produced=[ev("OrderShipped", [scal("orderId"), scal("carrierBookingId")])],
522
+ )
523
+
524
+ testSync(
525
+ "a hint its chapter backs overrides inference instead of contradicting it",
526
+ () => {
527
+ let issues = check([placeOrder, shipOrder])
528
+ expect(issues.contradictions)->toEqual([])
529
+ expect(issues.redundancies)->toEqual([])
530
+ expect(issues.overrides->names)->toEqual(["ShipOrder"])
531
+ // and the hint is what the boundary is partitioned by
532
+ expect(
533
+ DSI.resolvePartitions([placeOrder, shipOrder]).partitionBySlice->Dict.get("ShipOrder"),
534
+ )->toEqual(Some("orderId"))
535
+ },
536
+ )
537
+
538
+ testSync(
539
+ "without a chapter the same hint is still a contradiction",
540
+ () => {
541
+ let noChapter = (s: DSI.sliceShape) => {...s, chapter: ?None}
542
+ let issues = check([noChapter(placeOrder), noChapter(shipOrder)])
543
+ expect(issues.contradictions->names)->toEqual(["ShipOrder"])
544
+ expect(issues.overrides)->toEqual([])
545
+ },
546
+ )
547
+
548
+ testSync(
549
+ "a hint its chapter disagrees with is still a contradiction",
550
+ () => {
551
+ // Every event under `Product/` carries `productId`, not `categoryId`.
552
+ let inProduct = {...hinted(addProduct, "categoryId"), chapter: "Product"}
553
+ let issues = check([inProduct, {...addCategory, chapter: "Category"}])
554
+ expect(issues.contradictions->names)->toEqual(["AddProduct"])
555
+ expect(issues.overrides)->toEqual([])
556
+ },
557
+ )
558
+
559
+ testSync(
560
+ "a chapter-backed hint read off another entity's events is still a contradiction",
561
+ () => {
562
+ // `WarehouseAssigned` carries the order but belongs to the warehouse.
563
+ let assign = slice(
564
+ "AssignWarehouse",
565
+ ~chapter="Warehouse",
566
+ ~partitionHint="warehouseId",
567
+ ~produced=[ev("WarehouseAssigned", [scal("warehouseId"), scal("orderId")])],
568
+ )
569
+ let ship = {
570
+ ...shipOrder,
571
+ consumed: shipOrder.consumed->Array.concat([
572
+ ev("WarehouseAssigned", [scal("warehouseId"), scal("orderId")]),
573
+ ]),
574
+ }
575
+ let issues = check([placeOrder, ship, assign])
576
+ expect(issues.overrides->names)->toEqual([])
577
+ expect(issues.contradictions->names)->toEqual(["ShipOrder"])
578
+ },
579
+ )
506
580
  })
507
581
 
508
582
  describe("ambiguity surfacing", () => {
@@ -814,6 +814,123 @@ globalThis.describe("DcbScopeInference:", () => {
814
814
  globalThis.expect(issues.contradictions).toEqual([]);
815
815
  globalThis.expect(issues.redundancies).toEqual([]);
816
816
  });
817
+ let placeOrder = slice("PlaceOrder", undefined, undefined, [{
818
+ eventType: "OrderPlaced",
819
+ idFields: [{
820
+ name: "orderId",
821
+ isList: false
822
+ }]
823
+ }], undefined, "Order");
824
+ let shipOrder = slice("ShipOrder", [
825
+ {
826
+ name: "orderId",
827
+ isList: false
828
+ },
829
+ {
830
+ name: "carrierBookingId",
831
+ isList: false
832
+ }
833
+ ], [
834
+ {
835
+ eventType: "OrderPlaced",
836
+ idFields: [{
837
+ name: "orderId",
838
+ isList: false
839
+ }]
840
+ },
841
+ {
842
+ eventType: "OrderShipped",
843
+ idFields: [{
844
+ name: "orderId",
845
+ isList: false
846
+ }]
847
+ }
848
+ ], [{
849
+ eventType: "OrderShipped",
850
+ idFields: [
851
+ {
852
+ name: "orderId",
853
+ isList: false
854
+ },
855
+ {
856
+ name: "carrierBookingId",
857
+ isList: false
858
+ }
859
+ ]
860
+ }], "orderId", "Order");
861
+ globalThis.test("a hint its chapter backs overrides inference instead of contradicting it", () => {
862
+ let issues = DcbValidation$Reventless.validatePartitionHintsVsInference([
863
+ placeOrder,
864
+ shipOrder
865
+ ]);
866
+ globalThis.expect(issues.contradictions).toEqual([]);
867
+ globalThis.expect(issues.redundancies).toEqual([]);
868
+ globalThis.expect(issues.overrides.map(e => e.sliceName)).toEqual(["ShipOrder"]);
869
+ globalThis.expect(DcbScopeInference$Reventless.resolvePartitions([
870
+ placeOrder,
871
+ shipOrder
872
+ ]).partitionBySlice["ShipOrder"]).toEqual("orderId");
873
+ });
874
+ globalThis.test("without a chapter the same hint is still a contradiction", () => {
875
+ let noChapter = s => {
876
+ let newrecord = {...s};
877
+ newrecord.chapter = undefined;
878
+ return newrecord;
879
+ };
880
+ let issues = DcbValidation$Reventless.validatePartitionHintsVsInference([
881
+ noChapter(placeOrder),
882
+ noChapter(shipOrder)
883
+ ]);
884
+ globalThis.expect(issues.contradictions.map(e => e.sliceName)).toEqual(["ShipOrder"]);
885
+ globalThis.expect(issues.overrides).toEqual([]);
886
+ });
887
+ globalThis.test("a hint its chapter disagrees with is still a contradiction", () => {
888
+ let newrecord = {...hinted(addProduct, "categoryId")};
889
+ newrecord.chapter = "Product";
890
+ let newrecord$1 = {...addCategory};
891
+ let issues = DcbValidation$Reventless.validatePartitionHintsVsInference([
892
+ newrecord,
893
+ (newrecord$1.chapter = "Category", newrecord$1)
894
+ ]);
895
+ globalThis.expect(issues.contradictions.map(e => e.sliceName)).toEqual(["AddProduct"]);
896
+ globalThis.expect(issues.overrides).toEqual([]);
897
+ });
898
+ globalThis.test("a chapter-backed hint read off another entity's events is still a contradiction", () => {
899
+ let assign = slice("AssignWarehouse", undefined, undefined, [{
900
+ eventType: "WarehouseAssigned",
901
+ idFields: [
902
+ {
903
+ name: "warehouseId",
904
+ isList: false
905
+ },
906
+ {
907
+ name: "orderId",
908
+ isList: false
909
+ }
910
+ ]
911
+ }], "warehouseId", "Warehouse");
912
+ let newrecord = {...shipOrder};
913
+ newrecord.consumed = shipOrder.consumed.concat([{
914
+ eventType: "WarehouseAssigned",
915
+ idFields: [
916
+ {
917
+ name: "warehouseId",
918
+ isList: false
919
+ },
920
+ {
921
+ name: "orderId",
922
+ isList: false
923
+ }
924
+ ]
925
+ }]);
926
+ let issues = DcbValidation$Reventless.validatePartitionHintsVsInference([
927
+ placeOrder,
928
+ newrecord,
929
+ assign
930
+ ]);
931
+ globalThis.expect(issues.overrides.map(e => e.sliceName)).toEqual([]);
932
+ globalThis.expect(issues.contradictions.map(e => e.sliceName)).toEqual(["ShipOrder"]);
933
+ });
817
934
  });
818
935
  globalThis.describe("ambiguity surfacing", () => {
819
936
  let aOwner = slice("A", undefined, undefined, [{