@tryinget/pi-agent-vent 0.2.2 → 0.3.1
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/docs/engineering.local.md +1 -1
- package/package.json +9 -10
- package/policy/engineering-lane.json +11 -5
- package/scripts/release-check.sh +19 -77
- package/scripts/release-smoke.sh +7 -3
- package/scripts/validate-structure.mjs +4 -3
- package/scripts/validate-structure.sh +0 -2
- package/tests/vent-store-curation.test.js +427 -0
- package/tests/vent-store-diagnostics.test.js +148 -0
- package/tests/vent-store-lifecycle.test.js +245 -0
- package/tests/vent-store-records.test.js +304 -0
- package/tests/vent-store-retention.test.js +606 -0
- package/tests/vent-store-review-outcomes.test.js +378 -0
- package/scripts/docs-list.sh +0 -55
- package/tests/vent-store.test.js +0 -2015
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
summary: "Vent store curation events, cycle guards, facet summaries, and queue guidance; split from vent-store.test.js."
|
|
3
|
+
read_when:
|
|
4
|
+
- "You change curation events, cycle guards, facet summaries, and queue guidance behavior."
|
|
5
|
+
*/
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import {
|
|
12
|
+
appendVentRecord,
|
|
13
|
+
assertCanCurateRecurrence,
|
|
14
|
+
buildFacetSummary,
|
|
15
|
+
buildReviewDetail,
|
|
16
|
+
createCurationEvent,
|
|
17
|
+
createReviewEvent,
|
|
18
|
+
createVentRecord,
|
|
19
|
+
formatFacetSummary,
|
|
20
|
+
formatReviewDetail,
|
|
21
|
+
formatReviewQueue,
|
|
22
|
+
readVentRecords,
|
|
23
|
+
resolveRecurrenceGroup,
|
|
24
|
+
summarizeRecords,
|
|
25
|
+
summarizeReviewQueue,
|
|
26
|
+
} from "../src/vent-store.js";
|
|
27
|
+
|
|
28
|
+
test("stale vent-store lock files are removed before append", () => {
|
|
29
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-stale-lock-"));
|
|
30
|
+
const filePath = path.join(dir, "vents.jsonl");
|
|
31
|
+
const lockPath = `${filePath}.lock`;
|
|
32
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
33
|
+
fs.writeFileSync(lockPath, "stale\n", "utf8");
|
|
34
|
+
const old = new Date(Date.now() - 60_000);
|
|
35
|
+
fs.utimesSync(lockPath, old, old);
|
|
36
|
+
appendVentRecord(filePath, createVentRecord({ summary: "Stale lock clears" }, { id: "v1" }));
|
|
37
|
+
|
|
38
|
+
assert.equal(readVentRecords(filePath).records.length, 1);
|
|
39
|
+
assert.equal(fs.existsSync(lockPath), false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("curation events merge recurrence groups without rewriting source records", () => {
|
|
43
|
+
const first = createVentRecord(
|
|
44
|
+
{ summary: "Reload drops tool", category: "bug", recurrenceKey: "reload-tool-a" },
|
|
45
|
+
{ id: "v1" },
|
|
46
|
+
);
|
|
47
|
+
const second = createVentRecord(
|
|
48
|
+
{ summary: "Reload loses tool", category: "bug", recurrenceKey: "reload-tool-b" },
|
|
49
|
+
{ id: "v2" },
|
|
50
|
+
);
|
|
51
|
+
const records = [first, second];
|
|
52
|
+
const curation = createCurationEvent({
|
|
53
|
+
action: "merge",
|
|
54
|
+
sourceRecurrenceKey: second.recurrenceKey,
|
|
55
|
+
targetRecurrenceKey: first.recurrenceKey,
|
|
56
|
+
note: "same bug token=abc123",
|
|
57
|
+
});
|
|
58
|
+
assert.equal(curation.privacy.redacted, true);
|
|
59
|
+
assert.match(curation.note, /token=\[REDACTED\]/);
|
|
60
|
+
|
|
61
|
+
const summary = summarizeRecords(records, { curationEvents: [curation] });
|
|
62
|
+
assert.equal(summary.groupCount, 1);
|
|
63
|
+
assert.equal(summary.groups[0].count, 2);
|
|
64
|
+
assert.equal(summary.groups[0].recurrenceKey, first.recurrenceKey);
|
|
65
|
+
assert.equal(second.recurrenceKey, "bug:reload-tool-b");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("curation fails closed for unknown source, self merge, and cycles", () => {
|
|
69
|
+
const records = [
|
|
70
|
+
createVentRecord({ summary: "Alpha", category: "bug", recurrenceKey: "alpha" }, { id: "v1" }),
|
|
71
|
+
createVentRecord({ summary: "Beta", category: "bug", recurrenceKey: "beta" }, { id: "v2" }),
|
|
72
|
+
];
|
|
73
|
+
assert.throws(
|
|
74
|
+
() =>
|
|
75
|
+
assertCanCurateRecurrence(records, [], {
|
|
76
|
+
action: "merge",
|
|
77
|
+
sourceRecurrenceKey: "bug:nope",
|
|
78
|
+
targetRecurrenceKey: "bug:alpha",
|
|
79
|
+
}),
|
|
80
|
+
/unknown recurrence group/,
|
|
81
|
+
);
|
|
82
|
+
assert.throws(
|
|
83
|
+
() =>
|
|
84
|
+
createCurationEvent({
|
|
85
|
+
action: "merge",
|
|
86
|
+
sourceRecurrenceKey: "bug:alpha",
|
|
87
|
+
targetRecurrenceKey: "bug:alpha",
|
|
88
|
+
}),
|
|
89
|
+
/source and target must differ/,
|
|
90
|
+
);
|
|
91
|
+
const first = createCurationEvent({
|
|
92
|
+
action: "merge",
|
|
93
|
+
sourceRecurrenceKey: "bug:alpha",
|
|
94
|
+
targetRecurrenceKey: "bug:beta",
|
|
95
|
+
});
|
|
96
|
+
assert.throws(
|
|
97
|
+
() =>
|
|
98
|
+
assertCanCurateRecurrence(records, [first], {
|
|
99
|
+
action: "merge",
|
|
100
|
+
sourceRecurrenceKey: "bug:beta",
|
|
101
|
+
targetRecurrenceKey: "bug:alpha",
|
|
102
|
+
}),
|
|
103
|
+
/cycle detected/,
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("review state follows curated recurrence key", () => {
|
|
108
|
+
const first = createVentRecord(
|
|
109
|
+
{ summary: "Primary", category: "workflow", recurrenceKey: "primary" },
|
|
110
|
+
{ id: "v1" },
|
|
111
|
+
);
|
|
112
|
+
const second = createVentRecord(
|
|
113
|
+
{ summary: "Duplicate", category: "workflow", recurrenceKey: "duplicate" },
|
|
114
|
+
{ id: "v2" },
|
|
115
|
+
);
|
|
116
|
+
const curation = createCurationEvent({
|
|
117
|
+
action: "merge",
|
|
118
|
+
sourceRecurrenceKey: second.recurrenceKey,
|
|
119
|
+
targetRecurrenceKey: first.recurrenceKey,
|
|
120
|
+
});
|
|
121
|
+
const review = createReviewEvent({ recurrenceKey: second.recurrenceKey, state: "acknowledged" });
|
|
122
|
+
|
|
123
|
+
const resolved = resolveRecurrenceGroup([first, second], second.recurrenceKey, [curation]);
|
|
124
|
+
assert.equal(resolved.recurrenceKey, first.recurrenceKey);
|
|
125
|
+
assert.equal(resolved.resolvedThroughCuration, true);
|
|
126
|
+
const queue = summarizeReviewQueue([first, second], [review], { curationEvents: [curation] });
|
|
127
|
+
assert.equal(queue.groupCount, 1);
|
|
128
|
+
assert.equal(queue.items[0].reviewState, "acknowledged");
|
|
129
|
+
assert.equal(queue.items[0].recurrenceKey, first.recurrenceKey);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("facet summary counts local categories tags tools packages and review states", () => {
|
|
133
|
+
const first = createVentRecord(
|
|
134
|
+
{
|
|
135
|
+
summary: "Facet primary",
|
|
136
|
+
category: "tool_failure",
|
|
137
|
+
severity: "high",
|
|
138
|
+
recurrenceKey: "facet-primary",
|
|
139
|
+
tags: ["Reload", "Runtime"],
|
|
140
|
+
tool: "pi reload",
|
|
141
|
+
packageName: "@tryinget/pi-agent-vent",
|
|
142
|
+
},
|
|
143
|
+
{ id: "v1" },
|
|
144
|
+
);
|
|
145
|
+
const second = createVentRecord(
|
|
146
|
+
{
|
|
147
|
+
summary: "Facet duplicate token=abc123",
|
|
148
|
+
category: "workflow",
|
|
149
|
+
recurrenceKey: "facet-dupe",
|
|
150
|
+
tags: ["Reload"],
|
|
151
|
+
tool: "pi reload",
|
|
152
|
+
packageName: "@tryinget/pi-agent-vent",
|
|
153
|
+
},
|
|
154
|
+
{ id: "v2" },
|
|
155
|
+
);
|
|
156
|
+
const curation = createCurationEvent({
|
|
157
|
+
action: "merge",
|
|
158
|
+
sourceRecurrenceKey: second.recurrenceKey,
|
|
159
|
+
targetRecurrenceKey: first.recurrenceKey,
|
|
160
|
+
});
|
|
161
|
+
const review = createReviewEvent({ recurrenceKey: first.recurrenceKey, state: "acknowledged" });
|
|
162
|
+
|
|
163
|
+
const summary = buildFacetSummary({
|
|
164
|
+
records: [first, second],
|
|
165
|
+
reviewEvents: [review],
|
|
166
|
+
curationEvents: [curation],
|
|
167
|
+
limit: 1,
|
|
168
|
+
});
|
|
169
|
+
assert.equal(summary.totalRecords, 2);
|
|
170
|
+
assert.equal(summary.groupCount, 1);
|
|
171
|
+
assert.deepEqual(summary.records.tools, [{ name: "pi-reload", count: 2 }]);
|
|
172
|
+
assert.deepEqual(summary.records.packages, [{ name: "tryinget-pi-agent-vent", count: 2 }]);
|
|
173
|
+
assert.deepEqual(summary.groups.reviewStates, [{ name: "acknowledged", count: 1 }]);
|
|
174
|
+
const text = formatFacetSummary(summary);
|
|
175
|
+
assert.match(text, /not owner routing/);
|
|
176
|
+
assert.match(text, /tools: pi-reload=2/);
|
|
177
|
+
assert.doesNotMatch(text, /abc123/);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("review queue filters by local facets without owner-routing claims", () => {
|
|
181
|
+
const reload = createVentRecord(
|
|
182
|
+
{
|
|
183
|
+
summary: "Reload tool fails",
|
|
184
|
+
category: "tool_failure",
|
|
185
|
+
recurrenceKey: "reload-tool",
|
|
186
|
+
tags: ["Review Flow", "Reload"],
|
|
187
|
+
tool: "pi reload",
|
|
188
|
+
packageName: "@tryinget/pi-agent-vent",
|
|
189
|
+
},
|
|
190
|
+
{ id: "v1" },
|
|
191
|
+
);
|
|
192
|
+
const docs = createVentRecord(
|
|
193
|
+
{
|
|
194
|
+
summary: "Docs stale",
|
|
195
|
+
category: "documentation",
|
|
196
|
+
recurrenceKey: "docs-stale",
|
|
197
|
+
tags: ["Docs"],
|
|
198
|
+
tool: "docs-list",
|
|
199
|
+
packageName: "@tryinget/other-package",
|
|
200
|
+
},
|
|
201
|
+
{ id: "v2" },
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
assert.throws(
|
|
205
|
+
() => summarizeReviewQueue([reload, docs], [], { filters: { category: "bgu" } }),
|
|
206
|
+
/invalid agent_vent review filter category/,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const queue = summarizeReviewQueue([reload, docs], [], {
|
|
210
|
+
filters: {
|
|
211
|
+
category: "tool-failure",
|
|
212
|
+
tags: ["review flow"],
|
|
213
|
+
tool: "pi reload token=abc123",
|
|
214
|
+
packageName: "@tryinget/pi-agent-vent",
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
assert.equal(queue.groupCount, 2);
|
|
218
|
+
assert.equal(queue.matchingGroupCount, 0);
|
|
219
|
+
assert.equal(queue.queueCount, 0);
|
|
220
|
+
assert.equal(queue.filters.tool, "pi-reload-token-redacted");
|
|
221
|
+
const noMatchText = formatReviewQueue(queue);
|
|
222
|
+
assert.match(noMatchText, /not owner routing/);
|
|
223
|
+
assert.match(noMatchText, /token-redacted/);
|
|
224
|
+
assert.doesNotMatch(noMatchText, /abc123/);
|
|
225
|
+
|
|
226
|
+
const matchingQueue = summarizeReviewQueue([reload, docs], [], {
|
|
227
|
+
filters: {
|
|
228
|
+
category: "tool-failure",
|
|
229
|
+
tags: ["review flow", "reload"],
|
|
230
|
+
tool: "pi reload",
|
|
231
|
+
packageName: "@tryinget/pi-agent-vent",
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
assert.equal(matchingQueue.matchingGroupCount, 1);
|
|
235
|
+
assert.equal(matchingQueue.queueCount, 1);
|
|
236
|
+
assert.equal(matchingQueue.items[0].recurrenceKey, reload.recurrenceKey);
|
|
237
|
+
assert.equal(matchingQueue.filters.tool, "pi-reload");
|
|
238
|
+
assert.deepEqual(matchingQueue.filters.tags, ["review-flow", "reload"]);
|
|
239
|
+
const matchingText = formatReviewQueue(matchingQueue);
|
|
240
|
+
assert.match(
|
|
241
|
+
matchingText,
|
|
242
|
+
/Filters: category=tool_failure; tool=pi-reload; package=tryinget-pi-agent-vent; tags=review-flow,reload/,
|
|
243
|
+
);
|
|
244
|
+
assert.match(matchingText, /Filter note:/);
|
|
245
|
+
assert.doesNotMatch(matchingText, /owner assignment was created/);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("review queue facet filters are bounded for huge caller-controlled input", () => {
|
|
249
|
+
const record = createVentRecord({
|
|
250
|
+
summary: "Huge filter should not leak",
|
|
251
|
+
category: "workflow",
|
|
252
|
+
recurrenceKey: "huge-filter",
|
|
253
|
+
tags: ["safe"],
|
|
254
|
+
tool: "small-tool",
|
|
255
|
+
});
|
|
256
|
+
const queue = summarizeReviewQueue([record], [], {
|
|
257
|
+
filters: {
|
|
258
|
+
tool: `${"x".repeat(5000)} token=abc123`,
|
|
259
|
+
tags: Array.from({ length: 20 }, (_, index) => `tag-${index}`),
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
assert.equal(queue.queueCount, 0);
|
|
263
|
+
assert.ok(queue.filters.tool.length <= 80);
|
|
264
|
+
assert.equal(queue.filters.tags.length, 12);
|
|
265
|
+
const text = formatReviewQueue(queue);
|
|
266
|
+
assert.doesNotMatch(text, /abc123/);
|
|
267
|
+
assert.match(text, /not owner routing/);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("review detail expands curated groups with bounded redacted samples", () => {
|
|
271
|
+
const first = createVentRecord(
|
|
272
|
+
{
|
|
273
|
+
summary: "Primary reload failure",
|
|
274
|
+
category: "tool_failure",
|
|
275
|
+
severity: "high",
|
|
276
|
+
recurrenceKey: "reload-primary",
|
|
277
|
+
evidence: "Bearer abcdefghijklmnop should redact",
|
|
278
|
+
tags: ["Pi Tool", "Reload"],
|
|
279
|
+
packageName: "@tryinget/pi-agent-vent",
|
|
280
|
+
},
|
|
281
|
+
{ id: "v1", now: "2026-05-21T00:00:00.000Z" },
|
|
282
|
+
);
|
|
283
|
+
const second = createVentRecord(
|
|
284
|
+
{
|
|
285
|
+
summary: "Duplicate reload wording",
|
|
286
|
+
category: "workflow",
|
|
287
|
+
recurrenceKey: "reload-dupe",
|
|
288
|
+
reproduction: "Run reload with token=abc123 in shell",
|
|
289
|
+
tags: ["Reload"],
|
|
290
|
+
},
|
|
291
|
+
{ id: "v2", now: "2026-05-21T00:01:00.000Z" },
|
|
292
|
+
);
|
|
293
|
+
const curation = createCurationEvent({
|
|
294
|
+
action: "merge",
|
|
295
|
+
sourceRecurrenceKey: second.recurrenceKey,
|
|
296
|
+
targetRecurrenceKey: first.recurrenceKey,
|
|
297
|
+
});
|
|
298
|
+
const review = createReviewEvent({ recurrenceKey: second.recurrenceKey, state: "acknowledged" });
|
|
299
|
+
|
|
300
|
+
const detail = buildReviewDetail({
|
|
301
|
+
recurrenceKey: second.recurrenceKey,
|
|
302
|
+
records: [first, second],
|
|
303
|
+
reviewEvents: [review],
|
|
304
|
+
curationEvents: [curation],
|
|
305
|
+
limit: 999,
|
|
306
|
+
});
|
|
307
|
+
assert.equal(detail.recurrenceKey, first.recurrenceKey);
|
|
308
|
+
assert.equal(detail.group.count, 2);
|
|
309
|
+
assert.equal(detail.group.reviewState, "acknowledged");
|
|
310
|
+
assert.equal(detail.group.decisionPosture.state, "reviewed_retention_ready");
|
|
311
|
+
assert.deepEqual(detail.group.categories, ["tool_failure", "workflow"]);
|
|
312
|
+
assert.deepEqual(detail.group.tags, ["pi-tool", "reload"]);
|
|
313
|
+
assert.equal(detail.samples.length, 2);
|
|
314
|
+
const text = formatReviewDetail(detail);
|
|
315
|
+
assert.match(text, /Requested key resolved through local curation/);
|
|
316
|
+
assert.match(text, /Decision posture: reviewed_retention_ready/);
|
|
317
|
+
assert.match(
|
|
318
|
+
text,
|
|
319
|
+
/not resolution, assignment, evidence, publication, issue status, task truth, or incident state/,
|
|
320
|
+
);
|
|
321
|
+
assert.match(text, /Human review hints:/);
|
|
322
|
+
assert.match(text, /incident_review draft may help a human decide/);
|
|
323
|
+
assert.match(text, /maintainer_note draft may help package\/tool maintainers/);
|
|
324
|
+
assert.match(text, /github_issue draft may help/);
|
|
325
|
+
assert.match(text, /ak_task draft may help/);
|
|
326
|
+
assert.match(text, /not owner routing, assignment, filing, task creation, incident declaration/);
|
|
327
|
+
assert.match(text, /Local next actions:/);
|
|
328
|
+
assert.match(text, /draft github_issue/);
|
|
329
|
+
assert.match(text, /draft ak_task/);
|
|
330
|
+
assert.match(text, /draft incident_review/);
|
|
331
|
+
assert.match(text, /draft maintainer_note/);
|
|
332
|
+
assert.match(text, /retention preview/);
|
|
333
|
+
assert.match(
|
|
334
|
+
text,
|
|
335
|
+
/do not file, create, declare, assign, record evidence, publish, or mutate owner systems/,
|
|
336
|
+
);
|
|
337
|
+
assert.match(text, /Bearer \[REDACTED\]/);
|
|
338
|
+
assert.match(text, /token=\[REDACTED\]/);
|
|
339
|
+
assert.match(text, /No AK task, GitHub issue, incident, evidence/);
|
|
340
|
+
assert.doesNotMatch(text, /filed|declared|owner was assigned/);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("review output quotes generated commands for legacy recurrence keys", () => {
|
|
344
|
+
const legacy = {
|
|
345
|
+
...createVentRecord({ summary: "Legacy key with spaces", category: "bug" }),
|
|
346
|
+
recurrenceKey: 'bug:legacy key "quoted"',
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const queueText = formatReviewQueue(summarizeReviewQueue([legacy], []));
|
|
350
|
+
assert.match(queueText, /review show "bug:legacy key \\"quoted\\""/);
|
|
351
|
+
assert.match(queueText, /review set acknowledged "bug:legacy key \\"quoted\\""/);
|
|
352
|
+
assert.match(queueText, /draft github_issue "bug:legacy key \\"quoted\\""/);
|
|
353
|
+
|
|
354
|
+
const detailText = formatReviewDetail(
|
|
355
|
+
buildReviewDetail({ recurrenceKey: legacy.recurrenceKey, records: [legacy] }),
|
|
356
|
+
);
|
|
357
|
+
assert.match(detailText, /review set dismissed "bug:legacy key \\"quoted\\""/);
|
|
358
|
+
assert.match(detailText, /draft maintainer_note "bug:legacy key \\"quoted\\""/);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("review queue guidance hints remain advisory and authority-safe", () => {
|
|
362
|
+
const incident = createVentRecord({
|
|
363
|
+
summary: "Critical package bug",
|
|
364
|
+
category: "bug",
|
|
365
|
+
severity: "critical",
|
|
366
|
+
recurrenceKey: "critical-package-bug",
|
|
367
|
+
packageName: "@tryinget/pi-agent-vent",
|
|
368
|
+
});
|
|
369
|
+
const workflow = createVentRecord({
|
|
370
|
+
summary: "Workflow docs unclear",
|
|
371
|
+
category: "documentation",
|
|
372
|
+
recurrenceKey: "workflow-docs",
|
|
373
|
+
});
|
|
374
|
+
const toolOnly = createVentRecord({
|
|
375
|
+
summary: "Tool-only reload issue",
|
|
376
|
+
category: "workflow",
|
|
377
|
+
recurrenceKey: "tool-only",
|
|
378
|
+
tool: "pi reload",
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const queue = summarizeReviewQueue([incident, workflow, toolOnly], []);
|
|
382
|
+
assert.equal(queue.items[0].decisionPosture.priority, "human_review_candidate");
|
|
383
|
+
assert.equal(queue.items[0].decisionPosture.state, "needs_local_review");
|
|
384
|
+
const text = formatReviewQueue(queue);
|
|
385
|
+
assert.match(text, /decision posture: needs_local_review; priority=human_review_candidate/);
|
|
386
|
+
assert.match(text, /No local review decision has been recorded/);
|
|
387
|
+
assert.match(text, /incident_review draft may help a human decide/);
|
|
388
|
+
assert.match(text, /maintainer_note draft may help package\/tool maintainers/);
|
|
389
|
+
assert.match(text, /github_issue draft may help/);
|
|
390
|
+
assert.match(text, /ak_task draft may help/);
|
|
391
|
+
assert.match(text, /Hints are local diagnostics only/);
|
|
392
|
+
assert.doesNotMatch(
|
|
393
|
+
text,
|
|
394
|
+
/GitHub issue was created|AK task was created|incident was declared|owner was assigned|was filed/,
|
|
395
|
+
);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("review queue next actions are state-aware and authority-safe", () => {
|
|
399
|
+
const record = createVentRecord({
|
|
400
|
+
summary: "State-aware next action",
|
|
401
|
+
category: "workflow",
|
|
402
|
+
recurrenceKey: "state-aware",
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const newQueueText = formatReviewQueue(summarizeReviewQueue([record], []));
|
|
406
|
+
assert.match(newQueueText, /review first before local retention archive/);
|
|
407
|
+
assert.match(newQueueText, /draft ak_task/);
|
|
408
|
+
assert.match(
|
|
409
|
+
newQueueText,
|
|
410
|
+
/do not file, create, declare, assign, record evidence, publish, or mutate owner systems/,
|
|
411
|
+
);
|
|
412
|
+
assert.doesNotMatch(newQueueText, /optional local lifecycle: \/agent_vent retention preview/);
|
|
413
|
+
|
|
414
|
+
const reviewedQueueText = formatReviewQueue(
|
|
415
|
+
summarizeReviewQueue(
|
|
416
|
+
[record],
|
|
417
|
+
[createReviewEvent({ recurrenceKey: record.recurrenceKey, state: "acknowledged" })],
|
|
418
|
+
),
|
|
419
|
+
);
|
|
420
|
+
assert.match(reviewedQueueText, /decision posture: reviewed_retention_ready/);
|
|
421
|
+
assert.match(reviewedQueueText, /optional local lifecycle: \/agent_vent retention preview/);
|
|
422
|
+
assert.match(reviewedQueueText, /draft maintainer_note/);
|
|
423
|
+
assert.doesNotMatch(
|
|
424
|
+
reviewedQueueText,
|
|
425
|
+
/GitHub issue was created|AK task was created|incident was declared|was filed/,
|
|
426
|
+
);
|
|
427
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
summary: "Vent store diagnostic state membrane; split from vent-store.test.js."
|
|
3
|
+
read_when:
|
|
4
|
+
- "You change diagnostic state membrane behavior."
|
|
5
|
+
*/
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import {
|
|
12
|
+
appendVentRecord,
|
|
13
|
+
assertCanCurateRecurrence,
|
|
14
|
+
buildEscalationDraft,
|
|
15
|
+
buildReviewComparison,
|
|
16
|
+
createCurationEvent,
|
|
17
|
+
createVentRecord,
|
|
18
|
+
formatReviewComparison,
|
|
19
|
+
loadDiagnosticState,
|
|
20
|
+
MAX_JSONL_FILE_BYTES,
|
|
21
|
+
MAX_JSONL_LINE_BYTES,
|
|
22
|
+
readVentRecords,
|
|
23
|
+
summarizeRecords,
|
|
24
|
+
} from "../src/vent-store.js";
|
|
25
|
+
|
|
26
|
+
test("diagnostic state membrane redacts legacy valid JSONL before draft/export/compare", () => {
|
|
27
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-legacy-secret-"));
|
|
28
|
+
const storePath = path.join(dir, "vents.jsonl");
|
|
29
|
+
fs.writeFileSync(
|
|
30
|
+
storePath,
|
|
31
|
+
`${JSON.stringify({
|
|
32
|
+
id: "legacy-1",
|
|
33
|
+
createdAt: "2026-05-21T00:00:00.000Z",
|
|
34
|
+
category: "bug",
|
|
35
|
+
severity: "high",
|
|
36
|
+
recurrenceKey: "bug:legacy-secret",
|
|
37
|
+
summary: "Legacy unredacted payload",
|
|
38
|
+
evidence: "password=hunter2",
|
|
39
|
+
reproduction: "Bearer abcdefghijklmnop",
|
|
40
|
+
tool: "tool token=abc123",
|
|
41
|
+
tags: ["secret=tagged"],
|
|
42
|
+
privacy: {
|
|
43
|
+
classification: "local-diagnostic-user-data",
|
|
44
|
+
redacted: false,
|
|
45
|
+
redactionPatterns: [],
|
|
46
|
+
},
|
|
47
|
+
})}\n`,
|
|
48
|
+
"utf8",
|
|
49
|
+
);
|
|
50
|
+
const state = loadDiagnosticState({ storePath, reviewPath: path.join(dir, "review.jsonl") });
|
|
51
|
+
const draft = buildEscalationDraft({
|
|
52
|
+
target: "github_issue",
|
|
53
|
+
recurrenceKey: "bug:legacy-secret",
|
|
54
|
+
records: state.records,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
assert.equal(state.records[0].privacy.redacted, true);
|
|
58
|
+
assert.deepEqual(state.records[0].tags, ["secret-redacted"]);
|
|
59
|
+
assert.equal(state.records[0].tool, "tool-token-redacted");
|
|
60
|
+
const comparisonText = formatReviewComparison(
|
|
61
|
+
buildReviewComparison({ records: state.records, filters: { tool: "tool token=abc123" } }),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
assert.match(draft.text, /password=\[REDACTED\]/);
|
|
65
|
+
assert.match(draft.text, /Bearer \[REDACTED\]/);
|
|
66
|
+
assert.match(comparisonText, /tool-token-redacted/);
|
|
67
|
+
assert.doesNotMatch(`${draft.text}\n${comparisonText}`, /hunter2|abcdefghijklmnop|abc123/);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("draft lookup is not constrained by display limits", () => {
|
|
71
|
+
const records = [
|
|
72
|
+
createVentRecord(
|
|
73
|
+
{ summary: "Low-ranked target", category: "documentation", recurrenceKey: "target-old" },
|
|
74
|
+
{ id: "target", now: "2026-01-01T00:00:00.000Z" },
|
|
75
|
+
),
|
|
76
|
+
...Array.from({ length: 120 }, (_, index) =>
|
|
77
|
+
createVentRecord(
|
|
78
|
+
{
|
|
79
|
+
summary: `Higher ranked group ${index}`,
|
|
80
|
+
category: "documentation",
|
|
81
|
+
recurrenceKey: `higher-${index}`,
|
|
82
|
+
severity: "critical",
|
|
83
|
+
},
|
|
84
|
+
{ id: `v-${index}`, now: `2026-02-01T00:00:${String(index % 60).padStart(2, "0")}.000Z` },
|
|
85
|
+
),
|
|
86
|
+
),
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const draft = buildEscalationDraft({
|
|
90
|
+
target: "github_issue",
|
|
91
|
+
recurrenceKey: "documentation:target-old",
|
|
92
|
+
records,
|
|
93
|
+
});
|
|
94
|
+
assert.equal(draft.recurrenceKey, "documentation:target-old");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("diagnostic state quarantines semantic curation cycles", () => {
|
|
98
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-curation-cycle-"));
|
|
99
|
+
const storePath = path.join(dir, "vents.jsonl");
|
|
100
|
+
const curationPath = path.join(dir, "curation-events.jsonl");
|
|
101
|
+
const a = createVentRecord({ summary: "A", category: "bug", recurrenceKey: "a" });
|
|
102
|
+
const b = createVentRecord({ summary: "B", category: "bug", recurrenceKey: "b" });
|
|
103
|
+
appendVentRecord(storePath, a);
|
|
104
|
+
appendVentRecord(storePath, b);
|
|
105
|
+
fs.writeFileSync(
|
|
106
|
+
curationPath,
|
|
107
|
+
`${JSON.stringify({ eventType: "recurrence_curation", action: "merge", sourceRecurrenceKey: a.recurrenceKey, targetRecurrenceKey: b.recurrenceKey })}\n${JSON.stringify({ eventType: "recurrence_curation", action: "merge", sourceRecurrenceKey: b.recurrenceKey, targetRecurrenceKey: a.recurrenceKey })}\n`,
|
|
108
|
+
"utf8",
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const state = loadDiagnosticState({ storePath, curationPath });
|
|
112
|
+
assert.equal(state.quarantinedCurationEvents, 1);
|
|
113
|
+
assert.doesNotThrow(() =>
|
|
114
|
+
summarizeRecords(state.records, { curationEvents: state.curationEvents }),
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("curation remove is append-only rollback", () => {
|
|
119
|
+
const records = [
|
|
120
|
+
createVentRecord({ summary: "A", category: "bug", recurrenceKey: "a" }, { id: "a" }),
|
|
121
|
+
createVentRecord({ summary: "B", category: "bug", recurrenceKey: "b" }, { id: "b" }),
|
|
122
|
+
];
|
|
123
|
+
const merge = createCurationEvent({
|
|
124
|
+
action: "merge",
|
|
125
|
+
sourceRecurrenceKey: "bug:a",
|
|
126
|
+
targetRecurrenceKey: "bug:b",
|
|
127
|
+
});
|
|
128
|
+
assertCanCurateRecurrence(records, [merge], {
|
|
129
|
+
action: "remove",
|
|
130
|
+
sourceRecurrenceKey: "bug:a",
|
|
131
|
+
});
|
|
132
|
+
const remove = createCurationEvent({ action: "remove", sourceRecurrenceKey: "bug:a" });
|
|
133
|
+
const summary = summarizeRecords(records, { curationEvents: [merge, remove] });
|
|
134
|
+
assert.equal(summary.groupCount, 2);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("diagnostic state membrane bounds file and line size", () => {
|
|
138
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-size-"));
|
|
139
|
+
const linePath = path.join(dir, "oversized-line.jsonl");
|
|
140
|
+
fs.writeFileSync(linePath, `${"x".repeat(MAX_JSONL_LINE_BYTES + 1)}\n`, "utf8");
|
|
141
|
+
const lineResult = readVentRecords(linePath);
|
|
142
|
+
assert.equal(lineResult.oversizedLines, 1);
|
|
143
|
+
assert.equal(lineResult.records.length, 0);
|
|
144
|
+
|
|
145
|
+
const filePath = path.join(dir, "oversized-file.jsonl");
|
|
146
|
+
fs.writeFileSync(filePath, "x".repeat(MAX_JSONL_FILE_BYTES + 1), "utf8");
|
|
147
|
+
assert.throws(() => readVentRecords(filePath), /store file is too large/);
|
|
148
|
+
});
|