@substrat-run/contract-tests 0.138.0 → 0.139.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 (53) hide show
  1. package/dist/attachment-text-fixtures.d.ts +17 -0
  2. package/dist/attachment-text-fixtures.d.ts.map +1 -1
  3. package/dist/attachment-text-fixtures.js +344 -4
  4. package/dist/attachment-text-fixtures.js.map +1 -1
  5. package/dist/attachment-text-suite.d.ts +16 -1
  6. package/dist/attachment-text-suite.d.ts.map +1 -1
  7. package/dist/attachment-text-suite.js +212 -28
  8. package/dist/attachment-text-suite.js.map +1 -1
  9. package/dist/caused-by-module.d.ts +23 -0
  10. package/dist/caused-by-module.d.ts.map +1 -0
  11. package/dist/caused-by-module.js +56 -0
  12. package/dist/caused-by-module.js.map +1 -0
  13. package/dist/caused-by-suite.d.ts +28 -0
  14. package/dist/caused-by-suite.d.ts.map +1 -0
  15. package/dist/caused-by-suite.js +179 -0
  16. package/dist/caused-by-suite.js.map +1 -0
  17. package/dist/entity-state-module.d.ts +134 -0
  18. package/dist/entity-state-module.d.ts.map +1 -0
  19. package/dist/entity-state-module.js +140 -0
  20. package/dist/entity-state-module.js.map +1 -0
  21. package/dist/entity-state-suite.d.ts +9 -0
  22. package/dist/entity-state-suite.d.ts.map +1 -0
  23. package/dist/entity-state-suite.js +534 -0
  24. package/dist/entity-state-suite.js.map +1 -0
  25. package/dist/findings-atomic-suite.d.ts +21 -0
  26. package/dist/findings-atomic-suite.d.ts.map +1 -0
  27. package/dist/findings-atomic-suite.js +131 -0
  28. package/dist/findings-atomic-suite.js.map +1 -0
  29. package/dist/findings-suite.d.ts +11 -0
  30. package/dist/findings-suite.d.ts.map +1 -0
  31. package/dist/findings-suite.js +391 -0
  32. package/dist/findings-suite.js.map +1 -0
  33. package/dist/index.d.ts +9 -2
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +7 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/membership-executor-suite.d.ts +6 -2
  38. package/dist/membership-executor-suite.d.ts.map +1 -1
  39. package/dist/membership-executor-suite.js +260 -14
  40. package/dist/membership-executor-suite.js.map +1 -1
  41. package/dist/modules.d.ts +217 -0
  42. package/dist/modules.d.ts.map +1 -1
  43. package/dist/modules.js +4 -0
  44. package/dist/modules.js.map +1 -1
  45. package/dist/permission-suite.d.ts.map +1 -1
  46. package/dist/permission-suite.js +75 -0
  47. package/dist/permission-suite.js.map +1 -1
  48. package/dist/scope-host-suite.d.ts.map +1 -1
  49. package/dist/scope-host-suite.js +48 -0
  50. package/dist/scope-host-suite.js.map +1 -1
  51. package/dist/vertical-events-suite.d.ts +14 -0
  52. package/dist/vertical-events-suite.d.ts.map +1 -1
  53. package/package.json +3 -3
@@ -0,0 +1,21 @@
1
+ import { type ScopeHost } from '@substrat-run/kernel';
2
+ import type { ScopeHostFixture } from './scope-host-suite.js';
3
+ /**
4
+ * Raw SQL on the adapter's directory store — the one thing this suite needs that the seam does
5
+ * not offer. It installs and drops the fault triggers below, nothing else.
6
+ */
7
+ export type DirectoryExec = (host: ScopeHost, sql: string) => Promise<void>;
8
+ /**
9
+ * Findings (#1748): what is written together stays together, on every adapter.
10
+ *
11
+ * - The evidence and the finding it implies commit as one unit. A detector that fails takes the
12
+ * evidence with it, so the caller's retry — or a drain's replay under the same request id —
13
+ * writes both, rather than finding the evidence already there and the observation lost.
14
+ * - A verdict, a rule created and a rule revoked commit with their audit row, or not at all
15
+ * — and so does a stale resolution in `pruneFindings`.
16
+ *
17
+ * The failures are injected by SQLite triggers on the directory, the one fault both stores
18
+ * raise the same way, and each is dropped before the retry.
19
+ */
20
+ export declare function findingsAtomicContractSuite(adapterName: string, makeFixture: () => Promise<ScopeHostFixture>, exec: DirectoryExec): void;
21
+ //# sourceMappingURL=findings-atomic-suite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findings-atomic-suite.d.ts","sourceRoot":"","sources":["../src/findings-atomic-suite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5E;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,EAC5C,IAAI,EAAE,aAAa,GAClB,IAAI,CAqIN"}
@@ -0,0 +1,131 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import { instant, platformActorId, scopeId, tenantId } from '@substrat-run/contracts';
3
+ import { FINDING_RETENTION_DAYS, ulid } from '@substrat-run/kernel';
4
+ /**
5
+ * Findings (#1748): what is written together stays together, on every adapter.
6
+ *
7
+ * - The evidence and the finding it implies commit as one unit. A detector that fails takes the
8
+ * evidence with it, so the caller's retry — or a drain's replay under the same request id —
9
+ * writes both, rather than finding the evidence already there and the observation lost.
10
+ * - A verdict, a rule created and a rule revoked commit with their audit row, or not at all
11
+ * — and so does a stale resolution in `pruneFindings`.
12
+ *
13
+ * The failures are injected by SQLite triggers on the directory, the one fault both stores
14
+ * raise the same way, and each is dropped before the retry.
15
+ */
16
+ export function findingsAtomicContractSuite(adapterName, makeFixture, exec) {
17
+ describe(`findings commit with their evidence and their audit (#1748): ${adapterName}`, () => {
18
+ let fixture;
19
+ let host;
20
+ const staff = platformActorId.parse(ulid());
21
+ const t = tenantId.parse(ulid());
22
+ const s = scopeId.parse(ulid());
23
+ beforeAll(async () => {
24
+ fixture = await makeFixture();
25
+ host = fixture.host;
26
+ });
27
+ afterAll(async () => {
28
+ await fixture.cleanup();
29
+ });
30
+ /** Fail every write to `table` (optionally only rows matching `when`) until `heal`. */
31
+ const fault = async (name, table, op, when = '1') => {
32
+ await exec(host, `CREATE TRIGGER ${name} BEFORE ${op} ON ${table} WHEN ${when} BEGIN SELECT RAISE(ABORT, 'injected fault'); END`);
33
+ return () => exec(host, `DROP TRIGGER ${name}`);
34
+ };
35
+ const findingsOf = async (kind, subject) => (await host.admin.listFindings(staff, { tenantId: t, kind })).filter((f) => subject(f.subject));
36
+ it('a failed observation takes the sweep row with it, and the replay writes both once', async () => {
37
+ const unit = `${s}:atomic/replayed`;
38
+ const requestId = ulid();
39
+ const run = () => host.admin.recordSweepRun({
40
+ kind: 'schedule',
41
+ unit,
42
+ outcome: 'failed',
43
+ tenantId: t,
44
+ scopeId: s,
45
+ operation: 'atomic/replayed',
46
+ requestId,
47
+ });
48
+ const heal = await fault('fault_finding_insert', '_substrat_findings', 'INSERT', `NEW.tenant_id = '${t}'`);
49
+ await expect(run()).rejects.toThrow(/injected fault/);
50
+ expect(await host.admin.listSweepRuns(staff, { unit })).toHaveLength(0);
51
+ await heal();
52
+ await run();
53
+ await run(); // the drain replays again: the unique index ignores it, and nothing is counted twice
54
+ expect(await host.admin.listSweepRuns(staff, { unit })).toHaveLength(1);
55
+ const found = await findingsOf('invariant', (x) => x === unit);
56
+ expect(found).toHaveLength(1);
57
+ expect(found[0]).toMatchObject({ status: 'open', count: 1 });
58
+ });
59
+ it('a failed observation takes the ops failure and its issue with it, and the retry writes all three', async () => {
60
+ const operation = 'atomic.ops';
61
+ const record = () => host.admin.recordOpsFailure({ actor: staff, operation, tenantId: t, scopeId: s, message: 'x' });
62
+ const heal = await fault('fault_finding_insert_ops', '_substrat_findings', 'INSERT', `NEW.tenant_id = '${t}'`);
63
+ await expect(record()).rejects.toThrow(/injected fault/);
64
+ expect(await host.admin.listOpsFailures(staff, { operation })).toHaveLength(0);
65
+ expect(await host.admin.listIssues(staff, { operation })).toHaveLength(0);
66
+ await heal();
67
+ await record();
68
+ expect(await host.admin.listOpsFailures(staff, { operation })).toHaveLength(1);
69
+ const [issue] = await host.admin.listIssues(staff, { operation });
70
+ expect(issue.count).toBe(1);
71
+ const found = await findingsOf('recurring', (x) => x === issue.fingerprint);
72
+ expect(found).toHaveLength(1);
73
+ expect(found[0].count).toBe(1);
74
+ });
75
+ describe('an audit write that fails takes its mutation with it', () => {
76
+ const failAudit = (action) => fault(`fault_audit_${action}`, '_substrat_admin_log', 'INSERT', `NEW.action = '${action}'`);
77
+ const inADay = () => instant.parse(new Date(Date.now() + 86_400_000).toISOString());
78
+ const opened = async (operation) => {
79
+ await host.admin.recordOpsFailure({ actor: staff, operation, tenantId: t, scopeId: s, message: 'x' });
80
+ return (await host.admin.listFindings(staff, { tenantId: t })).find((f) => f.operation === operation);
81
+ };
82
+ it('a verdict', async () => {
83
+ const f = await opened('atomic.verdict');
84
+ const heal = await failAudit('setFindingStatus');
85
+ await expect(host.admin.setFindingStatus(staff, t, f.id, 'resolved')).rejects.toThrow(/injected fault/);
86
+ await heal();
87
+ expect((await host.admin.listFindings(staff, { tenantId: t })).find((x) => x.id === f.id).status).toBe('open');
88
+ // The twin: with the audit writable, the same verdict lands with its row.
89
+ await host.admin.setFindingStatus(staff, t, f.id, 'resolved');
90
+ const rows = await host.admin.auditLog(staff, { tenantId: t, action: 'setFindingStatus' });
91
+ expect(rows.filter((r) => r.after.id === f.id)).toHaveLength(1);
92
+ });
93
+ it('a rule created, and the findings it would have suppressed', async () => {
94
+ const f = await opened('atomic.rule');
95
+ const heal = await failAudit('createFindingRule');
96
+ await expect(host.admin.createFindingRule(staff, t, { operation: 'atomic.rule', expiresAt: inADay(), reason: 'r' })).rejects.toThrow(/injected fault/);
97
+ await heal();
98
+ expect(await host.admin.listFindingRules(staff, t)).toHaveLength(0);
99
+ expect((await host.admin.listFindings(staff, { tenantId: t })).find((x) => x.id === f.id).status).toBe('open');
100
+ });
101
+ it('a rule revoked', async () => {
102
+ const { rule } = await host.admin.createFindingRule(staff, t, {
103
+ operation: 'atomic.revoke',
104
+ expiresAt: inADay(),
105
+ reason: 'r',
106
+ });
107
+ const heal = await failAudit('revokeFindingRule');
108
+ await expect(host.admin.revokeFindingRule(staff, t, rule.id)).rejects.toThrow(/injected fault/);
109
+ await heal();
110
+ expect((await host.admin.listFindingRules(staff, t, { active: true })).map((r) => r.id)).toContain(rule.id);
111
+ });
112
+ it('a stale resolution', async () => {
113
+ const unit = `${s}:atomic/quiet`;
114
+ await host.admin.recordSweepRun({
115
+ kind: 'schedule',
116
+ unit,
117
+ outcome: 'failed',
118
+ tenantId: t,
119
+ scopeId: s,
120
+ operation: 'atomic/quiet',
121
+ at: instant.parse(new Date(Date.now() - (FINDING_RETENTION_DAYS + 10) * 86_400_000).toISOString()),
122
+ });
123
+ const heal = await failAudit('resolveStaleFinding');
124
+ await expect(host.admin.pruneFindings(staff, 500)).rejects.toThrow(/injected fault/);
125
+ await heal();
126
+ expect((await findingsOf('invariant', (x) => x === unit))[0].status).toBe('open');
127
+ });
128
+ });
129
+ });
130
+ }
131
+ //# sourceMappingURL=findings-atomic-suite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findings-atomic-suite.js","sourceRoot":"","sources":["../src/findings-atomic-suite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { ScopeHostFixture } from './scope-host-suite.js';
2
+ /**
3
+ * Findings (#1748) on every adapter: the tenant inbox with a lifecycle. A Recurring finding is
4
+ * the tenant's own projection of an `_substrat_issues` fingerprint; a failed schedule opens an
5
+ * Invariant one and a stale freshness verdict a Drift one. Acknowledge / resolve / reopen are
6
+ * verdicts, suppress is a rule with an expiry, and every one of them is audited. A tenant never
7
+ * sees, counts or moves another tenant's finding — and a quiet open finding is resolved as
8
+ * stale, audited, rather than deleted from under whoever was watching it.
9
+ */
10
+ export declare function findingsContractSuite(adapterName: string, makeFixture: () => Promise<ScopeHostFixture>): void;
11
+ //# sourceMappingURL=findings-suite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findings-suite.d.ts","sourceRoot":"","sources":["../src/findings-suite.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAua7G"}
@@ -0,0 +1,391 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import { errorCodeOf, instant, platformActorId, scopeId, tenantId, } from '@substrat-run/contracts';
3
+ import { FINDING_RETENTION_DAYS, ulid } from '@substrat-run/kernel';
4
+ /**
5
+ * Findings (#1748) on every adapter: the tenant inbox with a lifecycle. A Recurring finding is
6
+ * the tenant's own projection of an `_substrat_issues` fingerprint; a failed schedule opens an
7
+ * Invariant one and a stale freshness verdict a Drift one. Acknowledge / resolve / reopen are
8
+ * verdicts, suppress is a rule with an expiry, and every one of them is audited. A tenant never
9
+ * sees, counts or moves another tenant's finding — and a quiet open finding is resolved as
10
+ * stale, audited, rather than deleted from under whoever was watching it.
11
+ */
12
+ export function findingsContractSuite(adapterName, makeFixture) {
13
+ describe(`findings (#1748): ${adapterName}`, () => {
14
+ let fixture;
15
+ let host;
16
+ const staff = platformActorId.parse(ulid());
17
+ const ta = tenantId.parse(ulid());
18
+ const tb = tenantId.parse(ulid());
19
+ const sa = scopeId.parse(ulid());
20
+ const sb = scopeId.parse(ulid());
21
+ const V1 = '01JVERSIONFINDINGAAAAAAAA1';
22
+ const V2 = '01JVERSIONFINDINGAAAAAAAA2';
23
+ beforeAll(async () => {
24
+ fixture = await makeFixture();
25
+ host = fixture.host;
26
+ });
27
+ afterAll(async () => {
28
+ await fixture.cleanup();
29
+ });
30
+ /** One ops failure of `operation` for `tenant`, the shape every Recurring test records. */
31
+ const fail = (tenant, operation, opts = {}) => host.admin.recordOpsFailure({
32
+ actor: staff,
33
+ operation,
34
+ stage: 'terminal',
35
+ origin: 'platform',
36
+ code: opts.code ?? 'unavailable',
37
+ tenantId: tenant,
38
+ scopeId: tenant === null ? null : scopeId.parse(opts.scope ?? (tenant === ta ? sa : sb)),
39
+ vertical: 'acme/findings',
40
+ version: opts.version ?? null,
41
+ message: opts.message ?? `${operation} failed`,
42
+ });
43
+ const findingOf = async (tenant, operation) => (await host.admin.listFindings(staff, { tenantId: tenant, kind: 'recurring' })).find((f) => f.operation === operation);
44
+ describe('the lifecycle', () => {
45
+ it('opens on first sight, acks, resolves, and a fresh occurrence reopens it as regressed', async () => {
46
+ const op = 'deploy.lifecycle';
47
+ await fail(ta, op, { version: V1 });
48
+ const opened = (await findingOf(ta, op));
49
+ expect(opened).toMatchObject({
50
+ tenantId: ta,
51
+ kind: 'recurring',
52
+ status: 'open',
53
+ regressed: false,
54
+ count: 1,
55
+ codes: ['unavailable'],
56
+ lastVersion: V1,
57
+ likelyCause: null,
58
+ });
59
+ const acked = await host.admin.setFindingStatus(staff, ta, opened.id, 'acked');
60
+ expect(acked?.status).toBe('acked');
61
+ expect(acked?.acknowledgedAt).not.toBeNull();
62
+ // Acked stays acked on a fresh occurrence: somebody is already on it.
63
+ await fail(ta, op, { version: V1 });
64
+ expect((await findingOf(ta, op))).toMatchObject({ status: 'acked', count: 2 });
65
+ const resolved = await host.admin.setFindingStatus(staff, ta, opened.id, 'resolved');
66
+ expect(resolved).toMatchObject({ status: 'resolved', resolution: 'verdict', resolvedVersion: V1 });
67
+ // Seen again after resolved: back in the inbox, flagged regressed, with the version it
68
+ // came back under named as the likely cause.
69
+ await fail(ta, op, { version: V2 });
70
+ const regressed = (await findingOf(ta, op));
71
+ expect(regressed).toMatchObject({ status: 'open', regressed: true, count: 3, lastVersion: V2 });
72
+ expect(regressed.likelyCause).toMatchObject({ kind: 'deploy', version: V2 });
73
+ expect(regressed.likelyCause.reason).toContain(V1);
74
+ // The next resolve clears the badge; reopening drops the acknowledgement.
75
+ const again = await host.admin.setFindingStatus(staff, ta, opened.id, 'resolved');
76
+ expect(again?.regressed).toBe(false);
77
+ const reopened = await host.admin.setFindingStatus(staff, ta, opened.id, 'open');
78
+ expect(reopened).toMatchObject({ status: 'open', acknowledgedAt: null });
79
+ });
80
+ it('works the likely cause out afresh each cycle, never carrying an old one', async () => {
81
+ const op = 'deploy.two-cycles';
82
+ const V3 = '01JVERSIONFINDINGAAAAAAAA3';
83
+ await fail(ta, op, { version: V1 });
84
+ const id = (await findingOf(ta, op)).id;
85
+ // Cycle 1: resolved under V1, back under V2.
86
+ expect(await host.admin.setFindingStatus(staff, ta, id, 'resolved')).toMatchObject({ likelyCause: null });
87
+ await fail(ta, op, { version: V2 });
88
+ expect((await findingOf(ta, op)).likelyCause).toMatchObject({ version: V2, reason: expect.stringContaining(V1) });
89
+ // A resolve closes the cycle its cause explained.
90
+ expect(await host.admin.setFindingStatus(staff, ta, id, 'resolved')).toMatchObject({
91
+ resolvedVersion: V2,
92
+ likelyCause: null,
93
+ });
94
+ // Cycle 2a: back under the version it was just resolved under — no cause, not V1's.
95
+ await fail(ta, op, { version: V2 });
96
+ expect(await findingOf(ta, op)).toMatchObject({ status: 'open', regressed: true, likelyCause: null });
97
+ // Cycle 2b: resolved under V2, back under V3 — V3, explained by V2, not V1.
98
+ await host.admin.setFindingStatus(staff, ta, id, 'resolved');
99
+ await fail(ta, op, { version: V3 });
100
+ const third = (await findingOf(ta, op));
101
+ expect(third.likelyCause).toMatchObject({ version: V3 });
102
+ expect(third.likelyCause.reason).toContain(V2);
103
+ expect(third.likelyCause.reason).not.toContain(V1);
104
+ });
105
+ it('names no likely cause when the finding came back under the version it was resolved under', async () => {
106
+ const op = 'deploy.same-version';
107
+ await fail(ta, op, { version: V1 });
108
+ const f = (await findingOf(ta, op));
109
+ await host.admin.setFindingStatus(staff, ta, f.id, 'resolved');
110
+ await fail(ta, op, { version: V1 });
111
+ expect(await findingOf(ta, op)).toMatchObject({ status: 'open', regressed: true, likelyCause: null });
112
+ });
113
+ it('audits every verdict with the tenant and the before/after status', async () => {
114
+ const op = 'deploy.audited';
115
+ await fail(ta, op);
116
+ const f = (await findingOf(ta, op));
117
+ await host.admin.setFindingStatus(staff, ta, f.id, 'acked');
118
+ const [row] = await host.admin.auditLog(staff, { tenantId: ta, action: 'setFindingStatus', order: 'desc', limit: 1 });
119
+ expect(row).toMatchObject({
120
+ actor: staff,
121
+ tenantId: ta,
122
+ before: { id: f.id, status: 'open' },
123
+ after: { id: f.id, status: 'acked' },
124
+ });
125
+ });
126
+ });
127
+ describe('tenant isolation', () => {
128
+ it('counts, reads and moves each tenant’s finding apart, while the fleet issue sums both', async () => {
129
+ const op = 'intent.connector:shared';
130
+ await fail(ta, op, { message: 'tenant A secret prose 01JAAAAAAAAAAAAAAAAAAAAAAA' });
131
+ await fail(ta, op);
132
+ await fail(tb, op, { message: 'tenant B text' });
133
+ const a = (await findingOf(ta, op));
134
+ const b = (await findingOf(tb, op));
135
+ // Same fingerprint, two findings: the count is the tenant's own, never the fleet's.
136
+ expect(a.subject).toBe(b.subject);
137
+ expect(a.id).not.toBe(b.id);
138
+ expect(a.count).toBe(2);
139
+ expect(b.count).toBe(1);
140
+ const [issue] = await host.admin.listIssues(staff, { operation: op });
141
+ expect(issue.count).toBe(3);
142
+ // A tenant's read carries nothing of the other tenant: not its id, its scope, or the
143
+ // free text of its failures. A finding carries no evidence text at all.
144
+ const bRead = await host.admin.listFindings(staff, { tenantId: tb });
145
+ const serialized = JSON.stringify(bRead);
146
+ expect(bRead.every((f) => f.tenantId === tb)).toBe(true);
147
+ expect(serialized).not.toContain(ta);
148
+ expect(serialized).not.toContain(sa);
149
+ expect(serialized).not.toContain('tenant A secret prose');
150
+ expect(serialized).not.toContain('tenant B text');
151
+ expect(serialized).not.toContain(staff);
152
+ // B cannot move A's finding by its id: unknown to B, and A's is untouched.
153
+ expect(await host.admin.setFindingStatus(staff, tb, a.id, 'resolved')).toBeUndefined();
154
+ expect((await findingOf(ta, op)).status).toBe('open');
155
+ // Resolving B's leaves A's open.
156
+ await host.admin.setFindingStatus(staff, tb, b.id, 'resolved');
157
+ expect((await findingOf(ta, op)).status).toBe('open');
158
+ expect((await findingOf(tb, op)).status).toBe('resolved');
159
+ });
160
+ it('opens no finding for a failure that belongs to no tenant', async () => {
161
+ const op = 'platform.only';
162
+ await fail(null, op);
163
+ expect(await host.admin.listIssues(staff, { operation: op })).toHaveLength(1);
164
+ const fleet = await host.admin.listFindings(staff, { limit: 500 });
165
+ expect(fleet.some((f) => f.operation === op)).toBe(false);
166
+ });
167
+ it('keeps a tenant’s rules to that tenant', async () => {
168
+ const op = 'deploy.rule-isolation';
169
+ await fail(ta, op);
170
+ await fail(tb, op);
171
+ const { rule } = await host.admin.createFindingRule(staff, ta, {
172
+ operation: op,
173
+ expiresAt: instant.parse(new Date(Date.now() + 86_400_000).toISOString()),
174
+ reason: 'known flake',
175
+ });
176
+ // A's rule suppresses A's finding and leaves B's alone.
177
+ expect((await findingOf(ta, op)).status).toBe('suppressed');
178
+ expect((await findingOf(tb, op)).status).toBe('open');
179
+ await fail(tb, op);
180
+ expect((await findingOf(tb, op)).status).toBe('open');
181
+ // B neither sees nor revokes A's rule.
182
+ expect((await host.admin.listFindingRules(staff, tb)).some((r) => r.id === rule.id)).toBe(false);
183
+ expect(await host.admin.revokeFindingRule(staff, tb, rule.id)).toBeUndefined();
184
+ expect((await host.admin.listFindingRules(staff, ta, { active: true })).some((r) => r.id === rule.id)).toBe(true);
185
+ });
186
+ });
187
+ describe('suppress rules', () => {
188
+ const inADay = () => instant.parse(new Date(Date.now() + 86_400_000).toISOString());
189
+ it('suppresses what it covers now, still counts later occurrences, and audits the rule', async () => {
190
+ const op = 'deploy.suppressed';
191
+ await fail(ta, op, { code: 'conflict' });
192
+ const { rule, suppressed } = await host.admin.createFindingRule(staff, ta, {
193
+ code: 'conflict',
194
+ operation: op,
195
+ expiresAt: inADay(),
196
+ reason: 'expected during the migration',
197
+ });
198
+ expect(suppressed).toBe(1);
199
+ expect((await findingOf(ta, op))).toMatchObject({ status: 'suppressed', ruleId: rule.id, count: 1 });
200
+ // Recorded, but no finding opens.
201
+ await fail(ta, op, { code: 'conflict' });
202
+ expect((await findingOf(ta, op))).toMatchObject({ status: 'suppressed', ruleId: rule.id, count: 2 });
203
+ const [row] = await host.admin.auditLog(staff, { tenantId: ta, action: 'createFindingRule', order: 'desc', limit: 1 });
204
+ expect(row).toMatchObject({ actor: staff, tenantId: ta, after: { rule: { id: rule.id }, suppressed: 1 } });
205
+ });
206
+ it('covers only what it names: another operation, or another code, still opens', async () => {
207
+ const op = 'deploy.narrow';
208
+ await host.admin.createFindingRule(staff, ta, { operation: op, code: 'conflict', expiresAt: inADay(), reason: 'narrow' });
209
+ await fail(ta, op, { code: 'unavailable' });
210
+ await fail(ta, 'deploy.narrow-other', { code: 'conflict' });
211
+ expect((await findingOf(ta, op)).status).toBe('open');
212
+ expect((await findingOf(ta, 'deploy.narrow-other')).status).toBe('open');
213
+ });
214
+ it('suppresses a NEW finding it covers from its first occurrence', async () => {
215
+ const op = 'deploy.born-suppressed';
216
+ await host.admin.createFindingRule(staff, ta, { operation: op, expiresAt: inADay(), reason: 'pre-emptive' });
217
+ await fail(ta, op);
218
+ expect((await findingOf(ta, op)).status).toBe('suppressed');
219
+ });
220
+ it('lets a revoked rule go: the next occurrence opens the finding, and the revoke is audited', async () => {
221
+ const op = 'deploy.revoked';
222
+ await fail(ta, op);
223
+ const { rule } = await host.admin.createFindingRule(staff, ta, { operation: op, expiresAt: inADay(), reason: 'temp' });
224
+ expect((await findingOf(ta, op)).status).toBe('suppressed');
225
+ const revoked = await host.admin.revokeFindingRule(staff, ta, rule.id);
226
+ expect(revoked.expiresAt <= new Date().toISOString()).toBe(true);
227
+ expect((await host.admin.listFindingRules(staff, ta, { active: true })).some((r) => r.id === rule.id)).toBe(false);
228
+ await fail(ta, op);
229
+ expect((await findingOf(ta, op))).toMatchObject({ status: 'open', ruleId: null, count: 2 });
230
+ const [row] = await host.admin.auditLog(staff, { tenantId: ta, action: 'revokeFindingRule', order: 'desc', limit: 1 });
231
+ expect(row).toMatchObject({ before: { id: rule.id }, after: { id: rule.id } });
232
+ });
233
+ it('refuses a rule that expires in the past, or past the longest suppression, as validation_failed', async () => {
234
+ // The code, not only the words: the HTTP surface maps it to a 400, and on a host whose
235
+ // store sits behind an RPC hop it survives only if the refusal happens before the hop.
236
+ const refusal = async (expiresAt) => {
237
+ try {
238
+ await host.admin.createFindingRule(staff, ta, { operation: 'x', expiresAt: instant.parse(expiresAt), reason: 'r' });
239
+ }
240
+ catch (e) {
241
+ return { code: errorCodeOf(e), message: e.message };
242
+ }
243
+ throw new Error('the rule was created');
244
+ };
245
+ expect(await refusal(new Date(Date.now() - 1000).toISOString())).toMatchObject({
246
+ code: 'validation_failed',
247
+ message: expect.stringMatching(/future/),
248
+ });
249
+ expect(await refusal(new Date(Date.now() + 91 * 86_400_000).toISOString())).toMatchObject({
250
+ code: 'validation_failed',
251
+ message: expect.stringMatching(/at most 90 days/),
252
+ });
253
+ });
254
+ });
255
+ describe('the `_substrat_issues` mapping', () => {
256
+ it('maps new → open, regressed → open + regressed, resolved → resolved, ignored → suppressed', async () => {
257
+ const op = 'deploy.mapping';
258
+ await fail(ta, op);
259
+ const [issue] = await host.admin.listIssues(staff, { operation: op });
260
+ const f = (await findingOf(ta, op));
261
+ // One fingerprint, one subject; the evidence walks to the tenant's own exemplars.
262
+ expect(issue.status).toBe('new');
263
+ expect(f.status).toBe('open');
264
+ expect(f.subject).toBe(issue.fingerprint);
265
+ expect(f.evidence).toEqual({ source: 'ops-failures', fingerprint: issue.fingerprint });
266
+ const exemplars = await host.admin.listOpsFailures(staff, { tenantId: ta, fingerprint: issue.fingerprint });
267
+ expect(exemplars.length).toBe(1);
268
+ // resolved → resolved, and the same fresh arrival regresses both.
269
+ await host.admin.setIssueStatus(staff, issue.fingerprint, 'resolved');
270
+ await host.admin.setFindingStatus(staff, ta, f.id, 'resolved');
271
+ await fail(ta, op);
272
+ const [regressedIssue] = await host.admin.listIssues(staff, { operation: op });
273
+ expect(regressedIssue.status).toBe('regressed');
274
+ expect(await findingOf(ta, op)).toMatchObject({ status: 'open', regressed: true });
275
+ // ignored → suppressed: a fresh arrival changes neither.
276
+ await host.admin.setIssueStatus(staff, issue.fingerprint, 'ignored');
277
+ await host.admin.createFindingRule(staff, ta, {
278
+ subject: f.subject,
279
+ expiresAt: instant.parse(new Date(Date.now() + 86_400_000).toISOString()),
280
+ reason: 'ignored',
281
+ });
282
+ await fail(ta, op);
283
+ const [ignored] = await host.admin.listIssues(staff, { operation: op });
284
+ expect(ignored.status).toBe('ignored');
285
+ expect((await findingOf(ta, op)).status).toBe('suppressed');
286
+ });
287
+ });
288
+ describe('the Invariant and Drift sources', () => {
289
+ it('opens an Invariant finding for a failed schedule, and none for one that ran', async () => {
290
+ const unit = `${sa}:findings/nightly`;
291
+ const run = (outcome) => host.admin.recordSweepRun({
292
+ kind: 'schedule',
293
+ unit,
294
+ outcome,
295
+ tenantId: ta,
296
+ scopeId: sa,
297
+ operation: 'findings/nightly',
298
+ error: outcome === 'failed' ? 'handler threw: secret detail' : null,
299
+ });
300
+ await run('ok');
301
+ expect((await host.admin.listFindings(staff, { tenantId: ta, kind: 'invariant' })).some((f) => f.subject === unit)).toBe(false);
302
+ await run('failed');
303
+ await run('failed');
304
+ const [f] = (await host.admin.listFindings(staff, { tenantId: ta, kind: 'invariant' })).filter((x) => x.subject === unit);
305
+ expect(f).toMatchObject({
306
+ status: 'open',
307
+ severity: 'critical',
308
+ count: 2,
309
+ operation: 'findings/nightly',
310
+ evidence: { source: 'sweep-runs', kind: 'schedule', unit },
311
+ });
312
+ expect(JSON.stringify(f)).not.toContain('secret detail');
313
+ });
314
+ it('opens a Drift finding for a stale freshness verdict', async () => {
315
+ const unit = `${sa}:findings.thing-happened`;
316
+ await host.admin.recordSweepRun({
317
+ kind: 'freshness',
318
+ unit,
319
+ outcome: 'failed',
320
+ tenantId: ta,
321
+ scopeId: sa,
322
+ eventType: 'findings.thing-happened',
323
+ });
324
+ const [f] = (await host.admin.listFindings(staff, { tenantId: ta, kind: 'drift' })).filter((x) => x.subject === unit);
325
+ expect(f).toMatchObject({ status: 'open', severity: 'warning', title: 'findings.thing-happened has gone stale' });
326
+ });
327
+ it('counts a replayed drain once', async () => {
328
+ const unit = `${sa}:findings/replayed`;
329
+ const requestId = ulid();
330
+ for (let i = 0; i < 2; i++) {
331
+ await host.admin.recordSweepRun({
332
+ kind: 'schedule',
333
+ unit,
334
+ outcome: 'failed',
335
+ tenantId: ta,
336
+ scopeId: sa,
337
+ operation: 'findings/replayed',
338
+ requestId,
339
+ });
340
+ }
341
+ const [f] = (await host.admin.listFindings(staff, { tenantId: ta, kind: 'invariant' })).filter((x) => x.subject === unit);
342
+ expect(f.count).toBe(1);
343
+ });
344
+ });
345
+ describe('retention', () => {
346
+ const longAgo = () => instant.parse(new Date(Date.now() - (FINDING_RETENTION_DAYS + 10) * 86_400_000).toISOString());
347
+ it('resolves a quiet open finding as stale, audited, and deletes a quiet suppressed one', async () => {
348
+ const quiet = `${sb}:findings/quiet`;
349
+ const hidden = `${sb}:findings/hidden`;
350
+ await host.admin.createFindingRule(staff, tb, {
351
+ subject: hidden,
352
+ expiresAt: instant.parse(new Date(Date.now() + 86_400_000).toISOString()),
353
+ reason: 'retention fixture',
354
+ });
355
+ for (const unit of [quiet, hidden]) {
356
+ await host.admin.recordSweepRun({
357
+ kind: 'schedule',
358
+ unit,
359
+ outcome: 'failed',
360
+ tenantId: tb,
361
+ scopeId: sb,
362
+ operation: unit.split(':')[1],
363
+ at: longAgo(),
364
+ });
365
+ }
366
+ const before = await host.admin.listFindings(staff, { tenantId: tb, kind: 'invariant' });
367
+ expect(before.find((f) => f.subject === quiet).status).toBe('open');
368
+ expect(before.find((f) => f.subject === hidden).status).toBe('suppressed');
369
+ const report = await host.admin.pruneFindings(staff, 500);
370
+ expect(report.staled).toBeGreaterThanOrEqual(1);
371
+ expect(report.deleted).toBeGreaterThanOrEqual(1);
372
+ const after = await host.admin.listFindings(staff, { tenantId: tb, kind: 'invariant' });
373
+ const stale = after.find((f) => f.subject === quiet);
374
+ // Not deleted: resolved as stale, and kept readable for one more window.
375
+ expect(stale).toMatchObject({ status: 'resolved', resolution: 'stale' });
376
+ expect(after.some((f) => f.subject === hidden)).toBe(false);
377
+ const audit = await host.admin.auditLog(staff, { tenantId: tb, action: 'resolveStaleFinding' });
378
+ expect(audit.find((r) => r.after.id === stale.id)).toMatchObject({
379
+ actor: staff,
380
+ tenantId: tb,
381
+ before: { id: stale.id, status: 'open' },
382
+ after: { id: stale.id, status: 'resolved', resolution: 'stale' },
383
+ });
384
+ // A second pass changes nothing: the stale-resolved finding's resolve is recent.
385
+ await host.admin.pruneFindings(staff, 500);
386
+ expect((await host.admin.listFindings(staff, { tenantId: tb, kind: 'invariant' })).find((f) => f.id === stale.id)).toBeDefined();
387
+ });
388
+ });
389
+ });
390
+ }
391
+ //# sourceMappingURL=findings-suite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findings-suite.js","sourceRoot":"","sources":["../src/findings-suite.ts"],"names":[],"mappings":"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"}