@dommaker/harness 0.13.3 → 0.16.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.
- package/CHANGELOG.md +35 -0
- package/bin/harness-knowledge-check.js +38 -23
- package/bin/harness-knowledge-track.js +30 -4
- package/bin/harness.js +24 -1
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +4 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/knowledge.d.ts +21 -0
- package/dist/cli/commands/knowledge.d.ts.map +1 -1
- package/dist/cli/commands/knowledge.js +160 -9
- package/dist/cli/commands/knowledge.js.map +1 -1
- package/dist/cli/commands/release.d.ts.map +1 -1
- package/dist/cli/commands/release.js +43 -6
- package/dist/cli/commands/release.js.map +1 -1
- package/dist/core/constraints/checker.js +1 -1
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/definitions.d.ts.map +1 -1
- package/dist/core/constraints/definitions.js +34 -26
- package/dist/core/constraints/definitions.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/knowledge/audit.d.ts +76 -0
- package/dist/knowledge/audit.d.ts.map +1 -0
- package/dist/knowledge/audit.js +566 -0
- package/dist/knowledge/audit.js.map +1 -0
- package/dist/knowledge/doctor.d.ts +1 -1
- package/dist/knowledge/doctor.d.ts.map +1 -1
- package/dist/knowledge/import.d.ts +1 -1
- package/dist/knowledge/import.d.ts.map +1 -1
- package/dist/knowledge/import.js +3 -0
- package/dist/knowledge/import.js.map +1 -1
- package/dist/knowledge/index.d.ts +7 -2
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/knowledge/index.js +7 -2
- package/dist/knowledge/index.js.map +1 -1
- package/dist/knowledge/ingest.d.ts +28 -1
- package/dist/knowledge/ingest.d.ts.map +1 -1
- package/dist/knowledge/ingest.js +150 -5
- package/dist/knowledge/ingest.js.map +1 -1
- package/dist/knowledge/lifecycle-hooks.d.ts +1 -1
- package/dist/knowledge/lifecycle-hooks.d.ts.map +1 -1
- package/dist/knowledge/lifecycle-hooks.js +3 -0
- package/dist/knowledge/lifecycle-hooks.js.map +1 -1
- package/dist/knowledge/lifecycle.d.ts +60 -8
- package/dist/knowledge/lifecycle.d.ts.map +1 -1
- package/dist/knowledge/lifecycle.js +213 -52
- package/dist/knowledge/lifecycle.js.map +1 -1
- package/dist/knowledge/lint.d.ts +1 -1
- package/dist/knowledge/lint.d.ts.map +1 -1
- package/dist/knowledge/lint.js +44 -3
- package/dist/knowledge/lint.js.map +1 -1
- package/dist/knowledge/migration.d.ts +25 -0
- package/dist/knowledge/migration.d.ts.map +1 -0
- package/dist/knowledge/migration.js +96 -0
- package/dist/knowledge/migration.js.map +1 -0
- package/dist/knowledge/query.d.ts +30 -2
- package/dist/knowledge/query.d.ts.map +1 -1
- package/dist/knowledge/query.js +58 -0
- package/dist/knowledge/query.js.map +1 -1
- package/dist/knowledge/reference-tracker.d.ts +1 -1
- package/dist/knowledge/reference-tracker.d.ts.map +1 -1
- package/dist/knowledge/store.d.ts +50 -3
- package/dist/knowledge/store.d.ts.map +1 -1
- package/dist/knowledge/store.js +111 -9
- package/dist/knowledge/store.js.map +1 -1
- package/dist/knowledge/types.d.ts +43 -1
- package/dist/knowledge/types.d.ts.map +1 -1
- package/dist/knowledge/types.js +1 -0
- package/dist/knowledge/types.js.map +1 -1
- package/dist/monitoring/knowledge-evolver.d.ts +1 -1
- package/dist/monitoring/knowledge-evolver.d.ts.map +1 -1
- package/dist/types/constraint.d.ts +1 -1
- package/dist/types/constraint.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/checker.test.ts +4 -4
- package/src/cli/commands/__tests__/knowledge.test.ts +167 -0
- package/src/cli/commands/index.ts +1 -1
- package/src/cli/commands/knowledge.ts +176 -5
- package/src/cli/commands/release.ts +44 -6
- package/src/context/__tests__/knowledge-injector.test.ts +4 -1
- package/src/core/constraints/checker.ts +1 -1
- package/src/core/constraints/definitions.ts +35 -27
- package/src/dashboard/__tests__/data.test.ts +3 -0
- package/src/dashboard/__tests__/stats.test.ts +3 -0
- package/src/index.ts +3 -1
- package/src/knowledge/CONTEXT.md +29 -7
- package/src/knowledge/__tests__/audit.test.ts +598 -0
- package/src/knowledge/__tests__/doctor.test.ts +28 -1
- package/src/knowledge/__tests__/import.test.ts +1 -1
- package/src/knowledge/__tests__/ingest.test.ts +320 -1
- package/src/knowledge/__tests__/lifecycle-hooks.test.ts +4 -1
- package/src/knowledge/__tests__/lifecycle.test.ts +656 -3
- package/src/knowledge/__tests__/lint.test.ts +52 -1
- package/src/knowledge/__tests__/migration.test.ts +115 -0
- package/src/knowledge/__tests__/query.test.ts +87 -1
- package/src/knowledge/__tests__/reference-tracker.test.ts +4 -1
- package/src/knowledge/__tests__/store.test.ts +208 -1
- package/src/knowledge/audit.ts +641 -0
- package/src/knowledge/doctor.ts +1 -1
- package/src/knowledge/import.ts +4 -1
- package/src/knowledge/index.ts +7 -2
- package/src/knowledge/ingest.ts +170 -6
- package/src/knowledge/lifecycle-hooks.ts +4 -1
- package/src/knowledge/lifecycle.ts +219 -50
- package/src/knowledge/lint.ts +45 -4
- package/src/knowledge/migration.ts +73 -0
- package/src/knowledge/query.ts +72 -1
- package/src/knowledge/reference-tracker.ts +1 -1
- package/src/knowledge/store.ts +125 -8
- package/src/knowledge/types.ts +50 -1
- package/src/monitoring/__tests__/knowledge-doctor.test.ts +3 -0
- package/src/monitoring/__tests__/knowledge-evolver.test.ts +3 -0
- package/src/monitoring/knowledge-evolver.ts +1 -1
- package/src/types/constraint.ts +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, it, expect, beforeAll, afterAll, beforeEach } from '@jest/globals';
|
|
6
|
-
import { KnowledgeStore } from '../store';
|
|
6
|
+
import { FileKnowledgeStore as KnowledgeStore } from '../store';
|
|
7
7
|
import { KnowledgeLifecycle } from '../lifecycle';
|
|
8
8
|
import type { KnowledgeEntry } from '../types';
|
|
9
9
|
import * as fs from 'fs';
|
|
@@ -29,6 +29,9 @@ describe('KnowledgeLifecycle', () => {
|
|
|
29
29
|
applicablePhases: [],
|
|
30
30
|
sourceReferences: [],
|
|
31
31
|
referencedBy: [],
|
|
32
|
+
executionResults: [],
|
|
33
|
+
consumptionMode: 'reference',
|
|
34
|
+
origin: 'agent',
|
|
32
35
|
...overrides,
|
|
33
36
|
});
|
|
34
37
|
|
|
@@ -80,9 +83,224 @@ describe('KnowledgeLifecycle', () => {
|
|
|
80
83
|
});
|
|
81
84
|
});
|
|
82
85
|
|
|
86
|
+
describe('recordReference with execution result', () => {
|
|
87
|
+
it('should record success execution result', () => {
|
|
88
|
+
store.save(makeEntry());
|
|
89
|
+
lifecycle.recordReference('DEC-001', 'alice', true);
|
|
90
|
+
const entry = store.get('DEC-001');
|
|
91
|
+
expect(entry!.executionResults).toHaveLength(1);
|
|
92
|
+
expect(entry!.executionResults[0].success).toBe(true);
|
|
93
|
+
expect(entry!.executionResults[0].contributor).toBe('alice');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should record failure execution result', () => {
|
|
97
|
+
store.save(makeEntry());
|
|
98
|
+
lifecycle.recordReference('DEC-001', 'bob', false);
|
|
99
|
+
const entry = store.get('DEC-001');
|
|
100
|
+
expect(entry!.executionResults).toHaveLength(1);
|
|
101
|
+
expect(entry!.executionResults[0].success).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should not record execution result when success is undefined', () => {
|
|
105
|
+
store.save(makeEntry());
|
|
106
|
+
lifecycle.recordReference('DEC-001', 'alice');
|
|
107
|
+
const entry = store.get('DEC-001');
|
|
108
|
+
expect(entry!.executionResults).toHaveLength(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should record execution result even on same-day dedup', () => {
|
|
112
|
+
store.save(makeEntry());
|
|
113
|
+
lifecycle.recordReference('DEC-001', 'alice', true);
|
|
114
|
+
lifecycle.recordReference('DEC-001', 'alice', false); // same day, but different result
|
|
115
|
+
const entry = store.get('DEC-001');
|
|
116
|
+
expect(entry!.referencedBy).toHaveLength(1); // deduped
|
|
117
|
+
expect(entry!.executionResults).toHaveLength(2); // both recorded
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('getExecutionSuccessRate', () => {
|
|
122
|
+
it('should return undefined when no execution data', () => {
|
|
123
|
+
store.save(makeEntry());
|
|
124
|
+
expect(lifecycle.getExecutionSuccessRate('DEC-001')).toBeUndefined();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should compute success rate correctly', () => {
|
|
128
|
+
store.save(makeEntry());
|
|
129
|
+
lifecycle.recordReference('DEC-001', 'a', true);
|
|
130
|
+
lifecycle.recordReference('DEC-001', 'b', true);
|
|
131
|
+
lifecycle.recordReference('DEC-001', 'c', false);
|
|
132
|
+
const rate = lifecycle.getExecutionSuccessRate('DEC-001');
|
|
133
|
+
expect(rate).toBeDefined();
|
|
134
|
+
expect(rate!.total).toBe(3);
|
|
135
|
+
expect(rate!.rate).toBeCloseTo(2 / 3);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should return undefined for non-existent entry', () => {
|
|
139
|
+
expect(lifecycle.getExecutionSuccessRate('NON-EXISTENT')).toBeUndefined();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('getHumanSuccessRate', () => {
|
|
144
|
+
it('should return undefined when no execution data', () => {
|
|
145
|
+
store.save(makeEntry());
|
|
146
|
+
expect(lifecycle.getHumanSuccessRate('DEC-001')).toBeUndefined();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should return undefined when no human-sourced results', () => {
|
|
150
|
+
store.save(makeEntry());
|
|
151
|
+
lifecycle.recordReference('DEC-001', 'auto-bot', true, 'auto');
|
|
152
|
+
lifecycle.recordReference('DEC-001', 'auto-bot', true, 'auto');
|
|
153
|
+
expect(lifecycle.getHumanSuccessRate('DEC-001')).toBeUndefined();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should compute human-only success rate', () => {
|
|
157
|
+
store.save(makeEntry());
|
|
158
|
+
lifecycle.recordReference('DEC-001', 'alice', true, 'human');
|
|
159
|
+
lifecycle.recordReference('DEC-001', 'bob', true, 'human');
|
|
160
|
+
lifecycle.recordReference('DEC-001', 'auto-bot', true, 'auto');
|
|
161
|
+
lifecycle.recordReference('DEC-001', 'charlie', false, 'human');
|
|
162
|
+
const rate = lifecycle.getHumanSuccessRate('DEC-001');
|
|
163
|
+
expect(rate).toBeDefined();
|
|
164
|
+
expect(rate!.total).toBe(3); // 3 human results
|
|
165
|
+
expect(rate!.rate).toBeCloseTo(2 / 3); // 2 success out of 3 human
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should return undefined for non-existent entry', () => {
|
|
169
|
+
expect(lifecycle.getHumanSuccessRate('NON-EXISTENT')).toBeUndefined();
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe('recordReference with source', () => {
|
|
174
|
+
it('should store source in execution result', () => {
|
|
175
|
+
store.save(makeEntry());
|
|
176
|
+
lifecycle.recordReference('DEC-001', 'alice', true, 'human');
|
|
177
|
+
const entry = store.get('DEC-001');
|
|
178
|
+
expect(entry!.executionResults[0].source).toBe('human');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should default source to undefined when not provided', () => {
|
|
182
|
+
store.save(makeEntry());
|
|
183
|
+
lifecycle.recordReference('DEC-001', 'alice', true);
|
|
184
|
+
const entry = store.get('DEC-001');
|
|
185
|
+
expect(entry!.executionResults[0].source).toBeUndefined();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should auto-detect source for auto- prefixed contributors', () => {
|
|
189
|
+
store.save(makeEntry());
|
|
190
|
+
lifecycle.recordReference('DEC-001', 'auto-deploy', true);
|
|
191
|
+
const entry = store.get('DEC-001');
|
|
192
|
+
expect(entry!.executionResults[0].source).toBe('auto');
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('onReference callback', () => {
|
|
197
|
+
it('should fire callback on recordReference()', () => {
|
|
198
|
+
store.save(makeEntry());
|
|
199
|
+
const events: any[] = [];
|
|
200
|
+
lifecycle.onReference((event) => events.push(event));
|
|
201
|
+
|
|
202
|
+
lifecycle.recordReference('DEC-001', 'alice');
|
|
203
|
+
|
|
204
|
+
expect(events).toHaveLength(1);
|
|
205
|
+
expect(events[0].entryId).toBe('DEC-001');
|
|
206
|
+
expect(events[0].contributor).toBe('alice');
|
|
207
|
+
expect(events[0].timestamp).toBeDefined();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should fire multiple callbacks', () => {
|
|
211
|
+
store.save(makeEntry());
|
|
212
|
+
const events1: any[] = [];
|
|
213
|
+
const events2: any[] = [];
|
|
214
|
+
lifecycle.onReference((event) => events1.push(event));
|
|
215
|
+
lifecycle.onReference((event) => events2.push(event));
|
|
216
|
+
|
|
217
|
+
lifecycle.recordReference('DEC-001', 'bob');
|
|
218
|
+
|
|
219
|
+
expect(events1).toHaveLength(1);
|
|
220
|
+
expect(events2).toHaveLength(1);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should not fire callback on same-day dedup', () => {
|
|
224
|
+
store.save(makeEntry());
|
|
225
|
+
const events: any[] = [];
|
|
226
|
+
lifecycle.onReference((event) => events.push(event));
|
|
227
|
+
|
|
228
|
+
lifecycle.recordReference('DEC-001', 'alice');
|
|
229
|
+
lifecycle.recordReference('DEC-001', 'alice'); // same day dedup
|
|
230
|
+
|
|
231
|
+
expect(events).toHaveLength(1); // only first call fires
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should not fire callback for non-existent entry', () => {
|
|
235
|
+
const events: any[] = [];
|
|
236
|
+
lifecycle.onReference((event) => events.push(event));
|
|
237
|
+
|
|
238
|
+
lifecycle.recordReference('NONEXISTENT', 'alice');
|
|
239
|
+
|
|
240
|
+
expect(events).toHaveLength(0);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe('recordReference same-day dedup (B4)', () => {
|
|
245
|
+
it('should skip file write when same contributor references on same day', () => {
|
|
246
|
+
store.save(makeEntry());
|
|
247
|
+
const result1 = lifecycle.recordReference('DEC-001', 'alice');
|
|
248
|
+
expect(result1).toBeDefined();
|
|
249
|
+
expect(result1!.referencedBy).toHaveLength(1);
|
|
250
|
+
|
|
251
|
+
// Second call same day, same contributor — should return entry unchanged
|
|
252
|
+
const result2 = lifecycle.recordReference('DEC-001', 'alice');
|
|
253
|
+
expect(result2).toBeDefined();
|
|
254
|
+
expect(result2!.referencedBy).toHaveLength(1); // no new refKey added
|
|
255
|
+
expect(result2!.lastReferenced).toBe(result1!.lastReferenced); // unchanged
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should add ref for different contributor on same day', () => {
|
|
259
|
+
store.save(makeEntry());
|
|
260
|
+
lifecycle.recordReference('DEC-001', 'alice');
|
|
261
|
+
const result = lifecycle.recordReference('DEC-001', 'bob');
|
|
262
|
+
expect(result!.referencedBy).toHaveLength(2);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('referencedBy cap at 20 (B4)', () => {
|
|
267
|
+
it('should cap referencedBy at MAX_REFERENCED_BY (20)', () => {
|
|
268
|
+
// Pre-fill with 19 referencedBy entries
|
|
269
|
+
const refs = Array.from({ length: 19 }, (_, i) => `user-${i}:2026-05-${String(i + 1).padStart(2, '0')}`);
|
|
270
|
+
store.save(makeEntry({ referencedBy: refs }));
|
|
271
|
+
|
|
272
|
+
// Add a new unique ref
|
|
273
|
+
const result = lifecycle.recordReference('DEC-001', 'new-user');
|
|
274
|
+
expect(result!.referencedBy).toHaveLength(20);
|
|
275
|
+
expect(result!.referencedBy[19]).toMatch(/^new-user:/);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should drop oldest refs when exceeding cap', () => {
|
|
279
|
+
// Pre-fill with 25 referencedBy entries
|
|
280
|
+
const refs = Array.from({ length: 25 }, (_, i) => `user-${i}:2026-05-${String(i + 1).padStart(2, '0')}`);
|
|
281
|
+
store.save(makeEntry({ referencedBy: refs }));
|
|
282
|
+
|
|
283
|
+
// Add a new unique ref — total would be 26, should cap to 20
|
|
284
|
+
const result = lifecycle.recordReference('DEC-001', 'brand-new');
|
|
285
|
+
expect(result!.referencedBy).toHaveLength(20);
|
|
286
|
+
// Should have dropped the 6 oldest (indices 0-5)
|
|
287
|
+
expect(result!.referencedBy[0]).toBe('user-6:2026-05-07');
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
83
291
|
describe('checkPromotion', () => {
|
|
84
292
|
it('should promote draft to verified when referenced', () => {
|
|
85
|
-
store.save(makeEntry({ maturity: 'draft', lastReferenced: '2026-05-01' }));
|
|
293
|
+
store.save(makeEntry({ maturity: 'draft', lastReferenced: '2026-05-01', content: 'a'.repeat(60) }));
|
|
294
|
+
expect(lifecycle.checkPromotion('DEC-001')).toBe('verified');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should NOT promote draft with content < 50 chars', () => {
|
|
298
|
+
store.save(makeEntry({ maturity: 'draft', lastReferenced: '2026-05-01', content: 'short' }));
|
|
299
|
+
expect(lifecycle.checkPromotion('DEC-001')).toBeUndefined();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should promote draft with exactly 50 chars content', () => {
|
|
303
|
+
store.save(makeEntry({ maturity: 'draft', lastReferenced: '2026-05-01', content: 'a'.repeat(50) }));
|
|
86
304
|
expect(lifecycle.checkPromotion('DEC-001')).toBe('verified');
|
|
87
305
|
});
|
|
88
306
|
|
|
@@ -94,6 +312,7 @@ describe('KnowledgeLifecycle', () => {
|
|
|
94
312
|
it('should promote verified to proven when criteria met', () => {
|
|
95
313
|
store.save(makeEntry({
|
|
96
314
|
maturity: 'verified',
|
|
315
|
+
content: 'a'.repeat(100),
|
|
97
316
|
contributors: ['a', 'b', 'c'],
|
|
98
317
|
projects: ['p1', 'p2'],
|
|
99
318
|
}));
|
|
@@ -178,7 +397,7 @@ describe('KnowledgeLifecycle', () => {
|
|
|
178
397
|
|
|
179
398
|
describe('tryPromote', () => {
|
|
180
399
|
it('should promote eligible entry', () => {
|
|
181
|
-
store.save(makeEntry({ maturity: 'draft', lastReferenced: '2026-05-01' }));
|
|
400
|
+
store.save(makeEntry({ maturity: 'draft', lastReferenced: '2026-05-01', content: 'a'.repeat(60) }));
|
|
182
401
|
const change = lifecycle.tryPromote('DEC-001');
|
|
183
402
|
expect(change).toBeDefined();
|
|
184
403
|
expect(change!.from).toBe('draft');
|
|
@@ -212,6 +431,181 @@ describe('KnowledgeLifecycle', () => {
|
|
|
212
431
|
it('should return undefined for non-existent entry', () => {
|
|
213
432
|
expect(lifecycle.checkPromotion('NON-EXISTENT')).toBeUndefined();
|
|
214
433
|
});
|
|
434
|
+
|
|
435
|
+
// RC2: test ID exclusion
|
|
436
|
+
it('should NOT promote draft with test-* ID', () => {
|
|
437
|
+
store.save(makeEntry({ id: 'test-search-123', maturity: 'draft', lastReferenced: '2026-05-01', content: 'a'.repeat(60) }));
|
|
438
|
+
expect(lifecycle.checkPromotion('test-search-123')).toBeUndefined();
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('should NOT promote draft with inj-test-* ID', () => {
|
|
442
|
+
store.save(makeEntry({ id: 'inj-test-123', maturity: 'draft', lastReferenced: '2026-05-01', content: 'a'.repeat(60) }));
|
|
443
|
+
expect(lifecycle.checkPromotion('inj-test-123')).toBeUndefined();
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// RC2: verified→proven content quality gate
|
|
447
|
+
it('should NOT promote verified to proven with content < 100 chars', () => {
|
|
448
|
+
store.save(makeEntry({
|
|
449
|
+
maturity: 'verified',
|
|
450
|
+
content: 'short content',
|
|
451
|
+
contributors: ['a', 'b', 'c'],
|
|
452
|
+
projects: ['p1', 'p2'],
|
|
453
|
+
}));
|
|
454
|
+
expect(lifecycle.checkPromotion('DEC-001')).toBeUndefined();
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('should promote verified to proven with content >= 100 chars', () => {
|
|
458
|
+
store.save(makeEntry({
|
|
459
|
+
maturity: 'verified',
|
|
460
|
+
content: 'a'.repeat(100),
|
|
461
|
+
contributors: ['a', 'b', 'c'],
|
|
462
|
+
projects: ['p1', 'p2'],
|
|
463
|
+
}));
|
|
464
|
+
expect(lifecycle.checkPromotion('DEC-001')).toBe('proven');
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('should NOT promote verified via Path B with content < 100 chars', () => {
|
|
468
|
+
store.save(makeEntry({
|
|
469
|
+
maturity: 'verified',
|
|
470
|
+
content: 'short',
|
|
471
|
+
referencedBy: ['a:2026-05-01', 'b:2026-05-02', 'c:2026-05-03'],
|
|
472
|
+
sourceReferences: [
|
|
473
|
+
{ workflow: 'pattern:monitor', timestamp: '2026-05-01' },
|
|
474
|
+
{ workflow: 'pattern:triage', timestamp: '2026-05-02' },
|
|
475
|
+
],
|
|
476
|
+
}));
|
|
477
|
+
expect(lifecycle.checkPromotion('DEC-001')).toBeUndefined();
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Path C: execution success rate promotion
|
|
481
|
+
it('should promote verified to proven via execution success rate >= 80%', () => {
|
|
482
|
+
store.save(makeEntry({
|
|
483
|
+
maturity: 'verified',
|
|
484
|
+
content: 'a'.repeat(100),
|
|
485
|
+
executionResults: [
|
|
486
|
+
{ contributor: 'a', success: true, timestamp: '2026-05-01' },
|
|
487
|
+
{ contributor: 'b', success: true, timestamp: '2026-05-02' },
|
|
488
|
+
{ contributor: 'c', success: true, timestamp: '2026-05-03' },
|
|
489
|
+
{ contributor: 'd', success: true, timestamp: '2026-05-04' },
|
|
490
|
+
],
|
|
491
|
+
}));
|
|
492
|
+
expect(lifecycle.checkPromotion('DEC-001')).toBe('proven'); // 4/4 = 100%
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('should promote verified to proven with exactly 80% success rate', () => {
|
|
496
|
+
store.save(makeEntry({
|
|
497
|
+
maturity: 'verified',
|
|
498
|
+
content: 'a'.repeat(100),
|
|
499
|
+
executionResults: [
|
|
500
|
+
{ contributor: 'a', success: true, timestamp: '2026-05-01' },
|
|
501
|
+
{ contributor: 'b', success: true, timestamp: '2026-05-02' },
|
|
502
|
+
{ contributor: 'c', success: true, timestamp: '2026-05-03' },
|
|
503
|
+
{ contributor: 'd', success: true, timestamp: '2026-05-04' },
|
|
504
|
+
{ contributor: 'e', success: false, timestamp: '2026-05-05' },
|
|
505
|
+
],
|
|
506
|
+
}));
|
|
507
|
+
expect(lifecycle.checkPromotion('DEC-001')).toBe('proven'); // 4/5 = 80%
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('should NOT promote verified with execution success rate just below 80%', () => {
|
|
511
|
+
store.save(makeEntry({
|
|
512
|
+
maturity: 'verified',
|
|
513
|
+
content: 'a'.repeat(100),
|
|
514
|
+
executionResults: [
|
|
515
|
+
{ contributor: 'a', success: true, timestamp: '2026-05-01' },
|
|
516
|
+
{ contributor: 'b', success: true, timestamp: '2026-05-02' },
|
|
517
|
+
{ contributor: 'c', success: true, timestamp: '2026-05-03' },
|
|
518
|
+
{ contributor: 'd', success: false, timestamp: '2026-05-04' },
|
|
519
|
+
],
|
|
520
|
+
}));
|
|
521
|
+
expect(lifecycle.checkPromotion('DEC-001')).toBeUndefined(); // 3/4 = 75%
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('should NOT promote verified with execution success rate < 80%', () => {
|
|
525
|
+
store.save(makeEntry({
|
|
526
|
+
maturity: 'verified',
|
|
527
|
+
content: 'a'.repeat(100),
|
|
528
|
+
executionResults: [
|
|
529
|
+
{ contributor: 'a', success: true, timestamp: '2026-05-01' },
|
|
530
|
+
{ contributor: 'b', success: false, timestamp: '2026-05-02' },
|
|
531
|
+
{ contributor: 'c', success: false, timestamp: '2026-05-03' },
|
|
532
|
+
],
|
|
533
|
+
}));
|
|
534
|
+
expect(lifecycle.checkPromotion('DEC-001')).toBeUndefined(); // 1/3 = 33%
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('should NOT promote verified with < 3 execution results', () => {
|
|
538
|
+
store.save(makeEntry({
|
|
539
|
+
maturity: 'verified',
|
|
540
|
+
content: 'a'.repeat(100),
|
|
541
|
+
executionResults: [
|
|
542
|
+
{ contributor: 'a', success: true, timestamp: '2026-05-01' },
|
|
543
|
+
{ contributor: 'b', success: true, timestamp: '2026-05-02' },
|
|
544
|
+
],
|
|
545
|
+
}));
|
|
546
|
+
expect(lifecycle.checkPromotion('DEC-001')).toBeUndefined(); // only 2 results
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('should NOT promote verified via Path C with content < 100 chars', () => {
|
|
550
|
+
store.save(makeEntry({
|
|
551
|
+
maturity: 'verified',
|
|
552
|
+
content: 'short',
|
|
553
|
+
executionResults: [
|
|
554
|
+
{ contributor: 'a', success: true, timestamp: '2026-05-01' },
|
|
555
|
+
{ contributor: 'b', success: true, timestamp: '2026-05-02' },
|
|
556
|
+
{ contributor: 'c', success: true, timestamp: '2026-05-03' },
|
|
557
|
+
],
|
|
558
|
+
}));
|
|
559
|
+
expect(lifecycle.checkPromotion('DEC-001')).toBeUndefined(); // content gate blocks
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// Path C: human source preference
|
|
563
|
+
it('should promote verified via human success rate when mixed with auto', () => {
|
|
564
|
+
store.save(makeEntry({
|
|
565
|
+
maturity: 'verified',
|
|
566
|
+
content: 'a'.repeat(100),
|
|
567
|
+
executionResults: [
|
|
568
|
+
{ contributor: 'alice', success: true, timestamp: '2026-05-01', source: 'human' },
|
|
569
|
+
{ contributor: 'bob', success: true, timestamp: '2026-05-02', source: 'human' },
|
|
570
|
+
{ contributor: 'charlie', success: true, timestamp: '2026-05-03', source: 'human' },
|
|
571
|
+
{ contributor: 'auto-bot', success: false, timestamp: '2026-05-04', source: 'auto' },
|
|
572
|
+
],
|
|
573
|
+
}));
|
|
574
|
+
// Human rate: 3/3 = 100%, auto fails but human succeeds → promote
|
|
575
|
+
expect(lifecycle.checkPromotion('DEC-001')).toBe('proven');
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('should NOT promote when human rate < 80% even if overall rate >= 80%', () => {
|
|
579
|
+
store.save(makeEntry({
|
|
580
|
+
maturity: 'verified',
|
|
581
|
+
content: 'a'.repeat(100),
|
|
582
|
+
executionResults: [
|
|
583
|
+
{ contributor: 'alice', success: false, timestamp: '2026-05-01', source: 'human' },
|
|
584
|
+
{ contributor: 'bob', success: false, timestamp: '2026-05-02', source: 'human' },
|
|
585
|
+
{ contributor: 'charlie', success: true, timestamp: '2026-05-03', source: 'human' },
|
|
586
|
+
{ contributor: 'auto-1', success: true, timestamp: '2026-05-04', source: 'auto' },
|
|
587
|
+
{ contributor: 'auto-2', success: true, timestamp: '2026-05-05', source: 'auto' },
|
|
588
|
+
{ contributor: 'auto-3', success: true, timestamp: '2026-05-06', source: 'auto' },
|
|
589
|
+
],
|
|
590
|
+
}));
|
|
591
|
+
// Human rate: 1/3 = 33%, overall: 4/6 = 67% — neither meets threshold
|
|
592
|
+
expect(lifecycle.checkPromotion('DEC-001')).toBeUndefined();
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it('should fall back to overall rate when no human results', () => {
|
|
596
|
+
store.save(makeEntry({
|
|
597
|
+
maturity: 'verified',
|
|
598
|
+
content: 'a'.repeat(100),
|
|
599
|
+
executionResults: [
|
|
600
|
+
{ contributor: 'auto-1', success: true, timestamp: '2026-05-01', source: 'auto' },
|
|
601
|
+
{ contributor: 'auto-2', success: true, timestamp: '2026-05-02', source: 'auto' },
|
|
602
|
+
{ contributor: 'auto-3', success: true, timestamp: '2026-05-03', source: 'auto' },
|
|
603
|
+
{ contributor: 'auto-4', success: true, timestamp: '2026-05-04', source: 'auto' },
|
|
604
|
+
],
|
|
605
|
+
}));
|
|
606
|
+
// No human results → fall back to overall: 4/4 = 100%
|
|
607
|
+
expect(lifecycle.checkPromotion('DEC-001')).toBe('proven');
|
|
608
|
+
});
|
|
215
609
|
});
|
|
216
610
|
|
|
217
611
|
describe('checkEntryDecay edge cases', () => {
|
|
@@ -245,4 +639,263 @@ describe('KnowledgeLifecycle', () => {
|
|
|
245
639
|
expect(lifecycle.checkEntryDecay('DEC-001')).toBe('verified');
|
|
246
640
|
});
|
|
247
641
|
});
|
|
642
|
+
|
|
643
|
+
// ── decayAt hard expiry ────────────────────────────────────
|
|
644
|
+
|
|
645
|
+
describe('decayAt hard expiry', () => {
|
|
646
|
+
it('should archive entry when decayAt is in the past', () => {
|
|
647
|
+
store.save(makeEntry({
|
|
648
|
+
maturity: 'proven',
|
|
649
|
+
decayAt: '2026-01-01T00:00:00.000Z',
|
|
650
|
+
}));
|
|
651
|
+
expect(lifecycle.checkEntryDecay('DEC-001')).toBe('archived');
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it('should NOT archive entry when decayAt is in the future', () => {
|
|
655
|
+
const future = new Date();
|
|
656
|
+
future.setFullYear(future.getFullYear() + 1);
|
|
657
|
+
store.save(makeEntry({
|
|
658
|
+
maturity: 'proven',
|
|
659
|
+
decayAt: future.toISOString(),
|
|
660
|
+
lastReferenced: new Date().toISOString(),
|
|
661
|
+
}));
|
|
662
|
+
expect(lifecycle.checkEntryDecay('DEC-001')).toBeUndefined();
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it('should take precedence over mode-specific decay', () => {
|
|
666
|
+
// proven entry with decayAt in the past should go straight to archived
|
|
667
|
+
// even though proven→verified takes 12 months normally
|
|
668
|
+
store.save(makeEntry({
|
|
669
|
+
maturity: 'proven',
|
|
670
|
+
decayAt: '2026-01-01T00:00:00.000Z',
|
|
671
|
+
lastReferenced: new Date().toISOString(), // recent
|
|
672
|
+
}));
|
|
673
|
+
expect(lifecycle.checkEntryDecay('DEC-001')).toBe('archived');
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// ── rule lifecycle ─────────────────────────────────────────
|
|
678
|
+
|
|
679
|
+
describe('rule lifecycle', () => {
|
|
680
|
+
it('should promote draft→active with 1 success execution', () => {
|
|
681
|
+
store.save(makeEntry({
|
|
682
|
+
id: 'RULE-001',
|
|
683
|
+
consumptionMode: 'rule',
|
|
684
|
+
maturity: 'draft',
|
|
685
|
+
content: 'API port must be 13101',
|
|
686
|
+
executionResults: [
|
|
687
|
+
{ contributor: 'alice', success: true, timestamp: '2026-05-01T00:00:00.000Z', source: 'human' },
|
|
688
|
+
],
|
|
689
|
+
}));
|
|
690
|
+
expect(lifecycle.checkPromotion('RULE-001')).toBe('active');
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it('should NOT promote draft→active with 0 executions', () => {
|
|
694
|
+
store.save(makeEntry({
|
|
695
|
+
id: 'RULE-001',
|
|
696
|
+
consumptionMode: 'rule',
|
|
697
|
+
maturity: 'draft',
|
|
698
|
+
content: 'API port must be 13101',
|
|
699
|
+
executionResults: [],
|
|
700
|
+
}));
|
|
701
|
+
expect(lifecycle.checkPromotion('RULE-001')).toBeUndefined();
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
it('should NOT promote draft→active with failure execution', () => {
|
|
705
|
+
store.save(makeEntry({
|
|
706
|
+
id: 'RULE-001',
|
|
707
|
+
consumptionMode: 'rule',
|
|
708
|
+
maturity: 'draft',
|
|
709
|
+
content: 'API port must be 13101',
|
|
710
|
+
executionResults: [
|
|
711
|
+
{ contributor: 'alice', success: false, timestamp: '2026-05-01T00:00:00.000Z', source: 'human' },
|
|
712
|
+
],
|
|
713
|
+
}));
|
|
714
|
+
expect(lifecycle.checkPromotion('RULE-001')).toBeUndefined();
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
it('should decay active→deprecated when fail rate >= 50% with 3+ results', () => {
|
|
718
|
+
store.save(makeEntry({
|
|
719
|
+
id: 'RULE-001',
|
|
720
|
+
consumptionMode: 'rule',
|
|
721
|
+
maturity: 'active',
|
|
722
|
+
content: 'API port must be 13101',
|
|
723
|
+
executionResults: [
|
|
724
|
+
{ contributor: 'alice', success: false, timestamp: '2026-05-01T00:00:00.000Z' },
|
|
725
|
+
{ contributor: 'bob', success: false, timestamp: '2026-05-02T00:00:00.000Z' },
|
|
726
|
+
{ contributor: 'charlie', success: true, timestamp: '2026-05-03T00:00:00.000Z' },
|
|
727
|
+
],
|
|
728
|
+
}));
|
|
729
|
+
expect(lifecycle.checkEntryDecay('RULE-001')).toBe('deprecated');
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it('should NOT decay active→deprecated when fail rate < 50%', () => {
|
|
733
|
+
store.save(makeEntry({
|
|
734
|
+
id: 'RULE-001',
|
|
735
|
+
consumptionMode: 'rule',
|
|
736
|
+
maturity: 'active',
|
|
737
|
+
content: 'API port must be 13101',
|
|
738
|
+
executionResults: [
|
|
739
|
+
{ contributor: 'alice', success: true, timestamp: '2026-05-01T00:00:00.000Z' },
|
|
740
|
+
{ contributor: 'bob', success: true, timestamp: '2026-05-02T00:00:00.000Z' },
|
|
741
|
+
{ contributor: 'charlie', success: false, timestamp: '2026-05-03T00:00:00.000Z' },
|
|
742
|
+
],
|
|
743
|
+
}));
|
|
744
|
+
expect(lifecycle.checkEntryDecay('RULE-001')).toBeUndefined();
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
it('should NOT decay active with < 3 results', () => {
|
|
748
|
+
store.save(makeEntry({
|
|
749
|
+
id: 'RULE-001',
|
|
750
|
+
consumptionMode: 'rule',
|
|
751
|
+
maturity: 'active',
|
|
752
|
+
content: 'API port must be 13101',
|
|
753
|
+
executionResults: [
|
|
754
|
+
{ contributor: 'alice', success: false, timestamp: '2026-05-01T00:00:00.000Z' },
|
|
755
|
+
],
|
|
756
|
+
}));
|
|
757
|
+
expect(lifecycle.checkEntryDecay('RULE-001')).toBeUndefined();
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
it('should NOT promote or decay deprecated', () => {
|
|
761
|
+
store.save(makeEntry({
|
|
762
|
+
id: 'RULE-001',
|
|
763
|
+
consumptionMode: 'rule',
|
|
764
|
+
maturity: 'deprecated',
|
|
765
|
+
content: 'Old rule',
|
|
766
|
+
}));
|
|
767
|
+
expect(lifecycle.checkPromotion('RULE-001')).toBeUndefined();
|
|
768
|
+
expect(lifecycle.checkEntryDecay('RULE-001')).toBeUndefined();
|
|
769
|
+
});
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
// ── context lifecycle ──────────────────────────────────────
|
|
773
|
+
|
|
774
|
+
describe('context lifecycle', () => {
|
|
775
|
+
it('should promote draft→active with 1 reference', () => {
|
|
776
|
+
store.save(makeEntry({
|
|
777
|
+
id: 'CTX-001',
|
|
778
|
+
consumptionMode: 'context',
|
|
779
|
+
maturity: 'draft',
|
|
780
|
+
content: 'Project uses TypeScript 5.0',
|
|
781
|
+
referencedBy: ['user:2026-05-01'],
|
|
782
|
+
}));
|
|
783
|
+
expect(lifecycle.checkPromotion('CTX-001')).toBe('active');
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
it('should NOT promote draft→active with 0 references', () => {
|
|
787
|
+
store.save(makeEntry({
|
|
788
|
+
id: 'CTX-001',
|
|
789
|
+
consumptionMode: 'context',
|
|
790
|
+
maturity: 'draft',
|
|
791
|
+
content: 'Project uses TypeScript 5.0',
|
|
792
|
+
referencedBy: [],
|
|
793
|
+
}));
|
|
794
|
+
expect(lifecycle.checkPromotion('CTX-001')).toBeUndefined();
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
it('should decay active→archived after 3 months unreferenced', () => {
|
|
798
|
+
const oldDate = new Date();
|
|
799
|
+
oldDate.setMonth(oldDate.getMonth() - 4);
|
|
800
|
+
store.save(makeEntry({
|
|
801
|
+
id: 'CTX-001',
|
|
802
|
+
consumptionMode: 'context',
|
|
803
|
+
maturity: 'active',
|
|
804
|
+
content: 'Project uses TypeScript 5.0',
|
|
805
|
+
lastReferenced: oldDate.toISOString(),
|
|
806
|
+
}));
|
|
807
|
+
expect(lifecycle.checkEntryDecay('CTX-001')).toBe('archived');
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
it('should NOT decay active when recently referenced', () => {
|
|
811
|
+
store.save(makeEntry({
|
|
812
|
+
id: 'CTX-001',
|
|
813
|
+
consumptionMode: 'context',
|
|
814
|
+
maturity: 'active',
|
|
815
|
+
content: 'Project uses TypeScript 5.0',
|
|
816
|
+
lastReferenced: new Date().toISOString(),
|
|
817
|
+
}));
|
|
818
|
+
expect(lifecycle.checkEntryDecay('CTX-001')).toBeUndefined();
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
it('should NOT promote active or archived', () => {
|
|
822
|
+
store.save(makeEntry({
|
|
823
|
+
id: 'CTX-001',
|
|
824
|
+
consumptionMode: 'context',
|
|
825
|
+
maturity: 'active',
|
|
826
|
+
content: 'Project uses TypeScript 5.0',
|
|
827
|
+
}));
|
|
828
|
+
expect(lifecycle.checkPromotion('CTX-001')).toBeUndefined();
|
|
829
|
+
});
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
// ── signal lifecycle ───────────────────────────────────────
|
|
833
|
+
|
|
834
|
+
describe('signal lifecycle', () => {
|
|
835
|
+
it('should NOT promote signal entries', () => {
|
|
836
|
+
store.save(makeEntry({
|
|
837
|
+
id: 'SIG-001',
|
|
838
|
+
consumptionMode: 'signal',
|
|
839
|
+
maturity: 'active',
|
|
840
|
+
content: 'CPU usage spike detected',
|
|
841
|
+
tags: ['monitoring'],
|
|
842
|
+
}));
|
|
843
|
+
expect(lifecycle.checkPromotion('SIG-001')).toBeUndefined();
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
it('should archive saturated signal with newer same-tag entry', () => {
|
|
847
|
+
store.save(makeEntry({
|
|
848
|
+
id: 'SIG-001',
|
|
849
|
+
consumptionMode: 'signal',
|
|
850
|
+
maturity: 'active',
|
|
851
|
+
content: 'Old monitoring alert',
|
|
852
|
+
tags: ['monitoring'],
|
|
853
|
+
created: '2026-05-01T00:00:00.000Z',
|
|
854
|
+
referencedBy: ['a:2026-05-01', 'b:2026-05-02', 'c:2026-05-03'],
|
|
855
|
+
}));
|
|
856
|
+
store.save(makeEntry({
|
|
857
|
+
id: 'SIG-002',
|
|
858
|
+
consumptionMode: 'signal',
|
|
859
|
+
maturity: 'active',
|
|
860
|
+
content: 'New monitoring alert',
|
|
861
|
+
tags: ['monitoring'],
|
|
862
|
+
created: '2026-05-10T00:00:00.000Z',
|
|
863
|
+
}));
|
|
864
|
+
expect(lifecycle.checkEntryDecay('SIG-001')).toBe('archived');
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
it('should NOT archive saturated signal without newer same-tag entry', () => {
|
|
868
|
+
store.save(makeEntry({
|
|
869
|
+
id: 'SIG-001',
|
|
870
|
+
consumptionMode: 'signal',
|
|
871
|
+
maturity: 'active',
|
|
872
|
+
content: 'Latest monitoring alert',
|
|
873
|
+
tags: ['monitoring'],
|
|
874
|
+
created: '2026-05-10T00:00:00.000Z',
|
|
875
|
+
referencedBy: ['a:2026-05-01', 'b:2026-05-02', 'c:2026-05-03'],
|
|
876
|
+
}));
|
|
877
|
+
expect(lifecycle.checkEntryDecay('SIG-001')).toBeUndefined();
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
it('should NOT archive unsaturated signal even with newer entry', () => {
|
|
881
|
+
store.save(makeEntry({
|
|
882
|
+
id: 'SIG-001',
|
|
883
|
+
consumptionMode: 'signal',
|
|
884
|
+
maturity: 'active',
|
|
885
|
+
content: 'Old monitoring alert',
|
|
886
|
+
tags: ['monitoring'],
|
|
887
|
+
created: '2026-05-01T00:00:00.000Z',
|
|
888
|
+
referencedBy: ['a:2026-05-01'], // only 1 reference, not saturated
|
|
889
|
+
}));
|
|
890
|
+
store.save(makeEntry({
|
|
891
|
+
id: 'SIG-002',
|
|
892
|
+
consumptionMode: 'signal',
|
|
893
|
+
maturity: 'active',
|
|
894
|
+
content: 'New monitoring alert',
|
|
895
|
+
tags: ['monitoring'],
|
|
896
|
+
created: '2026-05-10T00:00:00.000Z',
|
|
897
|
+
}));
|
|
898
|
+
expect(lifecycle.checkEntryDecay('SIG-001')).toBeUndefined();
|
|
899
|
+
});
|
|
900
|
+
});
|
|
248
901
|
});
|