@substrat-run/contract-tests 0.117.0 → 0.119.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.
@@ -0,0 +1,357 @@
1
+ /**
2
+ * The peer door (#1706), against both adapters — one vertical of a tenant calling another's
3
+ * operations through the platform, as `{ vertical, scope }`.
4
+ *
5
+ * Every "this is safe because…" the door claims is a test here, next to its positive twin: the
6
+ * same call as a principal who holds the key, or the same peer on the call it IS allowed, so a
7
+ * refusal cannot pass by refusing everything.
8
+ *
9
+ * - **Declared, or nothing.** The keys a peer holds are exactly the ones the target's manifest
10
+ * declared (`peers`), seated at provisioning; an undeclared vertical, and an operation off the
11
+ * allowlist, are refused at the door (`forbidden`, no K-35 row). A receive-only peer
12
+ * (`operations: []`) holds its key and invokes nothing.
13
+ * - **On the spine as itself.** Every event and every denial names `{ vertical, scope }`.
14
+ * - **Revocation is the next call.** The per-(scope, peer) kill switch refuses a stub obtained
15
+ * before it, answers `peerCovers` with nothing held, and survives a re-provision.
16
+ * - **Tenant-bound.** The door fails closed on a (tenant, scope) pair that does not match, and
17
+ * the resolution only ever searches the tenant it is given.
18
+ * - **No person crosses.** The door takes no principal; a peer cannot mint a capability (the verb
19
+ * that needs a person to vouch for it); its idempotency keys are its own instance's.
20
+ *
21
+ * The caller's LIVENESS (a live primary instance of its vertical) is the platform's gate, not the
22
+ * door's — the local broker's suite (adapter-sqlite) and the router hold it.
23
+ */
24
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
25
+ import { errorCodeOf, permissionKey, platformActorId, principalId, scopeId, tenantId, } from '@substrat-run/contracts';
26
+ import { ulid } from '@substrat-run/kernel';
27
+ import { PEER_CALLER, PEER_LISTENER, peerMod } from './modules.js';
28
+ const READ = permissionKey.parse('peer:read');
29
+ const WRITE = permissionKey.parse('peer:write');
30
+ const ADMIN = permissionKey.parse('peer:admin');
31
+ /** The refusal a promise settles with, or `undefined` when it resolved. */
32
+ const refusal = (p) => p.then(() => undefined, (e) => e);
33
+ export function peerContractSuite(adapterName, makeFixture) {
34
+ describe(`peer door (#1706): ${adapterName}`, () => {
35
+ let fixture;
36
+ let host;
37
+ const staff = platformActorId.parse(ulid());
38
+ const t = tenantId.parse(ulid());
39
+ const u = tenantId.parse(ulid()); // another tenant
40
+ const s = scopeId.parse(ulid()); // the target instance, in t
41
+ const su = scopeId.parse(ulid()); // an instance in u
42
+ const alice = principalId.parse(ulid()); // holds every key, tenant-wide
43
+ const callerScope = scopeId.parse(ulid()); // the calling instance (another vertical's scope)
44
+ const secondCaller = scopeId.parse(ulid()); // a second instance of the same calling vertical
45
+ const caller = { vertical: PEER_CALLER, scope: callerScope };
46
+ const listener = { vertical: PEER_LISTENER, scope: scopeId.parse(ulid()) };
47
+ const reason = 'incident: the caller is misbehaving';
48
+ const asAlice = (scope = s) => host.getScope(alice, t, scope);
49
+ const asPeer = (c = caller, scope = s, tenant = t) => host.getVerticalScope(c, tenant, scope);
50
+ const outbox = async () => (await asAlice()).invoke('peer/outbox');
51
+ const denials = async () => (await asAlice()).invoke('peer/denials');
52
+ const tuples = async () => (await asAlice()).invoke('peer/tuples');
53
+ const covers = (vertical, keys = [READ, WRITE, ADMIN]) => host.peerCovers(t, s, vertical, keys);
54
+ const held = async (vertical) => (await covers(vertical)).filter((c) => c.held).map((c) => c.permission);
55
+ const off = (vertical = PEER_CALLER) => host.admin.revokeFromPeer(staff, { vertical, node: { tenantId: t, scopeId: s }, reason });
56
+ const on = (vertical = PEER_CALLER) => host.admin.restoreToPeer(staff, { vertical, node: { tenantId: t, scopeId: s }, reason: 'resolved' });
57
+ beforeAll(async () => {
58
+ fixture = await makeFixture();
59
+ host = fixture.host;
60
+ host.registerModule(peerMod);
61
+ for (const [tenant, scope, slug] of [
62
+ [t, s, 'peer-t'],
63
+ [u, su, 'peer-u'],
64
+ ]) {
65
+ await host.admin.createTenant(staff, { id: tenant, slug: `${slug}-${tenant.slice(-8).toLowerCase()}`, name: slug });
66
+ await host.admin.grantEntitlement(staff, tenant, 'peer');
67
+ await host.provisionScope(staff, { tenantId: tenant, scopeId: scope });
68
+ await host.admin.activateScope(staff, tenant, scope);
69
+ await host.admin.defineRole(staff, tenant, {
70
+ key: 'owner',
71
+ permissions: [READ, WRITE, ADMIN],
72
+ source: 'vertical',
73
+ });
74
+ }
75
+ await host.admin.assignRole(staff, { principalId: alice, roleKey: 'owner', node: { tenantId: t, scopeId: null } });
76
+ });
77
+ afterAll(async () => {
78
+ await fixture.cleanup();
79
+ });
80
+ describe('declared, or nothing', () => {
81
+ it('provisioning seats exactly the declared keys, per peer, and nothing for an undeclared one', async () => {
82
+ const live = (await tuples())
83
+ .filter((r) => r.revoked_at === null)
84
+ .map((r) => `${r.subject} ${r.relation} ${r.object}`);
85
+ expect(live.sort()).toEqual([
86
+ `vertical:${PEER_CALLER} granted:peer:read scope:${s}`,
87
+ `vertical:${PEER_CALLER} granted:peer:write scope:${s}`,
88
+ `vertical:${PEER_LISTENER} granted:peer:read scope:${s}`,
89
+ ].sort());
90
+ });
91
+ it('an allowlisted operation runs as the peer — the event names { vertical, scope }', async () => {
92
+ const peer = await asPeer();
93
+ await expect(peer.invoke('peer/note', { id: 'n-peer', body: 'from the board room' })).resolves.toEqual({
94
+ id: 'n-peer',
95
+ });
96
+ const row = (await outbox()).find((r) => r.entity_id === 'n-peer');
97
+ expect(JSON.parse(row.actor)).toEqual({ vertical: PEER_CALLER, scope: callerScope });
98
+ expect(row.operation).toBe('peer/note');
99
+ // K-34: authorized by the declared key, recorded like any other authorization.
100
+ expect(JSON.parse(row.authorization ?? '[]')).toEqual([expect.objectContaining({ permission: WRITE })]);
101
+ });
102
+ it('twin: the same write by a person is recorded as that person', async () => {
103
+ await (await asAlice()).invoke('peer/note', { id: 'n-alice', body: 'from alice' });
104
+ const row = (await outbox()).find((r) => r.entity_id === 'n-alice');
105
+ expect(JSON.parse(row.actor)).toBe(alice);
106
+ });
107
+ it('a read through a declared key sees the scope', async () => {
108
+ const notes = await (await asPeer()).invoke('peer/list');
109
+ expect(notes.map((n) => n.id)).toEqual(expect.arrayContaining(['n-alice', 'n-peer']));
110
+ });
111
+ it('an allowlisted operation checking an UNDECLARED key is a K-35 denial against the peer', async () => {
112
+ const err = await refusal((await asPeer()).invoke('peer/admin'));
113
+ expect(errorCodeOf(err)).toBe('permission_denied');
114
+ const row = (await denials()).find((d) => d.operation === 'peer/admin');
115
+ expect(JSON.parse(row.actor)).toEqual({ vertical: PEER_CALLER, scope: callerScope });
116
+ expect(row.permission).toBe(ADMIN);
117
+ });
118
+ it('twin: a person holding that key runs it', async () => {
119
+ await expect((await asAlice()).invoke('peer/admin')).resolves.toEqual({ admin: true });
120
+ });
121
+ it('an operation OFF the allowlist is refused at the door — forbidden, and no K-35 row', async () => {
122
+ const before = (await denials()).length;
123
+ const err = await refusal((await asPeer()).invoke('peer/off-list'));
124
+ expect(errorCodeOf(err)).toBe('forbidden');
125
+ expect((await denials()).length).toBe(before);
126
+ });
127
+ it('twin: a person reaches that operation', async () => {
128
+ await expect((await asAlice()).invoke('peer/off-list')).resolves.toEqual({ reached: true });
129
+ });
130
+ it('a vertical the target never declared is refused at the door, whatever it asks for', async () => {
131
+ const stranger = { vertical: 'acme/stranger', scope: callerScope };
132
+ for (const op of ['peer/list', 'peer/note', 'peer/off-list']) {
133
+ expect(errorCodeOf(await refusal((await asPeer(stranger)).invoke(op, { id: 'x', body: 'x' })))).toBe('forbidden');
134
+ }
135
+ expect(await held('acme/stranger')).toEqual([]);
136
+ });
137
+ it('a receive-only peer (`operations: []`) holds its key and invokes nothing', async () => {
138
+ expect(await held(PEER_LISTENER)).toEqual([READ]);
139
+ for (const op of ['peer/list', 'peer/note']) {
140
+ expect(errorCodeOf(await refusal((await asPeer(listener)).invoke(op, { id: 'y', body: 'y' })))).toBe('forbidden');
141
+ }
142
+ });
143
+ it('peerCovers answers the checker’s own view, key by key', async () => {
144
+ expect(await covers(PEER_CALLER)).toEqual([
145
+ { permission: READ, held: true },
146
+ { permission: WRITE, held: true },
147
+ { permission: ADMIN, held: false },
148
+ ]);
149
+ });
150
+ });
151
+ describe('revocation is the next call: the per-(scope, peer) kill switch', () => {
152
+ it('OFF refuses a stub obtained BEFORE it, and every key reads as not held', async () => {
153
+ const early = await asPeer();
154
+ await expect(early.invoke('peer/list')).resolves.toBeDefined();
155
+ const result = await off();
156
+ expect(result).toMatchObject({ vertical: PEER_CALLER, calls: 'off', changed: true });
157
+ expect([...result.permissions].sort()).toEqual([READ, WRITE]);
158
+ expect(errorCodeOf(await refusal(early.invoke('peer/list')))).toBe('forbidden');
159
+ expect(errorCodeOf(await refusal((await asPeer()).invoke('peer/list')))).toBe('forbidden');
160
+ expect(await held(PEER_CALLER)).toEqual([]);
161
+ });
162
+ it('is per peer: the receive-only peer keeps its key', async () => {
163
+ expect(await held(PEER_LISTENER)).toEqual([READ]);
164
+ });
165
+ it('a re-provision does not seat a switched-off peer back', async () => {
166
+ await host.provisionScope(staff, { tenantId: t, scopeId: s });
167
+ expect(await held(PEER_CALLER)).toEqual([]);
168
+ expect(errorCodeOf(await refusal((await asPeer()).invoke('peer/list')))).toBe('forbidden');
169
+ });
170
+ it('a repeat OFF changes nothing and says so', async () => {
171
+ await expect(off()).resolves.toMatchObject({ calls: 'off', changed: false, permissions: [] });
172
+ });
173
+ it('restoreToPeer gives back exactly what OFF took, and the peer calls again', async () => {
174
+ const result = await on();
175
+ expect(result).toMatchObject({ vertical: PEER_CALLER, calls: 'on', changed: true });
176
+ expect([...result.permissions].sort()).toEqual([READ, WRITE]);
177
+ expect(await held(PEER_CALLER)).toEqual([READ, WRITE]);
178
+ await expect((await asPeer()).invoke('peer/list')).resolves.toBeDefined();
179
+ });
180
+ it('a switch for a peer the scope never held writes nothing and says not_found', async () => {
181
+ expect(errorCodeOf(await refusal(off('acme/stranger')))).toBe('not_found');
182
+ expect((await tuples()).some((r) => r.subject === 'vertical:acme/stranger')).toBe(false);
183
+ });
184
+ });
185
+ /**
186
+ * The switch's READ half (#1706) — what a tenant is shown about "which of my other apps
187
+ * may call into here". It must answer from the same predicate the door refuses on, or a
188
+ * tenant reads `on` for a peer whose next call is refused; the ordering below runs the
189
+ * switch and the read against each other rather than asserting the read alone.
190
+ */
191
+ describe('the status read', () => {
192
+ const statusOf = async (vertical) => (await host.admin.peerGrantsStatus(staff, { tenantId: t, scopeId: s })).find((p) => p.vertical === vertical);
193
+ it('lists every declared peer as on, with no explanation to give', async () => {
194
+ const all = await host.admin.peerGrantsStatus(staff, { tenantId: t, scopeId: s });
195
+ expect(all).toEqual([
196
+ { vertical: PEER_CALLER, calls: 'on', switchedOff: null },
197
+ { vertical: PEER_LISTENER, calls: 'on', switchedOff: null },
198
+ ]);
199
+ });
200
+ it('a peer the scope holds no row for is absent, not reported', async () => {
201
+ expect(await statusOf('acme/stranger')).toBeUndefined();
202
+ });
203
+ it('OFF shows as off, and names who, when and why — while the peer keeps its row', async () => {
204
+ await off();
205
+ const entry = await statusOf(PEER_CALLER);
206
+ expect(entry).toMatchObject({ vertical: PEER_CALLER, calls: 'off' });
207
+ expect(entry?.switchedOff).toMatchObject({ actor: staff, reason: expect.any(String) });
208
+ expect(entry?.switchedOff?.at).toEqual(expect.any(String));
209
+ // The read and the door agree, which is the whole point of sharing the predicate.
210
+ expect(errorCodeOf(await refusal((await asPeer()).invoke('peer/list')))).toBe('forbidden');
211
+ });
212
+ it('is per peer: the other one is untouched and still explains nothing', async () => {
213
+ expect(await statusOf(PEER_LISTENER)).toEqual({
214
+ vertical: PEER_LISTENER,
215
+ calls: 'on',
216
+ switchedOff: null,
217
+ });
218
+ });
219
+ it('ON clears the explanation with the position — a stale reason is worse than none', async () => {
220
+ await on();
221
+ expect(await statusOf(PEER_CALLER)).toEqual({ vertical: PEER_CALLER, calls: 'on', switchedOff: null });
222
+ await expect((await asPeer()).invoke('peer/list')).resolves.toBeDefined();
223
+ });
224
+ });
225
+ describe('tenant-bound', () => {
226
+ it('the door fails closed on a scope of another tenant, named under this one', async () => {
227
+ // K-3's pair check — the confinement this whole door rests on, so the test pins the
228
+ // CODE and not merely "something threw" (#1714 review). `not_found` on both adapters:
229
+ // a scope of another tenant reads exactly as one that does not exist.
230
+ for (const refused of [refusal(asPeer(caller, su, t)), refusal(host.peerCovers(t, su, PEER_CALLER, [READ]))]) {
231
+ const err = await refused;
232
+ expect(errorCodeOf(err)).toBe('not_found');
233
+ expect(String(err.message)).toMatch(/unknown scope/);
234
+ }
235
+ });
236
+ it('twin: that scope, under its own tenant, is a door like any other', async () => {
237
+ await expect((await asPeer(caller, su, u)).invoke('peer/list')).resolves.toEqual([]);
238
+ });
239
+ });
240
+ describe('no person crosses', () => {
241
+ it('a peer cannot mint a capability — the verb that needs a person to vouch for it', async () => {
242
+ const err = await refusal((await asPeer()).invoke('peer/share', { id: 'n-peer' }));
243
+ expect(String(err?.message)).toMatch(/only a principal may mint/);
244
+ });
245
+ it('twin: the person who holds the key mints one', async () => {
246
+ await expect((await asAlice()).invoke('peer/share', { id: 'n-peer' })).resolves.toMatchObject({
247
+ id: expect.any(String),
248
+ });
249
+ });
250
+ it('every record a peer call left names the peer, never a person', async () => {
251
+ const peerRows = (await outbox()).filter((r) => typeof JSON.parse(r.actor) !== 'string');
252
+ expect(peerRows.length).toBeGreaterThan(0);
253
+ for (const row of peerRows)
254
+ expect(JSON.parse(row.actor)).toMatchObject({ vertical: PEER_CALLER });
255
+ for (const d of (await denials()).filter((d) => d.operation === 'peer/admin')) {
256
+ expect(JSON.parse(d.actor)).toMatchObject({ vertical: PEER_CALLER });
257
+ }
258
+ });
259
+ it('an idempotency key is the calling INSTANCE’s: a second instance’s same key is not a replay', async () => {
260
+ const first = await asPeer();
261
+ await first.invoke('peer/note', { id: 'idem-1', body: 'one' }, { idempotencyKey: 'k-1' });
262
+ // Same instance, same key, same request → a replay: nothing new is written.
263
+ await first.invoke('peer/note', { id: 'idem-1', body: 'one' }, { idempotencyKey: 'k-1' });
264
+ expect((await outbox()).filter((r) => r.entity_id === 'idem-1')).toHaveLength(1);
265
+ // A SECOND instance of the same vertical choosing the same key is a different caller.
266
+ const second = await asPeer({ vertical: PEER_CALLER, scope: secondCaller });
267
+ await second.invoke('peer/note', { id: 'idem-2', body: 'two' }, { idempotencyKey: 'k-1' });
268
+ const row = (await outbox()).find((r) => r.entity_id === 'idem-2');
269
+ expect(JSON.parse(row.actor)).toEqual({ vertical: PEER_CALLER, scope: secondCaller });
270
+ });
271
+ });
272
+ });
273
+ }
274
+ /**
275
+ * `HostAdmin.resolveVerticalInstance` (#1706) — the directory's half, against both adapters: the
276
+ * kernel's one rule (same tenant, primary, active, exactly one) over each adapter's own directory.
277
+ */
278
+ export function verticalResolutionContractSuite(adapterName, makeFixture) {
279
+ describe(`vertical instance resolution (#1706): ${adapterName}`, () => {
280
+ let fixture;
281
+ let host;
282
+ const staff = platformActorId.parse(ulid());
283
+ const t = tenantId.parse(ulid());
284
+ const u = tenantId.parse(ulid());
285
+ // A vertical slug no other suite binds a scope to, so the answer is this suite's alone.
286
+ const slug = `acme/resolve-${t.slice(-6).toLowerCase()}`;
287
+ const other = `acme/other-${t.slice(-6).toLowerCase()}`;
288
+ const scope = async (tenant, extra = {}, activate = true) => {
289
+ const s = scopeId.parse(ulid());
290
+ await host.provisionScope(staff, { tenantId: tenant, scopeId: s, vertical: slug, ...extra });
291
+ if (activate)
292
+ await host.admin.activateScope(staff, tenant, s);
293
+ return s;
294
+ };
295
+ beforeAll(async () => {
296
+ fixture = await makeFixture();
297
+ host = fixture.host;
298
+ for (const tenant of [t, u]) {
299
+ await host.admin.createTenant(staff, {
300
+ id: tenant,
301
+ slug: `resolve-${tenant.slice(-10).toLowerCase()}`,
302
+ name: 'Resolve',
303
+ });
304
+ }
305
+ });
306
+ afterAll(async () => {
307
+ await fixture.cleanup();
308
+ });
309
+ it('not installed: nothing of that vertical in the tenant', async () => {
310
+ await expect(host.admin.resolveVerticalInstance(t, slug)).resolves.toEqual({
311
+ outcome: 'not-installed',
312
+ tenantId: t,
313
+ vertical: slug,
314
+ });
315
+ });
316
+ let primary;
317
+ it('a preview, a fork and a still-provisioning scope are never the instance', async () => {
318
+ await scope(t, { kind: 'preview' });
319
+ const source = await scope(u); // a fork's source, in the OTHER tenant so it does not count here
320
+ await scope(t, { forkedFrom: source, forkedAt: new Date().toISOString() });
321
+ await scope(t, {}, false);
322
+ expect((await host.admin.resolveVerticalInstance(t, slug)).outcome).toBe('not-installed');
323
+ });
324
+ it('twin: one primary, active scope resolves — and only in its own tenant', async () => {
325
+ primary = await scope(t);
326
+ await expect(host.admin.resolveVerticalInstance(t, slug)).resolves.toEqual({
327
+ outcome: 'resolved',
328
+ instance: { tenantId: t, scopeId: primary, vertical: slug },
329
+ });
330
+ // `u` holds the fork source — a primary, active instance of the same vertical — and `t`
331
+ // never sees it, nor `u` the one in `t`.
332
+ const inU = await host.admin.resolveVerticalInstance(u, slug);
333
+ expect(inU.outcome).toBe('resolved');
334
+ if (inU.outcome === 'resolved')
335
+ expect(inU.instance.tenantId).toBe(u);
336
+ });
337
+ it('another vertical of the same tenant is a different question', async () => {
338
+ expect((await host.admin.resolveVerticalInstance(t, other)).outcome).toBe('not-installed');
339
+ });
340
+ it('a suspended instance is not a callable one', async () => {
341
+ await host.admin.suspendScope(staff, t, primary);
342
+ expect((await host.admin.resolveVerticalInstance(t, slug)).outcome).toBe('not-installed');
343
+ await host.admin.unsuspendScope(staff, t, primary);
344
+ expect((await host.admin.resolveVerticalInstance(t, slug)).outcome).toBe('resolved');
345
+ });
346
+ it('two live instances are ambiguous — refused, never guessed', async () => {
347
+ await scope(t);
348
+ await expect(host.admin.resolveVerticalInstance(t, slug)).resolves.toEqual({
349
+ outcome: 'ambiguous',
350
+ tenantId: t,
351
+ vertical: slug,
352
+ count: 2,
353
+ });
354
+ });
355
+ });
356
+ }
357
+ //# sourceMappingURL=peer-suite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-suite.js","sourceRoot":"","sources":["../src/peer-suite.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"scope-host-suite.d.ts","sourceRoot":"","sources":["../src/scope-host-suite.ts"],"names":[],"mappings":"AA4BA,OAAO,EAML,KAAK,SAAS,EACf,MAAM,sBAAsB,CAAC;AAW9B,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,SAAS,CAAC;IAChB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AA6DD,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,EAC5C,IAAI,GAAE,qBAA0B,GAC/B,IAAI,CAouNN"}
1
+ {"version":3,"file":"scope-host-suite.d.ts","sourceRoot":"","sources":["../src/scope-host-suite.ts"],"names":[],"mappings":"AA4BA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,sBAAsB,CAAC;AAW9B,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,SAAS,CAAC;IAChB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AA6DD,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,EAC5C,IAAI,GAAE,qBAA0B,GAC/B,IAAI,CAu0NN"}
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
2
  import { connectorCalls, connectorTestFetch, resetConnectorCalls } from './connector-fixture.js';
3
3
  import { connectionId, dataSubjectId, domainEvent, errorCodeOf, eventId, instant, moduleManifest, orgId, AUTO_ADMISSION_NOTE, permissionKey, platformActorId, platformRequestId, principalId, scopeId, tenantId, SCOPE_QUERY_ROW_MAX, } from '@substrat-run/contracts';
4
- import { isSearchIndexTable, REDACTED_INTENT_MARKER, runPlatformSweep, ulid, } from '@substrat-run/kernel';
4
+ import { isSearchIndexTable, REDACTED_DELIVERY_NOTE, REDACTED_INTENT_MARKER, runPlatformSweep, ulid, } from '@substrat-run/kernel';
5
5
  import { billedMod, contractTestBareOps, contractTestInitialModules, gateModManifest, lateMod, testModManifest, victimModManifest, } from './modules.js';
6
6
  /**
7
7
  * Assert a refusal by the CODE it declared, not by the sentence it happens to carry
@@ -1719,6 +1719,59 @@ export function scopeHostContractSuite(adapterName, makeFixture, opts = {}) {
1719
1719
  await expect(host.admin.shredSubject(staff, t2, s1, subject)).rejects.toThrow();
1720
1720
  await expect(host.admin.sealSubjectPayloads(staff, t2, s1, [{ subjectId: subject, plaintext: 'x' }])).rejects.toThrow();
1721
1721
  });
1722
+ it('redacts a failed delivery\'s error about the subject\'s event, and only that (#1632)', async () => {
1723
+ // A consumer's or executor's throw can quote the payload it choked on, and it lands
1724
+ // in `_substrat_deliveries.error` beside the event's id. Keyed by event id, so the
1725
+ // outbox's own predicate names exactly the rows to rewrite. Planted through a
1726
+ // restore because making a real consumer fail on a chosen event is a different
1727
+ // suite's business; the rows are the adapter's own export, plus two deliveries.
1728
+ const alicia = dataSubjectId.parse(ulid());
1729
+ const bruno = dataSubjectId.parse(ulid());
1730
+ const src = await host.getScope(alice, t1, s1);
1731
+ await src.invoke('test/emit-event', { subject: alicia, secret: 'alicia-was-here' });
1732
+ await src.invoke('test/emit-event', { subject: bruno, secret: 'bruno-was-here' });
1733
+ const rows = (await src.invoke('test/read-outbox', undefined));
1734
+ const eventOf = (subject) => rows.find((r) => r.subject_id === subject).id;
1735
+ const s = scopeId.parse(ulid());
1736
+ await host.provisionScope(staff, { tenantId: t1, scopeId: s, jurisdiction: 'eu', vertical: 'connector-vertical' });
1737
+ await host.admin.activateScope(staff, t1, s);
1738
+ const backup = await host.admin.exportScope(staff, t1, s1);
1739
+ const deliveries = backup.tables.find((t) => t.name === '_substrat_deliveries');
1740
+ expect(deliveries).toBeDefined();
1741
+ const plant = (eventId, error, consumer) => deliveries.columns.map((c) => ({
1742
+ event_id: eventId,
1743
+ consumer_module: consumer,
1744
+ delivered_at: '2026-09-20T00:00:00.000Z',
1745
+ error,
1746
+ attempts: 1,
1747
+ })[c] ?? null);
1748
+ await host.restoreScope(staff, t1, s, {
1749
+ ...backup,
1750
+ scopeId: s,
1751
+ tables: backup.tables.map((t) => t === deliveries
1752
+ ? {
1753
+ ...t,
1754
+ rows: [
1755
+ ...t.rows,
1756
+ plant(eventOf(alicia), 'consumer choked on alicia-was-here', '@test/dead-a'),
1757
+ plant(eventOf(alicia), null, '@test/delivered-a'),
1758
+ plant(eventOf(bruno), 'consumer choked on bruno-was-here', '@test/dead-b'),
1759
+ ],
1760
+ }
1761
+ : t),
1762
+ });
1763
+ await host.admin.shredSubject(staff, t1, s, alicia);
1764
+ const dump = await host.admin.exportScope(staff, t1, s);
1765
+ const table = dump.tables.find((t) => t.name === '_substrat_deliveries');
1766
+ const col = (row, c) => row[table.columns.indexOf(c)];
1767
+ const by = (consumer) => table.rows.find((r) => col(r, 'consumer_module') === consumer);
1768
+ expect(JSON.stringify(table.rows)).not.toContain('alicia-was-here');
1769
+ expect(col(by('@test/dead-a'), 'error')).toBe(REDACTED_DELIVERY_NOTE);
1770
+ // A delivered row stays delivered — a note there would read as dead-lettered.
1771
+ expect(col(by('@test/delivered-a'), 'error')).toBeNull();
1772
+ // The other subject's failure still says what it said.
1773
+ expect(col(by('@test/dead-b'), 'error')).toBe('consumer choked on bruno-was-here');
1774
+ });
1722
1775
  // -- the spine's OTHER copy of an event (#1600) -------------------------
1723
1776
  //
1724
1777
  // `_substrat_platform_requests` holds whole `DomainEvent`s. A CP-less host cannot
@@ -1843,31 +1896,65 @@ export function scopeHostContractSuite(adapterName, makeFixture, opts = {}) {
1843
1896
  // Already settled, so the redaction does not re-date it or reopen it.
1844
1897
  expect(after.status).toBe('failed');
1845
1898
  });
1846
- it('keeps `result`, which is envelope — what the intent DID, not what it said', async () => {
1847
- // The redaction spares `result` on purpose, and until now nothing held it to
1848
- // that: for a routed dispatch it is `{ eventId }`, which names the event the
1849
- // outbox already keeps a redacted row for, so it is the same class of fact as
1850
- // `kind` and `requestedAt`. Asserted beside the two columns that DO go, so the
1851
- // line between them is a test rather than a sentence in the PR.
1899
+ it('redacts `result` and `last_failure` too — the provider\'s answer is content (#1632)', async () => {
1900
+ // #1600 spared `result` as envelope, reasoning from a routed dispatch whose result
1901
+ // is `{ eventId }`. But `result` is whatever the drain's handler returned, and a
1902
+ // connector's return is the provider's answer about delivering THIS person's data
1903
+ // — `last_error`'s reason, one column over. `last_failure` carries no text, and
1904
+ // goes because it is `last_error`'s attribution: kept, `origin: 'provider'` would
1905
+ // caption the platform's redaction note as the provider's words.
1852
1906
  const kind = `connector:erasure-${ulid()}`;
1853
1907
  const erased = dataSubjectId.parse(ulid());
1854
1908
  const id = await routeIntent(kind, erased, 'Anna Ek');
1855
1909
  await host.settlePlatformRequest(t1, s1, id, {
1856
1910
  status: 'failed',
1857
- result: { eventId: 'evt-01', deliveredAt: '2026-09-20T00:00:00.000Z' },
1911
+ result: { documentId: 'doc-1', signer: 'Anna Ek' },
1858
1912
  lastError: 'HTTP 409: Anna Ek requires a valid personal number field',
1913
+ failure: { origin: 'provider', code: null, permission: null },
1859
1914
  });
1915
+ const before = (await journal(kind)).find((r) => r.id === id);
1916
+ expect(JSON.stringify(before.result)).toContain('Anna Ek');
1917
+ expect(before.failure?.origin).toBe('provider');
1860
1918
  await host.admin.shredSubject(staff, t1, s1, erased);
1861
1919
  const after = (await journal(kind)).find((r) => r.id === id);
1862
- // What was said goes — both columns that could carry it.
1863
- expect(JSON.stringify(after.payload)).not.toContain('Anna Ek');
1864
- expect(after.lastError).not.toContain('Anna Ek');
1865
- expect(after.lastError).toContain('subject erasure');
1866
- // What happened stays, unchanged and whole.
1920
+ // THE property: no trace of the name anywhere in the row.
1921
+ expect(JSON.stringify(after)).not.toContain('Anna Ek');
1922
+ // What replaced it says what happened, in the payload's own convention.
1867
1923
  expect(after.result).toEqual({
1868
- eventId: 'evt-01',
1869
- deliveredAt: '2026-09-20T00:00:00.000Z',
1924
+ [REDACTED_INTENT_MARKER]: expect.objectContaining({
1925
+ reason: 'subject-erasure',
1926
+ subjectId: erased,
1927
+ }),
1870
1928
  });
1929
+ expect(after.lastError).toContain('subject erasure');
1930
+ expect(after.failure).toBeNull();
1931
+ // Envelope survives: still failed, still dated, still decodes whole.
1932
+ expect(after.status).toBe('failed');
1933
+ expect(after.settledAt).toBe(before.settledAt);
1934
+ expect(after.decodeError).toBeUndefined();
1935
+ });
1936
+ it('leaves a NULL `result` NULL, and a neighbour\'s result and attribution intact', async () => {
1937
+ // A tombstone where there was no result would claim an answer nobody gave. And
1938
+ // the twin that stops a redact-every-result bug passing the test above.
1939
+ const kind = `connector:erasure-${ulid()}`;
1940
+ const erased = dataSubjectId.parse(ulid());
1941
+ const spared = dataSubjectId.parse(ulid());
1942
+ const mine = await routeIntent(kind, erased, 'Anna Ek');
1943
+ const theirs = await routeIntent(kind, spared, 'Bo Lund');
1944
+ await host.settlePlatformRequest(t1, s1, mine, { status: 'failed', lastError: 'no answer' });
1945
+ await host.settlePlatformRequest(t1, s1, theirs, {
1946
+ status: 'failed',
1947
+ result: { signer: 'Bo Lund' },
1948
+ lastError: 'HTTP 409: Bo Lund requires a valid personal number field',
1949
+ failure: { origin: 'provider', code: null, permission: null },
1950
+ });
1951
+ await host.admin.shredSubject(staff, t1, s1, erased);
1952
+ const after = await journal(kind);
1953
+ expect(after.find((r) => r.id === mine).result).toBeNull();
1954
+ const other = after.find((r) => r.id === theirs);
1955
+ expect(other.result).toEqual({ signer: 'Bo Lund' });
1956
+ expect(other.lastError).toContain('Bo Lund');
1957
+ expect(other.failure).toEqual({ origin: 'provider', code: null, permission: null });
1871
1958
  });
1872
1959
  it('refuses a stale drain settling a row the erasure already redacted', async () => {
1873
1960
  // The drain reads pending rows, runs the handler, then settles — so a settle can