@skillsmith/core 0.10.0 → 0.11.0

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 (129) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts +23 -0
  4. package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts.map +1 -1
  5. package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js +137 -0
  6. package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js.map +1 -1
  7. package/dist/src/analysis/tree-sitter/pythonIncremental.js +83 -10
  8. package/dist/src/analysis/tree-sitter/pythonIncremental.js.map +1 -1
  9. package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js +6 -1
  10. package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js.map +1 -1
  11. package/dist/src/config/audit-notify-state.d.ts +46 -0
  12. package/dist/src/config/audit-notify-state.d.ts.map +1 -0
  13. package/dist/src/config/audit-notify-state.js +55 -0
  14. package/dist/src/config/audit-notify-state.js.map +1 -0
  15. package/dist/src/config/audit-notify-state.test.d.ts +9 -0
  16. package/dist/src/config/audit-notify-state.test.d.ts.map +1 -0
  17. package/dist/src/config/audit-notify-state.test.js +63 -0
  18. package/dist/src/config/audit-notify-state.test.js.map +1 -0
  19. package/dist/src/config/index.d.ts +10 -0
  20. package/dist/src/config/index.d.ts.map +1 -1
  21. package/dist/src/config/index.js.map +1 -1
  22. package/dist/src/exports/services.d.ts +1 -1
  23. package/dist/src/exports/services.d.ts.map +1 -1
  24. package/dist/src/exports/services.js +3 -1
  25. package/dist/src/exports/services.js.map +1 -1
  26. package/dist/src/index.d.ts +4 -1
  27. package/dist/src/index.d.ts.map +1 -1
  28. package/dist/src/index.js +7 -1
  29. package/dist/src/index.js.map +1 -1
  30. package/dist/src/logging/context.d.ts +58 -0
  31. package/dist/src/logging/context.d.ts.map +1 -0
  32. package/dist/src/logging/context.js +62 -0
  33. package/dist/src/logging/context.js.map +1 -0
  34. package/dist/src/logging/context.test.d.ts +14 -0
  35. package/dist/src/logging/context.test.d.ts.map +1 -0
  36. package/dist/src/logging/context.test.js +88 -0
  37. package/dist/src/logging/context.test.js.map +1 -0
  38. package/dist/src/logging/index.d.ts +13 -0
  39. package/dist/src/logging/index.d.ts.map +1 -0
  40. package/dist/src/logging/index.js +12 -0
  41. package/dist/src/logging/index.js.map +1 -0
  42. package/dist/src/logging/logger.d.ts +71 -0
  43. package/dist/src/logging/logger.d.ts.map +1 -0
  44. package/dist/src/logging/logger.js +264 -0
  45. package/dist/src/logging/logger.js.map +1 -0
  46. package/dist/src/logging/logger.test.d.ts +9 -0
  47. package/dist/src/logging/logger.test.d.ts.map +1 -0
  48. package/dist/src/logging/logger.test.js +320 -0
  49. package/dist/src/logging/logger.test.js.map +1 -0
  50. package/dist/src/logging/redact.d.ts +35 -0
  51. package/dist/src/logging/redact.d.ts.map +1 -0
  52. package/dist/src/logging/redact.js +152 -0
  53. package/dist/src/logging/redact.js.map +1 -0
  54. package/dist/src/logging/redact.test.d.ts +8 -0
  55. package/dist/src/logging/redact.test.d.ts.map +1 -0
  56. package/dist/src/logging/redact.test.js +330 -0
  57. package/dist/src/logging/redact.test.js.map +1 -0
  58. package/dist/src/logging/rotation.d.ts +75 -0
  59. package/dist/src/logging/rotation.d.ts.map +1 -0
  60. package/dist/src/logging/rotation.js +284 -0
  61. package/dist/src/logging/rotation.js.map +1 -0
  62. package/dist/src/logging/rotation.test.d.ts +11 -0
  63. package/dist/src/logging/rotation.test.d.ts.map +1 -0
  64. package/dist/src/logging/rotation.test.js +132 -0
  65. package/dist/src/logging/rotation.test.js.map +1 -0
  66. package/dist/src/logging/types.d.ts +69 -0
  67. package/dist/src/logging/types.d.ts.map +1 -0
  68. package/dist/src/logging/types.js +9 -0
  69. package/dist/src/logging/types.js.map +1 -0
  70. package/dist/src/security/index.d.ts +2 -0
  71. package/dist/src/security/index.d.ts.map +1 -1
  72. package/dist/src/security/index.js +4 -0
  73. package/dist/src/security/index.js.map +1 -1
  74. package/dist/src/security/scanner/SecurityScanner.hostile-update.d.ts +46 -0
  75. package/dist/src/security/scanner/SecurityScanner.hostile-update.d.ts.map +1 -0
  76. package/dist/src/security/scanner/SecurityScanner.hostile-update.js +209 -0
  77. package/dist/src/security/scanner/SecurityScanner.hostile-update.js.map +1 -0
  78. package/dist/src/security/scanner/index.d.ts +2 -1
  79. package/dist/src/security/scanner/index.d.ts.map +1 -1
  80. package/dist/src/security/scanner/index.js +2 -0
  81. package/dist/src/security/scanner/index.js.map +1 -1
  82. package/dist/src/security/scanner/types.d.ts +21 -0
  83. package/dist/src/security/scanner/types.d.ts.map +1 -1
  84. package/dist/src/services/skill-installation.io.d.ts.map +1 -1
  85. package/dist/src/services/skill-installation.io.js +19 -2
  86. package/dist/src/services/skill-installation.io.js.map +1 -1
  87. package/dist/src/sync/access-token.d.ts +27 -0
  88. package/dist/src/sync/access-token.d.ts.map +1 -0
  89. package/dist/src/sync/access-token.js +40 -0
  90. package/dist/src/sync/access-token.js.map +1 -0
  91. package/dist/src/sync/audit-notify-client.d.ts +83 -0
  92. package/dist/src/sync/audit-notify-client.d.ts.map +1 -0
  93. package/dist/src/sync/audit-notify-client.js +126 -0
  94. package/dist/src/sync/audit-notify-client.js.map +1 -0
  95. package/dist/src/sync/audit-notify-client.test.d.ts +14 -0
  96. package/dist/src/sync/audit-notify-client.test.d.ts.map +1 -0
  97. package/dist/src/sync/audit-notify-client.test.js +133 -0
  98. package/dist/src/sync/audit-notify-client.test.js.map +1 -0
  99. package/dist/src/sync/index.d.ts +1 -0
  100. package/dist/src/sync/index.d.ts.map +1 -1
  101. package/dist/src/sync/index.js +2 -0
  102. package/dist/src/sync/index.js.map +1 -1
  103. package/dist/src/sync/inventory-client.d.ts.map +1 -1
  104. package/dist/src/sync/inventory-client.js +5 -16
  105. package/dist/src/sync/inventory-client.js.map +1 -1
  106. package/dist/src/telemetry/posthog.d.ts +15 -0
  107. package/dist/src/telemetry/posthog.d.ts.map +1 -1
  108. package/dist/src/telemetry/posthog.js +6 -1
  109. package/dist/src/telemetry/posthog.js.map +1 -1
  110. package/dist/src/telemetry/wrap.bench.js +5 -0
  111. package/dist/src/telemetry/wrap.bench.js.map +1 -1
  112. package/dist/src/telemetry/wrap.correlation.test.d.ts +20 -0
  113. package/dist/src/telemetry/wrap.correlation.test.d.ts.map +1 -0
  114. package/dist/src/telemetry/wrap.correlation.test.js +174 -0
  115. package/dist/src/telemetry/wrap.correlation.test.js.map +1 -0
  116. package/dist/src/telemetry/wrap.d.ts.map +1 -1
  117. package/dist/src/telemetry/wrap.gate.test.js +5 -0
  118. package/dist/src/telemetry/wrap.gate.test.js.map +1 -1
  119. package/dist/src/telemetry/wrap.js +111 -52
  120. package/dist/src/telemetry/wrap.js.map +1 -1
  121. package/dist/src/telemetry/wrap.marker.test.js +5 -0
  122. package/dist/src/telemetry/wrap.marker.test.js.map +1 -1
  123. package/dist/src/telemetry/wrap.test.js +10 -0
  124. package/dist/src/telemetry/wrap.test.js.map +1 -1
  125. package/dist/src/types.d.ts +6 -0
  126. package/dist/src/types.d.ts.map +1 -1
  127. package/dist/tests/security/scanner-regression-guard.test.js +179 -1
  128. package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
  129. package/package.json +7 -2
@@ -0,0 +1,284 @@
1
+ /**
2
+ * SMI-5615: Per-surface serialized JSONL writer with daily/size rollover and
3
+ * retention sweep.
4
+ *
5
+ * Write-serialization invariant (F2, from
6
+ * docs/internal/implementation/production-error-logging.md — the plan's
7
+ * original P-5 draft omitted this; it was the Opus `concurrency-auditor`
8
+ * plan-audit's most likely real-world race):
9
+ *
10
+ * A redacted error record (stack ≤20 frames) can exceed `PIPE_BUF`
11
+ * (4096 bytes), so concurrent, independent `appendFile`/`appendFileSync`
12
+ * calls from parallel async tool invocations are NOT safe here — two
13
+ * large concurrent writes can interleave/tear into one invalid JSONL line.
14
+ *
15
+ * Fix: one long-lived `fs.createWriteStream(path, { flags: 'a' })` per
16
+ * surface, created lazily on first write and cached in a module-scoped
17
+ * map. ALL writes for a given surface go through THIS single stream.
18
+ *
19
+ * That alone (Node serializing writes queued on one stream) would already
20
+ * prevent tearing between two writes to the SAME stream object. This
21
+ * module goes one step further for simplicity and an even stronger
22
+ * guarantee: every write is wrapped in an async mutex (`runExclusive`) keyed
23
+ * by surface, so at most one write is ever in flight to a surface's
24
+ * underlying file descriptor at a time — including the write that decides
25
+ * whether a daily/size rollover is needed. That decision-and-possible-swap
26
+ * (open a new stream, `end()` the old one) happens INSIDE the same
27
+ * exclusive section as the write that triggered it, so a rotation can
28
+ * never race a write: no write can land on a stream that has already been
29
+ * told to `end()`, and no two writers can simultaneously decide "today's
30
+ * file doesn't exist yet" and both create a stream.
31
+ *
32
+ * Daily rollover: `skillsmith-<surface>-<YYYY-MM-DD>.jsonl`, keyed off the
33
+ * calendar date at write time (mocked by `vi.setSystemTime` in tests — no
34
+ * separate clock seam needed).
35
+ *
36
+ * Size cap: ~10MB per file. When the currently-open file for TODAY would
37
+ * exceed the cap, roll to a `.1`/`.2`/... suffixed continuation file for the
38
+ * same day (`skillsmith-<surface>-<YYYY-MM-DD>.jsonl.<n>`), through the same
39
+ * serialization point as everything else.
40
+ *
41
+ * Retention: on module init, asynchronously (NOT awaited — never blocks
42
+ * import) sweep `~/.skillsmith/logs/` and delete files whose mtime is older
43
+ * than 14 days. `pruneExpiredLogs` is also exported directly so tests can
44
+ * await a deterministic run against a temp directory instead of racing the
45
+ * fire-and-forget module-init sweep.
46
+ */
47
+ import { createWriteStream, existsSync, statSync } from 'node:fs';
48
+ import { mkdir, readdir, stat, unlink } from 'node:fs/promises';
49
+ import { homedir } from 'node:os';
50
+ import { join } from 'node:path';
51
+ const SIZE_CAP_BYTES = 10 * 1024 * 1024; // ~10MB per file (F2)
52
+ const RETENTION_DAYS = 14;
53
+ /**
54
+ * Log directory. Overridable via `SKILLSMITH_LOG_DIR` — a test-only seam
55
+ * (not part of the plan's documented env-var surface) so `rotation.test.ts`
56
+ * can point writes/sweeps at a temp directory instead of the real
57
+ * `~/.skillsmith/logs/`. Read lazily (not frozen at import time) so a test
58
+ * can set the env var before its first write without needing
59
+ * `vi.resetModules()`.
60
+ */
61
+ function getLogDir() {
62
+ return process.env.SKILLSMITH_LOG_DIR || join(homedir(), '.skillsmith', 'logs');
63
+ }
64
+ function dailyFilePath(surface, date) {
65
+ return join(getLogDir(), `skillsmith-${surface}-${date}.jsonl`);
66
+ }
67
+ /** Next available `.N` continuation suffix for today's file, same day only. */
68
+ function nextRolledFilePath(surface, date) {
69
+ const base = dailyFilePath(surface, date);
70
+ let n = 1;
71
+ while (existsSync(`${base}.${n}`))
72
+ n++;
73
+ return `${base}.${n}`;
74
+ }
75
+ function statSizeOrZero(filePath) {
76
+ try {
77
+ return statSync(filePath).size;
78
+ }
79
+ catch {
80
+ return 0;
81
+ }
82
+ }
83
+ function todayDateString() {
84
+ return new Date().toISOString().split('T')[0];
85
+ }
86
+ // ---------------------------------------------------------------------------
87
+ // Async mutex — one FIFO queue per surface. `runExclusive` serializes both
88
+ // the rollover decision AND the write itself, so at most one write is ever
89
+ // in flight per surface (F2).
90
+ // ---------------------------------------------------------------------------
91
+ const queueTails = new Map();
92
+ function runExclusive(surface, task) {
93
+ const tail = queueTails.get(surface) ?? Promise.resolve();
94
+ // Run `task` after the prior tail settles, whether it resolved or
95
+ // rejected — one surface's failed write must not permanently jam the
96
+ // queue for that surface's subsequent writes.
97
+ const result = tail.then(task, task);
98
+ // Keep the queue's tail alive regardless of `task`'s outcome; the real
99
+ // outcome is still observable to the caller via `result`.
100
+ queueTails.set(surface, result.then(() => undefined, () => undefined));
101
+ return result;
102
+ }
103
+ const states = new Map();
104
+ function getState(surface) {
105
+ let state = states.get(surface);
106
+ if (!state) {
107
+ state = { stream: null, date: null, sizeBytes: 0 };
108
+ states.set(surface, state);
109
+ }
110
+ return state;
111
+ }
112
+ /**
113
+ * Ends a stream and resolves once it has fully finished (or errored) — never
114
+ * rejects, so a close failure can't block the caller from opening the
115
+ * replacement stream.
116
+ */
117
+ function closeStream(stream) {
118
+ return new Promise((resolve) => {
119
+ const done = () => resolve();
120
+ stream.once('error', done);
121
+ stream.end(done);
122
+ });
123
+ }
124
+ /**
125
+ * Opens `filePath` for append and resolves once the underlying fd is open
126
+ * (or rejects if `open()` itself fails, e.g. permission denied) — waiting
127
+ * for `'open'`/`'error'` rather than trusting `createWriteStream`'s
128
+ * synchronous return lets an unwritable path surface as a rejected Promise
129
+ * instead of a later unhandled `'error'` event.
130
+ */
131
+ function openStream(filePath) {
132
+ return new Promise((resolve, reject) => {
133
+ const stream = createWriteStream(filePath, { flags: 'a' });
134
+ const onOpen = () => {
135
+ cleanup();
136
+ resolve(stream);
137
+ };
138
+ const onError = (err) => {
139
+ cleanup();
140
+ reject(err);
141
+ };
142
+ function cleanup() {
143
+ stream.off('open', onOpen);
144
+ stream.off('error', onError);
145
+ }
146
+ stream.once('open', onOpen);
147
+ stream.once('error', onError);
148
+ }).then((stream) => {
149
+ // Persistent handler for errors that occur AFTER open (disk full, etc.)
150
+ // — required so an unlistened 'error' event never crashes the process.
151
+ // Actual write failures are still propagated to the caller via the
152
+ // write() callback in `writeChunk`; this is a pure safety net.
153
+ stream.on('error', (err) => {
154
+ console.error('[skillsmith] log stream error:', err instanceof Error ? err.message : err);
155
+ });
156
+ return stream;
157
+ });
158
+ }
159
+ function writeChunk(stream, buf) {
160
+ return new Promise((resolve, reject) => {
161
+ stream.write(buf, (err) => {
162
+ if (err)
163
+ reject(err);
164
+ else
165
+ resolve();
166
+ });
167
+ });
168
+ }
169
+ /**
170
+ * Resolves the stream to write to for `surface` RIGHT NOW, opening a new one
171
+ * (and ending the old one) if the calendar day changed or the current file
172
+ * has exceeded the size cap. Must only be called from inside
173
+ * `runExclusive(surface, ...)` — it is not itself lock-protected.
174
+ */
175
+ async function resolveStream(surface) {
176
+ const state = getState(surface);
177
+ const today = todayDateString();
178
+ const isNewDay = state.stream === null || state.date !== today;
179
+ const isOversized = !isNewDay && state.sizeBytes >= SIZE_CAP_BYTES;
180
+ if (isNewDay || isOversized) {
181
+ if (state.stream) {
182
+ await closeStream(state.stream);
183
+ }
184
+ await mkdir(getLogDir(), { recursive: true });
185
+ const filePath = isOversized
186
+ ? nextRolledFilePath(surface, today)
187
+ : dailyFilePath(surface, today);
188
+ const stream = await openStream(filePath);
189
+ state.stream = stream;
190
+ state.date = today;
191
+ // Stat the target file so reopening a same-day file across a process
192
+ // restart still enforces the cap accurately, instead of resetting to 0
193
+ // and silently growing the already-oversized base file further.
194
+ state.sizeBytes = statSizeOrZero(filePath);
195
+ }
196
+ // `resolveStream` only sets `state.stream` to a non-null value above, and
197
+ // this function is the sole place that ever nulls or reassigns it, so the
198
+ // non-null assertion here is safe.
199
+ return state.stream;
200
+ }
201
+ /**
202
+ * Serializes `line` for `surface`: acquires that surface's exclusive queue,
203
+ * resolves (opening/rotating as needed) the current stream, and writes.
204
+ * Resolves once the OS-level write has completed; rejects (never throws
205
+ * synchronously) if the directory/file couldn't be created or the write
206
+ * failed — callers (see `logger.ts`) must handle rejection to honor the
207
+ * "logger never throws" invariant.
208
+ */
209
+ export function writeLogLine(surface, line) {
210
+ return runExclusive(surface, async () => {
211
+ const stream = await resolveStream(surface);
212
+ const buf = Buffer.from(line.endsWith('\n') ? line : `${line}\n`, 'utf8');
213
+ await writeChunk(stream, buf);
214
+ getState(surface).sizeBytes += buf.length;
215
+ });
216
+ }
217
+ // ---------------------------------------------------------------------------
218
+ // Retention sweep
219
+ // ---------------------------------------------------------------------------
220
+ /**
221
+ * Deletes files under the log directory whose mtime is older than
222
+ * `RETENTION_DAYS`. Best-effort: a missing directory or a per-file stat/
223
+ * unlink failure (permissions, a concurrent process already deleted it,
224
+ * etc.) is swallowed rather than thrown — this must never be able to crash
225
+ * module init or take down a caller that awaits it in a test.
226
+ *
227
+ * Exported (not just fired at module init) so tests can await a
228
+ * deterministic run against a temp directory rather than racing the
229
+ * fire-and-forget call below.
230
+ */
231
+ export async function pruneExpiredLogs() {
232
+ const dir = getLogDir();
233
+ try {
234
+ if (!existsSync(dir))
235
+ return;
236
+ const entries = await readdir(dir);
237
+ const cutoff = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1000;
238
+ await Promise.all(entries.map(async (name) => {
239
+ const full = join(dir, name);
240
+ try {
241
+ const info = await stat(full);
242
+ if (info.isFile() && info.mtimeMs < cutoff) {
243
+ await unlink(full);
244
+ }
245
+ }
246
+ catch {
247
+ // Best-effort per-file; ignore and move on.
248
+ }
249
+ }));
250
+ }
251
+ catch {
252
+ // Best-effort overall; never throw from module init.
253
+ }
254
+ }
255
+ // Fire-and-forget at module load — async, non-blocking, intentionally not
256
+ // awaited (F2: "unrelated to the active writer"). Skipped under Vitest
257
+ // (`process.env.VITEST` is set automatically by the test runner, not
258
+ // something call sites configure) and via an explicit opt-out, so merely
259
+ // IMPORTING this module — which every `logger.test.ts`/`redact.test.ts` run
260
+ // does transitively — never sweeps the real `~/.skillsmith/logs/` directory
261
+ // as a side effect. Tests call `pruneExpiredLogs()` directly against
262
+ // `SKILLSMITH_LOG_DIR` instead, for a deterministic, awaitable run.
263
+ if (process.env.VITEST !== 'true' && process.env.SKILLSMITH_LOG_DISABLE_STARTUP_SWEEP !== '1') {
264
+ void pruneExpiredLogs();
265
+ }
266
+ // ---------------------------------------------------------------------------
267
+ // Test-only reset
268
+ // ---------------------------------------------------------------------------
269
+ /**
270
+ * Closes any open streams and clears all in-memory state. Test-only — used
271
+ * by `rotation.test.ts` between cases so state from one temp-dir scenario
272
+ * never leaks into the next.
273
+ */
274
+ export async function __resetLoggingStateForTests() {
275
+ const closes = [];
276
+ for (const state of states.values()) {
277
+ if (state.stream)
278
+ closes.push(closeStream(state.stream));
279
+ }
280
+ await Promise.all(closes);
281
+ states.clear();
282
+ queueTails.clear();
283
+ }
284
+ //# sourceMappingURL=rotation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rotation.js","sourceRoot":"","sources":["../../../src/logging/rotation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * SMI-5615: Tests for the per-surface serialized JSONL writer (`rotation.ts`).
3
+ *
4
+ * Covers the plan's P-5 required test ("fire N concurrent large ...
5
+ * writes ... assert every line parses as valid JSON with no
6
+ * interleaved/torn content"), plus daily rollover, size-cap rollover, and
7
+ * the 14-day retention sweep. All tests point `SKILLSMITH_LOG_DIR` at a temp
8
+ * directory — never the real `~/.skillsmith/logs/`.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=rotation.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rotation.test.d.ts","sourceRoot":"","sources":["../../../src/logging/rotation.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * SMI-5615: Tests for the per-surface serialized JSONL writer (`rotation.ts`).
3
+ *
4
+ * Covers the plan's P-5 required test ("fire N concurrent large ...
5
+ * writes ... assert every line parses as valid JSON with no
6
+ * interleaved/torn content"), plus daily rollover, size-cap rollover, and
7
+ * the 14-day retention sweep. All tests point `SKILLSMITH_LOG_DIR` at a temp
8
+ * directory — never the real `~/.skillsmith/logs/`.
9
+ */
10
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
11
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, utimesSync, writeFileSync, } from 'node:fs';
12
+ import { tmpdir } from 'node:os';
13
+ import { join } from 'node:path';
14
+ import { __resetLoggingStateForTests, pruneExpiredLogs, writeLogLine } from './rotation.js';
15
+ let tempDir;
16
+ let originalLogDir;
17
+ beforeEach(() => {
18
+ tempDir = mkdtempSync(join(tmpdir(), 'skillsmith-log-rotation-'));
19
+ originalLogDir = process.env.SKILLSMITH_LOG_DIR;
20
+ process.env.SKILLSMITH_LOG_DIR = tempDir;
21
+ });
22
+ afterEach(async () => {
23
+ await __resetLoggingStateForTests();
24
+ if (originalLogDir === undefined) {
25
+ delete process.env.SKILLSMITH_LOG_DIR;
26
+ }
27
+ else {
28
+ process.env.SKILLSMITH_LOG_DIR = originalLogDir;
29
+ }
30
+ vi.useRealTimers();
31
+ rmSync(tempDir, { recursive: true, force: true });
32
+ });
33
+ describe('rotation.ts — write serialization (F2)', () => {
34
+ it('serializes N concurrent large (>PIPE_BUF) writes without interleaving or tearing', async () => {
35
+ const N = 25;
36
+ const PAYLOAD_SIZE = 6000; // > PIPE_BUF (4096 bytes)
37
+ const writes = Array.from({ length: N }, (_, i) => writeLogLine('mcp', JSON.stringify({ id: i, blob: 'x'.repeat(PAYLOAD_SIZE) })));
38
+ await Promise.all(writes);
39
+ const files = readdirSync(tempDir).filter((f) => f.startsWith('skillsmith-mcp-'));
40
+ expect(files).toHaveLength(1);
41
+ const content = readFileSync(join(tempDir, files[0]), 'utf8');
42
+ const lines = content.split('\n').filter((line) => line.length > 0);
43
+ expect(lines).toHaveLength(N);
44
+ const seenIds = new Set();
45
+ for (const line of lines) {
46
+ // JSON.parse throws on a torn/interleaved line — this is the actual
47
+ // no-tearing assertion, not just the id-uniqueness check below.
48
+ const parsed = JSON.parse(line);
49
+ expect(parsed.blob).toHaveLength(PAYLOAD_SIZE);
50
+ seenIds.add(parsed.id);
51
+ }
52
+ expect(seenIds.size).toBe(N);
53
+ });
54
+ it('keeps concurrent writes to different surfaces independent', async () => {
55
+ await Promise.all([
56
+ writeLogLine('mcp', JSON.stringify({ surface: 'mcp' })),
57
+ writeLogLine('cli', JSON.stringify({ surface: 'cli' })),
58
+ writeLogLine('vscode', JSON.stringify({ surface: 'vscode' })),
59
+ ]);
60
+ const files = readdirSync(tempDir);
61
+ expect(files.some((f) => f.startsWith('skillsmith-mcp-'))).toBe(true);
62
+ expect(files.some((f) => f.startsWith('skillsmith-cli-'))).toBe(true);
63
+ expect(files.some((f) => f.startsWith('skillsmith-vscode-'))).toBe(true);
64
+ });
65
+ });
66
+ describe('rotation.ts — daily rollover', () => {
67
+ it('rolls to a new dated file when the calendar date changes', async () => {
68
+ vi.useFakeTimers();
69
+ vi.setSystemTime(new Date('2026-01-01T23:59:00Z'));
70
+ await writeLogLine('cli', JSON.stringify({ day: 1 }));
71
+ vi.setSystemTime(new Date('2026-01-02T00:01:00Z'));
72
+ await writeLogLine('cli', JSON.stringify({ day: 2 }));
73
+ const files = readdirSync(tempDir)
74
+ .filter((f) => f.startsWith('skillsmith-cli-'))
75
+ .sort();
76
+ expect(files).toEqual(['skillsmith-cli-2026-01-01.jsonl', 'skillsmith-cli-2026-01-02.jsonl']);
77
+ const day1 = readFileSync(join(tempDir, 'skillsmith-cli-2026-01-01.jsonl'), 'utf8').trim();
78
+ const day2 = readFileSync(join(tempDir, 'skillsmith-cli-2026-01-02.jsonl'), 'utf8').trim();
79
+ expect(JSON.parse(day1)).toEqual({ day: 1 });
80
+ expect(JSON.parse(day2)).toEqual({ day: 2 });
81
+ });
82
+ });
83
+ describe('rotation.ts — size-cap rollover', () => {
84
+ it('rolls to a .1 continuation file once the current file reaches the ~10MB cap', async () => {
85
+ vi.useFakeTimers();
86
+ vi.setSystemTime(new Date('2026-02-01T12:00:00Z'));
87
+ // First write pushes the base file to (just over) the 10MB cap in one
88
+ // shot — far faster than looping thousands of small writes to get there,
89
+ // and exercises the same in-memory size-tracking path either way.
90
+ const bigPayload = 'x'.repeat(10 * 1024 * 1024);
91
+ await writeLogLine('cli', JSON.stringify({ big: bigPayload }));
92
+ // Second write, same day: resolveStream sees sizeBytes >= cap and rolls
93
+ // to a `.1` continuation file instead of continuing to grow the base file.
94
+ await writeLogLine('cli', JSON.stringify({ rolled: true }));
95
+ const baseName = 'skillsmith-cli-2026-02-01.jsonl';
96
+ expect(existsSync(join(tempDir, `${baseName}.1`))).toBe(true);
97
+ const rolledContent = readFileSync(join(tempDir, `${baseName}.1`), 'utf8').trim();
98
+ expect(JSON.parse(rolledContent)).toEqual({ rolled: true });
99
+ const baseLines = readFileSync(join(tempDir, baseName), 'utf8')
100
+ .split('\n')
101
+ .filter((line) => line.length > 0);
102
+ expect(baseLines).toHaveLength(1);
103
+ }, 15000);
104
+ });
105
+ describe('rotation.ts — retention sweep', () => {
106
+ it('deletes files older than 14 days and keeps recent ones', async () => {
107
+ const oldFile = join(tempDir, 'skillsmith-mcp-2020-01-01.jsonl');
108
+ const recentFile = join(tempDir, 'skillsmith-mcp-2026-01-01.jsonl');
109
+ writeFileSync(oldFile, '{}\n');
110
+ writeFileSync(recentFile, '{}\n');
111
+ const fifteenDaysAgoSec = (Date.now() - 15 * 24 * 60 * 60 * 1000) / 1000;
112
+ const oneDayAgoSec = (Date.now() - 1 * 24 * 60 * 60 * 1000) / 1000;
113
+ utimesSync(oldFile, fifteenDaysAgoSec, fifteenDaysAgoSec);
114
+ utimesSync(recentFile, oneDayAgoSec, oneDayAgoSec);
115
+ await pruneExpiredLogs();
116
+ expect(existsSync(oldFile)).toBe(false);
117
+ expect(existsSync(recentFile)).toBe(true);
118
+ });
119
+ it('does not delete directories, only files', async () => {
120
+ const oldDir = join(tempDir, 'not-a-log-file');
121
+ mkdirSync(oldDir);
122
+ const fifteenDaysAgoSec = (Date.now() - 15 * 24 * 60 * 60 * 1000) / 1000;
123
+ utimesSync(oldDir, fifteenDaysAgoSec, fifteenDaysAgoSec);
124
+ await expect(pruneExpiredLogs()).resolves.toBeUndefined();
125
+ expect(existsSync(oldDir)).toBe(true);
126
+ });
127
+ it('is a no-op (never throws) when the log directory does not exist', async () => {
128
+ process.env.SKILLSMITH_LOG_DIR = join(tempDir, 'does-not-exist');
129
+ await expect(pruneExpiredLogs()).resolves.toBeUndefined();
130
+ });
131
+ });
132
+ //# sourceMappingURL=rotation.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rotation.test.js","sourceRoot":"","sources":["../../../src/logging/rotation.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * SMI-5615: Shared types for the production error-logging module
3
+ * (`packages/core/src/logging/`).
4
+ *
5
+ * Field list and shape verified against
6
+ * docs/internal/implementation/production-error-logging.md §1.
7
+ */
8
+ /** Log severity, ordered `debug < info < warn < error`. */
9
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
10
+ /** Which invocation surface produced a record — one JSONL file per surface. */
11
+ export type Surface = 'mcp' | 'cli' | 'vscode';
12
+ /** Normalized, already-redacted error shape embedded in a `LogRecord`. */
13
+ export interface LogRecordError {
14
+ name: string;
15
+ message: string;
16
+ /** Redacted, capped to the first 20 frames. */
17
+ stack?: string;
18
+ }
19
+ /**
20
+ * One JSON-line record written to
21
+ * `~/.skillsmith/logs/skillsmith-<surface>-<YYYY-MM-DD>.jsonl`.
22
+ *
23
+ * Field order mirrors the plan's listing (ts, level, surface, event, msg,
24
+ * err, correlationId, toolOrCommand, skillId, version, pid) — `logger.ts`
25
+ * constructs records in this order so `JSON.stringify` output (insertion
26
+ * order for string keys) reads consistently on disk.
27
+ */
28
+ export interface LogRecord {
29
+ ts: string;
30
+ level: LogLevel;
31
+ surface: Surface;
32
+ /** Short machine-readable event/category tag. Defaults to `level` when omitted. */
33
+ event: string;
34
+ /** Redacted human-readable message. */
35
+ msg: string;
36
+ err?: LogRecordError;
37
+ correlationId?: string;
38
+ toolOrCommand?: string;
39
+ skillId?: string;
40
+ version: string;
41
+ pid: number;
42
+ /**
43
+ * Additional structured context beyond the plan's core field list —
44
+ * carries forward the old (dead) `mcp-server/src/logger.ts`'s freeform
45
+ * `details` capability so callers don't lose arbitrary context. Redacted
46
+ * via `redactSensitiveObject` like every other field. Omitted entirely
47
+ * when the caller passes no extra keys.
48
+ */
49
+ details?: Record<string, unknown>;
50
+ }
51
+ /**
52
+ * The `details` argument accepted by every `Logger` method. Recognized keys
53
+ * are lifted into their own `LogRecord` fields; anything else is folded into
54
+ * `LogRecord.details` (redacted). Matches the old logger's
55
+ * `(message: string, details?: unknown)` call shape while giving structure to
56
+ * the fields the new record format cares about.
57
+ */
58
+ export interface LogDetails {
59
+ /** Short machine-readable event/category tag for this record. */
60
+ event?: string;
61
+ /** Raw error to normalize into `LogRecord.err` (redacted). */
62
+ err?: unknown;
63
+ /** MCP tool name or CLI command name associated with this record. */
64
+ toolOrCommand?: string;
65
+ /** Skill ID associated with this record, when applicable. */
66
+ skillId?: string;
67
+ [key: string]: unknown;
68
+ }
69
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/logging/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2DAA2D;AAC3D,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAE1D,+EAA+E;AAC/E,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,CAAA;AAE9C,0EAA0E;AAC1E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,QAAQ,CAAA;IACf,OAAO,EAAE,OAAO,CAAA;IAChB,mFAAmF;IACnF,KAAK,EAAE,MAAM,CAAA;IACb,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8DAA8D;IAC9D,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * SMI-5615: Shared types for the production error-logging module
3
+ * (`packages/core/src/logging/`).
4
+ *
5
+ * Field list and shape verified against
6
+ * docs/internal/implementation/production-error-logging.md §1.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/logging/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -9,6 +9,8 @@
9
9
  */
10
10
  export { SecurityScanner } from './scanner/index.js';
11
11
  export type { SecurityFinding, SecurityFindingType, SecuritySeverity, ScanReport, ScannerOptions, RiskScoreBreakdown, } from './scanner/index.js';
12
+ export { compareScanReports, DEFAULT_RISK_THRESHOLD } from './scanner/index.js';
13
+ export type { HostileUpdateVerdict } from './scanner/index.js';
12
14
  export { sanitizeHtml, sanitizeFileName, sanitizePath, sanitizeUrl, sanitizeText, isValidStripeId, sanitizeStripeCustomerId, sanitizeStripeSubscriptionId, sanitizeStripePriceId, sanitizeStripeInvoiceId, sanitizeStripeEventId, } from './sanitization.js';
13
15
  export { validateDbPath, validateDbPathOrThrow, isPathSafe, DEFAULT_ALLOWED_DIRS, } from './pathValidation.js';
14
16
  export type { PathValidationOptions, PathValidationResult } from './pathValidation.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/security/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,kBAAkB,GACnB,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,YAAY,EAEZ,eAAe,EACf,wBAAwB,EACxB,4BAA4B,EAC5B,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,UAAU,EACV,oBAAoB,GACrB,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAGtF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,YAAY,EACV,aAAa,EACb,cAAc,EACd,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,UAAU,GACX,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EACL,WAAW,EACX,wBAAwB,EACxB,kBAAkB,EAClB,2BAA2B,EAC3B,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,yBAAyB,CAAA;AAChC,YAAY,EACV,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,yBAAyB,CAAA;AAGhC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGtD,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACtF,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/security/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,kBAAkB,GACnB,MAAM,oBAAoB,CAAA;AAI3B,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAC/E,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAG9D,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,YAAY,EAEZ,eAAe,EACf,wBAAwB,EACxB,4BAA4B,EAC5B,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,UAAU,EACV,oBAAoB,GACrB,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAGtF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,YAAY,EACV,aAAa,EACb,cAAc,EACd,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,UAAU,GACX,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EACL,WAAW,EACX,wBAAwB,EACxB,kBAAkB,EAClB,2BAA2B,EAC3B,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,yBAAyB,CAAA;AAChC,YAAY,EACV,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,yBAAyB,CAAA;AAGhC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGtD,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACtF,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA"}
@@ -9,6 +9,10 @@
9
9
  */
10
10
  // Scanner module
11
11
  export { SecurityScanner } from './scanner/index.js';
12
+ // SMI-5535 R0 Wave 2A: the hostile-update (rug-pull) comparator is a public
13
+ // feature; surface it (+ its default threshold + verdict type) through the
14
+ // security barrel so it is reachable from `@skillsmith/core`.
15
+ export { compareScanReports, DEFAULT_RISK_THRESHOLD } from './scanner/index.js';
12
16
  // Sanitization
13
17
  export { sanitizeHtml, sanitizeFileName, sanitizePath, sanitizeUrl, sanitizeText,
14
18
  // SMI-1062: Stripe ID validators
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/security/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,iBAAiB;AACjB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAUpD,eAAe;AACf,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,YAAY;AACZ,iCAAiC;AACjC,eAAe,EACf,wBAAwB,EACxB,4BAA4B,EAC5B,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAE1B,kBAAkB;AAClB,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,UAAU,EACV,oBAAoB,GACrB,MAAM,qBAAqB,CAAA;AAG5B,eAAe;AACf,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAU9C,sBAAsB;AACtB,OAAO,EACL,WAAW,EACX,wBAAwB,EACxB,kBAAkB,EAClB,2BAA2B,EAC3B,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,yBAAyB,CAAA;AAQhC,iCAAiC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGjD,0CAA0C;AAC1C,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/security/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,iBAAiB;AACjB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AASpD,4EAA4E;AAC5E,2EAA2E;AAC3E,8DAA8D;AAC9D,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAG/E,eAAe;AACf,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,YAAY;AACZ,iCAAiC;AACjC,eAAe,EACf,wBAAwB,EACxB,4BAA4B,EAC5B,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAE1B,kBAAkB;AAClB,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,UAAU,EACV,oBAAoB,GACrB,MAAM,qBAAqB,CAAA;AAG5B,eAAe;AACf,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAU9C,sBAAsB;AACtB,OAAO,EACL,WAAW,EACX,wBAAwB,EACxB,kBAAkB,EAClB,2BAA2B,EAC3B,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,yBAAyB,CAAA;AAQhC,iCAAiC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGjD,0CAA0C;AAC1C,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Hostile-Update Detection — R0 Wave 2A (SMI-5535)
3
+ * @module @skillsmith/core/security/scanner/SecurityScanner.hostile-update
4
+ *
5
+ * A PURE comparator (no I/O, no clock, no mutation of its inputs) that detects a
6
+ * benign→malicious "rug-pull" between two SecurityScanner scans of the SAME
7
+ * skill. The scanner already blocks a skill that is malicious on first sight;
8
+ * this detector catches the harder case where an initially-clean skill is
9
+ * silently updated to exfiltrate credentials or execute remote payloads.
10
+ *
11
+ * The verdict is delta-based: it never re-scores content, it only reasons about
12
+ * what changed between `previous` and `current`. "hostile" is reserved for the
13
+ * benign→malicious transition — a skill that was ALREADY flagged and merely got
14
+ * worse is a worsening (`suspicious`), not a fresh rug-pull.
15
+ */
16
+ import type { HostileUpdateVerdict, ScanReport } from './types.js';
17
+ /**
18
+ * Default risk-score threshold. Mirrors SecurityScanner's `riskThreshold`
19
+ * default (SMI-5359) so the "crossed the failure bar" signal here lines up with
20
+ * the `passed` computation in `SecurityScanner.scan`.
21
+ */
22
+ export declare const DEFAULT_RISK_THRESHOLD = 40;
23
+ /**
24
+ * Compare two scans of the same skill and classify the transition.
25
+ *
26
+ * Ordering is hostile → suspicious → benign so the strongest signal wins.
27
+ *
28
+ * @param previous Scan of the prior (trusted) version.
29
+ * @param current Scan of the incoming version.
30
+ * @param threshold Risk-score failure bar; defaults to {@link DEFAULT_RISK_THRESHOLD}.
31
+ *
32
+ * CALLER CONTRACT (LOW-2): this value MUST equal the `riskThreshold` the
33
+ * `SecurityScanner` instance used to produce BOTH `previous` and `current`
34
+ * (i.e. the same `ScannerOptions.riskThreshold` — or the scanner's own
35
+ * default of 40, which is why this parameter also defaults to
36
+ * {@link DEFAULT_RISK_THRESHOLD}). `previouslyBenign` below is derived from
37
+ * `previous.passed`, which was computed by the scanner against ITS OWN
38
+ * `riskThreshold` at scan time — if the caller passes a `threshold` here
39
+ * that differs from that value, `crossedThreshold`'s "did the score cross
40
+ * the bar" signal and `previouslyBenign`'s "was it passing" signal are
41
+ * reasoning about two different bars, and the hostile/suspicious/benign
42
+ * verdict can disagree with what `previous.passed` / `current.passed`
43
+ * would say under a freshly-run scan.
44
+ */
45
+ export declare function compareScanReports(previous: ScanReport, current: ScanReport, threshold?: number): HostileUpdateVerdict;
46
+ //# sourceMappingURL=SecurityScanner.hostile-update.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecurityScanner.hostile-update.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.hostile-update.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EAIX,MAAM,YAAY,CAAA;AAEnB;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,KAAK,CAAA;AA4GxC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,UAAU,EACpB,OAAO,EAAE,UAAU,EACnB,SAAS,GAAE,MAA+B,GACzC,oBAAoB,CA+EtB"}