@opensip-tools/fitness 1.0.8 → 1.0.9

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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @opensip-tools/fitness@1.0.8 build /home/runner/work/opensip-tools/opensip-tools/packages/fitness/engine
2
+ > @opensip-tools/fitness@1.0.9 build /home/runner/work/opensip-tools/opensip-tools/packages/fitness/engine
3
3
  > tsc
4
4
 
@@ -1,49 +1,49 @@
1
1
 
2
- > @opensip-tools/fitness@1.0.8 test /home/runner/work/opensip-tools/opensip-tools/packages/fitness/engine
2
+ > @opensip-tools/fitness@1.0.9 test /home/runner/work/opensip-tools/opensip-tools/packages/fitness/engine
3
3
  > vitest run --passWithNoTests
4
4
 
5
5
 
6
6
   RUN  v2.1.9 /home/runner/work/opensip-tools/opensip-tools/packages/fitness/engine
7
7
 
8
- ✓ src/__tests__/gate.test.ts (28 tests) 258ms
9
- ✓ src/recipes/__tests__/service.test.ts (28 tests) 672ms
10
- ✓ src/framework/__tests__/registry.test.ts (24 tests) 73ms
11
- ✓ src/framework/__tests__/import-graph.test.ts (20 tests) 778ms
12
- ✓ findStronglyConnectedComponents > handles a deep graph without recursion blowing the stack 533ms
13
- ✓ src/plugins/__tests__/check-package-discovery.test.ts (16 tests) 244ms
14
- ✓ src/recipes/__tests__/check-resolution.test.ts (14 tests) 155ms
15
- ✓ src/__tests__/sarif.test.ts (15 tests) 70ms
16
- ✓ src/plugins/__tests__/lang-domain.test.ts (8 tests) 142ms
17
- ✓ src/framework/__tests__/result-builder.test.ts (25 tests) 114ms
18
- ✓ src/framework/__tests__/scope-resolver.test.ts (8 tests) 265ms
19
- ✓ src/plugins/__tests__/loader.test.ts (10 tests) 6800ms
20
- ✓ loadPlugin > registers Check instances exported as named exports (no array wrapper) 6459ms
21
- ✓ src/framework/__tests__/content-filter.test.ts (10 tests) 74ms
22
- ✓ src/framework/__tests__/define-check.test.ts (17 tests) 62ms
23
- ✓ src/framework/__tests__/file-cache.test.ts (15 tests) 137ms
24
- ✓ src/framework/__tests__/path-matcher.test.ts (12 tests) 194ms
25
- ✓ src/framework/__tests__/content-filter-dispatch.test.ts (6 tests) 93ms
26
- ✓ src/recipes/__tests__/registry.test.ts (15 tests) 20ms
27
- ✓ src/targets/__tests__/loader.test.ts (11 tests) 94ms
28
- ✓ src/framework/__tests__/file-accessor.test.ts (11 tests) 106ms
29
- ✓ src/framework/__tests__/strip-literals.test.ts (17 tests) 26ms
30
- ✓ src/framework/__tests__/ast-utilities.test.ts (19 tests) 96ms
31
- ✓ src/recipes/__tests__/built-in-recipes.test.ts (14 tests) 62ms
32
- ✓ src/targets/__tests__/target-registry.test.ts (11 tests) 43ms
33
- ✓ src/framework/__tests__/execution-context.test.ts (4 tests) 118ms
34
- ✓ src/signalers/__tests__/loader.test.ts (8 tests) 109ms
35
- ✓ src/recipes/__tests__/retry.test.ts (6 tests) 20ms
36
- ✓ src/framework/__tests__/directive-parsing.test.ts (8 tests) 8ms
37
- ✓ src/targets/__tests__/resolver.test.ts (6 tests) 67ms
38
- ✓ src/framework/__tests__/command-executor.test.ts (5 tests) 106ms
39
- ✓ src/framework/__tests__/directive-inventory.test.ts (9 tests) 8ms
40
- ✓ src/framework/__tests__/check-config.test.ts (6 tests) 16ms
41
- ✓ src/framework/__tests__/severity-mapping.test.ts (13 tests) 17ms
42
- ✓ src/framework/__tests__/register-helpers.test.ts (4 tests) 17ms
43
- ✓ src/recipes/__tests__/check-config.test.ts (4 tests) 12ms
8
+ ✓ src/__tests__/gate.test.ts (28 tests) 230ms
9
+ ✓ src/framework/__tests__/registry.test.ts (24 tests) 65ms
10
+ ✓ src/recipes/__tests__/service.test.ts (28 tests) 709ms
11
+ ✓ src/framework/__tests__/import-graph.test.ts (20 tests) 865ms
12
+ ✓ findStronglyConnectedComponents > handles a deep graph without recursion blowing the stack 642ms
13
+ ✓ src/plugins/__tests__/check-package-discovery.test.ts (16 tests) 298ms
14
+ ✓ src/recipes/__tests__/check-resolution.test.ts (14 tests) 43ms
15
+ ✓ src/__tests__/sarif.test.ts (15 tests) 48ms
16
+ ✓ src/plugins/__tests__/lang-domain.test.ts (8 tests) 138ms
17
+ ✓ src/framework/__tests__/result-builder.test.ts (25 tests) 97ms
18
+ ✓ src/framework/__tests__/scope-resolver.test.ts (8 tests) 264ms
19
+ ✓ src/plugins/__tests__/loader.test.ts (10 tests) 6358ms
20
+ ✓ loadPlugin > registers Check instances exported as named exports (no array wrapper) 6076ms
21
+ ✓ src/framework/__tests__/content-filter.test.ts (10 tests) 41ms
22
+ ✓ src/framework/__tests__/define-check.test.ts (17 tests) 51ms
23
+ ✓ src/framework/__tests__/file-cache.test.ts (15 tests) 101ms
24
+ ✓ src/framework/__tests__/path-matcher.test.ts (12 tests) 151ms
25
+ ✓ src/framework/__tests__/content-filter-dispatch.test.ts (6 tests) 44ms
26
+ ✓ src/recipes/__tests__/registry.test.ts (15 tests) 78ms
27
+ ✓ src/targets/__tests__/loader.test.ts (11 tests) 74ms
28
+ ✓ src/framework/__tests__/strip-literals.test.ts (17 tests) 36ms
29
+ ✓ src/framework/__tests__/file-accessor.test.ts (11 tests) 162ms
30
+ ✓ src/framework/__tests__/ast-utilities.test.ts (19 tests) 81ms
31
+ ✓ src/targets/__tests__/target-registry.test.ts (11 tests) 23ms
32
+ ✓ src/recipes/__tests__/built-in-recipes.test.ts (14 tests) 37ms
33
+ ✓ src/framework/__tests__/execution-context.test.ts (4 tests) 100ms
34
+ ✓ src/signalers/__tests__/loader.test.ts (8 tests) 73ms
35
+ ✓ src/recipes/__tests__/retry.test.ts (6 tests) 31ms
36
+ ✓ src/framework/__tests__/directive-parsing.test.ts (8 tests) 13ms
37
+ ✓ src/recipes/__tests__/check-config.test.ts (5 tests) 12ms
38
+ ✓ src/targets/__tests__/resolver.test.ts (6 tests) 71ms
39
+ ✓ src/framework/__tests__/directive-inventory.test.ts (9 tests) 18ms
40
+ ✓ src/framework/__tests__/command-executor.test.ts (5 tests) 89ms
41
+ ✓ src/framework/__tests__/check-config.test.ts (6 tests) 31ms
42
+ ✓ src/framework/__tests__/severity-mapping.test.ts (13 tests) 28ms
43
+ ✓ src/framework/__tests__/register-helpers.test.ts (4 tests) 19ms
44
44
 
45
45
   Test Files  34 passed (34)
46
-  Tests  427 passed (427)
47
-  Start at  21:42:26
48
-  Duration  24.97s (transform 7.13s, setup 0ms, collect 24.18s, tests 11.07s, environment 62ms, prepare 13.30s)
46
+  Tests  428 passed (428)
47
+  Start at  22:05:49
48
+  Duration  25.18s (transform 6.39s, setup 0ms, collect 23.57s, tests 10.48s, environment 37ms, prepare 12.94s)
49
49
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @opensip-tools/fitness@1.0.8 typecheck /home/runner/work/opensip-tools/opensip-tools/packages/fitness/engine
2
+ > @opensip-tools/fitness@1.0.9 typecheck /home/runner/work/opensip-tools/opensip-tools/packages/fitness/engine
3
3
  > tsc --noEmit
4
4
 
@@ -33,5 +33,26 @@ describe('getCheckConfig', () => {
33
33
  const cfg = getCheckConfig('sample-check');
34
34
  expect(cfg).toEqual({});
35
35
  });
36
+ it('shares state with a separately-loaded copy of this module (multi-instance contract)', () => {
37
+ // Regression test for the multi-instance bug fixed in 1.0.9. The
38
+ // runtime frequently has TWO copies of `@opensip-tools/fitness`:
39
+ // the CLI's bundled copy (running the recipe service) and the
40
+ // plugin pack's resolved copy (running the check). Each copy has
41
+ // its own module-scope state; the prior `let currentRecipeCheckConfig`
42
+ // implementation made cross-copy state invisible. The fix moves
43
+ // the slot onto a `Symbol.for(...)` keyed `globalThis` entry so
44
+ // every copy reads + writes the same slot.
45
+ //
46
+ // Simulating "two copies" within one test: stash a value, look it
47
+ // up under the same well-known symbol from a separate import path
48
+ // (here: globalThis directly), and confirm the values match.
49
+ setCurrentRecipeCheckConfig({
50
+ 'sample-check': { additionalEntries: ['cross-copy'] },
51
+ });
52
+ const KEY = Symbol.for('@opensip-tools/fitness/currentRecipeCheckConfig');
53
+ const slot = globalThis[KEY];
54
+ expect(slot).toBeDefined();
55
+ expect(slot['sample-check'].additionalEntries).toEqual(['cross-copy']);
56
+ });
36
57
  });
37
58
  //# sourceMappingURL=check-config.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"check-config.test.js","sourceRoot":"","sources":["../../../src/recipes/__tests__/check-config.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAExD,OAAO,EACL,cAAc,EACd,2BAA2B,EAC3B,6BAA6B,GAC9B,MAAM,oBAAoB,CAAA;AAM3B,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,SAAS,CAAC,GAAG,EAAE;QACb,6BAA6B,EAAE,CAAA;IACjC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,GAAG,GAAG,cAAc,CAAe,UAAU,CAAC,CAAA;QACpD,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACzB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,2BAA2B,CAAC;YAC1B,aAAa,EAAE,EAAE,iBAAiB,EAAE,CAAC,GAAG,CAAC,EAAE;SAC5C,CAAC,CAAA;QACF,MAAM,GAAG,GAAG,cAAc,CAAe,SAAS,CAAC,CAAA;QACnD,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACzB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,2BAA2B,CAAC;YAC1B,cAAc,EAAE,EAAE,iBAAiB,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;SAClD,CAAC,CAAA;QACF,MAAM,GAAG,GAAG,cAAc,CAAe,cAAc,CAAC,CAAA;QACxD,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,2BAA2B,CAAC;YAC1B,cAAc,EAAE,EAAE,iBAAiB,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;SAClD,CAAC,CAAA;QACF,6BAA6B,EAAE,CAAA;QAC/B,MAAM,GAAG,GAAG,cAAc,CAAe,cAAc,CAAC,CAAA;QACxD,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACzB,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"check-config.test.js","sourceRoot":"","sources":["../../../src/recipes/__tests__/check-config.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAExD,OAAO,EACL,cAAc,EACd,2BAA2B,EAC3B,6BAA6B,GAC9B,MAAM,oBAAoB,CAAA;AAM3B,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,SAAS,CAAC,GAAG,EAAE;QACb,6BAA6B,EAAE,CAAA;IACjC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,GAAG,GAAG,cAAc,CAAe,UAAU,CAAC,CAAA;QACpD,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACzB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,2BAA2B,CAAC;YAC1B,aAAa,EAAE,EAAE,iBAAiB,EAAE,CAAC,GAAG,CAAC,EAAE;SAC5C,CAAC,CAAA;QACF,MAAM,GAAG,GAAG,cAAc,CAAe,SAAS,CAAC,CAAA;QACnD,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACzB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,2BAA2B,CAAC;YAC1B,cAAc,EAAE,EAAE,iBAAiB,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;SAClD,CAAC,CAAA;QACF,MAAM,GAAG,GAAG,cAAc,CAAe,cAAc,CAAC,CAAA;QACxD,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,2BAA2B,CAAC;YAC1B,cAAc,EAAE,EAAE,iBAAiB,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;SAClD,CAAC,CAAA;QACF,6BAA6B,EAAE,CAAA;QAC/B,MAAM,GAAG,GAAG,cAAc,CAAe,cAAc,CAAC,CAAA;QACxD,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACzB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qFAAqF,EAAE,GAAG,EAAE;QAC7F,iEAAiE;QACjE,iEAAiE;QACjE,8DAA8D;QAC9D,iEAAiE;QACjE,uEAAuE;QACvE,gEAAgE;QAChE,gEAAgE;QAChE,2CAA2C;QAC3C,EAAE;QACF,kEAAkE;QAClE,kEAAkE;QAClE,6DAA6D;QAC7D,2BAA2B,CAAC;YAC1B,cAAc,EAAE,EAAE,iBAAiB,EAAE,CAAC,YAAY,CAAC,EAAE;SACtD,CAAC,CAAA;QACF,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAA;QACzE,MAAM,IAAI,GAAI,UAAiD,CAAC,GAAG,CAAC,CAAA;QACpE,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;QAC1B,MAAM,CAAE,IAA4D,CAAC,cAAc,CAAC,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAA;IACjI,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -28,13 +28,13 @@ import type { RecipeCheckConfigMap } from './types.js';
28
28
  */
29
29
  export declare function getCheckConfig<T extends Record<string, unknown>>(slug: string): T;
30
30
  /**
31
- * Replace the module-level recipe config. Called by the recipe service at
31
+ * Replace the global recipe config. Called by the recipe service at
32
32
  * the start of a recipe run, before any check executes.
33
33
  */
34
34
  export declare function setCurrentRecipeCheckConfig(config: RecipeCheckConfigMap | undefined): void;
35
35
  /**
36
- * Clear the module-level recipe config. Called by the recipe service at
37
- * the end of a recipe run (success or failure).
36
+ * Clear the global recipe config. Called by the recipe service at the end
37
+ * of a recipe run (success or failure).
38
38
  */
39
39
  export declare function clearCurrentRecipeCheckConfig(): void;
40
40
  //# sourceMappingURL=check-config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"check-config.d.ts","sourceRoot":"","sources":["../../src/recipes/check-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AActD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAKjF;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,oBAAoB,GAAG,SAAS,GAAG,IAAI,CAE1F;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}
1
+ {"version":3,"file":"check-config.d.ts","sourceRoot":"","sources":["../../src/recipes/check-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAmCtD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAMjF;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,oBAAoB,GAAG,SAAS,GAAG,IAAI,CAE1F;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}
@@ -16,16 +16,32 @@
16
16
  * extend the safe-lists with project-specific names.
17
17
  */
18
18
  /**
19
- * Module-scoped current-recipe config. Populated by the recipe service at
19
+ * Process-shared current-recipe config. Populated by the recipe service at
20
20
  * the start of a run and cleared at the end. Checks read from it lazily via
21
21
  * {@link getCheckConfig}.
22
22
  *
23
- * Module-level singleton is acceptable here because a recipe service runs
24
- * one session at a time (the service throws SESSION_IN_PROGRESS otherwise).
25
- * If we ever support concurrent recipe runs in the same process, this needs
26
- * to move into AsyncLocalStorage.
23
+ * `globalThis` (not module-local `let`) is load-bearing — the runtime
24
+ * frequently has TWO copies of `@opensip-tools/fitness` loaded:
25
+ *
26
+ * 1. The CLI's bundled copy (running the recipe service).
27
+ * 2. The plugin pack's resolved copy (running the check, calling
28
+ * `getCheckConfig(slug)`).
29
+ *
30
+ * Each copy has its own module-scope state, so a module-local `let` here
31
+ * means `setCurrentRecipeCheckConfig(...)` in copy 1 is invisible to
32
+ * `getCheckConfig(...)` in copy 2 — the recipe's `additionalSyncFunctions`
33
+ * (and every other per-check allowlist) silently never reaches the checks
34
+ * that read it. Storing the map on `globalThis` under a single well-known
35
+ * symbol means every copy reads + writes the same slot.
36
+ *
37
+ * The single-session contract still holds (the recipe service throws
38
+ * SESSION_IN_PROGRESS otherwise); the only thing that changes vs the prior
39
+ * design is the storage location, not the lifecycle.
27
40
  */
28
- let currentRecipeCheckConfig;
41
+ const GLOBAL_KEY = Symbol.for('@opensip-tools/fitness/currentRecipeCheckConfig');
42
+ function slot() {
43
+ return globalThis;
44
+ }
29
45
  /**
30
46
  * Read the per-check config slice for the given slug.
31
47
  *
@@ -37,25 +53,26 @@ let currentRecipeCheckConfig;
37
53
  * its own config interface.
38
54
  */
39
55
  export function getCheckConfig(slug) {
40
- if (!currentRecipeCheckConfig)
56
+ const current = slot()[GLOBAL_KEY];
57
+ if (!current)
41
58
  return {};
42
- const entry = currentRecipeCheckConfig[slug];
59
+ const entry = current[slug];
43
60
  if (!entry)
44
61
  return {};
45
62
  return entry;
46
63
  }
47
64
  /**
48
- * Replace the module-level recipe config. Called by the recipe service at
65
+ * Replace the global recipe config. Called by the recipe service at
49
66
  * the start of a recipe run, before any check executes.
50
67
  */
51
68
  export function setCurrentRecipeCheckConfig(config) {
52
- currentRecipeCheckConfig = config;
69
+ slot()[GLOBAL_KEY] = config;
53
70
  }
54
71
  /**
55
- * Clear the module-level recipe config. Called by the recipe service at
56
- * the end of a recipe run (success or failure).
72
+ * Clear the global recipe config. Called by the recipe service at the end
73
+ * of a recipe run (success or failure).
57
74
  */
58
75
  export function clearCurrentRecipeCheckConfig() {
59
- currentRecipeCheckConfig = undefined;
76
+ slot()[GLOBAL_KEY] = undefined;
60
77
  }
61
78
  //# sourceMappingURL=check-config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"check-config.js","sourceRoot":"","sources":["../../src/recipes/check-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH;;;;;;;;;GASG;AACH,IAAI,wBAA0D,CAAA;AAE9D;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAoC,IAAY;IAC5E,IAAI,CAAC,wBAAwB;QAAE,OAAO,EAAO,CAAA;IAC7C,MAAM,KAAK,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAA;IAC5C,IAAI,CAAC,KAAK;QAAE,OAAO,EAAO,CAAA;IAC1B,OAAO,KAAU,CAAA;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,MAAwC;IAClF,wBAAwB,GAAG,MAAM,CAAA;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,6BAA6B;IAC3C,wBAAwB,GAAG,SAAS,CAAA;AACtC,CAAC"}
1
+ {"version":3,"file":"check-config.js","sourceRoot":"","sources":["../../src/recipes/check-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAA;AAMhF,SAAS,IAAI;IACX,OAAO,UAAmC,CAAA;AAC5C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAoC,IAAY;IAC5E,MAAM,OAAO,GAAG,IAAI,EAAE,CAAC,UAAU,CAAC,CAAA;IAClC,IAAI,CAAC,OAAO;QAAE,OAAO,EAAO,CAAA;IAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,IAAI,CAAC,KAAK;QAAE,OAAO,EAAO,CAAA;IAC1B,OAAO,KAAU,CAAA;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,MAAwC;IAClF,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,MAAM,CAAA;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,6BAA6B;IAC3C,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,SAAS,CAAA;AAChC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opensip-tools/fitness",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "license": "MIT",
5
5
  "description": "Fitness checks engine for OpenSIP Tools",
6
6
  "repository": {
@@ -28,8 +28,8 @@
28
28
  "minimatch": "^10.0.0",
29
29
  "typescript": "~5.7.0",
30
30
  "zod": "^3.24.0",
31
- "@opensip-tools/core": "1.0.8",
32
- "@opensip-tools/contracts": "1.0.8"
31
+ "@opensip-tools/contracts": "1.0.9",
32
+ "@opensip-tools/core": "1.0.9"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/js-yaml": "^4.0.0",
@@ -48,4 +48,26 @@ describe('getCheckConfig', () => {
48
48
  const cfg = getCheckConfig<SampleConfig>('sample-check')
49
49
  expect(cfg).toEqual({})
50
50
  })
51
+
52
+ it('shares state with a separately-loaded copy of this module (multi-instance contract)', () => {
53
+ // Regression test for the multi-instance bug fixed in 1.0.9. The
54
+ // runtime frequently has TWO copies of `@opensip-tools/fitness`:
55
+ // the CLI's bundled copy (running the recipe service) and the
56
+ // plugin pack's resolved copy (running the check). Each copy has
57
+ // its own module-scope state; the prior `let currentRecipeCheckConfig`
58
+ // implementation made cross-copy state invisible. The fix moves
59
+ // the slot onto a `Symbol.for(...)` keyed `globalThis` entry so
60
+ // every copy reads + writes the same slot.
61
+ //
62
+ // Simulating "two copies" within one test: stash a value, look it
63
+ // up under the same well-known symbol from a separate import path
64
+ // (here: globalThis directly), and confirm the values match.
65
+ setCurrentRecipeCheckConfig({
66
+ 'sample-check': { additionalEntries: ['cross-copy'] },
67
+ })
68
+ const KEY = Symbol.for('@opensip-tools/fitness/currentRecipeCheckConfig')
69
+ const slot = (globalThis as unknown as Record<symbol, unknown>)[KEY]
70
+ expect(slot).toBeDefined()
71
+ expect((slot as { 'sample-check': { additionalEntries: string[] } })['sample-check'].additionalEntries).toEqual(['cross-copy'])
72
+ })
51
73
  })
@@ -19,16 +19,37 @@
19
19
  import type { RecipeCheckConfigMap } from './types.js'
20
20
 
21
21
  /**
22
- * Module-scoped current-recipe config. Populated by the recipe service at
22
+ * Process-shared current-recipe config. Populated by the recipe service at
23
23
  * the start of a run and cleared at the end. Checks read from it lazily via
24
24
  * {@link getCheckConfig}.
25
25
  *
26
- * Module-level singleton is acceptable here because a recipe service runs
27
- * one session at a time (the service throws SESSION_IN_PROGRESS otherwise).
28
- * If we ever support concurrent recipe runs in the same process, this needs
29
- * to move into AsyncLocalStorage.
26
+ * `globalThis` (not module-local `let`) is load-bearing — the runtime
27
+ * frequently has TWO copies of `@opensip-tools/fitness` loaded:
28
+ *
29
+ * 1. The CLI's bundled copy (running the recipe service).
30
+ * 2. The plugin pack's resolved copy (running the check, calling
31
+ * `getCheckConfig(slug)`).
32
+ *
33
+ * Each copy has its own module-scope state, so a module-local `let` here
34
+ * means `setCurrentRecipeCheckConfig(...)` in copy 1 is invisible to
35
+ * `getCheckConfig(...)` in copy 2 — the recipe's `additionalSyncFunctions`
36
+ * (and every other per-check allowlist) silently never reaches the checks
37
+ * that read it. Storing the map on `globalThis` under a single well-known
38
+ * symbol means every copy reads + writes the same slot.
39
+ *
40
+ * The single-session contract still holds (the recipe service throws
41
+ * SESSION_IN_PROGRESS otherwise); the only thing that changes vs the prior
42
+ * design is the storage location, not the lifecycle.
30
43
  */
31
- let currentRecipeCheckConfig: RecipeCheckConfigMap | undefined
44
+ const GLOBAL_KEY = Symbol.for('@opensip-tools/fitness/currentRecipeCheckConfig')
45
+
46
+ interface GlobalSlot {
47
+ [GLOBAL_KEY]?: RecipeCheckConfigMap | undefined
48
+ }
49
+
50
+ function slot(): GlobalSlot {
51
+ return globalThis as unknown as GlobalSlot
52
+ }
32
53
 
33
54
  /**
34
55
  * Read the per-check config slice for the given slug.
@@ -41,24 +62,25 @@ let currentRecipeCheckConfig: RecipeCheckConfigMap | undefined
41
62
  * its own config interface.
42
63
  */
43
64
  export function getCheckConfig<T extends Record<string, unknown>>(slug: string): T {
44
- if (!currentRecipeCheckConfig) return {} as T
45
- const entry = currentRecipeCheckConfig[slug]
65
+ const current = slot()[GLOBAL_KEY]
66
+ if (!current) return {} as T
67
+ const entry = current[slug]
46
68
  if (!entry) return {} as T
47
69
  return entry as T
48
70
  }
49
71
 
50
72
  /**
51
- * Replace the module-level recipe config. Called by the recipe service at
73
+ * Replace the global recipe config. Called by the recipe service at
52
74
  * the start of a recipe run, before any check executes.
53
75
  */
54
76
  export function setCurrentRecipeCheckConfig(config: RecipeCheckConfigMap | undefined): void {
55
- currentRecipeCheckConfig = config
77
+ slot()[GLOBAL_KEY] = config
56
78
  }
57
79
 
58
80
  /**
59
- * Clear the module-level recipe config. Called by the recipe service at
60
- * the end of a recipe run (success or failure).
81
+ * Clear the global recipe config. Called by the recipe service at the end
82
+ * of a recipe run (success or failure).
61
83
  */
62
84
  export function clearCurrentRecipeCheckConfig(): void {
63
- currentRecipeCheckConfig = undefined
85
+ slot()[GLOBAL_KEY] = undefined
64
86
  }