@substrat-run/contract-tests 0.118.0 → 0.120.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capability-attachment-suite.d.ts +3 -0
- package/dist/capability-attachment-suite.d.ts.map +1 -0
- package/dist/capability-attachment-suite.js +284 -0
- package/dist/capability-attachment-suite.js.map +1 -0
- package/dist/capability-expiry-suite.d.ts.map +1 -1
- package/dist/capability-expiry-suite.js +40 -1
- package/dist/capability-expiry-suite.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/modules.d.ts +400 -0
- package/dist/modules.d.ts.map +1 -1
- package/dist/modules.js +94 -1
- package/dist/modules.js.map +1 -1
- package/dist/peer-suite.d.ts +8 -0
- package/dist/peer-suite.d.ts.map +1 -0
- package/dist/peer-suite.js +357 -0
- package/dist/peer-suite.js.map +1 -0
- package/dist/scope-host-suite.d.ts.map +1 -1
- package/dist/scope-host-suite.js +97 -1
- package/dist/scope-host-suite.js.map +1 -1
- package/dist/system-switch-suite.d.ts.map +1 -1
- package/dist/system-switch-suite.js +57 -1
- package/dist/system-switch-suite.js.map +1 -1
- package/dist/vertical-events-suite.d.ts +282 -0
- package/dist/vertical-events-suite.d.ts.map +1 -0
- package/dist/vertical-events-suite.js +584 -0
- package/dist/vertical-events-suite.js.map +1 -0
- package/package.json +3 -3
|
@@ -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,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,
|
|
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,CAw6NN"}
|
package/dist/scope-host-suite.js
CHANGED
|
@@ -4424,6 +4424,54 @@ export function scopeHostContractSuite(adapterName, makeFixture, opts = {}) {
|
|
|
4424
4424
|
await host.admin.promoteVersion(staff, 'tenant-a/crm', 'prod', v3);
|
|
4425
4425
|
expect((await host.admin.getScopeRecord(staff, t2, appScope))?.verticalVersionId).toBe(v3);
|
|
4426
4426
|
});
|
|
4427
|
+
it('embedded prod promotion leaves previews and real forks intact beside advancing installs (#1724)', async () => {
|
|
4428
|
+
const slug = 'tenant-a/preview-prevention';
|
|
4429
|
+
await host.admin.registerVertical(staff, { slug, name: 'Preview prevention', source: 'cli', ownerTenant: t2 });
|
|
4430
|
+
const first = await publishPrivate(slug, '1.0.0');
|
|
4431
|
+
const next = await publishPrivate(slug, '1.0.1');
|
|
4432
|
+
const make = async (kind, tenant = t2, active = true) => {
|
|
4433
|
+
const id = scopeId.parse(ulid());
|
|
4434
|
+
await host.provisionScope(staff, { tenantId: tenant, scopeId: id, vertical: slug, kind });
|
|
4435
|
+
if (active)
|
|
4436
|
+
await host.admin.activateScope(staff, tenant, id);
|
|
4437
|
+
await host.admin.bindScopeVersion(staff, tenant, id, first);
|
|
4438
|
+
return id;
|
|
4439
|
+
};
|
|
4440
|
+
const install = await make('production');
|
|
4441
|
+
const clean = await make('preview');
|
|
4442
|
+
const pinned = await make('preview');
|
|
4443
|
+
await host.admin.setScopeServingRef(staff, t2, pinned, 'historical-serving-script');
|
|
4444
|
+
const suspended = await make('production');
|
|
4445
|
+
await host.admin.suspendScope(staff, t2, suspended);
|
|
4446
|
+
const provisioning = await make('production', t2, false);
|
|
4447
|
+
const foreign = await make('production', t1);
|
|
4448
|
+
await (await host.getScope(alice, t2, clean)).invoke('test/write-marker', { v: 'preview-data' });
|
|
4449
|
+
// An actual snapshot copies data and records forkedFrom, rather than naming a fork in the title.
|
|
4450
|
+
const fork = await host.snapshotScope(staff, t2, clean);
|
|
4451
|
+
expect((await host.admin.getScopeRecord(staff, t2, fork))?.forkedFrom).toBe(clean);
|
|
4452
|
+
const frozen = [clean, pinned, fork, suspended, provisioning];
|
|
4453
|
+
const before = await Promise.all(frozen.map((id) => host.admin.getScopeRecord(staff, t2, id)));
|
|
4454
|
+
const dataBefore = await host.admin.readScopeTable(staff, t2, clean, { table: 'marker', limit: 200, offset: 0 });
|
|
4455
|
+
const hostname = `preview-${clean.toLowerCase()}.example.test`;
|
|
4456
|
+
await host.admin.bindHostname(staff, { hostname, tenantId: t2, scopeId: clean, surface: 'app', region: null, canonical: false });
|
|
4457
|
+
await host.admin.setHostnameStatus(staff, hostname, 'active');
|
|
4458
|
+
const routeBefore = await host.admin.resolveHostname(hostname);
|
|
4459
|
+
expect(routeBefore).toMatchObject({ scopeId: clean, deploymentRef: null });
|
|
4460
|
+
for (let retry = 0; retry < 2; retry++) {
|
|
4461
|
+
await host.admin.promoteVersion(staff, slug, 'prod', next);
|
|
4462
|
+
expect((await host.admin.getScopeRecord(staff, t2, install))?.verticalVersionId).toBe(next);
|
|
4463
|
+
expect(await Promise.all(frozen.map((id) => host.admin.getScopeRecord(staff, t2, id)))).toEqual(before);
|
|
4464
|
+
expect((await host.admin.getScopeRecord(staff, t1, foreign))?.verticalVersionId).toBe(first);
|
|
4465
|
+
expect(await host.admin.resolveHostname(hostname)).toEqual(routeBefore);
|
|
4466
|
+
expect(await host.admin.readScopeTable(staff, t2, clean, { table: 'marker', limit: 200, offset: 0 })).toEqual(dataBefore);
|
|
4467
|
+
expect(await host.admin.readScopeTable(staff, t2, fork, { table: 'marker', limit: 200, offset: 0 })).toEqual(dataBefore);
|
|
4468
|
+
}
|
|
4469
|
+
// Listing retains the existing opt-in update behavior for ordinary installs too.
|
|
4470
|
+
await host.admin.admitVersion(staff, next);
|
|
4471
|
+
await host.admin.setVerticalListed(staff, slug, true);
|
|
4472
|
+
await host.admin.promoteVersion(staff, slug, 'prod', first);
|
|
4473
|
+
expect((await host.admin.getScopeRecord(staff, t2, install))?.verticalVersionId).toBe(next);
|
|
4474
|
+
});
|
|
4427
4475
|
it('publish refuses an auto-admitted prod version until a staff admit vouches for it', async () => {
|
|
4428
4476
|
// Listing is the moment OTHER tenants start trusting this code, so the version
|
|
4429
4477
|
// they would install needs a recorded human decision — the auto-admission note
|
|
@@ -4491,6 +4539,53 @@ export function scopeHostContractSuite(adapterName, makeFixture, opts = {}) {
|
|
|
4491
4539
|
// -- the hostname map (K-26) ---------------------------------------------
|
|
4492
4540
|
// §4.2 provisions a scope; this is what finally gives it a URL. The router
|
|
4493
4541
|
// resolves against these rows before dispatching to a vertical's worker.
|
|
4542
|
+
it('gates hostname routing on scope and tenant lifecycle without changing bindings (#1713)', async () => {
|
|
4543
|
+
const tenant = tenantId.parse(ulid());
|
|
4544
|
+
const otherTenant = tenantId.parse(ulid());
|
|
4545
|
+
const target = scopeId.parse(ulid());
|
|
4546
|
+
const sibling = scopeId.parse(ulid());
|
|
4547
|
+
const other = scopeId.parse(ulid());
|
|
4548
|
+
for (const id of [tenant, otherTenant]) {
|
|
4549
|
+
await host.admin.createTenant(staff, { id, slug: `route-${id.toLowerCase()}`, name: 'Routing' });
|
|
4550
|
+
}
|
|
4551
|
+
for (const [t, sc] of [[tenant, target], [tenant, sibling], [otherTenant, other]]) {
|
|
4552
|
+
await host.provisionScope(staff, { tenantId: t, scopeId: sc });
|
|
4553
|
+
await host.admin.bindHostname(staff, {
|
|
4554
|
+
hostname: `${sc.toLowerCase()}.example.com`, tenantId: t, scopeId: sc,
|
|
4555
|
+
surface: 'app', region: null, canonical: true,
|
|
4556
|
+
});
|
|
4557
|
+
await host.admin.setHostnameStatus(staff, `${sc.toLowerCase()}.example.com`, 'active');
|
|
4558
|
+
}
|
|
4559
|
+
const resolve = (sc) => host.admin.resolveHostname(`${sc.toLowerCase()}.example.com`);
|
|
4560
|
+
const binding = () => host.admin.listHostnames(staff, { scopeId: target });
|
|
4561
|
+
const original = await binding();
|
|
4562
|
+
expect(original[0]?.status).toBe('active');
|
|
4563
|
+
expect(await resolve(target)).toBeUndefined(); // provisioning, despite active binding
|
|
4564
|
+
for (const [t, sc] of [[tenant, target], [tenant, sibling], [otherTenant, other]]) {
|
|
4565
|
+
await host.admin.activateScope(staff, t, sc);
|
|
4566
|
+
}
|
|
4567
|
+
const active = await resolve(target);
|
|
4568
|
+
expect(active).toMatchObject({ tenantId: tenant, scopeId: target, deploymentRef: null });
|
|
4569
|
+
await host.admin.suspendScope(staff, tenant, target);
|
|
4570
|
+
expect(await resolve(target)).toBeUndefined();
|
|
4571
|
+
expect(await resolve(sibling)).toBeDefined();
|
|
4572
|
+
expect(await resolve(other)).toBeDefined();
|
|
4573
|
+
expect(await binding()).toEqual(original);
|
|
4574
|
+
await host.admin.unsuspendScope(staff, tenant, target);
|
|
4575
|
+
expect(await resolve(target)).toEqual(active);
|
|
4576
|
+
for (const status of ['suspended', 'deleting']) {
|
|
4577
|
+
await host.admin.setTenantStatus(staff, tenant, status);
|
|
4578
|
+
expect(await resolve(target)).toBeUndefined();
|
|
4579
|
+
expect(await resolve(sibling)).toBeUndefined();
|
|
4580
|
+
expect(await resolve(other)).toBeDefined();
|
|
4581
|
+
expect(await binding()).toEqual(original);
|
|
4582
|
+
await host.admin.setTenantStatus(staff, tenant, 'active');
|
|
4583
|
+
expect(await resolve(target)).toEqual(active);
|
|
4584
|
+
}
|
|
4585
|
+
await host.admin.archiveScope(staff, tenant, target);
|
|
4586
|
+
expect(await resolve(target)).toBeUndefined();
|
|
4587
|
+
expect(await binding()).toEqual(original);
|
|
4588
|
+
});
|
|
4494
4589
|
it('binds a hostname as pending, and resolves nothing until it is active', async () => {
|
|
4495
4590
|
await host.admin.bindHostname(staff, {
|
|
4496
4591
|
hostname: 'acme.example.com',
|
|
@@ -4516,7 +4611,7 @@ export function scopeHostContractSuite(adapterName, makeFixture, opts = {}) {
|
|
|
4516
4611
|
// A hostname routes to exactly one place, so a rebind that would move another
|
|
4517
4612
|
// scope's traffic is refused — unless the holder is archived or reaped, which is
|
|
4518
4613
|
// how a deleted app's name becomes claimable again. Both adapters read the
|
|
4519
|
-
// holder's scope status to decide
|
|
4614
|
+
// holder's scope status to decide; routing independently requires active lifecycle.
|
|
4520
4615
|
const holder = scopeId.parse(ulid());
|
|
4521
4616
|
const claimant = scopeId.parse(ulid());
|
|
4522
4617
|
for (const sc of [holder, claimant]) {
|
|
@@ -4547,6 +4642,7 @@ export function scopeHostContractSuite(adapterName, makeFixture, opts = {}) {
|
|
|
4547
4642
|
// the row, then the read-back 400d at the Zod boundary and the app got no URL.
|
|
4548
4643
|
const sc = scopeId.parse(ulid());
|
|
4549
4644
|
await host.provisionScope(staff, { tenantId: t1, scopeId: sc, vertical: 't-acme/crm', jurisdiction: 'eu' });
|
|
4645
|
+
await host.admin.activateScope(staff, t1, sc);
|
|
4550
4646
|
await host.admin.bindHostname(staff, {
|
|
4551
4647
|
hostname: 'crm-acme.global.example.com',
|
|
4552
4648
|
tenantId: t1,
|