@starci/eslint-canon-fe 9.5.23 → 9.6.2

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 (164) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/docs/button-intent.md +1 -1
  3. package/package.json +1 -1
  4. package/runtime/bundle.json +192 -0
  5. package/runtime/engine/by-code-unit.mjs +6 -6
  6. package/runtime/engine/model-config.mjs +98 -76
  7. package/runtime/engine/orca-config.mjs +22 -17
  8. package/runtime/engine/release-config.mjs +22 -18
  9. package/runtime/engine/removed-vocabulary.mjs +21 -20
  10. package/runtime/engine/resources-config.mjs +42 -35
  11. package/runtime/engine/runtime-root.mjs +11 -13
  12. package/runtime/engine/secrets.mjs +170 -102
  13. package/runtime/engine/sonar-config.mjs +16 -13
  14. package/runtime/engine/temp-root.mjs +3 -9
  15. package/runtime/knowledge/hfs/canon-pins.yaml +10 -10
  16. package/runtime/knowledge/hfs/slots.yaml +2 -2
  17. package/runtime/knowledge/sonar-gate.yaml +1 -1
  18. package/runtime/packages/hfs/templates/app/package-scripts/package.json +1 -1
  19. package/runtime/scripts/api/git/cat-file.mjs +0 -1
  20. package/runtime/scripts/api/git/config-get.mjs +1 -2
  21. package/runtime/scripts/api/git/diff.mjs +0 -1
  22. package/runtime/scripts/api/git/lib.mjs +32 -32
  23. package/runtime/scripts/api/git/ls-files.mjs +0 -1
  24. package/runtime/scripts/api/git/ls-tree.mjs +0 -1
  25. package/runtime/scripts/api/git/merge-base.mjs +2 -3
  26. package/runtime/scripts/api/git/remote.mjs +0 -1
  27. package/runtime/scripts/api/git/rev-parse-query.mjs +0 -1
  28. package/runtime/scripts/api/git/show.mjs +0 -1
  29. package/runtime/scripts/hfs/allows.mjs +37 -38
  30. package/runtime/scripts/hfs/architecture/ast-walks.mjs +180 -163
  31. package/runtime/scripts/hfs/architecture/automatic-gates.mjs +80 -73
  32. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +88 -77
  33. package/runtime/scripts/hfs/architecture/backend.mjs +378 -357
  34. package/runtime/scripts/hfs/architecture/background-unowned.mjs +201 -187
  35. package/runtime/scripts/hfs/architecture/canon-clones.mjs +260 -209
  36. package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +149 -116
  37. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +72 -68
  38. package/runtime/scripts/hfs/architecture/clones.mjs +212 -217
  39. package/runtime/scripts/hfs/architecture/config-unread.mjs +7 -9
  40. package/runtime/scripts/hfs/architecture/config.mjs +302 -268
  41. package/runtime/scripts/hfs/architecture/connection-map.mjs +258 -225
  42. package/runtime/scripts/hfs/architecture/constructor-deps.mjs +80 -71
  43. package/runtime/scripts/hfs/architecture/context-coupling.mjs +82 -77
  44. package/runtime/scripts/hfs/architecture/context-map.mjs +267 -234
  45. package/runtime/scripts/hfs/architecture/context-owner.mjs +73 -61
  46. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -43
  47. package/runtime/scripts/hfs/architecture/context-transaction.mjs +54 -52
  48. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +113 -104
  49. package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +288 -270
  50. package/runtime/scripts/hfs/architecture/contracts.mjs +408 -393
  51. package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +50 -51
  52. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +143 -119
  53. package/runtime/scripts/hfs/architecture/dead-exports.mjs +286 -228
  54. package/runtime/scripts/hfs/architecture/default-deny.mjs +129 -110
  55. package/runtime/scripts/hfs/architecture/doc-language.mjs +24 -19
  56. package/runtime/scripts/hfs/architecture/entity-registry.mjs +20 -22
  57. package/runtime/scripts/hfs/architecture/entrypoint.mjs +47 -45
  58. package/runtime/scripts/hfs/architecture/error-codes.mjs +37 -35
  59. package/runtime/scripts/hfs/architecture/error-masked.mjs +55 -59
  60. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +24 -23
  61. package/runtime/scripts/hfs/architecture/feature-shape.mjs +40 -38
  62. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +21 -21
  63. package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +106 -98
  64. package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +153 -150
  65. package/runtime/scripts/hfs/architecture/frontend-routing.mjs +161 -149
  66. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +343 -305
  67. package/runtime/scripts/hfs/architecture/frontend-world.mjs +340 -294
  68. package/runtime/scripts/hfs/architecture/frontend.mjs +174 -184
  69. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +124 -106
  70. package/runtime/scripts/hfs/architecture/hfs-graph.mjs +52 -48
  71. package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +81 -71
  72. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +155 -141
  73. package/runtime/scripts/hfs/architecture/identity.mjs +141 -125
  74. package/runtime/scripts/hfs/architecture/index.mjs +272 -272
  75. package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +45 -43
  76. package/runtime/scripts/hfs/architecture/jwt-ast.mjs +195 -176
  77. package/runtime/scripts/hfs/architecture/machine-ast.mjs +163 -155
  78. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +19 -20
  79. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +177 -172
  80. package/runtime/scripts/hfs/architecture/next-data-contract.mjs +100 -78
  81. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +170 -154
  82. package/runtime/scripts/hfs/architecture/next-data-key.mjs +311 -275
  83. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +17 -17
  84. package/runtime/scripts/hfs/architecture/next-data.mjs +351 -334
  85. package/runtime/scripts/hfs/architecture/operation-contract-roots.mjs +39 -37
  86. package/runtime/scripts/hfs/architecture/owners.mjs +97 -98
  87. package/runtime/scripts/hfs/architecture/package-shape.mjs +56 -55
  88. package/runtime/scripts/hfs/architecture/presentation.mjs +138 -130
  89. package/runtime/scripts/hfs/architecture/reachability.mjs +258 -241
  90. package/runtime/scripts/hfs/architecture/register-once.mjs +152 -139
  91. package/runtime/scripts/hfs/architecture/registration.mjs +301 -267
  92. package/runtime/scripts/hfs/architecture/required-files.mjs +172 -167
  93. package/runtime/scripts/hfs/architecture/route-files-thin.mjs +85 -77
  94. package/runtime/scripts/hfs/architecture/schema-owner.mjs +206 -181
  95. package/runtime/scripts/hfs/architecture/server-action.mjs +48 -48
  96. package/runtime/scripts/hfs/architecture/side-boundary.mjs +2 -5
  97. package/runtime/scripts/hfs/architecture/source-names-shape.mjs +261 -259
  98. package/runtime/scripts/hfs/architecture/source-names.mjs +304 -278
  99. package/runtime/scripts/hfs/architecture/sql-keys.mjs +277 -194
  100. package/runtime/scripts/hfs/architecture/sql-owner.mjs +236 -194
  101. package/runtime/scripts/hfs/architecture/sql-returning.mjs +46 -45
  102. package/runtime/scripts/hfs/architecture/sql-token-forms.mjs +86 -74
  103. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +456 -341
  104. package/runtime/scripts/hfs/architecture/staged-consumers.mjs +35 -30
  105. package/runtime/scripts/hfs/architecture/surface.mjs +64 -74
  106. package/runtime/scripts/hfs/architecture/symbols.mjs +209 -216
  107. package/runtime/scripts/hfs/architecture/test-world-files.mjs +165 -155
  108. package/runtime/scripts/hfs/architecture/tiers.mjs +161 -154
  109. package/runtime/scripts/hfs/architecture/transport-owner.mjs +155 -149
  110. package/runtime/scripts/hfs/architecture/type-context.mjs +255 -250
  111. package/runtime/scripts/hfs/architecture/typescript.mjs +358 -318
  112. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +110 -92
  113. package/runtime/scripts/hfs/declaration-shape.mjs +140 -103
  114. package/runtime/scripts/hfs/declaration-slots.mjs +64 -61
  115. package/runtime/scripts/hfs/edition-slots.mjs +43 -40
  116. package/runtime/scripts/hfs/linear-text.mjs +20 -20
  117. package/runtime/scripts/hfs/param-names.mjs +6 -11
  118. package/runtime/scripts/hfs/path-findings.mjs +120 -100
  119. package/runtime/scripts/hfs/repo-identity.mjs +55 -42
  120. package/runtime/scripts/hfs/rule-catalog.mjs +353 -257
  121. package/runtime/scripts/hfs/rule-params-shape.mjs +60 -47
  122. package/runtime/scripts/hfs/rules/database-migrations.mjs +428 -388
  123. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +37 -36
  124. package/runtime/scripts/hfs/rules/read.mjs +19 -15
  125. package/runtime/scripts/hfs/rules/secrets.mjs +1 -1
  126. package/runtime/scripts/hfs/rules/spec-placement.mjs +12 -13
  127. package/runtime/scripts/hfs/slot-app-view.mjs +69 -74
  128. package/runtime/scripts/hfs/slot-classify.mjs +42 -38
  129. package/runtime/scripts/hfs/slot-errors.mjs +8 -7
  130. package/runtime/scripts/hfs/slot-imports.mjs +70 -63
  131. package/runtime/scripts/hfs/slot-manifest-shape.mjs +74 -52
  132. package/runtime/scripts/hfs/slot-match.mjs +122 -111
  133. package/runtime/scripts/hfs/slot-path.mjs +0 -2
  134. package/runtime/scripts/hfs/slot-required.mjs +65 -59
  135. package/runtime/scripts/hfs/slot-semantic-problems.mjs +138 -112
  136. package/runtime/scripts/hfs/slot-side-problems.mjs +23 -19
  137. package/runtime/scripts/hfs/slots.mjs +175 -152
  138. package/runtime/scripts/hfs/test-world-slot.mjs +0 -1
  139. package/runtime/scripts/hfs/trailing-slashes.mjs +4 -4
  140. package/runtime/scripts/hfs/typescript-programs.mjs +66 -69
  141. package/runtime/scripts/hfs/view.mjs +38 -40
  142. package/runtime/scripts/lib/dockerfile.mjs +131 -114
  143. package/runtime/scripts/lib/env.mjs +3 -6
  144. package/runtime/scripts/lib/event-contract.mjs +73 -78
  145. package/runtime/scripts/lib/git.mjs +15 -16
  146. package/runtime/scripts/lib/glob.mjs +23 -14
  147. package/runtime/scripts/lib/grammar-lint.mjs +33 -37
  148. package/runtime/scripts/lib/i18n.mjs +32 -35
  149. package/runtime/scripts/lib/in-order.mjs +57 -38
  150. package/runtime/scripts/lib/json-clone.mjs +45 -36
  151. package/runtime/scripts/lib/json.mjs +26 -14
  152. package/runtime/scripts/lib/language.mjs +133 -129
  153. package/runtime/scripts/lib/list.mjs +9 -14
  154. package/runtime/scripts/lib/mutation-fence.mjs +10 -5
  155. package/runtime/scripts/lib/path-key.mjs +53 -38
  156. package/runtime/scripts/lib/read-text.mjs +30 -17
  157. package/runtime/scripts/lib/regex.mjs +1 -2
  158. package/runtime/scripts/lib/same-text.mjs +0 -1
  159. package/runtime/scripts/lib/sops-envelope.mjs +148 -120
  160. package/runtime/scripts/lib/stack-services.mjs +128 -129
  161. package/runtime/scripts/lib/ts-analysis.mjs +28 -26
  162. package/runtime/scripts/lib/ts-ast.mjs +36 -42
  163. package/runtime/scripts/lib/walk.mjs +33 -27
  164. package/runtime/scripts/lib/yaml-cached.mjs +4 -4
@@ -1,6 +1,5 @@
1
- import {isPlainObject as plain} from './plain-object.mjs';
2
- import {invalid} from './invalid-config.mjs';
3
-
1
+ import { isPlainObject as plain } from './plain-object.mjs';
2
+ import { invalid } from './invalid-config.mjs';
4
3
  /**
5
4
  * config.yaml `resources` — the owner's host-capacity floors, which override the shipped numbers of
6
5
  * modules/models/runtimes.yaml `allocation.resources`. minFreeDiskGb: the free space a drive holding the temp root or the
@@ -8,45 +7,53 @@ import {invalid} from './invalid-config.mjs';
8
7
  * percentage of the drive's size; when both are set the larger requirement applies. minFreeRamPct: the free-RAM floor below
9
8
  * which no new heavy op starts. A null or absent key leaves the shipped number.
10
9
  */
11
- const FLOOR_LIMITS=Object.freeze({minFreeDiskGb:Infinity,minFreeDiskPct:100,minFreeRamPct:100});
12
- const inRange=(value,limit)=>typeof value==='number'&&Number.isFinite(value)&&value>0&&value<=limit;
13
-
10
+ const FLOOR_LIMITS = Object.freeze({ minFreeDiskGb: Infinity, minFreeDiskPct: 100, minFreeRamPct: 100 });
11
+ const inRange = (value, limit) => typeof value === 'number' && Number.isFinite(value) && value > 0 && value <= limit;
14
12
  /** The keys of the resources block. */
15
- export const RESOURCE_KEYS=Object.freeze(Object.keys(FLOOR_LIMITS));
16
-
17
- export function validateResources(resources){
18
- if(resources===null)return;
19
- const bad=invalid('resources');
20
- const keys=RESOURCE_KEYS;
21
- const shape=keys.map(key=>`${key}?`).join(', ');
22
- if(!plain(resources))bad(` must be {${shape}} or null.`);
23
- for(const key of Object.keys(resources))if(!keys.includes(key))bad(` has unknown key ${key} (allowed: ${keys.join(', ')}).`);
24
- for(const key of keys){
25
- const value=resources[key];
26
- if(value!==undefined&&value!==null&&!inRange(value,FLOOR_LIMITS[key])){
27
- const cap=FLOOR_LIMITS[key]===Infinity?'':` and at most ${FLOOR_LIMITS[key]}`;
28
- bad(`.${key} must be a number above 0${cap}, or null.`);
13
+ export const RESOURCE_KEYS = Object.freeze(Object.keys(FLOOR_LIMITS));
14
+ const isFloorKey = (key) => Object.hasOwn(FLOOR_LIMITS, key);
15
+ export function validateResources(resources) {
16
+ if (resources === null)
17
+ return;
18
+ const bad = invalid('resources');
19
+ const keys = RESOURCE_KEYS;
20
+ const shape = keys.map(key => `${key}?`).join(', ');
21
+ if (!plain(resources))
22
+ return bad(` must be {${shape}} or null.`);
23
+ for (const key of Object.keys(resources))
24
+ if (!keys.includes(key))
25
+ bad(` has unknown key ${key} (allowed: ${keys.join(', ')}).`);
26
+ for (const key of keys) {
27
+ if (!isFloorKey(key))
28
+ continue;
29
+ const value = resources[key];
30
+ if (value !== undefined && value !== null && !inRange(value, FLOOR_LIMITS[key])) {
31
+ const cap = FLOOR_LIMITS[key] === Infinity ? '' : ` and at most ${FLOOR_LIMITS[key]}`;
32
+ bad(`.${key} must be a number above 0${cap}, or null.`);
33
+ }
29
34
  }
30
- }
31
35
  }
32
-
33
- const declared=(block,key,source)=>{
34
- const value=block?.[key];
35
- if(value===undefined||value===null)return null;
36
- if(!inRange(value,FLOOR_LIMITS[key]))throw new Error(`${source}.${key} must be a number above 0 (at most ${FLOOR_LIMITS[key]})`);
37
- return value;
36
+ const declared = (block, key, source) => {
37
+ const value = block?.[key];
38
+ if (value === undefined || value === null)
39
+ return null;
40
+ if (!inRange(value, FLOOR_LIMITS[key]))
41
+ throw new Error(`${source}.${key} must be a number above 0 (at most ${FLOOR_LIMITS[key]})`);
42
+ return value;
38
43
  };
39
-
40
44
  /**
41
45
  * The floors in force: the owner's `resources` (config.yaml) over the shipped `allocation.resources`.
42
46
  * {minFreeDiskGb, minFreeDiskPct (null when unset), minFreeRamPct}. The shipped policy owns the default numbers: when it
43
47
  * omits or misspells minFreeDiskGb or minFreeRamPct this refuses, there is no fallback constant.
44
48
  */
45
- export function hostFloors(shipped,owner=null){
46
- const source='modules/models/runtimes.yaml allocation.resources';
47
- const base={minFreeDiskGb:declared(shipped,'minFreeDiskGb',source),minFreeDiskPct:declared(shipped,'minFreeDiskPct',source),minFreeRamPct:declared(shipped,'minFreeRamPct',source)};
48
- for(const key of ['minFreeDiskGb','minFreeRamPct'])if(base[key]===null)throw new Error(`${source}.${key} must declare a positive number`);
49
- if(owner!==null&&owner!==undefined)validateResources(owner);
50
- const pick=key=>declared(owner,key,'config.yaml resources')??base[key];
51
- return {minFreeDiskGb:pick('minFreeDiskGb'),minFreeDiskPct:pick('minFreeDiskPct'),minFreeRamPct:pick('minFreeRamPct')};
49
+ export function hostFloors(shipped, owner = null) {
50
+ const source = 'modules/models/runtimes.yaml allocation.resources';
51
+ const base = { minFreeDiskGb: declared(shipped, 'minFreeDiskGb', source), minFreeDiskPct: declared(shipped, 'minFreeDiskPct', source), minFreeRamPct: declared(shipped, 'minFreeRamPct', source) };
52
+ for (const key of ['minFreeDiskGb', 'minFreeRamPct'])
53
+ if (base[key] === null)
54
+ throw new Error(`${source}.${key} must declare a positive number`);
55
+ if (owner !== null && owner !== undefined)
56
+ validateResources(owner);
57
+ const pick = (key) => declared(owner, key, 'config.yaml resources') ?? base[key];
58
+ return { minFreeDiskGb: pick('minFreeDiskGb'), minFreeDiskPct: pick('minFreeDiskPct'), minFreeRamPct: pick('minFreeRamPct') };
52
59
  }
@@ -1,8 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import {fileURLToPath} from 'node:url';
4
- import {parseYaml} from './yaml.mjs';
5
-
3
+ import { fileURLToPath } from 'node:url';
4
+ import { parseYaml } from './yaml.mjs';
6
5
  /**
7
6
  * Runtime root helper. The runtime reads this source tree directly. `skillRoot` is the
8
7
  * directory containing `package.json` (this module's parent) — the checkout, or the copy of it
@@ -13,7 +12,6 @@ import {parseYaml} from './yaml.mjs';
13
12
  * path under `skillRoot` and parses the file by its extension.
14
13
  */
15
14
  const moduleRoot = path.dirname(fileURLToPath(new URL('../package.json', import.meta.url)));
16
-
17
15
  /** The runtime root: this source tree (or an immutable sealed payload of it). */
18
16
  export const skillRoot = moduleRoot;
19
17
  /**
@@ -21,7 +19,6 @@ export const skillRoot = moduleRoot;
21
19
  * holding this runtime checkout. Parameterized over `env`, so a spec can inject a fixture Source.
22
20
  */
23
21
  export const starciSourceRoot = (env = process.env) => (env.STARCI_SOURCE_ROOT ? path.resolve(env.STARCI_SOURCE_ROOT) : path.dirname(skillRoot));
24
-
25
22
  /** Overrides the per-host state base wholesale (debug probes, spec trees); the one seam starciLocalRoot reads. */
26
23
  export const LOCAL_ROOT_ENV = 'STARCI_LOCAL_ROOT';
27
24
  /** The host-data directory of a runtime root. Git-ignored and never packed; the example fixtures under examples/.runtimes are a separate, public exception. */
@@ -38,17 +35,18 @@ export const runtimeStateDir = (root = skillRoot) => path.join(root, RUNTIME_STA
38
35
  * LOCAL_ROOT_ENV replaces it wholesale. Nothing is read from or moved out of the earlier %LOCALAPPDATA%/StarCi and ~/.starci locations.
39
36
  */
40
37
  export const starciLocalRoot = (env = process.env, root = skillRoot) => (env[LOCAL_ROOT_ENV] ? path.resolve(env[LOCAL_ROOT_ENV]) : runtimeStateDir(root));
41
-
42
38
  /**
43
39
  * Read a runtime contract document. `parts` are path segments under `skillRoot`
44
40
  * (e.g. readModuleJson('modules', 'models', 'kinds.yaml')).
45
41
  */
42
+ // reason: a parsed YAML/JSON contract document read from disk (parsed-input boundary); callers index into it by contract shape.
46
43
  export function readModuleJson(...parts) {
47
- const rel = parts.join('/');
48
- const file = path.join(skillRoot, rel);
49
- if (!(file.startsWith(skillRoot) && fs.existsSync(file) && fs.statSync(file).isFile()))
50
- throw new Error(`Required contract not found for ${rel} under ${skillRoot}`);
51
- const text = fs.readFileSync(file, 'utf8');
52
- if (/\.ya?ml$/i.test(file)) return parseYaml(text);
53
- return JSON.parse(text);
44
+ const rel = parts.join('/');
45
+ const file = path.join(skillRoot, rel);
46
+ if (!(file.startsWith(skillRoot) && fs.existsSync(file) && fs.statSync(file).isFile()))
47
+ throw new Error(`Required contract not found for ${rel} under ${skillRoot}`);
48
+ const text = fs.readFileSync(file, 'utf8');
49
+ if (/\.ya?ml$/i.test(file))
50
+ return parseYaml(text);
51
+ return JSON.parse(text);
54
52
  }
@@ -1,134 +1,202 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
-
4
3
  /** The host-local credential filename, resolved only against a verified runtime main root. */
5
- export const SECRET_ENV_FILE='secret.env';
4
+ export const SECRET_ENV_FILE = 'secret.env';
6
5
  /** The configured credential variable grammar shared by validation and secret resolution. */
7
- export const ENV_NAME=/^[A-Z_][A-Z0-9_]{0,63}$/;
8
- export const CREDENTIAL_FILE_MAX_BYTES=1024*1024;
6
+ export const ENV_NAME = /^[A-Z_][A-Z0-9_]{0,63}$/;
7
+ export const CREDENTIAL_FILE_MAX_BYTES = 1024 * 1024;
9
8
  const resolvedValues = new Set();
10
- const remember = value => {
11
- if (typeof value === 'string' && value.length >= 6) resolvedValues.add(value);
12
- return value;
9
+ const remember = (value) => {
10
+ if (typeof value === 'string' && value.length >= 6)
11
+ resolvedValues.add(value);
12
+ return value;
13
13
  };
14
-
15
14
  /** Filter credentials resolved by this process; the values never leave their resolution owner. */
16
15
  export function redactResolvedSecrets(text) {
17
- let clean = text;
18
- for (const value of resolvedValues) if (clean.includes(value)) clean = clean.split(value).join('[redacted:resolved-secret]');
19
- return clean;
16
+ let clean = text;
17
+ for (const value of resolvedValues)
18
+ if (clean.includes(value))
19
+ clean = clean.split(value).join('[redacted:resolved-secret]');
20
+ return clean;
20
21
  }
21
-
22
22
  /** Check lexical availability only; blank and conventional template markers never prove provider validity. */
23
- export function credentialPresent(value){
24
- if(typeof value!=='string'||!value.trim())return false;
25
- return !/^(?:CHANGE_ME|REPLACE_ME|PLACEHOLDER|TODO|<[^>]*>|YOUR_[A-Z0-9_]+)$/i.test(value.trim());
23
+ export function credentialPresent(value) {
24
+ if (typeof value !== 'string' || !value.trim())
25
+ return false;
26
+ return !/^(?:CHANGE_ME|REPLACE_ME|PLACEHOLDER|TODO|<[^>]*>|YOUR_[A-Z0-9_]+)$/i.test(value.trim());
26
27
  }
27
-
28
28
  /** Read one configured credential file with a finite byte budget; no effects beyond the read. */
29
- export function readSecretBytes(file){
30
- const before=fs.lstatSync(file);
31
- if(!before.isFile()||before.isSymbolicLink())throw new Error('credential file must be a regular file');
32
- if(before.size>CREDENTIAL_FILE_MAX_BYTES)throw new Error('credential file exceeds the byte budget');
33
- const fd=fs.openSync(file,fs.constants.O_RDONLY|(fs.constants.O_NOFOLLOW??0));
34
- let bytes;
35
- try{
36
- const stat=fs.fstatSync(fd);
37
- if(!stat.isFile()||stat.dev!==before.dev||stat.ino!==before.ino)throw new Error('credential file changed during resolution');
38
- bytes=Buffer.alloc(CREDENTIAL_FILE_MAX_BYTES+1);
39
- let count=0;
40
- while(count<bytes.length){const n=fs.readSync(fd,bytes,count,bytes.length-count,null);if(n===0){break;}count+=n;}
41
- if(count>CREDENTIAL_FILE_MAX_BYTES)throw new Error('credential file exceeds the byte budget');
42
- return bytes.subarray(0,count);
43
- }catch(error){bytes?.fill(0);throw error;}finally{fs.closeSync(fd);}
29
+ export function readSecretBytes(file) {
30
+ const before = fs.lstatSync(file);
31
+ if (!before.isFile() || before.isSymbolicLink())
32
+ throw new Error('credential file must be a regular file');
33
+ if (before.size > CREDENTIAL_FILE_MAX_BYTES)
34
+ throw new Error('credential file exceeds the byte budget');
35
+ const fd = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
36
+ let bytes;
37
+ try {
38
+ const stat = fs.fstatSync(fd);
39
+ if (!stat.isFile() || stat.dev !== before.dev || stat.ino !== before.ino)
40
+ throw new Error('credential file changed during resolution');
41
+ bytes = Buffer.alloc(CREDENTIAL_FILE_MAX_BYTES + 1);
42
+ let count = 0;
43
+ while (count < bytes.length) {
44
+ const n = fs.readSync(fd, bytes, count, bytes.length - count, null);
45
+ if (n === 0) {
46
+ break;
47
+ }
48
+ count += n;
49
+ }
50
+ if (count > CREDENTIAL_FILE_MAX_BYTES)
51
+ throw new Error('credential file exceeds the byte budget');
52
+ return bytes.subarray(0, count);
53
+ }
54
+ catch (error) {
55
+ bytes?.fill(0);
56
+ throw error;
57
+ }
58
+ finally {
59
+ fs.closeSync(fd);
60
+ }
44
61
  }
45
-
46
- const readCredentialFile = file => { const bytes=readSecretBytes(file); try{return bytes.toString('utf8');}finally{bytes.fill(0);} };
47
-
48
- const DOTENV_LINE_PARTS = [String.raw`^\s*`, String.raw`(?:export\s+)?`, String.raw`([A-Za-z_]\w*)`, String.raw`\s*=\s*`, String.raw`([^\r\n\u2028\u2029]*)`, String.raw`\s*$`];
62
+ const readCredentialFile = (file) => { const bytes = readSecretBytes(file); try {
63
+ return bytes.toString('utf8');
64
+ }
65
+ finally {
66
+ bytes.fill(0);
67
+ } };
68
+ const DOTENV_LINE_PARTS = [String.raw `^\s*`, String.raw `(?:export\s+)?`, String.raw `([A-Za-z_]\w*)`, String.raw `\s*=\s*`, String.raw `([^\r\n\u2028\u2029]*)`, String.raw `\s*$`];
49
69
  const DOTENV_LINE = new RegExp(DOTENV_LINE_PARTS.join(''));
50
-
51
70
  /** Parse a dotenv file (KEY=VALUE lines, # comments, optional export/quotes). An absent file is {}. */
52
- export function readDotenv(file){
53
- let text='';try{text=readCredentialFile(file);}catch(error){if(error?.code==='ENOENT'){return {};}throw error;}
54
- const out={};
55
- for(const line of text.split(/\r?\n/)){
56
- const m=DOTENV_LINE.exec(line);
57
- if(!m)continue;
58
- let value=m[2].trimEnd();
59
- if(value.length>=2&&(value.startsWith('"')||value.startsWith("'"))&&value.at(-1)===value[0])value=value.slice(1,-1);
60
- out[m[1]]=value;
61
- }
62
- return out;
71
+ export function readDotenv(file) {
72
+ let text = '';
73
+ try {
74
+ text = readCredentialFile(file);
75
+ }
76
+ catch (error) {
77
+ if (error?.code === 'ENOENT') {
78
+ return {};
79
+ }
80
+ throw error;
81
+ }
82
+ const out = {};
83
+ for (const line of text.split(/\r?\n/)) {
84
+ const m = DOTENV_LINE.exec(line);
85
+ if (!m)
86
+ continue;
87
+ const name = m[1];
88
+ let value = m[2].trimEnd();
89
+ if (value.length >= 2 && (value.startsWith('"') || value.startsWith("'")) && value.at(-1) === value[0])
90
+ value = value.slice(1, -1);
91
+ out[name] = value;
92
+ }
93
+ return out;
63
94
  }
64
-
65
95
  /**
66
96
  * Resolve a configured variable or its supported NAME_FILE custody pointer. An explicitly supplied
67
97
  * blank variable disables the value; it must not resurrect a file value. Never print the result.
68
98
  */
69
- export function connectorSecret(name,env=process.env){
70
- if(typeof name!=='string'||!ENV_NAME.test(name))return null;
71
- if(Object.hasOwn(env,name))return typeof env[name]==='string'?remember(env[name].trim()||null):null;
72
- const pointer=typeof env[`${name}_FILE`]==='string'?env[`${name}_FILE`].trim():'';
73
- if(!pointer)return null;
74
- try{const value=readCredentialFile(pointer).trim();return remember(value||null);}catch{return null;}
99
+ export function connectorSecret(name, env = process.env) {
100
+ if (typeof name !== 'string' || !ENV_NAME.test(name))
101
+ return null;
102
+ const direct = env[name];
103
+ if (Object.hasOwn(env, name))
104
+ return typeof direct === 'string' ? remember(direct.trim() || null) : null;
105
+ const filePointer = env[`${name}_FILE`];
106
+ const pointer = typeof filePointer === 'string' ? filePointer.trim() : '';
107
+ if (!pointer)
108
+ return null;
109
+ try {
110
+ const value = readCredentialFile(pointer).trim();
111
+ return remember(value || null);
112
+ }
113
+ catch {
114
+ return null;
115
+ }
75
116
  }
76
-
77
117
  /**
78
118
  * Load the verified runtime main's local credentials below the actual environment without mutating
79
119
  * either input. The caller owns repository identity; this base-tier reader never infers a root from cwd.
80
120
  * Absence is allowed so the action-specific gate can name only the credentials that action requires.
81
121
  */
82
- export function secretEnv(verifiedRuntimeRoot,env=process.env){
83
- if(typeof verifiedRuntimeRoot!=='string'||!path.isAbsolute(verifiedRuntimeRoot))
84
- throw new TypeError('secretEnv requires an absolute verified runtime root');
85
- if(env===null||typeof env!=='object'||Array.isArray(env))throw new TypeError('secretEnv requires an environment mapping');
86
- const root=fs.lstatSync(verifiedRuntimeRoot);
87
- if(!root.isDirectory()||root.isSymbolicLink())throw new Error('secretEnv runtime root must be a regular directory');
88
- const file=path.join(verifiedRuntimeRoot,SECRET_ENV_FILE);
89
- let stat;
90
- try{stat=fs.lstatSync(file);}catch(error){if(error?.code==='ENOENT'){return {...env};}throw error;}
91
- if(!stat.isFile()||stat.isSymbolicLink())throw new Error('secret.env must be a regular file');
92
- const local = readDotenv(file);
93
- const resolved = {...local,...env};
94
- for (const name of Object.keys(local)) if (typeof resolved[name] === 'string') remember(resolved[name]);
95
- return resolved;
122
+ export function secretEnv(verifiedRuntimeRoot, env = process.env) {
123
+ if (typeof verifiedRuntimeRoot !== 'string' || !path.isAbsolute(verifiedRuntimeRoot))
124
+ throw new TypeError('secretEnv requires an absolute verified runtime root');
125
+ if (env === null || typeof env !== 'object' || Array.isArray(env))
126
+ throw new TypeError('secretEnv requires an environment mapping');
127
+ const root = fs.lstatSync(verifiedRuntimeRoot);
128
+ if (!root.isDirectory() || root.isSymbolicLink())
129
+ throw new Error('secretEnv runtime root must be a regular directory');
130
+ const file = path.join(verifiedRuntimeRoot, SECRET_ENV_FILE);
131
+ let stat;
132
+ try {
133
+ stat = fs.lstatSync(file);
134
+ }
135
+ catch (error) {
136
+ if (error?.code === 'ENOENT') {
137
+ return { ...env };
138
+ }
139
+ throw error;
140
+ }
141
+ if (!stat.isFile() || stat.isSymbolicLink())
142
+ throw new Error('secret.env must be a regular file');
143
+ const local = readDotenv(file);
144
+ const resolved = { ...local, ...env };
145
+ for (const name of Object.keys(local))
146
+ if (typeof resolved[name] === 'string')
147
+ remember(resolved[name]);
148
+ return resolved;
96
149
  }
97
-
98
- const sopsRefuse=(reason,message)=>{
99
- const error=new Error(`SOPS identity [${reason}]: ${message}`);
100
- error.name='SopsIdentityRefusal';error.identityRefusal=reason;
101
- return {mode:'refused',env:null,error};
150
+ const sopsRefuse = (reason, message) => {
151
+ const error = new Error(`SOPS identity [${reason}]: ${message}`);
152
+ error.name = 'SopsIdentityRefusal';
153
+ error.identityRefusal = reason;
154
+ return { mode: 'refused', env: null, error };
102
155
  };
103
- const sopsInlineIdentity=(env,name)=>{
104
- if(!credentialPresent(env[name]))return sopsRefuse('disabled-inline','SOPS_AGE_KEY is explicitly empty or invalid');
105
- remember(env[name].trim());
106
- return {...sopsRefuse('inline-context-unqualified','the supplied SOPS_AGE_KEY awaits an owned selected-identity isolation capability; no SOPS process was launched'),inlineName:name};
156
+ const sopsInlineIdentity = (env, name) => {
157
+ const inline = env[name];
158
+ if (!credentialPresent(inline))
159
+ return sopsRefuse('disabled-inline', 'SOPS_AGE_KEY is explicitly empty or invalid');
160
+ remember(inline.trim());
161
+ return { ...sopsRefuse('inline-context-unqualified', 'the supplied SOPS_AGE_KEY awaits an owned selected-identity isolation capability; no SOPS process was launched'), inlineName: name };
107
162
  };
108
- const sopsFileIdentity=(env,file)=>{
109
- if(!credentialPresent(file))return sopsRefuse('invalid-file','the original identity file selection is empty or invalid');
110
- if(!path.isAbsolute(file))return sopsRefuse('identity-file-location-unqualified','select the original identity by its absolute path; caller and SOPS working directories may differ');
111
- try{
112
- const stat=fs.lstatSync(file);
113
- if(!stat.isFile()||stat.isSymbolicLink())return sopsRefuse('identity-file-unavailable','the explicitly selected original identity must be a regular non-symlink file');
114
- }catch{return sopsRefuse('identity-file-unavailable','the explicitly selected original identity file is unavailable');}
115
- return {mode:'file',env:{...env,SOPS_AGE_KEY_FILE:file},error:null};
163
+ const sopsFileIdentity = (env, file) => {
164
+ if (!credentialPresent(file))
165
+ return sopsRefuse('invalid-file', 'the original identity file selection is empty or invalid');
166
+ if (!path.isAbsolute(file))
167
+ return sopsRefuse('identity-file-location-unqualified', 'select the original identity by its absolute path; caller and SOPS working directories may differ');
168
+ try {
169
+ const stat = fs.lstatSync(file);
170
+ if (!stat.isFile() || stat.isSymbolicLink())
171
+ return sopsRefuse('identity-file-unavailable', 'the explicitly selected original identity must be a regular non-symlink file');
172
+ }
173
+ catch {
174
+ return sopsRefuse('identity-file-unavailable', 'the explicitly selected original identity file is unavailable');
175
+ }
176
+ return { mode: 'file', env: { ...env, SOPS_AGE_KEY_FILE: file }, error: null };
116
177
  };
117
178
  /** Select a supplied age identity without inventing custody or running a key tool. Inline execution awaits its owned isolation capability. */
118
- export function sopsIdentityEnv(env,{identity=null,required=false,platform=process.platform}={}){
119
- if(!env||typeof env!=='object'||Array.isArray(env))return sopsRefuse('invalid-environment','an environment mapping is required');
120
- const names=name=>Object.keys(env).filter(key=>platform==='win32'?key.toUpperCase()===name:key===name);
121
- const inline=names('SOPS_AGE_KEY'),files=names('SOPS_AGE_KEY_FILE');
122
- if(inline.length>1||files.length>1)return sopsRefuse('ambiguous-environment','multiple Windows aliases select an age identity');
123
- if(inline.length)return sopsInlineIdentity(env,inline[0]);
124
- let file=identity;
125
- if(files.length){
126
- const value=env[files[0]];
127
- if(!credentialPresent(value))return sopsRefuse('disabled-file','SOPS_AGE_KEY_FILE is explicitly empty or invalid');
128
- if(identity!==null&&identity!==undefined&&identity!==value)return sopsRefuse('conflicting-files','two different original identity files were explicitly selected');
129
- file=value;
130
- }
131
- if(file!==null&&file!==undefined)return sopsFileIdentity(env,file);
132
- if(required)return sopsRefuse('identity-not-supplied','supply SOPS_AGE_KEY locally or select the original SOPS_AGE_KEY_FILE; no home default was inferred');
133
- return {mode:'public-recipient',env:{...env},error:null};
179
+ export function sopsIdentityEnv(env, { identity = null, required = false, platform = process.platform } = {}) {
180
+ if (!env || typeof env !== 'object' || Array.isArray(env))
181
+ return sopsRefuse('invalid-environment', 'an environment mapping is required');
182
+ const names = (name) => Object.keys(env).filter(key => platform === 'win32' ? key.toUpperCase() === name : key === name);
183
+ const inline = names('SOPS_AGE_KEY'), files = names('SOPS_AGE_KEY_FILE');
184
+ if (inline.length > 1 || files.length > 1)
185
+ return sopsRefuse('ambiguous-environment', 'multiple Windows aliases select an age identity');
186
+ if (inline.length)
187
+ return sopsInlineIdentity(env, inline[0]);
188
+ let file = identity;
189
+ if (files.length) {
190
+ const value = env[files[0]];
191
+ if (!credentialPresent(value))
192
+ return sopsRefuse('disabled-file', 'SOPS_AGE_KEY_FILE is explicitly empty or invalid');
193
+ if (identity !== null && identity !== undefined && identity !== value)
194
+ return sopsRefuse('conflicting-files', 'two different original identity files were explicitly selected');
195
+ file = value;
196
+ }
197
+ if (file !== null && file !== undefined)
198
+ return sopsFileIdentity(env, file);
199
+ if (required)
200
+ return sopsRefuse('identity-not-supplied', 'supply SOPS_AGE_KEY locally or select the original SOPS_AGE_KEY_FILE; no home default was inferred');
201
+ return { mode: 'public-recipient', env: { ...env }, error: null };
134
202
  }
@@ -1,21 +1,24 @@
1
- import {isPlainObject as plain} from './plain-object.mjs';
2
- import {invalid} from './invalid-config.mjs';
3
-
1
+ import { isPlainObject as plain } from './plain-object.mjs';
2
+ import { invalid } from './invalid-config.mjs';
4
3
  /** The keys of the sonar block. */
5
- const SONAR_KEYS=Object.freeze(['organization']);
4
+ const SONAR_KEYS = Object.freeze(['organization']);
6
5
  /** A SonarCloud organization key: lowercase letters, digits, hyphens and underscores, starting with a letter or a digit. */
7
- const ORGANIZATION=/^[a-z0-9][a-z0-9_-]*$/;
8
-
6
+ const ORGANIZATION = /^[a-z0-9][a-z0-9_-]*$/;
9
7
  /**
10
8
  * config.yaml `sonar` - {organization?}: the key of the SonarCloud organization the release's Sonar proof of the example apps analyses in
11
9
  * (the project key is <organization>_<key the example declares>). It is configuration, not a secret; the environment variable
12
10
  * SONAR_ORGANIZATION wins over it, the repository variable of the same name is what CI reads. Null or absent means "not set yet".
13
11
  */
14
- export function validateSonar(sonar){
15
- if(sonar===null)return;
16
- const bad=invalid('sonar');
17
- if(!plain(sonar))bad(' must be {organization?: <SonarCloud organization key>} or null.');
18
- for(const key of Object.keys(sonar))if(!SONAR_KEYS.includes(key))bad(` has unknown key ${key} (allowed: ${SONAR_KEYS.join(', ')}).`);
19
- const value=sonar.organization;
20
- if(value!==undefined&&value!==null&&!(typeof value==='string'&&ORGANIZATION.test(value)))bad('.organization must be a SonarCloud organization key (lowercase letters, digits, - and _) or null.');
12
+ export function validateSonar(sonar) {
13
+ if (sonar === null)
14
+ return;
15
+ const bad = invalid('sonar');
16
+ if (!plain(sonar))
17
+ return bad(' must be {organization?: <SonarCloud organization key>} or null.');
18
+ for (const key of Object.keys(sonar))
19
+ if (!SONAR_KEYS.includes(key))
20
+ bad(` has unknown key ${key} (allowed: ${SONAR_KEYS.join(', ')}).`);
21
+ const value = sonar.organization;
22
+ if (value !== undefined && value !== null && !(typeof value === 'string' && ORGANIZATION.test(value)))
23
+ bad('.organization must be a SonarCloud organization key (lowercase letters, digits, - and _) or null.');
21
24
  }
@@ -8,25 +8,19 @@
8
8
  import os from 'node:os';
9
9
  import path from 'node:path';
10
10
  import { loadConfig } from './config.mjs';
11
-
12
11
  export const TEMP_ROOT_ENV = 'STARCI_TEMP_ROOT';
13
-
14
12
  /** The owner's `roots.temp` (config.yaml, validated by engine/invalid-config.mjs), or null. `config` is the owner config (default: loadConfig(), whose error for an invalid config.yaml propagates: the OS directory never replaces a root the owner set wrongly). */
15
13
  const ownerTemp = (config) => (config === undefined ? loadConfig() : config)?.roots?.temp ?? null;
16
-
17
14
  /** The operating-system temp directory of `env`: TEMP, TMP, TMPDIR, else os.tmpdir(). */
18
15
  export const osTempDir = (env = process.env) => path.resolve(String(env?.TEMP || env?.TMP || env?.TMPDIR || os.tmpdir()));
19
-
20
16
  /** The one temp root: env STARCI_TEMP_ROOT, then the owner config `roots.temp`, else the OS temp directory. Resolved, not created. */
21
17
  export function tempRoot({ env = process.env, config = undefined } = {}) {
22
- return path.resolve(String(env?.[TEMP_ROOT_ENV] || ownerTemp(config) || osTempDir(env)));
18
+ return path.resolve(String(env?.[TEMP_ROOT_ENV] || ownerTemp(config) || osTempDir(env)));
23
19
  }
24
-
25
20
  /** `env` (default: the process environment) as a copy whose TEMP, TMP and TMPDIR name the temp root. */
26
21
  export function tempChildEnv(env = process.env) {
27
- const root = tempRoot({ env });
28
- return { ...env, TEMP: root, TMP: root, TMPDIR: root };
22
+ const root = tempRoot({ env });
23
+ return { ...env, TEMP: root, TMP: root, TMPDIR: root };
29
24
  }
30
-
31
25
  /** The spawn options with `env` replaced by tempChildEnv of the options' own env (default: the process environment). */
32
26
  export const withTempEnv = (options = {}) => ({ ...options, env: tempChildEnv(options.env ?? process.env) });
@@ -17,7 +17,7 @@ pins:
17
17
  # --- @starci packages. Every @starci package is a PUBLISHED npm package (install: registry): a product repository
18
18
  # installs the exact pinned version from the npm registry in the root and every workspace package.json, never `file:`.
19
19
  '@starci/heroicons':
20
- version: 0.3.4
20
+ version: 0.3.5
21
21
  group: starci
22
22
  install: registry
23
23
  side: fe
@@ -31,35 +31,35 @@ pins:
31
31
  source: packages/event-stream/package.json
32
32
  why: 'the one server-sent-events client of every front end (fe-stack event-stream, owner:event-stream-package): the transport client layer (modules/api/sse/clients) creates one instance for the app with the bearer token source and the stream operations follow on it; it carries Last-Event-ID resume, the capped doubling backoff, the heartbeat timeout, the single 401 renewal and the useSWRSubscription adapter, so no app keeps a hand-written reader.'
33
33
  '@starci/grammar':
34
- version: 0.15.34
34
+ version: 0.16.2
35
35
  group: starci
36
36
  install: registry
37
37
  side: fe
38
38
  source: packages/grammar/package.json
39
39
  why: 'the grammar front ends read: the brand layer sets `--font-sans` and `--font-mono` and the grammar reads them; it carries the Input tel kind, the copyable and read-only Input, the IconButton disclosure props and count badge, ChatBubble, FillViewport, Textarea mentions and Enter-to-send, the leading PrimaryRailLayout rail, the plate-less Image, ListRow, the soft Alert, Progress steps, the floating primary IconButton, FlowGraph, the STATE-8 loading views and the bordered selectable TagGroup chips. The runtime source version wins for a @starci package.'
40
40
  '@starci/eslint-canon-be':
41
- version: 3.8.21
41
+ version: 3.9.2
42
42
  group: starci
43
43
  install: registry
44
44
  side: be
45
45
  source: packages/eslint-canon-be/package.json
46
46
  why: 'the back-end ESLint canon: `loadHfs(import.meta.url)` of be/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the be side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; `starciBeConfig({ hfs: loadHfs(import.meta.url) })` is the typed factory and the BE-CONVENTION laws are its rules.'
47
47
  '@starci/eslint-canon-fe':
48
- version: 9.5.23
48
+ version: 9.6.2
49
49
  group: starci
50
50
  install: registry
51
51
  side: fe
52
52
  source: packages/eslint-canon-fe/package.json
53
53
  why: 'the front-end ESLint canon: `loadHfs(import.meta.url)` of fe/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the fe side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; the front end has no tests (FE_NO_TESTS R97 in hfs) and no-vietnamese-in-source (R91) applies.'
54
54
  '@starci/eslint-grammar-core':
55
- version: 0.15.34
55
+ version: 0.16.2
56
56
  group: starci
57
57
  install: registry
58
58
  side: fe
59
59
  source: packages/eslint-grammar-core/package.json
60
60
  why: 'the StarCi Grammar component API lint, versioned with @starci/grammar; front ends compose this family package with the universal FE canon.'
61
61
  '@starci/eslint-grammar-offset-pop':
62
- version: 0.15.34
62
+ version: 0.16.2
63
63
  group: starci
64
64
  install: registry
65
65
  side: fe
@@ -85,28 +85,28 @@ pins:
85
85
  side: both
86
86
  source: packages/prettier-config/package.json
87
87
  '@starci/jest-preset':
88
- version: 2.5.2
88
+ version: 2.5.3
89
89
  group: starci
90
90
  install: registry
91
91
  side: be
92
92
  source: packages/jest-preset/package.json
93
93
  why: 'the Jest preset of a back end: the world runner runs up to min(--maxWorkers, slots) world files at once, each in its own process bound to one data slot of the test world (STARCI_TEST_WORLD_SLOT), and reads test-world state protocol 2 only: it pairs exactly with @starci/test-world, and any other state file is JEST_PRESET_WORLD_PAIR_MISMATCH. The unit run writes the lcov Sonar and Codecov import (services only); the integration, e2e and contract projects run every spec file in a worker process of its own (world-runner.cjs), so a process-global framework registry (@nestjs/graphql type metadata) never leaks between e2e files; it ships the unit kit (mockEntityManager, fakeTransaction, fakeQueryBuilder, queueQueryBuilders, fakeIds, FakeClock, Outcome matchers) and the recordingOutbox claim side.'
94
94
  '@starci/test-world':
95
- version: 1.3.2
95
+ version: 1.3.3
96
96
  group: starci
97
97
  install: registry
98
98
  side: be
99
99
  source: packages/test-world/package.json
100
100
  why: 'the shared e2e library of every back end (R47, R48), a devDependency of every back end: the warm stack behind toxiproxy, the network-edge fakes, the Nest boot, the typed useTestWorld handle, useSandbox for contract specs and the outage lock. A fake declared https (defineHttpFake) serves TLS with the run''s ephemeral in-memory fake CA and publishes its PEM as w.fake.<name>.ca for the app''s own client to trust; verification is never turned off. Kafka is real own infra (the one digest-pinned apache/kafka KRaft image, a broker listener and proxy per slot, slot-prefixed topics, consumer groups and client ids) and Postgres is schema-per-context (a schema and a login role per context in a shared database). Each worker has its own data slot (<ns>_w<k>) with its own databases, Keycloak realm, leased Redis DB, bucket/collection/topic prefixes, fakes host, toxiproxy proxies and outage lock; a run provisions min(jest workers, workers cap, default 2) slots, and state file protocol 2 pairs exactly with @starci/jest-preset (TEST_WORLD_PAIR_MISMATCH otherwise). A modules world boots real peer apps beside its modules, world.apps.<name>.during(fn) is the outage of a peer app, w.keycloak.clientSecret(client) answers the run-generated secret of a confidential realm client, world.keycloak.events/sessions read the realm user events and live sessions, world.infra.postgresql.connection(name) takes one database connection down while the others serve, and world.resolve and scope.resolve take any Nest token. Every path a declaration names (`stack`, seeds, the realm, Dockerfiles) resolves from the app root (the directory of hfs.json); the globalSetup registers the path aliases as TypeScript resolves them. Its CLI main is exported at ./cli for starci app stack.'
101
101
  '@starci/ai-router':
102
- version: 0.4.0
102
+ version: 0.4.1
103
103
  group: starci
104
104
  install: registry
105
105
  side: be
106
106
  source: packages/ai-router/package.json
107
107
  why: 'the AI pool router of a back end: a NestJS module that routes OpenAI-compatible chat, stream and embeddings calls across keys, models and providers, with @starci/ai-router/testing for its specs; @starci/jest-preset compiles it as an ESM-only dependency.'
108
108
  '@starci/hfs':
109
- version: 4.11.22
109
+ version: 4.12.2
110
110
  group: starci
111
111
  install: registry
112
112
  side: both