@substrat-run/contract-tests 0.138.0 → 0.139.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attachment-text-fixtures.d.ts +17 -0
- package/dist/attachment-text-fixtures.d.ts.map +1 -1
- package/dist/attachment-text-fixtures.js +344 -4
- package/dist/attachment-text-fixtures.js.map +1 -1
- package/dist/attachment-text-suite.d.ts +16 -1
- package/dist/attachment-text-suite.d.ts.map +1 -1
- package/dist/attachment-text-suite.js +212 -28
- package/dist/attachment-text-suite.js.map +1 -1
- package/dist/caused-by-module.d.ts +23 -0
- package/dist/caused-by-module.d.ts.map +1 -0
- package/dist/caused-by-module.js +56 -0
- package/dist/caused-by-module.js.map +1 -0
- package/dist/caused-by-suite.d.ts +28 -0
- package/dist/caused-by-suite.d.ts.map +1 -0
- package/dist/caused-by-suite.js +179 -0
- package/dist/caused-by-suite.js.map +1 -0
- package/dist/entity-state-module.d.ts +134 -0
- package/dist/entity-state-module.d.ts.map +1 -0
- package/dist/entity-state-module.js +140 -0
- package/dist/entity-state-module.js.map +1 -0
- package/dist/entity-state-suite.d.ts +9 -0
- package/dist/entity-state-suite.d.ts.map +1 -0
- package/dist/entity-state-suite.js +534 -0
- package/dist/entity-state-suite.js.map +1 -0
- package/dist/findings-atomic-suite.d.ts +21 -0
- package/dist/findings-atomic-suite.d.ts.map +1 -0
- package/dist/findings-atomic-suite.js +131 -0
- package/dist/findings-atomic-suite.js.map +1 -0
- package/dist/findings-suite.d.ts +11 -0
- package/dist/findings-suite.d.ts.map +1 -0
- package/dist/findings-suite.js +391 -0
- package/dist/findings-suite.js.map +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/membership-executor-suite.d.ts +6 -2
- package/dist/membership-executor-suite.d.ts.map +1 -1
- package/dist/membership-executor-suite.js +260 -14
- package/dist/membership-executor-suite.js.map +1 -1
- package/dist/modules.d.ts +217 -0
- package/dist/modules.d.ts.map +1 -1
- package/dist/modules.js +4 -0
- package/dist/modules.js.map +1 -1
- package/dist/permission-suite.d.ts.map +1 -1
- package/dist/permission-suite.js +75 -0
- package/dist/permission-suite.js.map +1 -1
- package/dist/scope-host-suite.d.ts.map +1 -1
- package/dist/scope-host-suite.js +48 -0
- package/dist/scope-host-suite.js.map +1 -1
- package/dist/vertical-events-suite.d.ts +14 -0
- package/dist/vertical-events-suite.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -6,8 +6,9 @@ export { impersonationContractSuite } from './impersonation-suite.js';
|
|
|
6
6
|
export { capabilityContractSuite } from './capability-suite.js';
|
|
7
7
|
export { capabilityAttachmentContractSuite } from './capability-attachment-suite.js';
|
|
8
8
|
export { attachmentTextContractSuite } from './attachment-text-suite.js';
|
|
9
|
-
export {
|
|
10
|
-
export
|
|
9
|
+
export type { AttachmentTextHostFixture, AttachmentTextHostOptions } from './attachment-text-suite.js';
|
|
10
|
+
export { ATTACHMENT_TEXT_FIXTURES, DOCX_LINES, hostilePdfs } from './attachment-text-fixtures.js';
|
|
11
|
+
export type { AttachmentTextFixture, HostilePdf } from './attachment-text-fixtures.js';
|
|
11
12
|
export { capabilityExpiryContractSuite } from './capability-expiry-suite.js';
|
|
12
13
|
export type { CapabilityExpiryFixture } from './capability-expiry-suite.js';
|
|
13
14
|
export { searchContractSuite } from './search-suite.js';
|
|
@@ -19,9 +20,15 @@ export { moduleLogContractSuite } from './module-log-suite.js';
|
|
|
19
20
|
export { asyncLogContractSuite, type AsyncLogFixture } from './async-log-suite.js';
|
|
20
21
|
export { idempotencyContractSuite } from './idempotency-suite.js';
|
|
21
22
|
export { listContractSuite } from './list-suite.js';
|
|
23
|
+
export { entityStateContractSuite } from './entity-state-suite.js';
|
|
24
|
+
export { stateMod } from './entity-state-module.js';
|
|
22
25
|
export { scheduleContractSuite } from './schedule-suite.js';
|
|
23
26
|
export { inertScopeContractSuite } from './inert-scope-suite.js';
|
|
27
|
+
export { causedByContractSuite, scopeCausedByContractSuite } from './caused-by-suite.js';
|
|
28
|
+
export { causedByMod } from './caused-by-module.js';
|
|
24
29
|
export { membershipExecutorContractSuite } from './membership-executor-suite.js';
|
|
30
|
+
export { findingsContractSuite } from './findings-suite.js';
|
|
31
|
+
export { findingsAtomicContractSuite, type DirectoryExec } from './findings-atomic-suite.js';
|
|
25
32
|
export { INVITEFIX_A, membershipFixtureMod } from './membership-module.js';
|
|
26
33
|
export { scheduleEntitlementContractSuite } from './schedule-entitlement-suite.js';
|
|
27
34
|
export { jobRunContractSuite } from './job-run-suite.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,YAAY,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACrF,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,iCAAiC,EAAE,MAAM,kCAAkC,CAAC;AACrF,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { impersonationContractSuite } from './impersonation-suite.js';
|
|
|
5
5
|
export { capabilityContractSuite } from './capability-suite.js';
|
|
6
6
|
export { capabilityAttachmentContractSuite } from './capability-attachment-suite.js';
|
|
7
7
|
export { attachmentTextContractSuite } from './attachment-text-suite.js';
|
|
8
|
-
export { ATTACHMENT_TEXT_FIXTURES, DOCX_LINES } from './attachment-text-fixtures.js';
|
|
8
|
+
export { ATTACHMENT_TEXT_FIXTURES, DOCX_LINES, hostilePdfs } from './attachment-text-fixtures.js';
|
|
9
9
|
export { capabilityExpiryContractSuite } from './capability-expiry-suite.js';
|
|
10
10
|
export { searchContractSuite } from './search-suite.js';
|
|
11
11
|
export { entityVersionContractSuite } from './entity-version-suite.js';
|
|
@@ -16,11 +16,17 @@ export { moduleLogContractSuite } from './module-log-suite.js';
|
|
|
16
16
|
export { asyncLogContractSuite } from './async-log-suite.js';
|
|
17
17
|
export { idempotencyContractSuite } from './idempotency-suite.js';
|
|
18
18
|
export { listContractSuite } from './list-suite.js';
|
|
19
|
+
export { entityStateContractSuite } from './entity-state-suite.js';
|
|
20
|
+
export { stateMod } from './entity-state-module.js';
|
|
19
21
|
export { scheduleContractSuite } from './schedule-suite.js';
|
|
20
22
|
// #2005: forks and previews cause no outbound effects — the in-scope doors.
|
|
21
23
|
export { inertScopeContractSuite } from './inert-scope-suite.js';
|
|
24
|
+
export { causedByContractSuite, scopeCausedByContractSuite } from './caused-by-suite.js';
|
|
25
|
+
export { causedByMod } from './caused-by-module.js';
|
|
22
26
|
// #1184: the membership executor — accept, redelivery, rollback, and the authority bound.
|
|
23
27
|
export { membershipExecutorContractSuite } from './membership-executor-suite.js';
|
|
28
|
+
export { findingsContractSuite } from './findings-suite.js';
|
|
29
|
+
export { findingsAtomicContractSuite } from './findings-atomic-suite.js';
|
|
24
30
|
export { INVITEFIX_A, membershipFixtureMod } from './membership-module.js';
|
|
25
31
|
export { scheduleEntitlementContractSuite } from './schedule-entitlement-suite.js';
|
|
26
32
|
export { jobRunContractSuite } from './job-run-suite.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,iCAAiC,EAAE,MAAM,kCAAkC,CAAC;AACrF,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { ScopeHostFixture } from './scope-host-suite.js';
|
|
2
2
|
/**
|
|
3
3
|
* The membership executor (#1184, K-22 §4.2) on every adapter: an accepted invite assigns the
|
|
4
|
-
* invited TENANT role — and joins no org — with the admin trail correlated, a
|
|
5
|
-
* nothing, a rolled-back accept effects nothing, and an invite for authority its sender does not
|
|
4
|
+
* invited TENANT role — and, by default, joins no org — with the admin trail correlated, a
|
|
5
|
+
* redelivery changes nothing, a rolled-back accept effects nothing, and an invite for authority its sender does not
|
|
6
6
|
* hold — or no longer holds, demoted or removed, before the add or while it is held in front of
|
|
7
7
|
* its directory unit — is refused for good, where both the accepting call and an admin can see
|
|
8
8
|
* it. Every tenant-level removal, staff's and no-op ones included, fences a pending add. Both
|
|
9
9
|
* delivery paths are held: the inline tail of the accept, and `drainDue` as backstop.
|
|
10
10
|
*
|
|
11
|
+
* Mounted with `orgs: 'join'` (#2047), an add also joins the org and a removal leaves it, bounded
|
|
12
|
+
* inside the same unit by the inviter's or remover's own live membership of that org: a scope-level
|
|
13
|
+
* org grant reaches nobody through a non-member, and a join expires no later than the inviter's.
|
|
14
|
+
*
|
|
11
15
|
* The fixture's checker must be a REAL one: the bound is a permission-set comparison, and an
|
|
12
16
|
* allow-all checker would cover everything and prove nothing.
|
|
13
17
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"membership-executor-suite.d.ts","sourceRoot":"","sources":["../src/membership-executor-suite.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"membership-executor-suite.d.ts","sourceRoot":"","sources":["../src/membership-executor-suite.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,+BAA+B,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAw6BvH"}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
-
import { orgId, platformActorId, principalId, scopeId, tenantId, } from '@substrat-run/contracts';
|
|
2
|
+
import { instant, orgId, platformActorId, principalId, scopeId, tenantId, } from '@substrat-run/contracts';
|
|
3
3
|
import { MEMBERSHIP_EXECUTOR_ID, registerMembershipExecutor, ulid, } from '@substrat-run/kernel';
|
|
4
4
|
import { INVITEFIX_A, INVITEFIX_B, membershipFixtureMod } from './membership-module.js';
|
|
5
5
|
/**
|
|
6
6
|
* The membership executor (#1184, K-22 §4.2) on every adapter: an accepted invite assigns the
|
|
7
|
-
* invited TENANT role — and joins no org — with the admin trail correlated, a
|
|
8
|
-
* nothing, a rolled-back accept effects nothing, and an invite for authority its sender does not
|
|
7
|
+
* invited TENANT role — and, by default, joins no org — with the admin trail correlated, a
|
|
8
|
+
* redelivery changes nothing, a rolled-back accept effects nothing, and an invite for authority its sender does not
|
|
9
9
|
* hold — or no longer holds, demoted or removed, before the add or while it is held in front of
|
|
10
10
|
* its directory unit — is refused for good, where both the accepting call and an admin can see
|
|
11
11
|
* it. Every tenant-level removal, staff's and no-op ones included, fences a pending add. Both
|
|
12
12
|
* delivery paths are held: the inline tail of the accept, and `drainDue` as backstop.
|
|
13
13
|
*
|
|
14
|
+
* Mounted with `orgs: 'join'` (#2047), an add also joins the org and a removal leaves it, bounded
|
|
15
|
+
* inside the same unit by the inviter's or remover's own live membership of that org: a scope-level
|
|
16
|
+
* org grant reaches nobody through a non-member, and a join expires no later than the inviter's.
|
|
17
|
+
*
|
|
14
18
|
* The fixture's checker must be a REAL one: the bound is a permission-set comparison, and an
|
|
15
19
|
* allow-all checker would cover everything and prove nothing.
|
|
16
20
|
*/
|
|
@@ -40,7 +44,7 @@ export function membershipExecutorContractSuite(adapterName, makeFixture) {
|
|
|
40
44
|
w.host = w.fixture.host;
|
|
41
45
|
w.host.registerModule(membershipFixtureMod);
|
|
42
46
|
// `baseDelayMs: 0` so the backstop case can retry inside a test.
|
|
43
|
-
registerMembershipExecutor(intercepting(w.host, w, opts.hold), { actor: staff, retry: { baseDelayMs: 0 } });
|
|
47
|
+
registerMembershipExecutor(intercepting(w.host, w, opts.hold), { actor: staff, retry: { baseDelayMs: 0 }, orgs: opts.orgs });
|
|
44
48
|
const { host, t } = w;
|
|
45
49
|
await host.admin.createTenant(staff, { id: t, slug: `invitefix-${t.slice(-10).toLowerCase()}`, name: 'Invitefix' });
|
|
46
50
|
await host.admin.grantEntitlement(staff, t, 'invitefix');
|
|
@@ -59,6 +63,23 @@ export function membershipExecutorContractSuite(adapterName, makeFixture) {
|
|
|
59
63
|
});
|
|
60
64
|
return w;
|
|
61
65
|
};
|
|
66
|
+
/**
|
|
67
|
+
* A gate every directory unit waits at while shut — the gap a concurrent removal or change of
|
|
68
|
+
* authority races into. Open, a unit passes straight through.
|
|
69
|
+
*/
|
|
70
|
+
const gated = () => {
|
|
71
|
+
const g = {
|
|
72
|
+
release: () => undefined,
|
|
73
|
+
gate: Promise.resolve(),
|
|
74
|
+
shut: () => {
|
|
75
|
+
g.gate = new Promise((r) => (g.release = r));
|
|
76
|
+
},
|
|
77
|
+
hold: () => g.gate,
|
|
78
|
+
/** Long enough for an invoke to reach the hold, and for a queued call to reach its own. */
|
|
79
|
+
settle: () => new Promise((r) => setTimeout(r, 50)),
|
|
80
|
+
};
|
|
81
|
+
return g;
|
|
82
|
+
};
|
|
62
83
|
/**
|
|
63
84
|
* `host`, except that an add's `applyMembership` fails while `w.failNextAdds` counts down,
|
|
64
85
|
* and every unit, an add's or a removal's, otherwise waits for `hold` before it runs.
|
|
@@ -79,7 +100,7 @@ export function membershipExecutorContractSuite(adapterName, makeFixture) {
|
|
|
79
100
|
return new Proxy(host, {
|
|
80
101
|
get: (t, key) => {
|
|
81
102
|
if (key === 'attributed')
|
|
82
|
-
return (
|
|
103
|
+
return (...a) => ({ admin: held(t.attributed(...a).admin) });
|
|
83
104
|
const v = Reflect.get(t, key);
|
|
84
105
|
return typeof v === 'function' ? v.bind(t) : v;
|
|
85
106
|
},
|
|
@@ -181,7 +202,7 @@ export function membershipExecutorContractSuite(adapterName, makeFixture) {
|
|
|
181
202
|
it('an add confers no org membership — whatever the org it names holds, at the tenant or in a scope', async () => {
|
|
182
203
|
// The org grants invitefix:b tenant-wide AND in one scope; bob holds only invitefix:a.
|
|
183
204
|
// Joining it would confer what bob cannot, and the scope grant lives where no directory
|
|
184
|
-
// unit can bound it — so the executor joins nobody to any org.
|
|
205
|
+
// unit can bound it — so the executor, mounted role-only, joins nobody to any org.
|
|
185
206
|
const strong = orgId.parse(ulid());
|
|
186
207
|
await w.host.admin.createOrg(staff, { id: strong, tenantId: w.t, slug: `strong-${strong.slice(-6).toLowerCase()}`, name: 'Strong' });
|
|
187
208
|
await w.host.admin.grantToOrg(staff, strong, INVITEFIX_B, tenantNode(w));
|
|
@@ -428,14 +449,10 @@ export function membershipExecutorContractSuite(adapterName, makeFixture) {
|
|
|
428
449
|
});
|
|
429
450
|
describe(`membership executor (#1184), an add held in front of its directory unit: ${adapterName}`, () => {
|
|
430
451
|
// Every add waits here, after the executor's own checks and before its directory unit.
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const
|
|
434
|
-
const
|
|
435
|
-
gate = new Promise((r) => (release = r));
|
|
436
|
-
};
|
|
437
|
-
/** Long enough for an invoke to reach the hold, and for a queued call to reach its own. */
|
|
438
|
-
const settle = () => new Promise((r) => setTimeout(r, 50));
|
|
452
|
+
const g = gated();
|
|
453
|
+
const w = world({ hold: g.hold });
|
|
454
|
+
const { shut, settle } = g;
|
|
455
|
+
const release = () => g.release();
|
|
439
456
|
it('a removal landing while an add is held cannot leave the access restored', async () => {
|
|
440
457
|
const joe = principalId.parse(ulid());
|
|
441
458
|
const node = tenantNode(w);
|
|
@@ -475,6 +492,27 @@ export function membershipExecutorContractSuite(adapterName, makeFixture) {
|
|
|
475
492
|
expect((await accepting).map((o) => o.outcome)).toEqual(['delivered']);
|
|
476
493
|
expect(await holds(w, joe, INVITEFIX_A)).toBe(true);
|
|
477
494
|
});
|
|
495
|
+
it('a staff call made while an add is held carries no cause, and the add\'s own row still carries its event (#2055)', async () => {
|
|
496
|
+
// The causedBy is the event's, bound to the handler's writes. A host-wide field set around
|
|
497
|
+
// the handler's await stamped this staff removal, which has nothing to do with the event.
|
|
498
|
+
const joe = principalId.parse(ulid());
|
|
499
|
+
const kim = principalId.parse(ulid());
|
|
500
|
+
const node = tenantNode(w);
|
|
501
|
+
await w.host.admin.assignRole(staff, { principalId: kim, roleKey: 'member', node });
|
|
502
|
+
const inv = await send(w, w.alice, 'member');
|
|
503
|
+
shut();
|
|
504
|
+
const accepting = asJoiner(w, joe, 'invitefix/accept', inv);
|
|
505
|
+
await settle();
|
|
506
|
+
await w.host.admin.unassignRole(staff, { principalId: kim, roleKey: 'member', node });
|
|
507
|
+
release();
|
|
508
|
+
const outcomes = await accepting;
|
|
509
|
+
expect(outcomes.map((o) => o.outcome)).toEqual(['delivered']);
|
|
510
|
+
const staffRows = await auditedFor(w, 'unassignRole', kim);
|
|
511
|
+
expect(staffRows.map((r) => r.causedBy)).toEqual([null]);
|
|
512
|
+
const rows = await causedBy(w, outcomes[0].eventId);
|
|
513
|
+
expect(rows.map((r) => r.action)).toEqual(['assignRole']);
|
|
514
|
+
expect(rows[0].onBehalfOf).toMatchObject({ principal: w.alice });
|
|
515
|
+
});
|
|
478
516
|
it('a removal and a held add released in the same tick: the revoke and its fence are one unit, so the access never survives', async () => {
|
|
479
517
|
// The removal's revoke goes out first and the add's unit right behind it. Were the revoke,
|
|
480
518
|
// the audit row and the fence separate directory calls, the add would land between them:
|
|
@@ -616,5 +654,213 @@ export function membershipExecutorContractSuite(adapterName, makeFixture) {
|
|
|
616
654
|
});
|
|
617
655
|
});
|
|
618
656
|
});
|
|
657
|
+
describe(`membership executor (#2047), org joins opted into: ${adapterName}`, () => {
|
|
658
|
+
const g = gated();
|
|
659
|
+
const w = world({ orgs: 'join', hold: g.hold });
|
|
660
|
+
const { shut, settle } = g;
|
|
661
|
+
const release = () => g.release();
|
|
662
|
+
/** An org of its own per test, granting `invitefix:b` ONLY in scope s2 — a grant no directory unit sees. */
|
|
663
|
+
const scopeOnlyOrg = async () => {
|
|
664
|
+
const org = orgId.parse(ulid());
|
|
665
|
+
await w.host.admin.createOrg(staff, { id: org, tenantId: w.t, slug: `org-${org.slice(-8).toLowerCase()}`, name: 'Org' });
|
|
666
|
+
await w.host.admin.grantToOrg(staff, org, INVITEFIX_B, { tenantId: w.t, scopeId: w.s2 });
|
|
667
|
+
return org;
|
|
668
|
+
};
|
|
669
|
+
/** A lead tenant-wide — the role is never what refuses in these tests — optionally a member of `org`. */
|
|
670
|
+
const lead = async (org, expiresAt) => {
|
|
671
|
+
const who = principalId.parse(ulid());
|
|
672
|
+
await w.host.admin.assignRole(staff, { principalId: who, roleKey: 'lead', node: tenantNode(w) });
|
|
673
|
+
if (org)
|
|
674
|
+
await w.host.admin.addMember(staff, w.t, who, org, expiresAt ? { expiresAt: instant.parse(expiresAt) } : undefined);
|
|
675
|
+
return who;
|
|
676
|
+
};
|
|
677
|
+
const membership = async (who, org) => (await w.host.admin.listMembers(staff, w.t, org)).find((m) => m.principal === who);
|
|
678
|
+
/** Refused for good, and nothing of it written: no role, no membership, no reach into s2, no trail. */
|
|
679
|
+
const expectNothing = async (outcomes, who, org, reason) => {
|
|
680
|
+
expect(outcomes.map((o) => o.outcome)).toEqual(['refused']);
|
|
681
|
+
expect(outcomes[0].error).toMatch(reason);
|
|
682
|
+
expect(await memberOf(w, who, org)).toBe(0);
|
|
683
|
+
expect(await holds(w, who, INVITEFIX_A)).toBe(false);
|
|
684
|
+
expect(await holds(w, who, INVITEFIX_B, w.s2)).toBe(false);
|
|
685
|
+
// No write of this add's: neither the role's row nor the membership's.
|
|
686
|
+
const written = (await causedBy(w, outcomes[0].eventId)).filter((r) => r.action === 'assignRole' || r.action === 'addMember');
|
|
687
|
+
expect(written).toEqual([]);
|
|
688
|
+
};
|
|
689
|
+
it('a member of the org joins someone to it: the role and the membership in one unit, and the org reaches them in every scope', async () => {
|
|
690
|
+
const org = await scopeOnlyOrg();
|
|
691
|
+
const alice = await lead(org);
|
|
692
|
+
const joe = principalId.parse(ulid());
|
|
693
|
+
const outcomes = await asJoiner(w, joe, 'invitefix/accept', await send(w, alice, 'member', org));
|
|
694
|
+
expect(outcomes.map((o) => o.outcome)).toEqual(['delivered']);
|
|
695
|
+
expect(await memberOf(w, joe, org)).toBe(1);
|
|
696
|
+
expect((await membership(joe, org))?.expiresAt).toBeNull();
|
|
697
|
+
expect(await holds(w, joe, INVITEFIX_A)).toBe(true);
|
|
698
|
+
// What the org confers, in the one scope it confers it in, and nowhere else.
|
|
699
|
+
expect(await holds(w, joe, INVITEFIX_B, w.s2)).toBe(true);
|
|
700
|
+
expect(await holds(w, joe, INVITEFIX_B)).toBe(false);
|
|
701
|
+
const rows = await causedBy(w, outcomes[0].eventId);
|
|
702
|
+
expect(rows.map((r) => r.action)).toEqual(['assignRole', 'addMember']);
|
|
703
|
+
for (const row of rows)
|
|
704
|
+
expect(row.onBehalfOf).toMatchObject({ principal: alice, tenantId: w.t });
|
|
705
|
+
});
|
|
706
|
+
it('an inviter who is not a member of the org is refused — the role, too — however much tenant authority they hold', async () => {
|
|
707
|
+
const org = await scopeOnlyOrg();
|
|
708
|
+
// A lead tenant-wide, who even holds invitefix:b in s2 directly: everything the org confers.
|
|
709
|
+
const admin = await lead();
|
|
710
|
+
await w.host.admin.grant(staff, { principalId: admin, permission: INVITEFIX_B, node: { tenantId: w.t, scopeId: w.s2 }, grantedBy: w.alice });
|
|
711
|
+
const joe = principalId.parse(ulid());
|
|
712
|
+
const outcomes = await asJoiner(w, joe, 'invitefix/accept', await send(w, admin, 'member', org));
|
|
713
|
+
await expectNothing(outcomes, joe, org, /inviter .* is not a member of org /);
|
|
714
|
+
expect((await w.host.executorDeadLetters(w.t, w.s)).find((d) => d.eventId === outcomes[0].eventId)?.error).toMatch(/^refused: /);
|
|
715
|
+
});
|
|
716
|
+
it('a scope-level org grant cannot be laundered through another org that holds the same grant', async () => {
|
|
717
|
+
// alice is a member of `mine`, which confers exactly what `theirs` does — at the same scope.
|
|
718
|
+
const mine = await scopeOnlyOrg();
|
|
719
|
+
const theirs = await scopeOnlyOrg();
|
|
720
|
+
const alice = await lead(mine);
|
|
721
|
+
const joe = principalId.parse(ulid());
|
|
722
|
+
await expectNothing(await asJoiner(w, joe, 'invitefix/accept', await send(w, alice, 'member', theirs)), joe, theirs, /is not a member of org /);
|
|
723
|
+
// Twin: the org she IS a member of, she can join someone to.
|
|
724
|
+
const kim = principalId.parse(ulid());
|
|
725
|
+
expect((await asJoiner(w, kim, 'invitefix/accept', await send(w, alice, 'member', mine))).map((o) => o.outcome)).toEqual(['delivered']);
|
|
726
|
+
expect(await holds(w, kim, INVITEFIX_B, w.s2)).toBe(true);
|
|
727
|
+
expect(await memberOf(w, kim, theirs)).toBe(0);
|
|
728
|
+
});
|
|
729
|
+
it('a request naming an org other than the one the invitation was sent for joins nobody — even one the sender is a member of', async () => {
|
|
730
|
+
// The payload is module-written: the org it names is bound to the kernel-stamped send, so a
|
|
731
|
+
// sender in X who invited to Y cannot have the request carry X and join someone to it.
|
|
732
|
+
const x = await scopeOnlyOrg();
|
|
733
|
+
const y = await scopeOnlyOrg();
|
|
734
|
+
const alice = await lead(x);
|
|
735
|
+
const joe = principalId.parse(ulid());
|
|
736
|
+
const inv = await send(w, alice, 'member', y);
|
|
737
|
+
await expectNothing(await asJoiner(w, joe, 'invitefix/accept', { ...inv, orgId: x }), joe, x, /does not match invitation/);
|
|
738
|
+
expect(await memberOf(w, joe, y)).toBe(0);
|
|
739
|
+
});
|
|
740
|
+
it('an inviter removed from the org while the add is held: refused in the unit, and neither the role nor the membership lands', async () => {
|
|
741
|
+
const org = await scopeOnlyOrg();
|
|
742
|
+
const carol = await lead(org);
|
|
743
|
+
const joe = principalId.parse(ulid());
|
|
744
|
+
const inv = await send(w, carol, 'member', org);
|
|
745
|
+
shut();
|
|
746
|
+
const accepting = asJoiner(w, joe, 'invitefix/accept', inv);
|
|
747
|
+
await settle();
|
|
748
|
+
await w.host.admin.removeMember(staff, w.t, carol, org);
|
|
749
|
+
release();
|
|
750
|
+
await expectNothing(await accepting, joe, org, /inviter .* is not a member of org /);
|
|
751
|
+
});
|
|
752
|
+
it('twin: an inviter who stays a member through the hold joins them', async () => {
|
|
753
|
+
const org = await scopeOnlyOrg();
|
|
754
|
+
const carol = await lead(org);
|
|
755
|
+
const joe = principalId.parse(ulid());
|
|
756
|
+
const inv = await send(w, carol, 'member', org);
|
|
757
|
+
shut();
|
|
758
|
+
const accepting = asJoiner(w, joe, 'invitefix/accept', inv);
|
|
759
|
+
await settle();
|
|
760
|
+
release();
|
|
761
|
+
expect((await accepting).map((o) => o.outcome)).toEqual(['delivered']);
|
|
762
|
+
expect(await memberOf(w, joe, org)).toBe(1);
|
|
763
|
+
expect(await holds(w, joe, INVITEFIX_B, w.s2)).toBe(true);
|
|
764
|
+
});
|
|
765
|
+
it('an inviter demoted while the add is held: the role\'s bound refuses, and the org is not joined either', async () => {
|
|
766
|
+
const org = await scopeOnlyOrg();
|
|
767
|
+
const carol = await lead(org);
|
|
768
|
+
const joe = principalId.parse(ulid());
|
|
769
|
+
const inv = await send(w, carol, 'lead', org);
|
|
770
|
+
shut();
|
|
771
|
+
const accepting = asJoiner(w, joe, 'invitefix/accept', inv);
|
|
772
|
+
await settle();
|
|
773
|
+
await w.host.admin.unassignRole(staff, { principalId: carol, roleKey: 'lead', node: tenantNode(w) });
|
|
774
|
+
release();
|
|
775
|
+
await expectNothing(await accepting, joe, org, /inviter .* no longer holds invitefix:a, invitefix:b/);
|
|
776
|
+
});
|
|
777
|
+
it('the joiner removed from the org while the add is held: the fence refuses both', async () => {
|
|
778
|
+
const org = await scopeOnlyOrg();
|
|
779
|
+
const alice = await lead(org);
|
|
780
|
+
const joe = principalId.parse(ulid());
|
|
781
|
+
const inv = await send(w, alice, 'member', org);
|
|
782
|
+
shut();
|
|
783
|
+
const accepting = asJoiner(w, joe, 'invitefix/accept', inv);
|
|
784
|
+
await settle();
|
|
785
|
+
await w.host.admin.removeMember(staff, w.t, joe, org);
|
|
786
|
+
release();
|
|
787
|
+
await expectNothing(await accepting, joe, org, /was removed after this request was made/);
|
|
788
|
+
});
|
|
789
|
+
describe('a temporary member cannot confer a permanent membership', () => {
|
|
790
|
+
const later = '2999-01-01T00:00:00.000Z';
|
|
791
|
+
it('the joiner\'s membership expires when the inviter\'s does', async () => {
|
|
792
|
+
const org = await scopeOnlyOrg();
|
|
793
|
+
const temp = await lead(org, later);
|
|
794
|
+
const joe = principalId.parse(ulid());
|
|
795
|
+
expect((await asJoiner(w, joe, 'invitefix/accept', await send(w, temp, 'member', org))).map((o) => o.outcome)).toEqual(['delivered']);
|
|
796
|
+
expect((await membership(joe, org))?.expiresAt).toBe(later);
|
|
797
|
+
const row = (await w.host.admin.auditLog(staff, { tenantId: w.t, action: 'addMember', limit: 500 })).find((r) => JSON.stringify(r.after).includes(joe));
|
|
798
|
+
expect(row?.after).toMatchObject({ principal: joe, orgId: org, expiresAt: later });
|
|
799
|
+
});
|
|
800
|
+
it('twin: an inviter whose membership never lapses confers one that never lapses', async () => {
|
|
801
|
+
const org = await scopeOnlyOrg();
|
|
802
|
+
const alice = await lead(org);
|
|
803
|
+
const joe = principalId.parse(ulid());
|
|
804
|
+
await asJoiner(w, joe, 'invitefix/accept', await send(w, alice, 'member', org));
|
|
805
|
+
expect(await membership(joe, org)).toMatchObject({ expiresAt: null, revokedAt: null });
|
|
806
|
+
});
|
|
807
|
+
it('a re-invitation by a temporary member does not cut short a membership the joiner already holds', async () => {
|
|
808
|
+
const org = await scopeOnlyOrg();
|
|
809
|
+
const temp = await lead(org, later);
|
|
810
|
+
const joe = principalId.parse(ulid());
|
|
811
|
+
await w.host.admin.addMember(staff, w.t, joe, org);
|
|
812
|
+
expect((await asJoiner(w, joe, 'invitefix/accept', await send(w, temp, 'member', org))).map((o) => o.outcome)).toEqual(['delivered']);
|
|
813
|
+
expect((await membership(joe, org))?.expiresAt).toBeNull();
|
|
814
|
+
});
|
|
815
|
+
it('an inviter whose membership has already lapsed is no member, and confers nothing', async () => {
|
|
816
|
+
const org = await scopeOnlyOrg();
|
|
817
|
+
const lapsed = await lead(org, '2000-01-01T00:00:00.000Z');
|
|
818
|
+
const joe = principalId.parse(ulid());
|
|
819
|
+
await expectNothing(await asJoiner(w, joe, 'invitefix/accept', await send(w, lapsed, 'member', org)), joe, org, /is not a member of org /);
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
describe('removal is bounded the same way', () => {
|
|
823
|
+
const joined = async () => {
|
|
824
|
+
const org = await scopeOnlyOrg();
|
|
825
|
+
const alice = await lead(org);
|
|
826
|
+
const joe = principalId.parse(ulid());
|
|
827
|
+
expect((await asJoiner(w, joe, 'invitefix/accept', await send(w, alice, 'member', org))).map((o) => o.outcome)).toEqual(['delivered']);
|
|
828
|
+
return { org, alice, joe };
|
|
829
|
+
};
|
|
830
|
+
it('a member removes: the role and the membership go together, each audited', async () => {
|
|
831
|
+
const { org, alice, joe } = await joined();
|
|
832
|
+
const outcomes = await removeAs(w, alice, joe, 'member', org);
|
|
833
|
+
expect(outcomes.map((o) => o.outcome)).toEqual(['delivered']);
|
|
834
|
+
expect(await memberOf(w, joe, org)).toBe(0);
|
|
835
|
+
expect(await holds(w, joe, INVITEFIX_A)).toBe(false);
|
|
836
|
+
expect(await holds(w, joe, INVITEFIX_B, w.s2)).toBe(false);
|
|
837
|
+
expect((await causedBy(w, outcomes[0].eventId)).map((r) => r.action)).toEqual(['unassignRole', 'removeMember']);
|
|
838
|
+
});
|
|
839
|
+
it('a remover who is not a member of the org removes nothing — neither the membership nor the role', async () => {
|
|
840
|
+
const { org, joe } = await joined();
|
|
841
|
+
const outsider = await lead();
|
|
842
|
+
const outcomes = await removeAs(w, outsider, joe, 'member', org);
|
|
843
|
+
expect(outcomes.map((o) => o.outcome)).toEqual(['refused']);
|
|
844
|
+
expect(outcomes[0].error).toMatch(/remover .* is not a member of org /);
|
|
845
|
+
expect(await memberOf(w, joe, org)).toBe(1);
|
|
846
|
+
expect(await holds(w, joe, INVITEFIX_A)).toBe(true);
|
|
847
|
+
expect(await holds(w, joe, INVITEFIX_B, w.s2)).toBe(true);
|
|
848
|
+
});
|
|
849
|
+
it('a remover taken out of the org while their removal is held removes nothing', async () => {
|
|
850
|
+
const { org, joe } = await joined();
|
|
851
|
+
const carol = await lead(org);
|
|
852
|
+
shut();
|
|
853
|
+
const removing = removeAs(w, carol, joe, 'member', org);
|
|
854
|
+
await settle();
|
|
855
|
+
await w.host.admin.removeMember(staff, w.t, carol, org);
|
|
856
|
+
release();
|
|
857
|
+
const outcomes = await removing;
|
|
858
|
+
expect(outcomes.map((o) => o.outcome)).toEqual(['refused']);
|
|
859
|
+
expect(outcomes[0].error).toMatch(/remover .* is not a member of org /);
|
|
860
|
+
expect(await memberOf(w, joe, org)).toBe(1);
|
|
861
|
+
expect(await holds(w, joe, INVITEFIX_A)).toBe(true);
|
|
862
|
+
});
|
|
863
|
+
});
|
|
864
|
+
});
|
|
619
865
|
}
|
|
620
866
|
//# sourceMappingURL=membership-executor-suite.js.map
|