@substrat-run/contract-tests 0.144.0 → 0.146.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 (61) hide show
  1. package/dist/async-log-suite.d.ts.map +1 -1
  2. package/dist/async-log-suite.js +5 -0
  3. package/dist/async-log-suite.js.map +1 -1
  4. package/dist/bind-module.d.ts +22 -1
  5. package/dist/bind-module.d.ts.map +1 -1
  6. package/dist/bind-module.js +32 -1
  7. package/dist/bind-module.js.map +1 -1
  8. package/dist/binding-invite-suite.d.ts.map +1 -1
  9. package/dist/binding-invite-suite.js +53 -0
  10. package/dist/binding-invite-suite.js.map +1 -1
  11. package/dist/derived-module.d.ts +72 -1
  12. package/dist/derived-module.d.ts.map +1 -1
  13. package/dist/derived-module.js +31 -3
  14. package/dist/derived-module.js.map +1 -1
  15. package/dist/derived-suite.d.ts.map +1 -1
  16. package/dist/derived-suite.js +91 -0
  17. package/dist/derived-suite.js.map +1 -1
  18. package/dist/entity-state-module.d.ts +16 -0
  19. package/dist/entity-state-module.d.ts.map +1 -1
  20. package/dist/entity-trash-module.d.ts +80 -0
  21. package/dist/entity-trash-module.d.ts.map +1 -1
  22. package/dist/entity-trash-module.js +73 -0
  23. package/dist/entity-trash-module.js.map +1 -1
  24. package/dist/entity-trash-suite.d.ts.map +1 -1
  25. package/dist/entity-trash-suite.js +94 -2
  26. package/dist/entity-trash-suite.js.map +1 -1
  27. package/dist/erasure-module.d.ts +8 -0
  28. package/dist/erasure-module.d.ts.map +1 -1
  29. package/dist/findings-suite.d.ts.map +1 -1
  30. package/dist/findings-suite.js +55 -1
  31. package/dist/findings-suite.js.map +1 -1
  32. package/dist/index.d.ts +2 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +2 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/module-log-suite.d.ts.map +1 -1
  37. package/dist/module-log-suite.js +12 -0
  38. package/dist/module-log-suite.js.map +1 -1
  39. package/dist/modules.d.ts +253 -1
  40. package/dist/modules.d.ts.map +1 -1
  41. package/dist/modules.js +17 -5
  42. package/dist/modules.js.map +1 -1
  43. package/dist/peer-suite.d.ts.map +1 -1
  44. package/dist/peer-suite.js +75 -1
  45. package/dist/peer-suite.js.map +1 -1
  46. package/dist/scope-host-suite.d.ts.map +1 -1
  47. package/dist/scope-host-suite.js +501 -0
  48. package/dist/scope-host-suite.js.map +1 -1
  49. package/dist/stamps-module.d.ts +462 -0
  50. package/dist/stamps-module.d.ts.map +1 -0
  51. package/dist/stamps-module.js +242 -0
  52. package/dist/stamps-module.js.map +1 -0
  53. package/dist/stamps-suite.d.ts +3 -0
  54. package/dist/stamps-suite.d.ts.map +1 -0
  55. package/dist/stamps-suite.js +170 -0
  56. package/dist/stamps-suite.js.map +1 -0
  57. package/dist/vertical-events-suite.d.ts +16 -0
  58. package/dist/vertical-events-suite.d.ts.map +1 -1
  59. package/dist/vertical-events-suite.js +30 -3
  60. package/dist/vertical-events-suite.js.map +1 -1
  61. package/package.json +3 -3
@@ -0,0 +1,242 @@
1
+ /**
2
+ * The fixture behind `updatedStampsContractSuite` (#2166): an entity that declares all four stamps,
3
+ * written by derived and authored handlers alike, and one that declares only `createdAt`.
4
+ *
5
+ * - `sdoc` — every stamp, and archivable: a derived create, get, update and archive, plus authored
6
+ * writes that stamp the updated pair right or wrong on purpose (`stamp`), an authored delete, a
7
+ * write inside a `ctx.atomic` that is rolled back, and a consumer that writes the row.
8
+ * - `splain` — the created stamp only: an authored write that stamps nothing is never checked.
9
+ *
10
+ * Inert for every other suite: nothing else invokes a `stamps/*` operation or reads its tables.
11
+ */
12
+ import { z } from 'zod';
13
+ import { defineEntities, defineOperations, entityStateOutput, manifestEntities, manifestOperations, moduleManifest, permissionKey, } from '@substrat-run/contracts';
14
+ import { assertAllowed, operationsFor, readHistory } from '@substrat-run/kernel';
15
+ export const STAMPS_MODULE_ID = '@test/stamps';
16
+ /** A time no call is ever made at. */
17
+ export const STALE_TIME = '2000-01-01T00:00:00.000Z';
18
+ export const stampsEntities = defineEntities({
19
+ sdoc: {
20
+ table: 'stamped_docs',
21
+ fields: z.object({
22
+ id: z.string(),
23
+ title: z.string(),
24
+ created_at: z.string(),
25
+ created_by: z.string(),
26
+ updated_at: z.string(),
27
+ updated_by: z.string(),
28
+ }),
29
+ archive: { permission: 'sdoc:archive' },
30
+ stamps: { created_at: 'createdAt', created_by: 'createdBy', updated_at: 'updatedAt', updated_by: 'updatedBy' },
31
+ },
32
+ splain: {
33
+ table: 'stamped_plain',
34
+ fields: z.object({ id: z.string(), title: z.string(), created_at: z.string() }),
35
+ stamps: { created_at: 'createdAt' },
36
+ },
37
+ });
38
+ /** How an authored write stamps the updated pair: right, or wrong in one particular way. */
39
+ export const STAMP_MODES = ['both', 'skip-at', 'skip-by', 'constant', 'other'];
40
+ const PERMISSIONS = ['sdoc:read', 'sdoc:write', 'sdoc:archive'];
41
+ const doc = (key) => ({ key, entity: 'sdoc', idFrom: 'docId' });
42
+ const byId = z.object({ id: z.string() });
43
+ const touched = { entity: 'sdoc', entityIdFrom: 'id', type: 'sdoc.touched', schemaVersion: 1, piiClass: 'none', payload: ['id'] };
44
+ export const stampsOperations = defineOperations(stampsEntities, PERMISSIONS)({
45
+ 'stamps/create-doc': {
46
+ summary: 'Add a doc, every stamp written by the platform',
47
+ derive: 'create',
48
+ permission: 'sdoc:write',
49
+ input: z.object({ title: z.string() }),
50
+ output: stampsEntities.sdoc.fields,
51
+ http: { method: 'POST', path: '/docs' },
52
+ emits: { entity: 'sdoc', entityIdFrom: 'id', type: 'sdoc.created', schemaVersion: 1, piiClass: 'none', payload: ['id', 'title'] },
53
+ },
54
+ 'stamps/get-doc': {
55
+ summary: 'One doc',
56
+ derive: 'get',
57
+ permission: doc('sdoc:read'),
58
+ input: z.object({ docId: z.string() }),
59
+ output: stampsEntities.sdoc.fields,
60
+ http: { method: 'GET', path: '/docs/{docId}' },
61
+ },
62
+ 'stamps/update-doc': {
63
+ summary: "Change a doc's title",
64
+ derive: 'update',
65
+ permission: doc('sdoc:write'),
66
+ input: z.object({ docId: z.string(), title: z.string().optional() }),
67
+ output: stampsEntities.sdoc.fields,
68
+ http: { method: 'PATCH', path: '/docs/{docId}' },
69
+ concurrency: { over: 'sdoc', idFrom: 'docId' },
70
+ emits: { entity: 'sdoc', entityIdFrom: 'id', type: 'sdoc.updated', schemaVersion: 1, piiClass: 'none', payload: ['id', 'title'] },
71
+ },
72
+ 'stamps/archive-doc': {
73
+ summary: 'File a doc away — a kernel move',
74
+ derive: 'archive',
75
+ permission: doc('sdoc:archive'),
76
+ input: z.object({ docId: z.string() }),
77
+ output: entityStateOutput,
78
+ http: { method: 'POST', path: '/docs/{docId}/archive' },
79
+ },
80
+ 'stamps/add-doc': {
81
+ summary: 'Add a doc by hand, with or without the updated pair',
82
+ permission: 'sdoc:write',
83
+ input: z.object({ id: z.string(), title: z.string(), stampUpdated: z.boolean() }),
84
+ output: byId,
85
+ emits: { entity: 'sdoc', entityIdFrom: 'id', type: 'sdoc.added', schemaVersion: 1, piiClass: 'none', payload: ['id'] },
86
+ },
87
+ 'stamps/touch-doc': {
88
+ summary: "Change a doc's title by hand, stamping as told",
89
+ permission: 'sdoc:write',
90
+ input: z.object({ id: z.string(), title: z.string(), stamp: z.enum(STAMP_MODES) }),
91
+ output: byId,
92
+ emits: touched,
93
+ },
94
+ 'stamps/atomic-touch': {
95
+ summary: 'Write a doc without its stamps inside a ctx.atomic that is rolled back',
96
+ permission: 'sdoc:write',
97
+ input: z.object({ id: z.string(), title: z.string() }),
98
+ output: byId,
99
+ emits: touched,
100
+ },
101
+ 'stamps/remove-doc': {
102
+ summary: 'Delete a doc by hand',
103
+ permission: 'sdoc:write',
104
+ input: byId,
105
+ output: byId,
106
+ emits: { entity: 'sdoc', entityIdFrom: 'id', type: 'sdoc.removed', schemaVersion: 1, piiClass: 'none', payload: ['id'] },
107
+ },
108
+ 'stamps/poke': {
109
+ summary: 'Ask the consumer to write a doc, stamping as told — the event is about the plain row',
110
+ permission: 'sdoc:write',
111
+ input: z.object({ id: z.string(), stamp: z.enum(STAMP_MODES) }),
112
+ output: byId,
113
+ emits: { entity: 'splain', entityIdFrom: 'id', type: 'sdoc.poked', schemaVersion: 1, piiClass: 'none', payload: ['id'] },
114
+ },
115
+ 'stamps/history': {
116
+ summary: "How many events name a doc — what a rolled-back write must leave unchanged",
117
+ permission: 'sdoc:read',
118
+ input: byId,
119
+ output: z.object({ events: z.number() }),
120
+ },
121
+ 'stamps/touch-plain': {
122
+ summary: 'Write a plain row, which declares no updated stamp',
123
+ permission: 'sdoc:write',
124
+ input: z.object({ id: z.string(), title: z.string() }),
125
+ output: byId,
126
+ emits: { entity: 'splain', entityIdFrom: 'id', type: 'splain.touched', schemaVersion: 1, piiClass: 'none', payload: ['id'] },
127
+ },
128
+ });
129
+ export const stampsManifest = moduleManifest.parse({
130
+ id: STAMPS_MODULE_ID,
131
+ version: '1.0.0',
132
+ kernelContract: '^0.0.1',
133
+ migrations: { journalDir: './migrations', compatibleFrom: '1.0.0' },
134
+ ...manifestOperations(stampsOperations, {
135
+ permissions: { 'sdoc:read': 'read docs', 'sdoc:write': 'write docs', 'sdoc:archive': 'archive docs' },
136
+ consumes: [{ type: 'sdoc.poked', schemaVersion: 1 }],
137
+ }),
138
+ ...manifestEntities(stampsEntities, {}),
139
+ entitlementKey: 'stamps',
140
+ });
141
+ const WRITE = permissionKey.parse('sdoc:write');
142
+ const READ = permissionKey.parse('sdoc:read');
143
+ const docRef = (id) => ({ entityType: 'sdoc', entityId: id });
144
+ /** The updated pair as `mode` writes it: this call's, or wrong in one particular way. */
145
+ function updatedPair(ctx, mode) {
146
+ const now = ctx.now();
147
+ const me = String(ctx.principal);
148
+ switch (mode) {
149
+ case 'both':
150
+ return { set: ', updated_at = ?, updated_by = ?', values: [now, me] };
151
+ case 'skip-at':
152
+ return { set: ', updated_by = ?', values: [me] };
153
+ case 'skip-by':
154
+ return { set: ', updated_at = ?', values: [now] };
155
+ case 'constant':
156
+ return { set: ', updated_at = ?, updated_by = ?', values: [STALE_TIME, me] };
157
+ case 'other':
158
+ return { set: ', updated_at = ?, updated_by = ?', values: [now, 'someone-else'] };
159
+ }
160
+ }
161
+ function touchDoc(ctx, id, title, mode) {
162
+ const pair = updatedPair(ctx, mode);
163
+ ctx.sql.exec(`UPDATE stamped_docs SET title = ?${pair.set} WHERE id = ?`, [title, ...pair.values, id]);
164
+ ctx.emit({ type: 'sdoc.touched', schemaVersion: 1, entity: docRef(id), piiClass: 'none', payload: { id } });
165
+ }
166
+ export const stampsMod = {
167
+ manifest: stampsManifest,
168
+ migrations: [
169
+ {
170
+ version: '0001-init',
171
+ sql: `CREATE TABLE stamped_docs (id TEXT PRIMARY KEY, title TEXT NOT NULL,
172
+ created_at TEXT NOT NULL, created_by TEXT NOT NULL,
173
+ updated_at TEXT NOT NULL, updated_by TEXT NOT NULL);
174
+ CREATE TABLE stamped_plain (id TEXT PRIMARY KEY, title TEXT NOT NULL, created_at TEXT NOT NULL);`,
175
+ },
176
+ ],
177
+ ...operationsFor(stampsOperations)({
178
+ 'stamps/add-doc': async (ctx, i) => {
179
+ assertAllowed(await ctx.check(WRITE));
180
+ const now = ctx.now();
181
+ const me = String(ctx.principal);
182
+ // Without the pair, an older update's: the row is new, so its updated stamps are this call's too.
183
+ const updated = i.stampUpdated ? [now, me] : [STALE_TIME, me];
184
+ ctx.sql.exec('INSERT INTO stamped_docs (id, title, created_at, created_by, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?)', [i.id, i.title, now, me, ...updated]);
185
+ ctx.emit({ type: 'sdoc.added', schemaVersion: 1, entity: docRef(i.id), piiClass: 'none', payload: { id: i.id } });
186
+ return { id: i.id };
187
+ },
188
+ 'stamps/touch-doc': async (ctx, i) => {
189
+ assertAllowed(await ctx.check(WRITE));
190
+ touchDoc(ctx, i.id, i.title, i.stamp);
191
+ return { id: i.id };
192
+ },
193
+ 'stamps/atomic-touch': async (ctx, i) => {
194
+ assertAllowed(await ctx.check(WRITE));
195
+ try {
196
+ await ctx.atomic(() => {
197
+ touchDoc(ctx, i.id, i.title, 'skip-at');
198
+ throw new Error('rolled back on purpose');
199
+ });
200
+ }
201
+ catch {
202
+ // The write and its event are gone; nothing this call commits names the doc.
203
+ }
204
+ return { id: i.id };
205
+ },
206
+ 'stamps/remove-doc': async (ctx, i) => {
207
+ assertAllowed(await ctx.check(WRITE));
208
+ ctx.sql.exec('DELETE FROM stamped_docs WHERE id = ?', [i.id]);
209
+ ctx.emit({ type: 'sdoc.removed', schemaVersion: 1, entity: docRef(i.id), piiClass: 'none', payload: { id: i.id } });
210
+ return { id: i.id };
211
+ },
212
+ 'stamps/poke': async (ctx, i) => {
213
+ assertAllowed(await ctx.check(WRITE));
214
+ ctx.emit({
215
+ type: 'sdoc.poked',
216
+ schemaVersion: 1,
217
+ entity: { entityType: 'splain', entityId: i.id },
218
+ piiClass: 'none',
219
+ payload: { id: i.id, stamp: i.stamp },
220
+ });
221
+ return { id: i.id };
222
+ },
223
+ 'stamps/history': async (ctx, i) => {
224
+ assertAllowed(await ctx.check(READ));
225
+ return { events: readHistory({ sql: ctx.sql }, docRef(i.id), { limit: 100 }).entries.length };
226
+ },
227
+ 'stamps/touch-plain': async (ctx, i) => {
228
+ assertAllowed(await ctx.check(WRITE));
229
+ ctx.sql.exec('INSERT INTO stamped_plain (id, title, created_at) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET title = excluded.title', [i.id, i.title, ctx.now()]);
230
+ ctx.emit({ type: 'splain.touched', schemaVersion: 1, entity: { entityType: 'splain', entityId: i.id }, piiClass: 'none', payload: { id: i.id } });
231
+ return { id: i.id };
232
+ },
233
+ }),
234
+ consumers: {
235
+ // Writes the doc the poke names as the consumer's own principal, stamping as the poke says.
236
+ 'sdoc.poked': async (ctx, event) => {
237
+ const { id, stamp } = z.object({ id: z.string(), stamp: z.enum(STAMP_MODES) }).parse(event.payload);
238
+ touchDoc(ctx, id, `consumed ${stamp}`, stamp);
239
+ },
240
+ },
241
+ };
242
+ //# sourceMappingURL=stamps-module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stamps-module.js","sourceRoot":"","sources":["../src/stamps-module.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ScopeHostFixture } from './scope-host-suite.js';
2
+ export declare function updatedStampsContractSuite(adapterName: string, makeFixture: () => Promise<ScopeHostFixture>): void;
3
+ //# sourceMappingURL=stamps-suite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stamps-suite.d.ts","sourceRoot":"","sources":["../src/stamps-suite.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAM9D,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI,CA4KlH"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Contract suite for the updated stamps the host holds true (#2166).
3
+ *
4
+ * What it pins, on each adapter's SQL: an entity that declares `updatedAt`/`updatedBy` holds them on
5
+ * every row an operation or a consumer emits about. A derived create writes all four stamps and a
6
+ * derived update the updated pair. An authored write that stamps the pair passes; one that skips
7
+ * either column, writes a constant time or another principal is refused `internal`, and its row and
8
+ * its event roll back with it. A deleted row, a kernel move, an emit rolled back inside a
9
+ * `ctx.atomic` and an entity without the pair are not checked. A consumer is held to the same rule,
10
+ * with its own instant and principal, and a refused delivery is dead-lettered.
11
+ */
12
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
13
+ import { errorCodeOf, permissionKey, platformActorId, principalId, scopeId, tenantId } from '@substrat-run/contracts';
14
+ import { ulid } from '@substrat-run/kernel';
15
+ import { stampsMod } from './stamps-module.js';
16
+ const KEYS = ['sdoc:read', 'sdoc:write', 'sdoc:archive'].map((k) => permissionKey.parse(k));
17
+ export function updatedStampsContractSuite(adapterName, makeFixture) {
18
+ describe(`updated stamps held by the host (#2166): ${adapterName}`, () => {
19
+ let fixture;
20
+ let host;
21
+ const t = tenantId.parse(ulid());
22
+ const s = scopeId.parse(ulid());
23
+ const staff = platformActorId.parse(ulid());
24
+ const alice = principalId.parse(ulid());
25
+ const carl = principalId.parse(ulid());
26
+ let as;
27
+ let asCarl;
28
+ const refusal = (call) => call.then(() => ({ code: 'answered', message: '' }), (e) => ({ code: errorCodeOf(e), message: e.message }));
29
+ const get = (id) => as.invoke('stamps/get-doc', { docId: id });
30
+ const events = async (id) => (await as.invoke('stamps/history', { id })).events;
31
+ const create = async (title) => as.invoke('stamps/create-doc', { title });
32
+ /**
33
+ * A stamp left alone is only stale once the clock has moved past it: in the same millisecond
34
+ * the old time IS this call's, and true. A timer, because workerd's clock stands still while
35
+ * code runs.
36
+ */
37
+ const pastStamp = async (d) => {
38
+ while (new Date().toISOString() <= d.updated_at)
39
+ await new Promise((r) => setTimeout(r, 2));
40
+ };
41
+ beforeAll(async () => {
42
+ fixture = await makeFixture();
43
+ host = fixture.host;
44
+ host.registerModule(stampsMod);
45
+ await host.admin.createTenant(staff, { id: t, slug: 'stamps-tenant', name: 'Stamps Tenant' });
46
+ await host.admin.grantEntitlement(staff, t, 'stamps');
47
+ await host.admin.defineRole(staff, t, { key: 'stamps-admin', permissions: KEYS, source: 'vertical' });
48
+ for (const who of [alice, carl]) {
49
+ await host.admin.assignRole(staff, { principalId: who, roleKey: 'stamps-admin', node: { tenantId: t, scopeId: null } });
50
+ }
51
+ await host.provisionScope(staff, { tenantId: t, scopeId: s, vertical: 'stamps-vertical' });
52
+ await host.admin.activateScope(staff, t, s);
53
+ as = await host.getScope(alice, t, s);
54
+ asCarl = await host.getScope(carl, t, s);
55
+ });
56
+ afterAll(async () => {
57
+ await fixture.cleanup();
58
+ });
59
+ describe('derived writes stamp the pair themselves', () => {
60
+ it('a derived create writes all four stamps: the call’s instant and the caller', async () => {
61
+ const before = new Date().toISOString();
62
+ const d = await create('first');
63
+ const after = new Date().toISOString();
64
+ expect(d.created_at).toBe(d.updated_at);
65
+ expect(d.created_at >= before && d.created_at <= after).toBe(true);
66
+ expect([d.created_by, d.updated_by]).toEqual([alice, alice]);
67
+ expect(await get(d.id)).toEqual(d);
68
+ });
69
+ it('a derived update moves the updated pair to its own caller, and leaves the created pair', async () => {
70
+ const d = await create('second');
71
+ const before = new Date().toISOString();
72
+ const after = await asCarl.invoke('stamps/update-doc', { docId: d.id, title: 'changed' });
73
+ expect(after).toEqual({ ...d, title: 'changed', updated_at: after.updated_at, updated_by: carl });
74
+ expect(after.updated_at >= before).toBe(true);
75
+ expect(await get(d.id)).toEqual(after);
76
+ });
77
+ it('a derived update that sends nothing writes nothing, so the pair stays', async () => {
78
+ const d = await create('untouched');
79
+ expect(await asCarl.invoke('stamps/update-doc', { docId: d.id })).toEqual(d);
80
+ });
81
+ });
82
+ describe('an authored write is held to the pair', () => {
83
+ it('an authored update that stamps the pair passes', async () => {
84
+ const d = await create('authored');
85
+ await asCarl.invoke('stamps/touch-doc', { id: d.id, title: 'by hand', stamp: 'both' });
86
+ const after = await get(d.id);
87
+ expect(after.title).toBe('by hand');
88
+ expect(after.updated_by).toBe(carl);
89
+ expect(after.created_by).toBe(alice);
90
+ });
91
+ it.each([
92
+ ['skip-at', 'updated_at', 'time'],
93
+ ['skip-by', 'updated_by', 'principal'],
94
+ ['constant', 'updated_at', 'time'],
95
+ ['other', 'updated_by', 'principal'],
96
+ ])('stamping %s is refused internal, and the write and its event roll back', async (stamp, column, what) => {
97
+ const d = await create(`refused ${stamp}`);
98
+ await pastStamp(d);
99
+ const seen = await events(d.id);
100
+ const r = await refusal(asCarl.invoke('stamps/touch-doc', { id: d.id, title: 'lost', stamp }));
101
+ expect(r.code).toBe('internal');
102
+ expect(r.message).toContain(`'${column}' does not hold this call's ${what}`);
103
+ expect(r.message).toContain('stamps/touch-doc');
104
+ expect(await get(d.id)).toEqual(d);
105
+ expect(await events(d.id)).toBe(seen);
106
+ });
107
+ it('an authored insert must stamp the updated pair too: without it, no row is left', async () => {
108
+ const ok = ulid();
109
+ await as.invoke('stamps/add-doc', { id: ok, title: 'with', stampUpdated: true });
110
+ expect((await get(ok)).updated_at).toBe((await get(ok)).created_at);
111
+ const stale = ulid();
112
+ const r = await refusal(as.invoke('stamps/add-doc', { id: stale, title: 'without', stampUpdated: false }));
113
+ expect(r.code).toBe('internal');
114
+ expect((await refusal(get(stale))).code).toBe('not_found');
115
+ });
116
+ });
117
+ describe('what is not checked', () => {
118
+ it('an emit about a deleted row: there is nothing left to stamp', async () => {
119
+ const d = await create('to remove');
120
+ await as.invoke('stamps/remove-doc', { id: d.id });
121
+ expect((await refusal(get(d.id))).code).toBe('not_found');
122
+ });
123
+ it('a kernel move: archiving writes only the platform’s columns, and leaves the pair', async () => {
124
+ const d = await create('to archive');
125
+ await asCarl.invoke('stamps/archive-doc', { docId: d.id });
126
+ expect(await get(d.id)).toEqual(d);
127
+ });
128
+ it('an emit rolled back inside ctx.atomic is gone from the outbox, so it is not held', async () => {
129
+ const d = await create('atomic');
130
+ // Another principal, past the create's millisecond: the rolled-back write left the row
131
+ // stale in time, so were its emit still counted, this call would be refused.
132
+ await pastStamp(d);
133
+ const seen = await events(d.id);
134
+ await asCarl.invoke('stamps/atomic-touch', { id: d.id, title: 'never' });
135
+ expect(await get(d.id)).toEqual(d);
136
+ expect(await events(d.id)).toBe(seen);
137
+ });
138
+ it('an entity that declares no updated stamp: an unstamped write passes', async () => {
139
+ const id = ulid();
140
+ await expect(as.invoke('stamps/touch-plain', { id, title: 'one' })).resolves.toEqual({ id });
141
+ await expect(asCarl.invoke('stamps/touch-plain', { id, title: 'two' })).resolves.toEqual({ id });
142
+ });
143
+ });
144
+ describe('a consumer is held to the pair, as its own principal', () => {
145
+ const poke = async (stamp) => {
146
+ const d = await create(`poked ${stamp}`);
147
+ await as.invoke('stamps/poke', { id: d.id, stamp });
148
+ return d;
149
+ };
150
+ const lettersFor = async (id) => (await host.admin.deadLetters(staff, t, s, {})).entries.filter((l) => String(l.error ?? '').includes(id));
151
+ it('a consumer that stamps the pair writes the row, as its own principal', async () => {
152
+ const d = await poke('both');
153
+ const after = await get(d.id);
154
+ expect(after.title).toBe('consumed both');
155
+ expect(after.updated_by).not.toBe(alice);
156
+ expect(after.updated_at >= d.updated_at).toBe(true);
157
+ expect(await lettersFor(d.id)).toEqual([]);
158
+ });
159
+ it('a consumer that skips the pair is dead-lettered, and its write rolls back', async () => {
160
+ const d = await poke('constant');
161
+ expect(await get(d.id)).toEqual(d);
162
+ const letters = await lettersFor(d.id);
163
+ expect(letters).toHaveLength(1);
164
+ expect(String(letters[0]?.error)).toContain("'updated_at' does not hold this call's time");
165
+ expect(String(letters[0]?.error)).toContain('consuming sdoc.poked');
166
+ });
167
+ });
168
+ });
169
+ }
170
+ //# sourceMappingURL=stamps-suite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stamps-suite.js","sourceRoot":"","sources":["../src/stamps-suite.ts"],"names":[],"mappings":"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"}
@@ -106,6 +106,7 @@ export declare const crmExportModManifest: {
106
106
  archivePermission?: (string & z.$brand<"PermissionKey">) | undefined;
107
107
  trashPermission?: (string & z.$brand<"PermissionKey">) | undefined;
108
108
  purgeAfterDays?: number | undefined;
109
+ purgeEarly?: boolean | undefined;
109
110
  table?: string | undefined;
110
111
  idColumn?: string | undefined;
111
112
  entityType: string;
@@ -123,6 +124,13 @@ export declare const crmExportModManifest: {
123
124
  }[];
124
125
  }[];
125
126
  } | undefined;
127
+ updatedStamps?: {
128
+ table: string;
129
+ idColumn: string;
130
+ at?: string | undefined;
131
+ by?: string | undefined;
132
+ entityType: string;
133
+ }[] | undefined;
126
134
  ui?: {
127
135
  routes?: {
128
136
  path: string;
@@ -255,6 +263,7 @@ export declare const boardImportModManifest: {
255
263
  archivePermission?: (string & z.$brand<"PermissionKey">) | undefined;
256
264
  trashPermission?: (string & z.$brand<"PermissionKey">) | undefined;
257
265
  purgeAfterDays?: number | undefined;
266
+ purgeEarly?: boolean | undefined;
258
267
  table?: string | undefined;
259
268
  idColumn?: string | undefined;
260
269
  entityType: string;
@@ -272,6 +281,13 @@ export declare const boardImportModManifest: {
272
281
  }[];
273
282
  }[];
274
283
  } | undefined;
284
+ updatedStamps?: {
285
+ table: string;
286
+ idColumn: string;
287
+ at?: string | undefined;
288
+ by?: string | undefined;
289
+ entityType: string;
290
+ }[] | undefined;
275
291
  ui?: {
276
292
  routes?: {
277
293
  path: string;
@@ -1 +1 @@
1
- {"version":3,"file":"vertical-events-suite.d.ts","sourceRoot":"","sources":["../src/vertical-events-suite.ts"],"names":[],"mappings":"AACA,OAAO,EASL,CAAC,EAED,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAGtB,KAAK,OAAO,EACZ,KAAK,QAAQ,EACd,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAOL,KAAK,kBAAkB,EAEvB,KAAK,kBAAkB,EAGvB,KAAK,SAAS,EAEf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAA4D,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAevH,eAAO,MAAM,YAAY,aAAa,CAAC;AACvC,eAAO,MAAM,cAAc,eAAe,CAAC;AAI3C,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiC/B,CAAC;AAiFH,eAAO,MAAM,YAAY,EAAE,kBAuC1B,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BjC,CAAC;AAgDH,eAAO,MAAM,cAAc,EAAE,kBA0D5B,CAAC;AAIF;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAC;IACnD;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF;;;;OAIG;IACH,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACrG;;;OAGG;IACH,IAAI,CAAC,EAAE,CACL,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,OAAO,KACb,OAAO,CAAC,SAAS;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAA;KAAE,EAAE,CAAC,CAAC;IACtG;;;;OAIG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;CACpD;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,GAChD,IAAI,CA24CN"}
1
+ {"version":3,"file":"vertical-events-suite.d.ts","sourceRoot":"","sources":["../src/vertical-events-suite.ts"],"names":[],"mappings":"AACA,OAAO,EASL,CAAC,EAED,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAGtB,KAAK,OAAO,EACZ,KAAK,QAAQ,EACd,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAOL,KAAK,kBAAkB,EAEvB,KAAK,kBAAkB,EAGvB,KAAK,SAAS,EAEf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAA4D,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAevH,eAAO,MAAM,YAAY,aAAa,CAAC;AACvC,eAAO,MAAM,cAAc,eAAe,CAAC;AAI3C,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiC/B,CAAC;AAiFH,eAAO,MAAM,YAAY,EAAE,kBAuC1B,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCjC,CAAC;AAgDH,eAAO,MAAM,cAAc,EAAE,kBAmE5B,CAAC;AAIF;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAC;IACnD;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF;;;;OAIG;IACH,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACrG;;;OAGG;IACH,IAAI,CAAC,EAAE,CACL,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,OAAO,KACb,OAAO,CAAC,SAAS;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAA;KAAE,EAAE,CAAC,CAAC;IACtG;;;;OAIG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;CACpD;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,GAChD,IAAI,CA65CN"}
@@ -194,6 +194,8 @@ export const boardImportModManifest = moduleManifest.parse({
194
194
  peers: [{ vertical: CRM_VERTICAL, operations: [], permissions: ['association:sync', 'association:read'] }],
195
195
  migrations: { journalDir: './migrations', compatibleFrom: '1.0.0' },
196
196
  attachmentTargets: [],
197
+ // #2166: an association holds its updated stamps, so an IMPORT handler is held to them too.
198
+ updatedStamps: [{ entityType: 'association', table: 'board_associations', idColumn: 'crm_id', at: 'updated_at', by: 'updated_by' }],
197
199
  entitlementKey: 'board-import',
198
200
  });
199
201
  const customerCreated = z.object({ id: z.string(), name: z.string() });
@@ -228,7 +230,7 @@ export const boardImportMod = {
228
230
  {
229
231
  version: '0001-init',
230
232
  sql: `
231
- CREATE TABLE board_associations (crm_id TEXT PRIMARY KEY, name TEXT NOT NULL);
233
+ CREATE TABLE board_associations (crm_id TEXT PRIMARY KEY, name TEXT NOT NULL, updated_at TEXT, updated_by TEXT);
232
234
  CREATE TABLE board_noted (event_id TEXT PRIMARY KEY);
233
235
  `,
234
236
  },
@@ -251,8 +253,11 @@ export const boardImportMod = {
251
253
  // The name is quoted, so a handler's own sentence can carry a subject's id (#1757).
252
254
  if (c.name.startsWith('poison'))
253
255
  throw new Error(`board refuses the poison customer ${c.name}`);
254
- ctx.sql.exec(`INSERT INTO board_associations (crm_id, name) VALUES (?, ?)
255
- ON CONFLICT (crm_id) DO UPDATE SET name = excluded.name`, [c.id, c.name]);
256
+ // #2166: a 'stale' customer is written with a time that is not this delivery's.
257
+ const at = c.name.startsWith('stale') ? '2000-01-01T00:00:00.000Z' : ctx.now();
258
+ ctx.sql.exec(`INSERT INTO board_associations (crm_id, name, updated_at, updated_by) VALUES (?, ?, ?, ?)
259
+ ON CONFLICT (crm_id) DO UPDATE SET name = excluded.name, updated_at = excluded.updated_at,
260
+ updated_by = excluded.updated_by`, [c.id, c.name, at, String(ctx.principal)]);
256
261
  ctx.emit({
257
262
  type: 'board.association-created',
258
263
  schemaVersion: 1,
@@ -268,6 +273,12 @@ export const boardImportMod = {
268
273
  'crm.customer-touched': async (ctx, event) => {
269
274
  assertAllowed(await ctx.check(key('association:sync')));
270
275
  const { id } = z.object({ id: z.string() }).parse(event.payload);
276
+ // #2166: an event about an association is a write to it, so it moves the stamps.
277
+ ctx.sql.exec('UPDATE board_associations SET updated_at = ?, updated_by = ? WHERE crm_id = ?', [
278
+ ctx.now(),
279
+ String(ctx.principal),
280
+ id,
281
+ ]);
271
282
  ctx.emit({
272
283
  type: 'board.association-linked',
273
284
  schemaVersion: 1,
@@ -582,6 +593,22 @@ export function verticalEventsContractSuite(adapterName, makeFixture) {
582
593
  expect(refused).toBeTruthy();
583
594
  expect(poison).toBeTruthy();
584
595
  });
596
+ it('an import handler is held to the updated stamps (#2166): a stale one is dead-lettered and rolls back', async () => {
597
+ const t = await newTenant();
598
+ const p = await install(t, CRM_VERTICAL);
599
+ const c = await install(t, BOARD_VERTICAL);
600
+ await create(t, p, 'stale customer');
601
+ await create(t, p, 'Fresh customer');
602
+ const { report } = await sweep();
603
+ expect(into(report, c)).toMatchObject({ state: 'delivered', delivered: 1, deadLettered: 1 });
604
+ const view = await board(t, c);
605
+ expect(view.associations.map((r) => r.name)).toEqual(['Fresh customer']);
606
+ const refused = view.deliveries.filter((d) => d.error?.includes("'updated_at' does not hold this call's time"));
607
+ expect(refused).toHaveLength(1);
608
+ expect(refused[0]?.error).toContain('consuming crm.customer-created');
609
+ // Its event rolled back with its row.
610
+ expect(view.outbox.filter((o) => o.type === 'board.association-created')).toHaveLength(1);
611
+ });
585
612
  it('at-least-once across the edge: a batch delivered twice runs each handler once', async () => {
586
613
  const t = await newTenant();
587
614
  const p = await install(t, CRM_VERTICAL);