@skillsmith/core 0.11.2 → 0.11.4

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 (235) hide show
  1. package/CHANGELOG.md +36 -9
  2. package/README.md +6 -7
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/src/analysis/McpReferenceExtractor.d.ts +59 -5
  5. package/dist/src/analysis/McpReferenceExtractor.d.ts.map +1 -1
  6. package/dist/src/analysis/McpReferenceExtractor.hardening.test.d.ts +2 -0
  7. package/dist/src/analysis/McpReferenceExtractor.hardening.test.d.ts.map +1 -0
  8. package/dist/src/analysis/McpReferenceExtractor.hardening.test.js +278 -0
  9. package/dist/src/analysis/McpReferenceExtractor.hardening.test.js.map +1 -0
  10. package/dist/src/analysis/McpReferenceExtractor.js +276 -20
  11. package/dist/src/analysis/McpReferenceExtractor.js.map +1 -1
  12. package/dist/src/analysis/McpReferenceExtractor.test.js +4 -0
  13. package/dist/src/analysis/McpReferenceExtractor.test.js.map +1 -1
  14. package/dist/src/config/config-atomic-write.d.ts +58 -0
  15. package/dist/src/config/config-atomic-write.d.ts.map +1 -0
  16. package/dist/src/config/config-atomic-write.js +151 -0
  17. package/dist/src/config/config-atomic-write.js.map +1 -0
  18. package/dist/src/config/config-atomic-write.test.d.ts +9 -0
  19. package/dist/src/config/config-atomic-write.test.d.ts.map +1 -0
  20. package/dist/src/config/config-atomic-write.test.js +92 -0
  21. package/dist/src/config/config-atomic-write.test.js.map +1 -0
  22. package/dist/src/config/device-identity.d.ts +16 -0
  23. package/dist/src/config/device-identity.d.ts.map +1 -1
  24. package/dist/src/config/device-identity.js +82 -14
  25. package/dist/src/config/device-identity.js.map +1 -1
  26. package/dist/src/config/device-identity.test.js +158 -1
  27. package/dist/src/config/device-identity.test.js.map +1 -1
  28. package/dist/src/config/index.d.ts +14 -0
  29. package/dist/src/config/index.d.ts.map +1 -1
  30. package/dist/src/config/index.js +31 -15
  31. package/dist/src/config/index.js.map +1 -1
  32. package/dist/src/exports/services.d.ts +2 -1
  33. package/dist/src/exports/services.d.ts.map +1 -1
  34. package/dist/src/exports/services.js +1 -0
  35. package/dist/src/exports/services.js.map +1 -1
  36. package/dist/src/index.d.ts +4 -1
  37. package/dist/src/index.d.ts.map +1 -1
  38. package/dist/src/index.js +13 -1
  39. package/dist/src/index.js.map +1 -1
  40. package/dist/src/indexer/SwarmIndexer.d.ts +1 -1
  41. package/dist/src/indexer/SwarmIndexer.js +2 -2
  42. package/dist/src/indexer/SwarmIndexer.js.map +1 -1
  43. package/dist/src/install/paths.d.ts +1 -1
  44. package/dist/src/install/paths.d.ts.map +1 -1
  45. package/dist/src/install/paths.js +2 -0
  46. package/dist/src/install/paths.js.map +1 -1
  47. package/dist/src/install/paths.test.js +10 -1
  48. package/dist/src/install/paths.test.js.map +1 -1
  49. package/dist/src/logging/logger.test.js +57 -0
  50. package/dist/src/logging/logger.test.js.map +1 -1
  51. package/dist/src/logging/rotation.d.ts +27 -2
  52. package/dist/src/logging/rotation.d.ts.map +1 -1
  53. package/dist/src/logging/rotation.js +44 -3
  54. package/dist/src/logging/rotation.js.map +1 -1
  55. package/dist/src/logging/rotation.test.js +69 -0
  56. package/dist/src/logging/rotation.test.js.map +1 -1
  57. package/dist/src/logging/types.d.ts +8 -2
  58. package/dist/src/logging/types.d.ts.map +1 -1
  59. package/dist/src/scripts/__tests__/scan-imported-skills.test.js +5 -0
  60. package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
  61. package/dist/src/security/audit-types.d.ts +1 -1
  62. package/dist/src/security/audit-types.d.ts.map +1 -1
  63. package/dist/src/security/audit-types.js.map +1 -1
  64. package/dist/src/security/index.d.ts +2 -1
  65. package/dist/src/security/index.d.ts.map +1 -1
  66. package/dist/src/security/index.js +3 -0
  67. package/dist/src/security/index.js.map +1 -1
  68. package/dist/src/security/scanner/SecurityScanner.d.ts +2 -2
  69. package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
  70. package/dist/src/security/scanner/SecurityScanner.evidence.d.ts +76 -0
  71. package/dist/src/security/scanner/SecurityScanner.evidence.d.ts.map +1 -0
  72. package/dist/src/security/scanner/SecurityScanner.evidence.js +176 -0
  73. package/dist/src/security/scanner/SecurityScanner.evidence.js.map +1 -0
  74. package/dist/src/security/scanner/SecurityScanner.exec.d.ts +15 -1
  75. package/dist/src/security/scanner/SecurityScanner.exec.d.ts.map +1 -1
  76. package/dist/src/security/scanner/SecurityScanner.exec.js +68 -76
  77. package/dist/src/security/scanner/SecurityScanner.exec.js.map +1 -1
  78. package/dist/src/security/scanner/SecurityScanner.formatters.d.ts +14 -1
  79. package/dist/src/security/scanner/SecurityScanner.formatters.d.ts.map +1 -1
  80. package/dist/src/security/scanner/SecurityScanner.formatters.js +33 -0
  81. package/dist/src/security/scanner/SecurityScanner.formatters.js.map +1 -1
  82. package/dist/src/security/scanner/SecurityScanner.formatters.test.d.ts +9 -0
  83. package/dist/src/security/scanner/SecurityScanner.formatters.test.d.ts.map +1 -0
  84. package/dist/src/security/scanner/SecurityScanner.formatters.test.js +65 -0
  85. package/dist/src/security/scanner/SecurityScanner.formatters.test.js.map +1 -0
  86. package/dist/src/security/scanner/SecurityScanner.helpers.d.ts +35 -10
  87. package/dist/src/security/scanner/SecurityScanner.helpers.d.ts.map +1 -1
  88. package/dist/src/security/scanner/SecurityScanner.helpers.js +121 -60
  89. package/dist/src/security/scanner/SecurityScanner.helpers.js.map +1 -1
  90. package/dist/src/security/scanner/SecurityScanner.js +41 -13
  91. package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
  92. package/dist/src/security/scanner/SecurityScanner.pii.d.ts.map +1 -1
  93. package/dist/src/security/scanner/SecurityScanner.pii.js +23 -1
  94. package/dist/src/security/scanner/SecurityScanner.pii.js.map +1 -1
  95. package/dist/src/security/scanner/SecurityScanner.scanners.d.ts.map +1 -1
  96. package/dist/src/security/scanner/SecurityScanner.scanners.js +15 -3
  97. package/dist/src/security/scanner/SecurityScanner.scanners.js.map +1 -1
  98. package/dist/src/security/scanner/SecurityScanner.ssrf.d.ts +11 -1
  99. package/dist/src/security/scanner/SecurityScanner.ssrf.d.ts.map +1 -1
  100. package/dist/src/security/scanner/SecurityScanner.ssrf.js +18 -7
  101. package/dist/src/security/scanner/SecurityScanner.ssrf.js.map +1 -1
  102. package/dist/src/security/scanner/confusables.d.ts +28 -0
  103. package/dist/src/security/scanner/confusables.d.ts.map +1 -0
  104. package/dist/src/security/scanner/confusables.js +87 -0
  105. package/dist/src/security/scanner/confusables.js.map +1 -0
  106. package/dist/src/security/scanner/confusables.test.d.ts +10 -0
  107. package/dist/src/security/scanner/confusables.test.d.ts.map +1 -0
  108. package/dist/src/security/scanner/confusables.test.js +44 -0
  109. package/dist/src/security/scanner/confusables.test.js.map +1 -0
  110. package/dist/src/security/scanner/index.d.ts +9 -3
  111. package/dist/src/security/scanner/index.d.ts.map +1 -1
  112. package/dist/src/security/scanner/index.js +18 -2
  113. package/dist/src/security/scanner/index.js.map +1 -1
  114. package/dist/src/security/scanner/patterns.d.ts +41 -14
  115. package/dist/src/security/scanner/patterns.d.ts.map +1 -1
  116. package/dist/src/security/scanner/patterns.jailbreak.d.ts +41 -0
  117. package/dist/src/security/scanner/patterns.jailbreak.d.ts.map +1 -0
  118. package/dist/src/security/scanner/patterns.jailbreak.evidence.d.ts +35 -0
  119. package/dist/src/security/scanner/patterns.jailbreak.evidence.d.ts.map +1 -0
  120. package/dist/src/security/scanner/patterns.jailbreak.evidence.js +119 -0
  121. package/dist/src/security/scanner/patterns.jailbreak.evidence.js.map +1 -0
  122. package/dist/src/security/scanner/patterns.jailbreak.js +370 -0
  123. package/dist/src/security/scanner/patterns.jailbreak.js.map +1 -0
  124. package/dist/src/security/scanner/patterns.js +101 -82
  125. package/dist/src/security/scanner/patterns.js.map +1 -1
  126. package/dist/src/security/scanner/patterns.scope.d.ts +62 -0
  127. package/dist/src/security/scanner/patterns.scope.d.ts.map +1 -0
  128. package/dist/src/security/scanner/patterns.scope.js +207 -0
  129. package/dist/src/security/scanner/patterns.scope.js.map +1 -0
  130. package/dist/src/security/scanner/regex-utils.d.ts +20 -0
  131. package/dist/src/security/scanner/regex-utils.d.ts.map +1 -1
  132. package/dist/src/security/scanner/regex-utils.js +20 -0
  133. package/dist/src/security/scanner/regex-utils.js.map +1 -1
  134. package/dist/src/security/scanner/types.d.ts +49 -1
  135. package/dist/src/security/scanner/types.d.ts.map +1 -1
  136. package/dist/src/security/scanner/typosquat-reference-list.d.ts +54 -0
  137. package/dist/src/security/scanner/typosquat-reference-list.d.ts.map +1 -0
  138. package/dist/src/security/scanner/typosquat-reference-list.js +55 -0
  139. package/dist/src/security/scanner/typosquat-reference-list.js.map +1 -0
  140. package/dist/src/security/scanner/typosquat-reference-list.test.d.ts +5 -0
  141. package/dist/src/security/scanner/typosquat-reference-list.test.d.ts.map +1 -0
  142. package/dist/src/security/scanner/typosquat-reference-list.test.js +52 -0
  143. package/dist/src/security/scanner/typosquat-reference-list.test.js.map +1 -0
  144. package/dist/src/security/scanner/typosquat.d.ts +100 -0
  145. package/dist/src/security/scanner/typosquat.d.ts.map +1 -0
  146. package/dist/src/security/scanner/typosquat.js +256 -0
  147. package/dist/src/security/scanner/typosquat.js.map +1 -0
  148. package/dist/src/security/scanner/typosquat.test.d.ts +12 -0
  149. package/dist/src/security/scanner/typosquat.test.d.ts.map +1 -0
  150. package/dist/src/security/scanner/typosquat.test.js +140 -0
  151. package/dist/src/security/scanner/typosquat.test.js.map +1 -0
  152. package/dist/src/security/scanner/weights.d.ts.map +1 -1
  153. package/dist/src/security/scanner/weights.js +10 -0
  154. package/dist/src/security/scanner/weights.js.map +1 -1
  155. package/dist/src/services/skill-installation.helpers.d.ts +21 -0
  156. package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
  157. package/dist/src/services/skill-installation.helpers.js +46 -2
  158. package/dist/src/services/skill-installation.helpers.js.map +1 -1
  159. package/dist/src/services/skill-installation.helpers.test.d.ts +2 -0
  160. package/dist/src/services/skill-installation.helpers.test.d.ts.map +1 -0
  161. package/dist/src/services/skill-installation.helpers.test.js +80 -0
  162. package/dist/src/services/skill-installation.helpers.test.js.map +1 -0
  163. package/dist/src/services/skill-installation.io.d.ts.map +1 -1
  164. package/dist/src/services/skill-installation.io.js +12 -1
  165. package/dist/src/services/skill-installation.io.js.map +1 -1
  166. package/dist/src/sync/inventory-collector.d.ts +15 -4
  167. package/dist/src/sync/inventory-collector.d.ts.map +1 -1
  168. package/dist/src/sync/inventory-collector.js +75 -26
  169. package/dist/src/sync/inventory-collector.js.map +1 -1
  170. package/dist/src/sync/inventory-collector.test.d.ts +15 -1
  171. package/dist/src/sync/inventory-collector.test.d.ts.map +1 -1
  172. package/dist/src/sync/inventory-collector.test.js +75 -5
  173. package/dist/src/sync/inventory-collector.test.js.map +1 -1
  174. package/dist/src/telemetry/tracer-imports.d.ts +1 -1
  175. package/dist/src/telemetry/tracer-imports.js +1 -1
  176. package/dist/src/telemetry/tracer.js +1 -1
  177. package/dist/tests/SecurityScanner.ai-defence.test.js +32 -7
  178. package/dist/tests/SecurityScanner.ai-defence.test.js.map +1 -1
  179. package/dist/tests/SecurityScanner.exec.test.js +79 -0
  180. package/dist/tests/SecurityScanner.exec.test.js.map +1 -1
  181. package/dist/tests/SecurityScanner.scoring.test.js +60 -0
  182. package/dist/tests/SecurityScanner.scoring.test.js.map +1 -1
  183. package/dist/tests/SwarmIndexer.test.js +4 -5
  184. package/dist/tests/SwarmIndexer.test.js.map +1 -1
  185. package/dist/tests/security/ContinuousSecurity.performance.test.js +6 -0
  186. package/dist/tests/security/ContinuousSecurity.performance.test.js.map +1 -1
  187. package/dist/tests/security/ContinuousSecurity.test.js +15 -0
  188. package/dist/tests/security/ContinuousSecurity.test.js.map +1 -1
  189. package/dist/tests/security/ad-crlf-injection.test.d.ts +18 -0
  190. package/dist/tests/security/ad-crlf-injection.test.d.ts.map +1 -0
  191. package/dist/tests/security/ad-crlf-injection.test.js +150 -0
  192. package/dist/tests/security/ad-crlf-injection.test.js.map +1 -0
  193. package/dist/tests/security/ai-defence-multiline-promotion.test.d.ts +25 -0
  194. package/dist/tests/security/ai-defence-multiline-promotion.test.d.ts.map +1 -0
  195. package/dist/tests/security/ai-defence-multiline-promotion.test.js +138 -0
  196. package/dist/tests/security/ai-defence-multiline-promotion.test.js.map +1 -0
  197. package/dist/tests/security/nested-instruction-long-attribute.test.d.ts +15 -0
  198. package/dist/tests/security/nested-instruction-long-attribute.test.d.ts.map +1 -0
  199. package/dist/tests/security/nested-instruction-long-attribute.test.js +60 -0
  200. package/dist/tests/security/nested-instruction-long-attribute.test.js.map +1 -0
  201. package/dist/tests/security/pattern-scope.test.d.ts +17 -0
  202. package/dist/tests/security/pattern-scope.test.d.ts.map +1 -0
  203. package/dist/tests/security/pattern-scope.test.js +128 -0
  204. package/dist/tests/security/pattern-scope.test.js.map +1 -0
  205. package/dist/tests/security/pii-detection.test.js +39 -0
  206. package/dist/tests/security/pii-detection.test.js.map +1 -1
  207. package/dist/tests/security/scanner-evidence-tiers.test.d.ts +30 -0
  208. package/dist/tests/security/scanner-evidence-tiers.test.d.ts.map +1 -0
  209. package/dist/tests/security/scanner-evidence-tiers.test.js +258 -0
  210. package/dist/tests/security/scanner-evidence-tiers.test.js.map +1 -0
  211. package/dist/tests/security/scanner-privesc-severity.test.d.ts +23 -0
  212. package/dist/tests/security/scanner-privesc-severity.test.d.ts.map +1 -0
  213. package/dist/tests/security/scanner-privesc-severity.test.js +43 -0
  214. package/dist/tests/security/scanner-privesc-severity.test.js.map +1 -0
  215. package/dist/tests/security/scanner-regression-guard.exec-locality.test.d.ts +20 -0
  216. package/dist/tests/security/scanner-regression-guard.exec-locality.test.d.ts.map +1 -0
  217. package/dist/tests/security/scanner-regression-guard.exec-locality.test.js +71 -0
  218. package/dist/tests/security/scanner-regression-guard.exec-locality.test.js.map +1 -0
  219. package/dist/tests/security/scanner-regression-guard.test.d.ts +24 -0
  220. package/dist/tests/security/scanner-regression-guard.test.d.ts.map +1 -1
  221. package/dist/tests/security/scanner-regression-guard.test.js +60 -3
  222. package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
  223. package/dist/tests/security/scanner-ssrf-word-boundary.test.d.ts +19 -0
  224. package/dist/tests/security/scanner-ssrf-word-boundary.test.d.ts.map +1 -0
  225. package/dist/tests/security/scanner-ssrf-word-boundary.test.js +65 -0
  226. package/dist/tests/security/scanner-ssrf-word-boundary.test.js.map +1 -0
  227. package/dist/tests/security.test.js +11 -2
  228. package/dist/tests/security.test.js.map +1 -1
  229. package/dist/tests/services/aidefence-feedback.test.js +1 -0
  230. package/dist/tests/services/aidefence-feedback.test.js.map +1 -1
  231. package/dist/tests/skill-scanner/allowlist.test.js +16 -7
  232. package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
  233. package/dist/tests/unit/services/skill-installation.io.test.js +54 -1
  234. package/dist/tests/unit/services/skill-installation.io.test.js.map +1 -1
  235. package/package.json +13 -8
@@ -0,0 +1,9 @@
1
+ /**
2
+ * SMI-5531: Atomic config-write primitive tests.
3
+ *
4
+ * Uses the same tmpdir-per-test isolation harness as device-identity.test.ts
5
+ * / index.test.ts (no shared HOME mutation needed here — these primitives
6
+ * take an explicit path, not the global config path).
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=config-atomic-write.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-atomic-write.test.d.ts","sourceRoot":"","sources":["../../../src/config/config-atomic-write.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * SMI-5531: Atomic config-write primitive tests.
3
+ *
4
+ * Uses the same tmpdir-per-test isolation harness as device-identity.test.ts
5
+ * / index.test.ts (no shared HOME mutation needed here — these primitives
6
+ * take an explicit path, not the global config path).
7
+ */
8
+ import { describe, it, expect } from 'vitest';
9
+ import { existsSync, mkdirSync, readFileSync, readdirSync, utimesSync, writeFileSync } from 'fs';
10
+ import * as path from 'path';
11
+ import * as os from 'os';
12
+ import { acquireConfigLock, atomicWriteFile } from './config-atomic-write.js';
13
+ function makeTempDir() {
14
+ const dir = path.join(os.tmpdir(), `skillsmith-atomic-write-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
15
+ mkdirSync(dir, { recursive: true });
16
+ return dir;
17
+ }
18
+ describe('acquireConfigLock — mutual exclusion', () => {
19
+ it('a second acquire attempt while the lock is held times out', () => {
20
+ const dir = makeTempDir();
21
+ const configPath = path.join(dir, 'config.json');
22
+ const release = acquireConfigLock(configPath);
23
+ try {
24
+ expect(() => acquireConfigLock(configPath, 200)).toThrow(/Timed out waiting for config lock/);
25
+ }
26
+ finally {
27
+ release();
28
+ }
29
+ });
30
+ it('a subsequent acquire succeeds immediately after release', () => {
31
+ const dir = makeTempDir();
32
+ const configPath = path.join(dir, 'config.json');
33
+ const release = acquireConfigLock(configPath);
34
+ release();
35
+ // Should not throw / should not need to wait out any timeout.
36
+ const secondRelease = acquireConfigLock(configPath, 200);
37
+ secondRelease();
38
+ });
39
+ it('force-clears a stale lock (older than the staleness threshold) instead of waiting out the full timeout', () => {
40
+ const dir = makeTempDir();
41
+ const configPath = path.join(dir, 'config.json');
42
+ const lockPath = `${configPath}.lock`;
43
+ // Simulate a lock abandoned by a crashed process: create it, then
44
+ // backdate its mtime well past the staleness threshold.
45
+ writeFileSync(lockPath, '99999');
46
+ const longAgo = new Date(Date.now() - 60_000);
47
+ utimesSync(lockPath, longAgo, longAgo);
48
+ const start = Date.now();
49
+ // Even with a tight timeout budget, stale-lock recovery should let this
50
+ // succeed almost immediately rather than burning the whole window.
51
+ const release = acquireConfigLock(configPath, 500);
52
+ const elapsedMs = Date.now() - start;
53
+ release();
54
+ expect(elapsedMs).toBeLessThan(500);
55
+ });
56
+ it('release() is idempotent — calling it twice does not throw', () => {
57
+ const dir = makeTempDir();
58
+ const configPath = path.join(dir, 'config.json');
59
+ const release = acquireConfigLock(configPath);
60
+ release();
61
+ expect(() => release()).not.toThrow();
62
+ });
63
+ });
64
+ describe('atomicWriteFile', () => {
65
+ it('writes the given content to the target path', () => {
66
+ const dir = makeTempDir();
67
+ const filePath = path.join(dir, 'config.json');
68
+ atomicWriteFile(filePath, JSON.stringify({ a: 1 }), 0o600);
69
+ expect(JSON.parse(readFileSync(filePath, 'utf-8'))).toEqual({ a: 1 });
70
+ });
71
+ it('leaves no stray temp file behind after a successful write', () => {
72
+ const dir = makeTempDir();
73
+ const filePath = path.join(dir, 'config.json');
74
+ atomicWriteFile(filePath, '{}', 0o600);
75
+ const entries = readdirSync(dir);
76
+ expect(entries.some((f) => f.endsWith('.tmp'))).toBe(false);
77
+ });
78
+ it('overwrites existing content atomically (rename-in-place, not append)', () => {
79
+ const dir = makeTempDir();
80
+ const filePath = path.join(dir, 'config.json');
81
+ atomicWriteFile(filePath, JSON.stringify({ a: 1 }), 0o600);
82
+ atomicWriteFile(filePath, JSON.stringify({ b: 2 }), 0o600);
83
+ expect(JSON.parse(readFileSync(filePath, 'utf-8'))).toEqual({ b: 2 });
84
+ });
85
+ it('sets the requested file mode', () => {
86
+ const dir = makeTempDir();
87
+ const filePath = path.join(dir, 'config.json');
88
+ atomicWriteFile(filePath, '{}', 0o600);
89
+ expect(existsSync(filePath)).toBe(true);
90
+ });
91
+ });
92
+ //# sourceMappingURL=config-atomic-write.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-atomic-write.test.js","sourceRoot":"","sources":["../../../src/config/config-atomic-write.test.ts"],"names":[],"mappings":"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"}
@@ -43,10 +43,26 @@ export declare function getDeviceId(): string | undefined;
43
43
  * `/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i`
44
44
  *
45
45
  * Preserves any existing `deviceLabel` and `lastPushAt` during the write.
46
+ * Concurrency-safe across processes (SMI-5531) — see
47
+ * {@link getOrCreatePersistedId}.
46
48
  *
47
49
  * SMI-5391
48
50
  */
49
51
  export declare function getOrCreateDeviceId(): string;
52
+ /**
53
+ * Return the persisted per-install telemetry identifier, creating and
54
+ * persisting a new one (`sha256(randomUUID())`) if none exists yet.
55
+ *
56
+ * SMI-5531: generated and persisted UNCONDITIONALLY — this call must
57
+ * succeed and persist an id regardless of `SKILLSMITH_TELEMETRY_ENABLED` /
58
+ * `POSTHOG_API_KEY`, which is exactly the env-gated condition that made the
59
+ * legacy `context.distinctId` inert for virtually every real user. If a
60
+ * future refactor re-couples this to that legacy gate, this fix is inert
61
+ * again under a new field name — do not add any env/flag check here.
62
+ *
63
+ * Concurrency-safe across processes — see {@link getOrCreatePersistedId}.
64
+ */
65
+ export declare function getOrCreateInstallId(): string;
50
66
  /**
51
67
  * Set (or clear) the optional user-facing label for this device.
52
68
  *
@@ -1 +1 @@
1
- {"version":3,"file":"device-identity.d.ts","sourceRoot":"","sources":["../../../src/config/device-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AASH;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,MAAM,GAAG,SAAS,CAEhD;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAa5C;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAQ9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAMD;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,IAAI,OAAO,CAIxD;AAMD;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAE3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAQ9D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,UAAU,CAAC,EAAE,MAAM,EACnB,UAAU,SAAsB,GAC/B,OAAO,CAKT"}
1
+ {"version":3,"file":"device-identity.d.ts","sourceRoot":"","sources":["../../../src/config/device-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAqEH;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,MAAM,GAAG,SAAS,CAEhD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAS5C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAS7C;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAQ9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAMD;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,IAAI,OAAO,CAIxD;AAMD;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAE3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAQ9D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,UAAU,CAAC,EAAE,MAAM,EACnB,UAAU,SAAsB,GAC/B,OAAO,CAKT"}
@@ -28,8 +28,63 @@
28
28
  * }
29
29
  * ```
30
30
  */
31
- import { randomUUID } from 'node:crypto';
32
- import { loadConfig, saveConfig } from './index.js';
31
+ import { randomUUID, createHash } from 'node:crypto';
32
+ import { chmodSync } from 'node:fs';
33
+ import { loadConfig, saveConfig, getConfigPath, ensureConfigDir } from './index.js';
34
+ import { acquireConfigLock, atomicWriteFile } from './config-atomic-write.js';
35
+ // ============================================================================
36
+ // Shared create-if-absent primitive (SMI-5531)
37
+ // ============================================================================
38
+ /**
39
+ * Create-if-absent primitive shared by `getOrCreateDeviceId` and
40
+ * `getOrCreateInstallId`.
41
+ *
42
+ * An unguarded fast-path read (`getExisting(loadConfig())`) covers the
43
+ * common case cheaply. Only on a miss do we pay for
44
+ * {@link acquireConfigLock} — and, critically, RE-READ config state after
45
+ * acquiring it: another process may have created the value in the window
46
+ * between our fast-path read and lock acquisition. Skipping that re-check
47
+ * (e.g. by calling the plain `saveConfig()` with a value already decided
48
+ * before the lock was held) is exactly the TOCTOU shape that let two
49
+ * concurrent callers generate and persist two DIFFERENT ids — whichever
50
+ * wrote last would win on disk, but the loser's caller would still return
51
+ * its own (now-orphaned) generated value, so the two callers would never
52
+ * converge. `saveConfig()` itself is deliberately NOT used inside the
53
+ * locked section below: it would try to (re-)acquire this same
54
+ * non-reentrant lock and deadlock.
55
+ *
56
+ * @param getExisting - Reads the target id out of a config snapshot.
57
+ * @param generate - Produces a new id when none exists.
58
+ * @param merge - Returns a new full config with the id applied on top of `latest`.
59
+ */
60
+ function getOrCreatePersistedId(getExisting, generate, merge) {
61
+ const fastPathExisting = getExisting(loadConfig());
62
+ if (fastPathExisting)
63
+ return fastPathExisting;
64
+ // The lock file lives alongside config.json — the directory must exist
65
+ // BEFORE we try to create it, not after acquiring the lock.
66
+ ensureConfigDir();
67
+ const configPath = getConfigPath();
68
+ const release = acquireConfigLock(configPath);
69
+ try {
70
+ const latest = loadConfig();
71
+ const alreadyCreated = getExisting(latest);
72
+ if (alreadyCreated)
73
+ return alreadyCreated;
74
+ const id = generate();
75
+ atomicWriteFile(configPath, JSON.stringify(merge(latest, id), null, 2), 0o600);
76
+ try {
77
+ chmodSync(configPath, 0o600);
78
+ }
79
+ catch {
80
+ // Ignore chmod errors on Windows.
81
+ }
82
+ return id;
83
+ }
84
+ finally {
85
+ release();
86
+ }
87
+ }
33
88
  // ============================================================================
34
89
  // Device identity
35
90
  // ============================================================================
@@ -50,22 +105,35 @@ export function getDeviceId() {
50
105
  * `/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i`
51
106
  *
52
107
  * Preserves any existing `deviceLabel` and `lastPushAt` during the write.
108
+ * Concurrency-safe across processes (SMI-5531) — see
109
+ * {@link getOrCreatePersistedId}.
53
110
  *
54
111
  * SMI-5391
55
112
  */
56
113
  export function getOrCreateDeviceId() {
57
- const config = loadConfig();
58
- const existing = config.inventory?.deviceId;
59
- if (existing)
60
- return existing;
61
- const deviceId = randomUUID();
62
- saveConfig({
63
- inventory: {
64
- ...config.inventory,
65
- deviceId,
66
- },
67
- });
68
- return deviceId;
114
+ return getOrCreatePersistedId((config) => config.inventory?.deviceId, () => randomUUID(), (latest, deviceId) => ({
115
+ ...latest,
116
+ inventory: { ...latest.inventory, deviceId },
117
+ }));
118
+ }
119
+ /**
120
+ * Return the persisted per-install telemetry identifier, creating and
121
+ * persisting a new one (`sha256(randomUUID())`) if none exists yet.
122
+ *
123
+ * SMI-5531: generated and persisted UNCONDITIONALLY — this call must
124
+ * succeed and persist an id regardless of `SKILLSMITH_TELEMETRY_ENABLED` /
125
+ * `POSTHOG_API_KEY`, which is exactly the env-gated condition that made the
126
+ * legacy `context.distinctId` inert for virtually every real user. If a
127
+ * future refactor re-couples this to that legacy gate, this fix is inert
128
+ * again under a new field name — do not add any env/flag check here.
129
+ *
130
+ * Concurrency-safe across processes — see {@link getOrCreatePersistedId}.
131
+ */
132
+ export function getOrCreateInstallId() {
133
+ return getOrCreatePersistedId((config) => config.telemetry?.installId, () => createHash('sha256').update(randomUUID()).digest('hex'), (latest, installId) => ({
134
+ ...latest,
135
+ telemetry: { ...latest.telemetry, installId },
136
+ }));
69
137
  }
70
138
  /**
71
139
  * Set (or clear) the optional user-facing label for this device.
@@ -1 +1 @@
1
- {"version":3,"file":"device-identity.js","sourceRoot":"","sources":["../../../src/config/device-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEnD,+EAA+E;AAC/E,kBAAkB;AAClB,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,UAAU,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAA;AACzC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAA;IAC3B,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAA;IAC3C,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAE7B,MAAM,QAAQ,GAAG,UAAU,EAAE,CAAA;IAC7B,UAAU,CAAC;QACT,SAAS,EAAE;YACT,GAAG,MAAM,CAAC,SAAS;YACnB,QAAQ;SACT;KACF,CAAC,CAAA;IACF,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,KAAyB;IACtD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAA;IAC3B,UAAU,CAAC;QACT,SAAS,EAAE;YACT,GAAG,MAAM,CAAC,SAAS;YACnB,WAAW,EAAE,KAAK;SACnB;KACF,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY;IAC1B,UAAU,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAA;AACtC,CAAC;AAED,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E;;;;;;;;;GASG;AACH,MAAM,UAAU,8BAA8B;IAC5C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAA;IACpD,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAA;IACtB,OAAO,GAAG,KAAK,GAAG,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,MAAM,CAAA;AACpD,CAAC;AAED,+EAA+E;AAC/E,gBAAgB;AAChB,+EAA+E;AAE/E;;;;GAIG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,UAAU,EAAE,CAAC,SAAS,EAAE,UAAU,CAAA;AAC3C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,YAAoB;IACtD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAA;IAC3B,UAAU,CAAC;QACT,SAAS,EAAE;YACT,GAAG,MAAM,CAAC,SAAS;YACnB,UAAU,EAAE,YAAY;SACzB;KACF,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAC5B,GAAW,EACX,UAAmB,EACnB,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;IAEhC,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAA;IAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;IACnC,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,OAAO,GAAG,GAAG,IAAI,IAAI,UAAU,CAAA;AACjC,CAAC"}
1
+ {"version":3,"file":"device-identity.js","sourceRoot":"","sources":["../../../src/config/device-identity.ts"],"names":[],"mappings":"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"}
@@ -9,22 +9,61 @@
9
9
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
10
10
  import * as path from 'path';
11
11
  import * as os from 'os';
12
- import { getDeviceId, getOrCreateDeviceId, setDeviceLabel, forgetDevice, isInventorySyncDisabledLocally, getLastInventoryPushAt, recordInventoryPush, shouldAutoPush, } from './device-identity.js';
12
+ import { execFile } from 'node:child_process';
13
+ import { promisify } from 'node:util';
14
+ import { mkdtempSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { getDeviceId, getOrCreateDeviceId, getOrCreateInstallId, setDeviceLabel, forgetDevice, isInventorySyncDisabledLocally, getLastInventoryPushAt, recordInventoryPush, shouldAutoPush, } from './device-identity.js';
17
+ import { loadConfig } from './index.js';
18
+ const execFileAsync = promisify(execFile);
13
19
  // ---------------------------------------------------------------------------
14
20
  // Helpers
15
21
  // ---------------------------------------------------------------------------
16
22
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
23
+ const SHA256_HEX_RE = /^[0-9a-f]{64}$/i;
17
24
  function makeTempConfigDir() {
18
25
  return path.join(os.tmpdir(), `skillsmith-device-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
19
26
  }
27
+ /**
28
+ * Absolute path to this module's source, so a spawned subprocess can import
29
+ * the SAME implementation under test (not a copy/reimplementation) via `tsx`
30
+ * (already a repo devDependency — see CLAUDE.md's `npx tsx` usage).
31
+ */
32
+ const DEVICE_IDENTITY_SRC = fileURLToPath(new URL('./device-identity.ts', import.meta.url));
33
+ /**
34
+ * Run `exportName()` (a zero-arg, string-returning export of
35
+ * device-identity.ts) in a real, separate OS process with `HOME` pointed at
36
+ * `homeDir`. Used to exercise genuine CROSS-PROCESS races that a single
37
+ * Node process's single-threaded event loop cannot produce on its own —
38
+ * `getOrCreateDeviceId`/`getOrCreateInstallId` are fully synchronous, so two
39
+ * "concurrent" calls within one process never actually interleave.
40
+ */
41
+ async function runInSubprocess(exportName, homeDir) {
42
+ const scriptDir = mkdtempSync(path.join(os.tmpdir(), 'skillsmith-concurrency-'));
43
+ const scriptPath = path.join(scriptDir, 'run.mts');
44
+ writeFileSync(scriptPath, `import { ${exportName} } from ${JSON.stringify(DEVICE_IDENTITY_SRC)}\n` +
45
+ `process.stdout.write(${exportName}())\n`);
46
+ const { stdout } = await execFileAsync('npx', ['tsx', scriptPath], {
47
+ env: { ...process.env, HOME: homeDir },
48
+ timeout: 15_000,
49
+ });
50
+ return stdout.trim();
51
+ }
20
52
  // ---------------------------------------------------------------------------
21
53
  // Setup / teardown
22
54
  // ---------------------------------------------------------------------------
23
55
  let savedHome;
24
56
  let savedInventoryDisable;
57
+ // SMI-5531: saved/restored so getOrCreateInstallId tests can assert
58
+ // unconditional generation under the EXACT conditions of a real end user's
59
+ // environment (both unset) without leaking into other tests/files.
60
+ let savedTelemetryEnabled;
61
+ let savedPostHogApiKey;
25
62
  beforeEach(() => {
26
63
  savedHome = process.env.HOME;
27
64
  savedInventoryDisable = process.env.SKILLSMITH_INVENTORY_DISABLE;
65
+ savedTelemetryEnabled = process.env.SKILLSMITH_TELEMETRY_ENABLED;
66
+ savedPostHogApiKey = process.env.POSTHOG_API_KEY;
28
67
  // Point HOME at a fresh tmpdir so each test begins with a blank config
29
68
  process.env.HOME = makeTempConfigDir();
30
69
  delete process.env.SKILLSMITH_INVENTORY_DISABLE;
@@ -42,6 +81,18 @@ afterEach(() => {
42
81
  else {
43
82
  delete process.env.SKILLSMITH_INVENTORY_DISABLE;
44
83
  }
84
+ if (savedTelemetryEnabled !== undefined) {
85
+ process.env.SKILLSMITH_TELEMETRY_ENABLED = savedTelemetryEnabled;
86
+ }
87
+ else {
88
+ delete process.env.SKILLSMITH_TELEMETRY_ENABLED;
89
+ }
90
+ if (savedPostHogApiKey !== undefined) {
91
+ process.env.POSTHOG_API_KEY = savedPostHogApiKey;
92
+ }
93
+ else {
94
+ delete process.env.POSTHOG_API_KEY;
95
+ }
45
96
  });
46
97
  // ---------------------------------------------------------------------------
47
98
  // getOrCreateDeviceId
@@ -62,6 +113,112 @@ describe('getOrCreateDeviceId', () => {
62
113
  });
63
114
  });
64
115
  // ---------------------------------------------------------------------------
116
+ // getOrCreateInstallId (SMI-5531)
117
+ //
118
+ // THE MOST IMPORTANT TEST SUITE IN THIS FILE: SMI-5531's root cause was that
119
+ // `context.distinctId` was only ever populated when BOTH
120
+ // SKILLSMITH_TELEMETRY_ENABLED=true AND a POSTHOG_API_KEY were present —
121
+ // conditions never true for a real end-user install. `getOrCreateInstallId`
122
+ // exists specifically to be immune to that. If these tests pass while a
123
+ // future refactor re-couples generation to that legacy gate, the fix is
124
+ // silently inert again under a new field name — the source-text guard test
125
+ // below is a second, independent line of defense against exactly that.
126
+ // ---------------------------------------------------------------------------
127
+ describe('getOrCreateInstallId — unconditional generation (the critical regression test)', () => {
128
+ it('generates and persists an id with SKILLSMITH_TELEMETRY_ENABLED unset and no POSTHOG_API_KEY present', () => {
129
+ delete process.env.SKILLSMITH_TELEMETRY_ENABLED;
130
+ delete process.env.POSTHOG_API_KEY;
131
+ // Sanity-check the test itself actually reflects a real end user's
132
+ // environment before asserting anything about the function under test.
133
+ expect(process.env.SKILLSMITH_TELEMETRY_ENABLED).toBeUndefined();
134
+ expect(process.env.POSTHOG_API_KEY).toBeUndefined();
135
+ const id = getOrCreateInstallId();
136
+ expect(SHA256_HEX_RE.test(id)).toBe(true);
137
+ // Persisted, not just returned in memory — a fresh read must see it too.
138
+ expect(loadConfig().telemetry?.installId).toBe(id);
139
+ });
140
+ it('still generates unconditionally when SKILLSMITH_TELEMETRY_ENABLED is explicitly "false"', () => {
141
+ process.env.SKILLSMITH_TELEMETRY_ENABLED = 'false';
142
+ delete process.env.POSTHOG_API_KEY;
143
+ const id = getOrCreateInstallId();
144
+ expect(SHA256_HEX_RE.test(id)).toBe(true);
145
+ });
146
+ it('is idempotent — second call returns the same id (no regeneration) with no env vars set', () => {
147
+ delete process.env.SKILLSMITH_TELEMETRY_ENABLED;
148
+ delete process.env.POSTHOG_API_KEY;
149
+ const first = getOrCreateInstallId();
150
+ const second = getOrCreateInstallId();
151
+ expect(second).toBe(first);
152
+ });
153
+ it('generates a DIFFERENT id than getOrCreateDeviceId — distinct namespaces, not aliases', () => {
154
+ delete process.env.SKILLSMITH_TELEMETRY_ENABLED;
155
+ delete process.env.POSTHOG_API_KEY;
156
+ const installId = getOrCreateInstallId();
157
+ const deviceId = getOrCreateDeviceId();
158
+ expect(installId).not.toBe(deviceId);
159
+ });
160
+ it('does not clobber an existing deviceId when creating an installId, and vice versa', () => {
161
+ delete process.env.SKILLSMITH_TELEMETRY_ENABLED;
162
+ delete process.env.POSTHOG_API_KEY;
163
+ const deviceId = getOrCreateDeviceId();
164
+ const installId = getOrCreateInstallId();
165
+ expect(getDeviceId()).toBe(deviceId);
166
+ expect(loadConfig().telemetry?.installId).toBe(installId);
167
+ });
168
+ it('the implementation never READS SKILLSMITH_TELEMETRY_ENABLED or POSTHOG_API_KEY (must stay decoupled from the legacy env-gated distinctId path)', () => {
169
+ // Checks for the actual `process.env.X` access pattern, not any textual
170
+ // mention — this module's own doc comments legitimately name these env
171
+ // vars in prose to explain why they're deliberately never read here.
172
+ const source = readFileSync(DEVICE_IDENTITY_SRC, 'utf-8');
173
+ expect(source).not.toMatch(/process\.env(\.|\[['"])SKILLSMITH_TELEMETRY_ENABLED/);
174
+ expect(source).not.toMatch(/process\.env(\.|\[['"])POSTHOG_API_KEY/);
175
+ });
176
+ });
177
+ // ---------------------------------------------------------------------------
178
+ // getOrCreateInstallId — cross-process concurrency (SMI-5531)
179
+ //
180
+ // getOrCreateDeviceId/getOrCreateInstallId are fully synchronous, so two
181
+ // "concurrent" calls WITHIN one Node process never truly interleave — real
182
+ // concurrency requires a real second OS process. These tests spawn genuine
183
+ // subprocesses (via tsx) pointed at the SAME HOME, so the underlying
184
+ // cross-process lock (config-atomic-write.ts) is actually exercised.
185
+ // ---------------------------------------------------------------------------
186
+ describe('getOrCreateInstallId — cross-process concurrency', () => {
187
+ it('two simultaneous calls from separate processes converge on the same id, with a valid (non-corrupted) config.json', async () => {
188
+ const sharedHome = makeTempConfigDir();
189
+ // No config.json exists yet — both processes race the "create" path.
190
+ const [idA, idB] = await Promise.all([
191
+ runInSubprocess('getOrCreateInstallId', sharedHome),
192
+ runInSubprocess('getOrCreateInstallId', sharedHome),
193
+ ]);
194
+ expect(idA).toBe(idB);
195
+ expect(SHA256_HEX_RE.test(idA)).toBe(true);
196
+ const skillsmithDir = path.join(sharedHome, '.skillsmith');
197
+ const configPath = path.join(skillsmithDir, 'config.json');
198
+ const raw = readFileSync(configPath, 'utf-8');
199
+ const parsed = JSON.parse(raw); // throws if corrupted
200
+ expect(parsed.telemetry?.installId).toBe(idA);
201
+ // No stray lock/temp files left behind after both processes finished.
202
+ const entries = readdirSync(skillsmithDir);
203
+ expect(entries.some((f) => f.endsWith('.lock'))).toBe(false);
204
+ expect(entries.some((f) => f.endsWith('.tmp'))).toBe(false);
205
+ }, 20_000);
206
+ it('a concurrent saveConfig write for a sibling key is never dropped (no lost update)', async () => {
207
+ const sharedHome = makeTempConfigDir();
208
+ const [installId] = await Promise.all([
209
+ runInSubprocess('getOrCreateInstallId', sharedHome),
210
+ runInSubprocess('getOrCreateDeviceId', sharedHome),
211
+ ]);
212
+ const configPath = path.join(sharedHome, '.skillsmith', 'config.json');
213
+ const parsed = JSON.parse(readFileSync(configPath, 'utf-8'));
214
+ // BOTH concurrently-written keys must be present — neither write may
215
+ // have silently clobbered the other's.
216
+ expect(parsed.telemetry?.installId).toBe(installId);
217
+ expect(SHA256_HEX_RE.test(parsed.telemetry?.installId ?? '')).toBe(true);
218
+ expect(UUID_RE.test(parsed.inventory?.deviceId ?? '')).toBe(true);
219
+ }, 20_000);
220
+ });
221
+ // ---------------------------------------------------------------------------
65
222
  // getDeviceId (pure read)
66
223
  // ---------------------------------------------------------------------------
67
224
  describe('getDeviceId', () => {
@@ -1 +1 @@
1
- {"version":3,"file":"device-identity.test.js","sourceRoot":"","sources":["../../../src/config/device-identity.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"device-identity.test.js","sourceRoot":"","sources":["../../../src/config/device-identity.test.ts"],"names":[],"mappings":"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"}
@@ -44,6 +44,12 @@ export interface SkillsmithConfig {
44
44
  telemetry?: {
45
45
  /** Enable telemetry (default: false, opt-in) */
46
46
  enabled?: boolean;
47
+ /**
48
+ * Stable per-install identifier (SMI-5531): `sha256(randomUUID())`,
49
+ * generated unconditionally, decoupled from `SKILLSMITH_TELEMETRY_ENABLED`
50
+ * / `POSTHOG_API_KEY`. See `getOrCreateInstallId()` in `./device-identity.ts`.
51
+ */
52
+ installId?: string;
47
53
  };
48
54
  /** Sync settings */
49
55
  sync?: {
@@ -113,6 +119,14 @@ export declare function loadConfig(): SkillsmithConfig;
113
119
  * Save configuration to ~/.skillsmith/config.json
114
120
  * Creates the file with 0600 permissions (owner read/write only)
115
121
  *
122
+ * SMI-5531: serialized under a cross-process lock ({@link acquireConfigLock})
123
+ * and written atomically (temp-file + rename, {@link atomicWriteFile}) —
124
+ * closes a lost-update race (two concurrent writers each dropping the
125
+ * other's key) and a torn-write race under a bare `writeFileSync`.
126
+ * `existingConfig` is (re-)read AFTER the lock is acquired, not before, or a
127
+ * writer that read stale state while waiting would reintroduce the same
128
+ * lost-update bug.
129
+ *
116
130
  * @param config - Configuration to save (merged with existing)
117
131
  * @param options - Save options
118
132
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/config/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAMH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,4BAA4B;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,yBAAyB;IACzB,SAAS,CAAC,EAAE;QACV,gDAAgD;QAChD,OAAO,CAAC,EAAE,OAAO,CAAA;KAClB,CAAA;IACD,oBAAoB;IACpB,IAAI,CAAC,EAAE;QACL,6BAA6B;QAC7B,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,oCAAoC;QACpC,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,qDAAqD;IACrD,SAAS,CAAC,EAAE;QACV,gDAAgD;QAChD,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,yCAAyC;QACzC,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,gFAAgF;QAChF,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,sDAAsD;IACtD,KAAK,CAAC,EAAE;QACN,kFAAkF;QAClF,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB;;;WAGG;QACH,cAAc,CAAC,EAAE,MAAM,CAAA;KACxB,CAAA;CACF;AA0DD;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAKtC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAUpC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,gBAAgB,CAc7C;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,EACjC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAoB,GAC7C,IAAI,CAmCN;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,IAAI,MAAM,GAAG,SAAS,CAU9C;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,UAAU,SAA+B,GAAG,MAAM,CAQ/E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAOxC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAU5C;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGxD;AAMD;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe/D;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC;IAC3C,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAC,CAgCD;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC;IAC7C,aAAa,EAAE,OAAO,CAAA;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAA;CAC9C,CAAC,CA2CD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/config/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAOH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,4BAA4B;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,yBAAyB;IACzB,SAAS,CAAC,EAAE;QACV,gDAAgD;QAChD,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB;;;;WAIG;QACH,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;IACD,oBAAoB;IACpB,IAAI,CAAC,EAAE;QACL,6BAA6B;QAC7B,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,oCAAoC;QACpC,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,qDAAqD;IACrD,SAAS,CAAC,EAAE;QACV,gDAAgD;QAChD,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,yCAAyC;QACzC,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,gFAAgF;QAChF,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,sDAAsD;IACtD,KAAK,CAAC,EAAE;QACN,kFAAkF;QAClF,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB;;;WAGG;QACH,cAAc,CAAC,EAAE,MAAM,CAAA;KACxB,CAAA;CACF;AA0DD;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAKtC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAUpC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,gBAAgB,CAc7C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,EACjC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAoB,GAC7C,IAAI,CAyCN;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,IAAI,MAAM,GAAG,SAAS,CAU9C;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,UAAU,SAA+B,GAAG,MAAM,CAQ/E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAOxC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAU5C;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGxD;AAMD;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe/D;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC;IAC3C,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAC,CAgCD;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC;IAC7C,aAAa,EAAE,OAAO,CAAA;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAA;CAC9C,CAAC,CA2CD"}
@@ -32,7 +32,8 @@
32
32
  */
33
33
  import { homedir } from 'os';
34
34
  import { join } from 'path';
35
- import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs';
35
+ import { existsSync, readFileSync, mkdirSync, chmodSync } from 'fs';
36
+ import { acquireConfigLock, atomicWriteFile } from './config-atomic-write.js';
36
37
  /** Default config directory name */
37
38
  const CONFIG_DIR = '.skillsmith';
38
39
  /** Default config file name */
@@ -139,28 +140,43 @@ export function loadConfig() {
139
140
  * Save configuration to ~/.skillsmith/config.json
140
141
  * Creates the file with 0600 permissions (owner read/write only)
141
142
  *
143
+ * SMI-5531: serialized under a cross-process lock ({@link acquireConfigLock})
144
+ * and written atomically (temp-file + rename, {@link atomicWriteFile}) —
145
+ * closes a lost-update race (two concurrent writers each dropping the
146
+ * other's key) and a torn-write race under a bare `writeFileSync`.
147
+ * `existingConfig` is (re-)read AFTER the lock is acquired, not before, or a
148
+ * writer that read stale state while waiting would reintroduce the same
149
+ * lost-update bug.
150
+ *
142
151
  * @param config - Configuration to save (merged with existing)
143
152
  * @param options - Save options
144
153
  */
145
154
  export function saveConfig(config, options = { merge: true }) {
146
155
  ensureConfigDir();
147
156
  const configPath = getConfigPath();
148
- let existingConfig = {};
149
- if (options.merge && existsSync(configPath)) {
150
- existingConfig = loadConfig();
157
+ const release = acquireConfigLock(configPath);
158
+ try {
159
+ let existingConfig = {};
160
+ if (options.merge && existsSync(configPath)) {
161
+ existingConfig = loadConfig();
162
+ }
163
+ // Remove undefined values so they are omitted from JSON output
164
+ const updates = Object.fromEntries(Object.entries(config).filter(([, v]) => v !== undefined));
165
+ // Explicit undefined fields are deletions — remove them from existing config
166
+ const deletions = Object.keys(config).filter((k) => config[k] === undefined);
167
+ const cleaned = { ...existingConfig };
168
+ for (const key of deletions) {
169
+ delete cleaned[key];
170
+ }
171
+ const mergedConfig = { ...cleaned, ...updates };
172
+ const configJson = JSON.stringify(mergedConfig, null, 2);
173
+ atomicWriteFile(configPath, configJson, 0o600);
151
174
  }
152
- // Remove undefined values so they are omitted from JSON output
153
- const updates = Object.fromEntries(Object.entries(config).filter(([, v]) => v !== undefined));
154
- // Explicit undefined fields are deletions — remove them from existing config
155
- const deletions = Object.keys(config).filter((k) => config[k] === undefined);
156
- const cleaned = { ...existingConfig };
157
- for (const key of deletions) {
158
- delete cleaned[key];
175
+ finally {
176
+ release();
159
177
  }
160
- const mergedConfig = { ...cleaned, ...updates };
161
- const configJson = JSON.stringify(mergedConfig, null, 2);
162
- writeFileSync(configPath, configJson, { encoding: 'utf-8', mode: 0o600 });
163
- // Ensure permissions are set correctly (in case file existed)
178
+ // Ensure permissions are set correctly (belt-and-braces — atomicWriteFile
179
+ // already chmods the file itself; in case the file existed pre-migration).
164
180
  try {
165
181
  chmodSync(configPath, 0o600);
166
182
  }