@reventlessdev/reventless-spec 3.0.0-alpha.147 → 3.0.0-alpha.149

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.149 (2026-09-25)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **spec:** the lifecycle harvest reads an app that installs at its root ([977b368](https://github.com/ReventlessDev/reventless-core/commit/977b3681c6acde78ae05b18ca72935c10e6a5154))
11
+
12
+
13
+ # 3.0.0-alpha.148 (2026-09-25)
14
+
15
+ ### Bug Fixes
16
+
17
+ * **spec:** a chapter that carries both keys backs neither ([cbd823e](https://github.com/ReventlessDev/reventless-core/commit/cbd823e4f87ddd5810debd929bb536b2003d0e4c))
18
+
19
+
6
20
  # 3.0.0-alpha.147 (2026-09-25)
7
21
 
8
22
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-spec",
3
- "version": "3.0.0-alpha.147",
3
+ "version": "3.0.0-alpha.149",
4
4
  "description": "Specifications for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -497,7 +497,8 @@ Checks each `@partitionTag` against what inference derives with that hint remove
497
497
  fence and read scope would all follow the wrong key.
498
498
  - **Redundant** — inference reaches the same key unaided; the annotation can go.
499
499
  - **Override** — inference disagrees, but the slice's chapter backs the hint: every
500
- id-carrying event written in the chapter carries the hinted key, and every foreign
500
+ id-carrying event written in the chapter carries the hinted key and not every one
501
+ carries the key inference chose, and every foreign
501
502
  event the slice reads by that key is written by a slice partitioned by it. Rule 1
502
503
  cannot tell an own key read back off a sibling's events from a reference
503
504
  (`ShipOrder` writing `{orderId, carrierBookingId}` and reading `OrderPlaced` has
@@ -519,11 +520,16 @@ let validatePartitionHintsVsInference = (
519
520
  shapes->Array.filter(p => p.produced->Array.some(e => e.eventType == eventType))
520
521
  // The chapter backs the hint, and what the slice reads by that key is its own
521
522
  // entity: each foreign arm carrying it comes from a slice partitioned by it.
522
- let backedByChapter = (s: DcbScopeInference.sliceShape, hint, partitionBySlice) => {
523
+ // Backing means telling the keys apart: a chapter whose events all carry both the
524
+ // hinted key and inference's (one slice, one event with both ids) says nothing
525
+ // about which one the slice is about, so it backs neither.
526
+ let backedByChapter = (s: DcbScopeInference.sliceShape, hint, inferred, partitionBySlice) => {
523
527
  let chapterAgrees =
524
528
  s.chapter
525
529
  ->Option.flatMap(c => chapterKeys->Dict.get(c))
526
- ->Option.mapOr(false, keys => keys->Array.includes(hint))
530
+ ->Option.mapOr(false, keys =>
531
+ keys->Array.includes(hint) && !(inferred->Array.some(k => keys->Array.includes(k)))
532
+ )
527
533
  let own = s.produced->Array.map(e => e.eventType)
528
534
  let readsOwnEntity =
529
535
  s.consumed
@@ -591,7 +597,7 @@ let validatePartitionHintsVsInference = (
591
597
  sliceName: s.sliceName,
592
598
  message: `@partitionTag ${hint} is what inference derives without it — the annotation is redundant and can be removed.`,
593
599
  })
594
- | Some(inferred) if backedByChapter(s, hint, unaided.partitionBySlice) =>
600
+ | Some(inferred) if backedByChapter(s, hint, [inferred], unaided.partitionBySlice) =>
595
601
  overridden(s, hint, `it derives ${inferred}`)
596
602
  | Some(inferred) =>
597
603
  contradictions->Array.push({
@@ -602,7 +608,7 @@ let validatePartitionHintsVsInference = (
602
608
  let candidates = unaided.candidatesBySlice->Dict.get(s.sliceName)->Option.getOr([])
603
609
  if candidates->Array.includes(hint) {
604
610
  ()
605
- } else if backedByChapter(s, hint, unaided.partitionBySlice) {
611
+ } else if backedByChapter(s, hint, candidates, unaided.partitionBySlice) {
606
612
  overridden(s, hint, `candidates: ${candidates->Array.join(", ")}`)
607
613
  } else {
608
614
  contradictions->Array.push({
@@ -435,8 +435,14 @@ function validatePartitionHintsVsInference(shapes) {
435
435
  let overrides = [];
436
436
  let chapterKeys = DcbScopeInference$Reventless.chapterKeys(shapes);
437
437
  let producersOf = eventType => shapes.filter(p => p.produced.some(e => e.eventType === eventType));
438
- let backedByChapter = (s, hint, partitionBySlice) => {
439
- let chapterAgrees = Stdlib_Option.mapOr(Stdlib_Option.flatMap(s.chapter, c => chapterKeys[c]), false, keys => keys.includes(hint));
438
+ let backedByChapter = (s, hint, inferred, partitionBySlice) => {
439
+ let chapterAgrees = Stdlib_Option.mapOr(Stdlib_Option.flatMap(s.chapter, c => chapterKeys[c]), false, keys => {
440
+ if (keys.includes(hint)) {
441
+ return !inferred.some(k => keys.includes(k));
442
+ } else {
443
+ return false;
444
+ }
445
+ });
440
446
  let own = s.produced.map(e => e.eventType);
441
447
  let readsOwnEntity = s.consumed.filter(e => {
442
448
  if (own.includes(e.eventType)) {
@@ -504,7 +510,7 @@ function validatePartitionHintsVsInference(shapes) {
504
510
  message: `@partitionTag ` + hint + ` is what inference derives without it — the annotation is redundant and can be removed.`
505
511
  });
506
512
  return;
507
- } else if (backedByChapter(s, hint, unaided.partitionBySlice)) {
513
+ } else if (backedByChapter(s, hint, [inferred], unaided.partitionBySlice)) {
508
514
  return overridden(s, hint, `it derives ` + inferred);
509
515
  } else {
510
516
  contradictions.push({
@@ -517,7 +523,7 @@ function validatePartitionHintsVsInference(shapes) {
517
523
  let candidates = Stdlib_Option.getOr(unaided.candidatesBySlice[s.sliceName], []);
518
524
  if (candidates.includes(hint)) {
519
525
  return;
520
- } else if (backedByChapter(s, hint, unaided.partitionBySlice)) {
526
+ } else if (backedByChapter(s, hint, candidates, unaided.partitionBySlice)) {
521
527
  return overridden(s, hint, `candidates: ` + candidates.join(", "));
522
528
  } else {
523
529
  contradictions.push({
@@ -18,13 +18,30 @@
18
18
  // schema describes the wire form, so validating an in-memory structure against
19
19
  // it fails on every optional that happens to be empty.
20
20
  import { existsSync } from "node:fs";
21
- import { join } from "node:path";
21
+ import { dirname, join, resolve } from "node:path";
22
22
 
23
- const localPlatformPath = (pluginDir) =>
24
- join(
25
- pluginDir,
26
- "node_modules/@reventlessdev/reventless-local/src/Platform.res.mjs",
27
- );
23
+ const localPlatformModule = "node_modules/@reventlessdev/reventless-local/src/Platform.res.mjs";
24
+
25
+ /**
26
+ * Where the local platform is installed for this plugin: the nearest
27
+ * `node_modules` from the plugin directory up, the way Node resolves a package.
28
+ * A monorepo member has its own copy; an app that installs its dependencies at
29
+ * its root (`node-linker=hoisted`) has one only there. Walked rather than
30
+ * resolved through the package, so its `exports` map does not decide what can be
31
+ * reached.
32
+ *
33
+ * @returns {string | undefined}
34
+ */
35
+ export const localPlatformPath = (pluginDir) => {
36
+ let dir = resolve(pluginDir);
37
+ for (;;) {
38
+ const candidate = join(dir, localPlatformModule);
39
+ if (existsSync(candidate)) return candidate;
40
+ const parent = dirname(dir);
41
+ if (parent === dir) return undefined;
42
+ dir = parent;
43
+ }
44
+ };
28
45
 
29
46
  const pluginEntryPath = (pluginDir) => join(pluginDir, "src/Plugin.res.mjs");
30
47
 
@@ -40,10 +57,10 @@ export const loadPluginStructure = async (pluginDir) => {
40
57
  error: `no compiled plugin at ${entry} — build the plugin first`,
41
58
  };
42
59
  }
43
- if (!existsSync(local)) {
60
+ if (local === undefined) {
44
61
  return {
45
62
  ok: false,
46
- error: `no local platform installed at ${local}`,
63
+ error: `no local platform installed for ${pluginDir} — none in any node_modules from there up`,
47
64
  };
48
65
  }
49
66
  try {