@substrat-run/contract-tests 0.143.0 → 0.145.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-refusal-suite.d.ts +3 -0
- package/dist/attachment-refusal-suite.d.ts.map +1 -0
- package/dist/attachment-refusal-suite.js +128 -0
- package/dist/attachment-refusal-suite.js.map +1 -0
- package/dist/bind-module.d.ts +220 -0
- package/dist/bind-module.d.ts.map +1 -0
- package/dist/bind-module.js +135 -0
- package/dist/bind-module.js.map +1 -0
- package/dist/binding-invite-suite.d.ts +12 -0
- package/dist/binding-invite-suite.d.ts.map +1 -0
- package/dist/binding-invite-suite.js +560 -0
- package/dist/binding-invite-suite.js.map +1 -0
- package/dist/derived-module.d.ts +414 -3
- package/dist/derived-module.d.ts.map +1 -1
- package/dist/derived-module.js +201 -6
- package/dist/derived-module.js.map +1 -1
- package/dist/derived-suite.d.ts.map +1 -1
- package/dist/derived-suite.js +351 -4
- package/dist/derived-suite.js.map +1 -1
- package/dist/entity-state-module.d.ts +16 -0
- package/dist/entity-state-module.d.ts.map +1 -1
- package/dist/entity-trash-module.d.ts +80 -0
- package/dist/entity-trash-module.d.ts.map +1 -1
- package/dist/entity-trash-module.js +73 -0
- package/dist/entity-trash-module.js.map +1 -1
- package/dist/entity-trash-suite.d.ts.map +1 -1
- package/dist/entity-trash-suite.js +94 -2
- package/dist/entity-trash-suite.js.map +1 -1
- package/dist/erasure-module.d.ts +8 -0
- package/dist/erasure-module.d.ts.map +1 -1
- package/dist/findings-suite.d.ts.map +1 -1
- package/dist/findings-suite.js +55 -1
- package/dist/findings-suite.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/modules.d.ts +248 -0
- package/dist/modules.d.ts.map +1 -1
- package/dist/modules.js +11 -1
- package/dist/modules.js.map +1 -1
- package/dist/scope-host-suite.d.ts.map +1 -1
- package/dist/scope-host-suite.js +501 -0
- package/dist/scope-host-suite.js.map +1 -1
- package/dist/shape-backfill-ties.d.ts +29 -0
- package/dist/shape-backfill-ties.d.ts.map +1 -0
- package/dist/shape-backfill-ties.js +113 -0
- package/dist/shape-backfill-ties.js.map +1 -0
- package/dist/shape-reconcile-plan.d.ts +19 -5
- package/dist/shape-reconcile-plan.d.ts.map +1 -1
- package/dist/shape-reconcile-plan.js +76 -20
- package/dist/shape-reconcile-plan.js.map +1 -1
- package/dist/stamps-module.d.ts +462 -0
- package/dist/stamps-module.d.ts.map +1 -0
- package/dist/stamps-module.js +242 -0
- package/dist/stamps-module.js.map +1 -0
- package/dist/stamps-suite.d.ts +3 -0
- package/dist/stamps-suite.d.ts.map +1 -0
- package/dist/stamps-suite.js +170 -0
- package/dist/stamps-suite.js.map +1 -0
- package/dist/vertical-events-suite.d.ts +16 -0
- package/dist/vertical-events-suite.d.ts.map +1 -1
- package/dist/vertical-events-suite.js +30 -3
- package/dist/vertical-events-suite.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-refusal-suite.d.ts","sourceRoot":"","sources":["../src/attachment-refusal-suite.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAmC9D,wBAAgB,8BAA8B,CAC5C,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,GAC3C,IAAI,CAsGN"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attachment refusals keep their code (#113).
|
|
3
|
+
*
|
|
4
|
+
* An attachment verb refused by its gate is a `PermissionDenied` where it is raised. On the
|
|
5
|
+
* pure adapter it is thrown in-process and reaches the caller as itself. On Cloudflare it is
|
|
6
|
+
* raised inside the ScopeDO, and a throw across that hop arrives as a bare `Error` carrying
|
|
7
|
+
* only its sentence, so every reader above it had to guess the status from the words. The
|
|
8
|
+
* verbs now answer a refusal as data (`…Reply`), and this suite holds both adapters to the
|
|
9
|
+
* same answer: the code, the `permission` extension and the sentence, for every subject an
|
|
10
|
+
* attachment surface is opened as (a principal, a connection, a module's system principal),
|
|
11
|
+
* with the denial still recorded where it is refused. Each refusal has its allowed twin.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
14
|
+
import { errorCodeOf, permissionKey, platformActorId, principalId, scopeId, tenantId, toProblem, } from '@substrat-run/contracts';
|
|
15
|
+
import { ulid } from '@substrat-run/kernel';
|
|
16
|
+
import { capMod } from './modules.js';
|
|
17
|
+
const CAP_READ = permissionKey.parse('cap:read');
|
|
18
|
+
const CAP_WRITE = permissionKey.parse('cap:write');
|
|
19
|
+
const doc = (id) => ({ entityType: 'doc', entityId: id });
|
|
20
|
+
const bytes = (s) => new TextEncoder().encode(s);
|
|
21
|
+
const refusal = (p) => p.then(() => undefined, (e) => e);
|
|
22
|
+
/**
|
|
23
|
+
* The refusal as a caller reads it: its code, its declared permission and its sentence —
|
|
24
|
+
* `expectRefusal`'s check, plus the `permission` extension it does not read. A call that
|
|
25
|
+
* resolved fails it the same way, since `errorCodeOf(undefined)` is never a code.
|
|
26
|
+
*/
|
|
27
|
+
const expectDenied = (err, permission) => {
|
|
28
|
+
expect(errorCodeOf(err)).toBe('permission_denied');
|
|
29
|
+
expect(toProblem(err)).toMatchObject({
|
|
30
|
+
code: 'permission_denied',
|
|
31
|
+
permission,
|
|
32
|
+
detail: `permission denied: ${permission}`,
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
export function attachmentRefusalContractSuite(adapterName, makeFixture) {
|
|
36
|
+
describe(`attachment refusals keep their code (#113): ${adapterName}`, () => {
|
|
37
|
+
let fixture;
|
|
38
|
+
let host;
|
|
39
|
+
const t1 = tenantId.parse(ulid());
|
|
40
|
+
const s1 = scopeId.parse(ulid());
|
|
41
|
+
const alice = principalId.parse(ulid()); // read + write
|
|
42
|
+
const bob = principalId.parse(ulid()); // read only
|
|
43
|
+
const carol = principalId.parse(ulid()); // nothing
|
|
44
|
+
const staff = platformActorId.parse(ulid());
|
|
45
|
+
let file;
|
|
46
|
+
const filesOf = (who) => host.attachments(who, t1, s1);
|
|
47
|
+
const upload = (surface, name) => surface.upload({ entity: doc('d1'), filename: `${name}.txt`, contentType: 'text/plain', visibility: 'internal', body: bytes(name) });
|
|
48
|
+
const denials = async () => (await host.getScope(alice, t1, s1)).invoke('cap/denials');
|
|
49
|
+
beforeAll(async () => {
|
|
50
|
+
fixture = await makeFixture();
|
|
51
|
+
host = fixture.host;
|
|
52
|
+
host.registerModule(capMod);
|
|
53
|
+
await host.admin.createTenant(staff, { id: t1, slug: `att-ref-${t1.toLowerCase()}`, name: 'Attachment refusals' });
|
|
54
|
+
await host.admin.grantEntitlement(staff, t1, 'cap');
|
|
55
|
+
await host.provisionScope(staff, { tenantId: t1, scopeId: s1, vertical: 'cap-vertical' });
|
|
56
|
+
await host.admin.activateScope(staff, t1, s1);
|
|
57
|
+
await host.provisionBlobStore(staff, { tenantId: t1, vertical: 'cap-vertical', binding: 'ATTACHMENTS' });
|
|
58
|
+
await host.admin.defineRole(staff, t1, { key: 'editor', permissions: [CAP_READ, CAP_WRITE], source: 'vertical' });
|
|
59
|
+
await host.admin.defineRole(staff, t1, { key: 'reader', permissions: [CAP_READ], source: 'vertical' });
|
|
60
|
+
const node = { tenantId: t1, scopeId: null };
|
|
61
|
+
await host.admin.assignRole(staff, { principalId: alice, roleKey: 'editor', node });
|
|
62
|
+
await host.admin.assignRole(staff, { principalId: bob, roleKey: 'reader', node });
|
|
63
|
+
file = await upload(await filesOf(alice), 'd1');
|
|
64
|
+
});
|
|
65
|
+
afterAll(async () => {
|
|
66
|
+
await fixture.cleanup();
|
|
67
|
+
});
|
|
68
|
+
it('a principal without the write key: upload and remove are permission_denied, and recorded', async () => {
|
|
69
|
+
const files = await filesOf(bob);
|
|
70
|
+
expectDenied(await refusal(upload(files, 'nope')), 'cap:write');
|
|
71
|
+
expectDenied(await refusal(files.remove(file.id)), 'cap:write');
|
|
72
|
+
const rows = (await denials()).filter((d) => d.actor.includes(bob));
|
|
73
|
+
expect(rows).toContainEqual(expect.objectContaining({ permission: 'cap:write', operation: 'attachments.upload' }));
|
|
74
|
+
expect(rows).toContainEqual(expect.objectContaining({ permission: 'cap:write', operation: 'attachments.remove' }));
|
|
75
|
+
// The twin: the same key held, the same verbs succeed.
|
|
76
|
+
const mine = await filesOf(alice);
|
|
77
|
+
const extra = await upload(mine, 'extra');
|
|
78
|
+
expect((await mine.remove(extra.id))?.id).toBe(extra.id);
|
|
79
|
+
});
|
|
80
|
+
it('a principal without the read key: list and open are permission_denied, and recorded', async () => {
|
|
81
|
+
const files = await filesOf(carol);
|
|
82
|
+
expectDenied(await refusal(files.list(doc('d1'))), 'cap:read');
|
|
83
|
+
expectDenied(await refusal(files.open(file.id)), 'cap:read');
|
|
84
|
+
const rows = (await denials()).filter((d) => d.actor.includes(carol));
|
|
85
|
+
expect(rows).toContainEqual(expect.objectContaining({ permission: 'cap:read', operation: 'attachments.list' }));
|
|
86
|
+
expect(rows).toContainEqual(expect.objectContaining({ permission: 'cap:read', operation: 'attachments.open' }));
|
|
87
|
+
// The twin: a reader lists and opens the same file.
|
|
88
|
+
const theirs = await filesOf(bob);
|
|
89
|
+
expect((await theirs.list(doc('d1'))).map((r) => r.id)).toContain(file.id);
|
|
90
|
+
expect((await theirs.open(file.id))?.record.id).toBe(file.id);
|
|
91
|
+
});
|
|
92
|
+
it('an unknown id is null, not a refusal', async () => {
|
|
93
|
+
const files = await filesOf(carol);
|
|
94
|
+
expect(await files.open(ulid())).toBeNull();
|
|
95
|
+
expect(await files.remove(ulid())).toBeNull();
|
|
96
|
+
});
|
|
97
|
+
it('a connection never granted the write key: upload is permission_denied', async () => {
|
|
98
|
+
const connId = ulid();
|
|
99
|
+
await host.admin.createConnection(staff, {
|
|
100
|
+
id: connId,
|
|
101
|
+
tenantId: t1,
|
|
102
|
+
vertical: 'cap-vertical',
|
|
103
|
+
provider: 'signer',
|
|
104
|
+
label: 'signer',
|
|
105
|
+
secret: { accessToken: 'x' },
|
|
106
|
+
});
|
|
107
|
+
const files = await host.getConnectorAttachments(connId, s1);
|
|
108
|
+
expectDenied(await refusal(upload(files, 'sealed')), 'cap:write');
|
|
109
|
+
// The twin: granted the key, the same connection uploads as itself.
|
|
110
|
+
await host.admin.grantToConnection(staff, {
|
|
111
|
+
connectionId: connId,
|
|
112
|
+
permission: CAP_WRITE,
|
|
113
|
+
node: { tenantId: t1, scopeId: s1 },
|
|
114
|
+
grantedBy: staff,
|
|
115
|
+
});
|
|
116
|
+
expect((await upload(files, 'sealed')).createdBy).toBe(connId);
|
|
117
|
+
});
|
|
118
|
+
it('a module’s system principal without the read grant: open is permission_denied', async () => {
|
|
119
|
+
const moduleId = capMod.manifest.id;
|
|
120
|
+
const system = await host.getSystemAttachments(moduleId, t1, s1);
|
|
121
|
+
expectDenied(await refusal(system.open(file.id)), 'cap:read');
|
|
122
|
+
// The twin: granted the read key, the same open answers the file.
|
|
123
|
+
await host.admin.grantToSystem(staff, { moduleId, permission: CAP_READ, node: { tenantId: t1, scopeId: s1 }, grantedBy: staff });
|
|
124
|
+
expect((await system.open(file.id))?.record.id).toBe(file.id);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=attachment-refusal-suite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-refusal-suite.js","sourceRoot":"","sources":["../src/attachment-refusal-suite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fixture behind `bindingInviteContractSuite` (#1686): one record type whose own column
|
|
3
|
+
* names its person — the shape of a support desk's contact, a shop's customer.
|
|
4
|
+
*
|
|
5
|
+
* - `brecord` — `bind_records (id, principal, email, fate)`. `principal` is the binding column,
|
|
6
|
+
* an erasure SUBJECT beside `id`, never `erasable`; `email` is erasable.
|
|
7
|
+
* - The hook reads the records by `principal` and logs what it saw (`bind_seen`), so the suite
|
|
8
|
+
* can see that the erasure's unbind waited for it; a record whose `fate` is `'drop'` is
|
|
9
|
+
* DELETED by it, so the suite can see a record the hook removed still released.
|
|
10
|
+
*
|
|
11
|
+
* - `bgroup` — `bind_groups (id)`, a record's declared parent: what a grant on a parent of the
|
|
12
|
+
* shape's type is made on (#2177).
|
|
13
|
+
*
|
|
14
|
+
* Operations are thin pass-throughs and check nothing themselves, except `bind/read`, which is
|
|
15
|
+
* the portal read a bound person makes: `bind:read-own` on that one record; `bind/allowed`,
|
|
16
|
+
* which answers the caller's own check of a key on an entity; and `bind/granted`, the caller's
|
|
17
|
+
* own grant-scoped listing.
|
|
18
|
+
*/
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
import { type EntityGrantShape } from '@substrat-run/contracts';
|
|
21
|
+
import { type ModuleRegistration } from '@substrat-run/kernel';
|
|
22
|
+
export declare const BIND_READ_OWN: string & z.$brand<"PermissionKey">;
|
|
23
|
+
export declare const BIND_INVITE: string & z.$brand<"PermissionKey">;
|
|
24
|
+
export declare const BIND_MEMBER: string & z.$brand<"PermissionKey">;
|
|
25
|
+
/** A key the shape used to carry — what an unbind must take back too. */
|
|
26
|
+
export declare const BIND_OLD: string & z.$brand<"PermissionKey">;
|
|
27
|
+
export declare const bindEntities: {
|
|
28
|
+
readonly brecord: {
|
|
29
|
+
readonly table: "bind_records";
|
|
30
|
+
readonly fields: z.ZodObject<{
|
|
31
|
+
id: z.ZodString;
|
|
32
|
+
principal: z.ZodNullable<z.ZodString>;
|
|
33
|
+
email: z.ZodNullable<z.ZodString>;
|
|
34
|
+
fate: z.ZodNullable<z.ZodString>;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
readonly erasable: readonly ["email"];
|
|
37
|
+
readonly erasure: {
|
|
38
|
+
readonly subjects: readonly ["id", "principal"];
|
|
39
|
+
};
|
|
40
|
+
/** A record may sit in a group — what a grant on a parent is made on (#2177). */
|
|
41
|
+
readonly parents: readonly ["bgroup"];
|
|
42
|
+
};
|
|
43
|
+
readonly bgroup: {
|
|
44
|
+
readonly table: "bind_groups";
|
|
45
|
+
readonly fields: z.ZodObject<{
|
|
46
|
+
id: z.ZodString;
|
|
47
|
+
}, z.core.$strip>;
|
|
48
|
+
};
|
|
49
|
+
/** What the erasure hook saw, by principal — written only by the hook. */
|
|
50
|
+
readonly bseen: {
|
|
51
|
+
readonly table: "bind_seen";
|
|
52
|
+
readonly fields: z.ZodObject<{
|
|
53
|
+
subject: z.ZodString;
|
|
54
|
+
record_id: z.ZodString;
|
|
55
|
+
}, z.core.$strip>;
|
|
56
|
+
readonly primaryKey: readonly ["subject", "record_id"];
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
export declare const bindModManifest: {
|
|
60
|
+
id: string & z.$brand<"ModuleId">;
|
|
61
|
+
version: string;
|
|
62
|
+
kernelContract: string;
|
|
63
|
+
permissions: {
|
|
64
|
+
key: string & z.$brand<"PermissionKey">;
|
|
65
|
+
description: string;
|
|
66
|
+
}[];
|
|
67
|
+
events: {
|
|
68
|
+
emits: {
|
|
69
|
+
type: string;
|
|
70
|
+
schemaVersion: number;
|
|
71
|
+
}[];
|
|
72
|
+
consumes: {
|
|
73
|
+
type: string;
|
|
74
|
+
schemaVersion: number;
|
|
75
|
+
from?: string | undefined;
|
|
76
|
+
}[];
|
|
77
|
+
exports?: {
|
|
78
|
+
type: string;
|
|
79
|
+
schemaVersion: number;
|
|
80
|
+
readPermission: string & z.$brand<"PermissionKey">;
|
|
81
|
+
}[] | undefined;
|
|
82
|
+
};
|
|
83
|
+
migrations: {
|
|
84
|
+
journalDir: string;
|
|
85
|
+
compatibleFrom: string;
|
|
86
|
+
};
|
|
87
|
+
attachmentTargets: {
|
|
88
|
+
entityType: string;
|
|
89
|
+
readPermission: string & z.$brand<"PermissionKey">;
|
|
90
|
+
writePermission?: (string & z.$brand<"PermissionKey">) | undefined;
|
|
91
|
+
}[];
|
|
92
|
+
liveTargets?: {
|
|
93
|
+
entityType: string;
|
|
94
|
+
readPermission: string & z.$brand<"PermissionKey">;
|
|
95
|
+
}[] | undefined;
|
|
96
|
+
entityRelations?: {
|
|
97
|
+
entityType: string;
|
|
98
|
+
parentType: string;
|
|
99
|
+
}[] | undefined;
|
|
100
|
+
guards?: {
|
|
101
|
+
before: string;
|
|
102
|
+
predicate: string;
|
|
103
|
+
config: Record<string, unknown>;
|
|
104
|
+
}[] | undefined;
|
|
105
|
+
schedules?: {
|
|
106
|
+
operation: string;
|
|
107
|
+
cadence: {
|
|
108
|
+
everyMinutes: number;
|
|
109
|
+
};
|
|
110
|
+
input?: Record<string, unknown> | undefined;
|
|
111
|
+
permissions: (string & z.$brand<"PermissionKey">)[];
|
|
112
|
+
purge?: {
|
|
113
|
+
entityType: string;
|
|
114
|
+
} | undefined;
|
|
115
|
+
}[] | undefined;
|
|
116
|
+
peers?: {
|
|
117
|
+
vertical: string;
|
|
118
|
+
operations: string[];
|
|
119
|
+
permissions: (string & z.$brand<"PermissionKey">)[];
|
|
120
|
+
}[] | undefined;
|
|
121
|
+
freshness?: {
|
|
122
|
+
eventType: string;
|
|
123
|
+
within: {
|
|
124
|
+
hours: number;
|
|
125
|
+
};
|
|
126
|
+
}[] | undefined;
|
|
127
|
+
withdraws?: string[] | undefined;
|
|
128
|
+
entitlementKey: string;
|
|
129
|
+
envSpec?: {
|
|
130
|
+
key: string;
|
|
131
|
+
label?: string | undefined;
|
|
132
|
+
description: string;
|
|
133
|
+
placeholder?: string | undefined;
|
|
134
|
+
required: boolean;
|
|
135
|
+
secret: boolean;
|
|
136
|
+
default?: string | undefined;
|
|
137
|
+
group?: string | undefined;
|
|
138
|
+
}[] | undefined;
|
|
139
|
+
ownerGrants?: (string & z.$brand<"PermissionKey">)[] | undefined;
|
|
140
|
+
entitlements?: string[] | undefined;
|
|
141
|
+
provides?: string[] | undefined;
|
|
142
|
+
requires?: string[] | undefined;
|
|
143
|
+
api?: string | undefined;
|
|
144
|
+
searchables?: {
|
|
145
|
+
entityType: string;
|
|
146
|
+
fields: string[];
|
|
147
|
+
table?: string | undefined;
|
|
148
|
+
idColumn?: string | undefined;
|
|
149
|
+
tokenizer?: "prefix" | "substring" | undefined;
|
|
150
|
+
}[] | undefined;
|
|
151
|
+
lists?: {
|
|
152
|
+
entityType: string;
|
|
153
|
+
sortable: string[];
|
|
154
|
+
filterable?: string[] | undefined;
|
|
155
|
+
table?: string | undefined;
|
|
156
|
+
idColumn?: string | undefined;
|
|
157
|
+
}[] | undefined;
|
|
158
|
+
entityStates?: {
|
|
159
|
+
archivePermission?: (string & z.$brand<"PermissionKey">) | undefined;
|
|
160
|
+
trashPermission?: (string & z.$brand<"PermissionKey">) | undefined;
|
|
161
|
+
purgeAfterDays?: number | undefined;
|
|
162
|
+
purgeEarly?: boolean | undefined;
|
|
163
|
+
table?: string | undefined;
|
|
164
|
+
idColumn?: string | undefined;
|
|
165
|
+
entityType: string;
|
|
166
|
+
}[] | undefined;
|
|
167
|
+
erasure?: {
|
|
168
|
+
tables: string[];
|
|
169
|
+
entities: {
|
|
170
|
+
entityType: string;
|
|
171
|
+
table: string;
|
|
172
|
+
mode: "blank" | "custom" | "delete" | "unreached";
|
|
173
|
+
subjects?: string[] | undefined;
|
|
174
|
+
fields: {
|
|
175
|
+
name: string;
|
|
176
|
+
blank: "" | null;
|
|
177
|
+
}[];
|
|
178
|
+
}[];
|
|
179
|
+
} | undefined;
|
|
180
|
+
updatedStamps?: {
|
|
181
|
+
table: string;
|
|
182
|
+
idColumn: string;
|
|
183
|
+
at?: string | undefined;
|
|
184
|
+
by?: string | undefined;
|
|
185
|
+
entityType: string;
|
|
186
|
+
}[] | undefined;
|
|
187
|
+
ui?: {
|
|
188
|
+
routes?: {
|
|
189
|
+
path: string;
|
|
190
|
+
screen: string;
|
|
191
|
+
permission: string & z.$brand<"PermissionKey">;
|
|
192
|
+
}[] | undefined;
|
|
193
|
+
nav?: {
|
|
194
|
+
label: string;
|
|
195
|
+
icon?: string | undefined;
|
|
196
|
+
to: string;
|
|
197
|
+
permission: string & z.$brand<"PermissionKey">;
|
|
198
|
+
}[] | undefined;
|
|
199
|
+
entityViews?: {
|
|
200
|
+
entityType: string;
|
|
201
|
+
view: string;
|
|
202
|
+
}[] | undefined;
|
|
203
|
+
widgets?: {
|
|
204
|
+
slot: string;
|
|
205
|
+
component: string;
|
|
206
|
+
permission: string & z.$brand<"PermissionKey">;
|
|
207
|
+
}[] | undefined;
|
|
208
|
+
settingsPanels?: {
|
|
209
|
+
label: string;
|
|
210
|
+
component: string;
|
|
211
|
+
permission: string & z.$brand<"PermissionKey">;
|
|
212
|
+
}[] | undefined;
|
|
213
|
+
} | undefined;
|
|
214
|
+
};
|
|
215
|
+
/** The binding shape, as a vertical's `ENTITY_GRANTS` declares it. */
|
|
216
|
+
export declare const BIND_SHAPE: EntityGrantShape;
|
|
217
|
+
/** A binding shape over `bind_dups`, whose `key` two rows may share. */
|
|
218
|
+
export declare const BIND_DUP_SHAPE: EntityGrantShape;
|
|
219
|
+
export declare const bindMod: ModuleRegistration;
|
|
220
|
+
//# sourceMappingURL=bind-module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind-module.d.ts","sourceRoot":"","sources":["../src/bind-module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAKL,KAAK,gBAAgB,EAGtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAiB,KAAK,kBAAkB,EAA+C,MAAM,sBAAsB,CAAC;AAG3H,eAAO,MAAM,aAAa,oCAAuC,CAAC;AAClE,eAAO,MAAM,WAAW,oCAAqC,CAAC;AAC9D,eAAO,MAAM,WAAW,oCAAqC,CAAC;AAC9D,yEAAyE;AACzE,eAAO,MAAM,QAAQ,oCAAkC,CAAC;AAExD,eAAO,MAAM,YAAY;;;;;;;;;;;;;QAMrB,iFAAiF;;;;;;;;;IAOnF,0EAA0E;;;;;;;;;CAM1E,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAc1B,CAAC;AAEH,sEAAsE;AACtE,eAAO,MAAM,UAAU,EAAE,gBAOxB,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,cAAc,EAAE,gBAM5B,CAAC;AAUF,eAAO,MAAM,OAAO,EAAE,kBAgDrB,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fixture behind `bindingInviteContractSuite` (#1686): one record type whose own column
|
|
3
|
+
* names its person — the shape of a support desk's contact, a shop's customer.
|
|
4
|
+
*
|
|
5
|
+
* - `brecord` — `bind_records (id, principal, email, fate)`. `principal` is the binding column,
|
|
6
|
+
* an erasure SUBJECT beside `id`, never `erasable`; `email` is erasable.
|
|
7
|
+
* - The hook reads the records by `principal` and logs what it saw (`bind_seen`), so the suite
|
|
8
|
+
* can see that the erasure's unbind waited for it; a record whose `fate` is `'drop'` is
|
|
9
|
+
* DELETED by it, so the suite can see a record the hook removed still released.
|
|
10
|
+
*
|
|
11
|
+
* - `bgroup` — `bind_groups (id)`, a record's declared parent: what a grant on a parent of the
|
|
12
|
+
* shape's type is made on (#2177).
|
|
13
|
+
*
|
|
14
|
+
* Operations are thin pass-throughs and check nothing themselves, except `bind/read`, which is
|
|
15
|
+
* the portal read a bound person makes: `bind:read-own` on that one record; `bind/allowed`,
|
|
16
|
+
* which answers the caller's own check of a key on an entity; and `bind/granted`, the caller's
|
|
17
|
+
* own grant-scoped listing.
|
|
18
|
+
*/
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
import { defineEntities, manifestEntities, moduleManifest, permissionKey, } from '@substrat-run/contracts';
|
|
21
|
+
import { assertAllowed } from '@substrat-run/kernel';
|
|
22
|
+
import { testOperations } from './test-operations.js';
|
|
23
|
+
export const BIND_READ_OWN = permissionKey.parse('bind:read-own');
|
|
24
|
+
export const BIND_INVITE = permissionKey.parse('bind:invite');
|
|
25
|
+
export const BIND_MEMBER = permissionKey.parse('bind:member');
|
|
26
|
+
/** A key the shape used to carry — what an unbind must take back too. */
|
|
27
|
+
export const BIND_OLD = permissionKey.parse('bind:old');
|
|
28
|
+
export const bindEntities = defineEntities({
|
|
29
|
+
brecord: {
|
|
30
|
+
table: 'bind_records',
|
|
31
|
+
fields: z.object({ id: z.string(), principal: z.string().nullable(), email: z.string().nullable(), fate: z.string().nullable() }),
|
|
32
|
+
erasable: ['email'],
|
|
33
|
+
erasure: { subjects: ['id', 'principal'] },
|
|
34
|
+
/** A record may sit in a group — what a grant on a parent is made on (#2177). */
|
|
35
|
+
parents: ['bgroup'],
|
|
36
|
+
},
|
|
37
|
+
bgroup: {
|
|
38
|
+
table: 'bind_groups',
|
|
39
|
+
fields: z.object({ id: z.string() }),
|
|
40
|
+
},
|
|
41
|
+
/** What the erasure hook saw, by principal — written only by the hook. */
|
|
42
|
+
bseen: {
|
|
43
|
+
table: 'bind_seen',
|
|
44
|
+
fields: z.object({ subject: z.string(), record_id: z.string() }),
|
|
45
|
+
primaryKey: ['subject', 'record_id'],
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
export const bindModManifest = moduleManifest.parse({
|
|
49
|
+
id: '@test/bind',
|
|
50
|
+
version: '1.0.0',
|
|
51
|
+
kernelContract: '^0.0.1',
|
|
52
|
+
permissions: [
|
|
53
|
+
{ key: 'bind:read-own', description: 'read one record — the one that names you' },
|
|
54
|
+
{ key: 'bind:invite', description: 'invite a record’s person, or unbind them' },
|
|
55
|
+
{ key: 'bind:member', description: 'be a member of the scope' },
|
|
56
|
+
{ key: 'bind:old', description: 'a key the shape retired' },
|
|
57
|
+
],
|
|
58
|
+
events: { emits: [], consumes: [] },
|
|
59
|
+
migrations: { journalDir: './migrations', compatibleFrom: '1.0.0' },
|
|
60
|
+
entitlementKey: 'bind',
|
|
61
|
+
...manifestEntities(bindEntities, {}),
|
|
62
|
+
});
|
|
63
|
+
/** The binding shape, as a vertical's `ENTITY_GRANTS` declares it. */
|
|
64
|
+
export const BIND_SHAPE = {
|
|
65
|
+
entityType: 'brecord',
|
|
66
|
+
permissions: [BIND_READ_OWN],
|
|
67
|
+
bootstrap: true,
|
|
68
|
+
holder: { table: 'bind_records', idColumn: 'id', principalColumn: 'principal' },
|
|
69
|
+
retired: [BIND_OLD],
|
|
70
|
+
bind: { permission: BIND_INVITE },
|
|
71
|
+
};
|
|
72
|
+
/** A binding shape over `bind_dups`, whose `key` two rows may share. */
|
|
73
|
+
export const BIND_DUP_SHAPE = {
|
|
74
|
+
entityType: 'bdup',
|
|
75
|
+
permissions: [BIND_READ_OWN],
|
|
76
|
+
bootstrap: true,
|
|
77
|
+
holder: { table: 'bind_dups', idColumn: 'key', principalColumn: 'holder' },
|
|
78
|
+
bind: { permission: BIND_INVITE },
|
|
79
|
+
};
|
|
80
|
+
const onSubjectErased = (ctx, { subjectId }) => {
|
|
81
|
+
const mine = ctx.sql.query('SELECT id, fate FROM bind_records WHERE principal = ?', [subjectId]);
|
|
82
|
+
for (const r of mine)
|
|
83
|
+
ctx.sql.exec('INSERT INTO bind_seen (subject, record_id) VALUES (?, ?)', [subjectId, r.id]);
|
|
84
|
+
ctx.sql.exec("DELETE FROM bind_records WHERE (id = ? OR principal = ?) AND fate = 'drop'", [subjectId, subjectId]);
|
|
85
|
+
};
|
|
86
|
+
export const bindMod = {
|
|
87
|
+
manifest: bindModManifest,
|
|
88
|
+
migrations: [
|
|
89
|
+
{
|
|
90
|
+
version: '0001-init',
|
|
91
|
+
// The index a vertical naturally has on its binding column — its portal and erasure find
|
|
92
|
+
// rows by it — and the one that makes a Durable Object's write count read one row as two.
|
|
93
|
+
sql: `CREATE TABLE bind_records (id TEXT PRIMARY KEY, principal TEXT, email TEXT, fate TEXT);
|
|
94
|
+
CREATE INDEX bind_records_by_principal ON bind_records (principal);
|
|
95
|
+
-- A holder table whose id is NOT unique: what a careless declaration can point at.
|
|
96
|
+
CREATE TABLE bind_dups (key TEXT NOT NULL, holder TEXT);
|
|
97
|
+
CREATE TABLE bind_seen (subject TEXT NOT NULL, record_id TEXT NOT NULL, PRIMARY KEY (subject, record_id));`,
|
|
98
|
+
},
|
|
99
|
+
{ version: '0002-groups', sql: 'CREATE TABLE bind_groups (id TEXT PRIMARY KEY);' },
|
|
100
|
+
],
|
|
101
|
+
...testOperations({
|
|
102
|
+
/** Write a fixture row: `{ sql, params }` against the module's own tables. */
|
|
103
|
+
'bind/put': ((ctx, input) => {
|
|
104
|
+
ctx.sql.exec(input.sql, input.params ?? []);
|
|
105
|
+
}),
|
|
106
|
+
/** Read rows back, the spine included: `{ sql, params }`. */
|
|
107
|
+
'bind/sql': ((ctx, input) => ctx.sql.query(input.sql, input.params ?? [])),
|
|
108
|
+
/** File a record under a group: `{ child, parent }`, entity refs. */
|
|
109
|
+
'bind/link': ((ctx, input) => {
|
|
110
|
+
ctx.link(input.child, input.parent);
|
|
111
|
+
}),
|
|
112
|
+
/** Whether the caller holds `{ permission }` on `{ entity }`, by the ordinary check. */
|
|
113
|
+
'bind/allowed': (async (ctx, input) => (await ctx.check(input.permission, input.entity)).allowed),
|
|
114
|
+
/** Every id the caller's grants list for `{ permission, entityType }`, all pages, or the page kind when it is not ids. */
|
|
115
|
+
'bind/granted': (async (ctx, input) => {
|
|
116
|
+
const ids = [];
|
|
117
|
+
let cursor;
|
|
118
|
+
do {
|
|
119
|
+
const page = await ctx.grantedEntities(input.permission, input.entityType, cursor === undefined ? {} : { cursor });
|
|
120
|
+
if (page.kind !== 'ids')
|
|
121
|
+
return page.kind;
|
|
122
|
+
ids.push(...page.ids);
|
|
123
|
+
cursor = page.nextCursor ?? undefined;
|
|
124
|
+
} while (cursor !== undefined);
|
|
125
|
+
return [...new Set(ids)].sort();
|
|
126
|
+
}),
|
|
127
|
+
/** The bound person's portal read: their own record, and nobody else's. */
|
|
128
|
+
'bind/read': (async (ctx, input) => {
|
|
129
|
+
assertAllowed(await ctx.check(BIND_READ_OWN, { entityType: 'brecord', entityId: input.id }));
|
|
130
|
+
return ctx.sql.query('SELECT id FROM bind_records WHERE id = ?', [input.id])[0] ?? null;
|
|
131
|
+
}),
|
|
132
|
+
}),
|
|
133
|
+
onSubjectErased,
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=bind-module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind-module.js","sourceRoot":"","sources":["../src/bind-module.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type BoundedBecomeMint, type BoundedUnbind, type EntityRef, type PrincipalBecomeCapabilityInput, type PrincipalId, type ScopeId, type TenantId } from '@substrat-run/contracts';
|
|
2
|
+
import type { ScopeHostFixture } from './scope-host-suite.js';
|
|
3
|
+
import type { RawScopeQuery } from './subject-erasure-suite.js';
|
|
4
|
+
/** The host verbs under test — on each adapter's host class, not on `ScopeHost`. */
|
|
5
|
+
export interface BindingVerbs {
|
|
6
|
+
mintBecomeCapabilityBounded(tenantId: TenantId, scopeId: ScopeId, caller: PrincipalId, input: PrincipalBecomeCapabilityInput): Promise<BoundedBecomeMint>;
|
|
7
|
+
unbindEntityHolderBounded(tenantId: TenantId, scopeId: ScopeId, caller: PrincipalId, entity: EntityRef): Promise<BoundedUnbind>;
|
|
8
|
+
}
|
|
9
|
+
export declare function bindingInviteContractSuite(adapterName: string, makeFixture: () => Promise<ScopeHostFixture & {
|
|
10
|
+
verbs: BindingVerbs;
|
|
11
|
+
}>, raw: RawScopeQuery): void;
|
|
12
|
+
//# sourceMappingURL=binding-invite-suite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding-invite-suite.d.ts","sourceRoot":"","sources":["../src/binding-invite-suite.ts"],"names":[],"mappings":"AAgCA,OAAO,EAUL,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAElB,KAAK,SAAS,EAEd,KAAK,8BAA8B,EACnC,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,QAAQ,EACd,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE,oFAAoF;AACpF,MAAM,WAAW,YAAY;IAC3B,2BAA2B,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,8BAA8B,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC1J,yBAAyB,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CACjI;AAID,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,OAAO,CAAC,gBAAgB,GAAG;IAAE,KAAK,EAAE,YAAY,CAAA;CAAE,CAAC,EACtE,GAAG,EAAE,aAAa,GACjB,IAAI,CA2lBN"}
|