@powerhousedao/switchboard 6.2.3-dev.5 → 6.2.3-dev.50
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/.tsbuild/test/attachments/byte-grants.test.d.ts +2 -0
- package/.tsbuild/test/attachments/byte-grants.test.d.ts.map +1 -0
- package/.tsbuild/test/attachments/byte-routes-read-gate.integration.test.d.ts +2 -0
- package/.tsbuild/test/attachments/byte-routes-read-gate.integration.test.d.ts.map +1 -0
- package/.tsbuild/test/attachments/reserve-metadata-parity.test.d.ts +2 -0
- package/.tsbuild/test/attachments/reserve-metadata-parity.test.d.ts.map +1 -0
- package/.tsbuild/test/attachments/url-signer.test.d.ts +2 -0
- package/.tsbuild/test/attachments/url-signer.test.d.ts.map +1 -0
- package/.tsbuild/test/boot-create-signature-policy.test.d.ts +2 -0
- package/.tsbuild/test/boot-create-signature-policy.test.d.ts.map +1 -0
- package/.tsbuild/test/boot-mcp-enabled.test.d.ts +2 -0
- package/.tsbuild/test/boot-mcp-enabled.test.d.ts.map +1 -0
- package/.tsbuild/test/boot-privacy.test.d.ts +2 -0
- package/.tsbuild/test/boot-privacy.test.d.ts.map +1 -0
- package/.tsbuild/test/boot-unsupported-stored-documents.test.d.ts +2 -0
- package/.tsbuild/test/boot-unsupported-stored-documents.test.d.ts.map +1 -0
- package/.tsbuild/test/boot-without-renown.test.d.ts +2 -0
- package/.tsbuild/test/boot-without-renown.test.d.ts.map +1 -0
- package/.tsbuild/test/boot-without-require-signatures.test.d.ts +2 -0
- package/.tsbuild/test/boot-without-require-signatures.test.d.ts.map +1 -0
- package/.tsbuild/test/boot-worker-trust-policy.test.d.ts +2 -0
- package/.tsbuild/test/boot-worker-trust-policy.test.d.ts.map +1 -0
- package/.tsbuild/test/builder-defaults.test.d.ts +2 -0
- package/.tsbuild/test/builder-defaults.test.d.ts.map +1 -0
- package/.tsbuild/test/create-signature-policy.test.d.ts +2 -0
- package/.tsbuild/test/create-signature-policy.test.d.ts.map +1 -0
- package/.tsbuild/test/durable-node-fs-pg16.test.d.ts +2 -0
- package/.tsbuild/test/durable-node-fs-pg16.test.d.ts.map +1 -0
- package/.tsbuild/test/fatal-shutdown.test.d.ts +2 -0
- package/.tsbuild/test/fatal-shutdown.test.d.ts.map +1 -0
- package/.tsbuild/test/mcp-flag.test.d.ts +2 -0
- package/.tsbuild/test/mcp-flag.test.d.ts.map +1 -0
- package/.tsbuild/test/observability-signals.test.d.ts +2 -0
- package/.tsbuild/test/observability-signals.test.d.ts.map +1 -0
- package/.tsbuild/test/pglite-preflight.test.d.ts +2 -0
- package/.tsbuild/test/pglite-preflight.test.d.ts.map +1 -0
- package/.tsbuild/test/renown-trust-policy.test.d.ts +2 -0
- package/.tsbuild/test/renown-trust-policy.test.d.ts.map +1 -0
- package/.tsbuild/test/snapshot-writer.d.ts +18 -0
- package/.tsbuild/test/snapshot-writer.d.ts.map +1 -0
- package/.tsbuild/test/unsupported-stored-documents.test.d.ts +2 -0
- package/.tsbuild/test/unsupported-stored-documents.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow/harness.d.ts +56 -0
- package/.tsbuild/test/workflow/harness.d.ts.map +1 -0
- package/.tsbuild/test/workflow/oauth.test.d.ts +2 -0
- package/.tsbuild/test/workflow/oauth.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow/resolvers.test.d.ts +2 -0
- package/.tsbuild/test/workflow/resolvers.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow/webhook-dedupe.test.d.ts +2 -0
- package/.tsbuild/test/workflow/webhook-dedupe.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow/webhook-signed.test.d.ts +2 -0
- package/.tsbuild/test/workflow/webhook-signed.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow/webhook-trigger-test.test.d.ts +2 -0
- package/.tsbuild/test/workflow/webhook-trigger-test.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow/webhook-unsigned.test.d.ts +2 -0
- package/.tsbuild/test/workflow/webhook-unsigned.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow-reactor-access.test.d.ts +2 -0
- package/.tsbuild/test/workflow-reactor-access.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow-reactor-enforcement.test.d.ts +2 -0
- package/.tsbuild/test/workflow-reactor-enforcement.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow-read-gate.test.d.ts +2 -0
- package/.tsbuild/test/workflow-read-gate.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow-runtime.test.d.ts +2 -0
- package/.tsbuild/test/workflow-runtime.test.d.ts.map +1 -0
- package/.tsbuild/tsconfig.tsbuildinfo +1 -1
- package/.tsbuild/tsdown.config.d.ts.map +1 -1
- package/.tsbuild/vitest.config.d.ts.map +1 -1
- package/Auth.md +94 -0
- package/CHANGELOG.md +509 -0
- package/README.md +35 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +48 -49
- package/dist/index.mjs.map +1 -1
- package/dist/install-packages.d.mts +2 -1
- package/dist/install-packages.mjs +2 -2
- package/dist/install-packages.mjs.map +1 -1
- package/dist/migrate.d.mts +2 -1
- package/dist/migrate.mjs +4 -3
- package/dist/migrate.mjs.map +1 -1
- package/dist/server-Zz7SqTwO.mjs +3335 -0
- package/dist/server-Zz7SqTwO.mjs.map +1 -0
- package/dist/server.d.mts +154 -30
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +4 -5
- package/dist/{utils-Baw7rThP.mjs → utils-uaBlzyjS.mjs} +15 -9
- package/dist/utils-uaBlzyjS.mjs.map +1 -0
- package/dist/utils.d.mts +4 -5
- package/dist/utils.d.mts.map +1 -1
- package/dist/utils.mjs +3 -3
- package/package.json +22 -18
- package/test/attachment-reference-read-model.test.ts +6 -1
- package/test/attachments/auth.test.ts +85 -0
- package/test/attachments/byte-grants.test.ts +290 -0
- package/test/attachments/byte-routes-read-gate.integration.test.ts +314 -0
- package/test/attachments/download-target.test.ts +201 -32
- package/test/attachments/index.test.ts +34 -3
- package/test/attachments/reserve-metadata-parity.test.ts +96 -0
- package/test/attachments/routes-integration.test.ts +60 -4
- package/test/attachments/routes.test.ts +166 -23
- package/test/attachments/service-config.test.ts +13 -3
- package/test/attachments/url-signer.test.ts +105 -0
- package/test/boot-create-signature-policy.test.ts +108 -0
- package/test/boot-mcp-enabled.test.ts +121 -0
- package/test/boot-privacy.test.ts +243 -0
- package/test/boot-unsupported-stored-documents.test.ts +213 -0
- package/test/boot-without-renown.test.ts +81 -0
- package/test/boot-without-require-signatures.test.ts +74 -0
- package/test/boot-worker-trust-policy.test.ts +90 -0
- package/test/builder-defaults.test.ts +80 -0
- package/test/create-signature-policy.test.ts +69 -0
- package/test/durable-node-fs-pg16.test.ts +80 -0
- package/test/fatal-shutdown.test.ts +165 -0
- package/test/mcp-flag.test.ts +56 -0
- package/test/observability-signals.test.ts +131 -0
- package/test/pglite-dialect.test.ts +159 -2
- package/test/pglite-preflight.test.ts +313 -0
- package/test/pglite-version.test.ts +9 -1
- package/test/renown-trust-policy.test.ts +315 -0
- package/test/snapshot-writer.ts +94 -0
- package/test/unsupported-stored-documents.test.ts +72 -0
- package/test/worker-pool.test.ts +27 -1
- package/test/workflow/harness.ts +289 -0
- package/test/workflow/oauth.test.ts +214 -0
- package/test/workflow/resolvers.test.ts +322 -0
- package/test/workflow/webhook-dedupe.test.ts +295 -0
- package/test/workflow/webhook-signed.test.ts +509 -0
- package/test/workflow/webhook-trigger-test.test.ts +82 -0
- package/test/workflow/webhook-unsigned.test.ts +345 -0
- package/test/workflow-reactor-access.test.ts +384 -0
- package/test/workflow-reactor-enforcement.test.ts +805 -0
- package/test/workflow-read-gate.test.ts +210 -0
- package/test/workflow-runtime.test.ts +572 -0
- package/tsconfig.json +9 -3
- package/tsdown.config.ts +3 -1
- package/dist/server-CiKkVE6g.mjs +0 -1555
- package/dist/server-CiKkVE6g.mjs.map +0 -1
- package/dist/utils-Baw7rThP.mjs.map +0 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { PGlite } from "@electric-sql/pglite";
|
|
2
|
+
import {
|
|
3
|
+
ReactorBuilder,
|
|
4
|
+
ReactorClientBuilder,
|
|
5
|
+
type InProcessReactorClientModule,
|
|
6
|
+
} from "@powerhousedao/reactor";
|
|
7
|
+
import {
|
|
8
|
+
AuthorizationPolicy,
|
|
9
|
+
type Context,
|
|
10
|
+
type IAuthorizationService,
|
|
11
|
+
} from "@powerhousedao/reactor-api";
|
|
12
|
+
import * as engine from "@powerhousedao/reactor-workflow";
|
|
13
|
+
import type { WorkflowRuntimeService } from "@powerhousedao/reactor-workflow";
|
|
14
|
+
import {
|
|
15
|
+
initializeAuth,
|
|
16
|
+
withSignaturePolicy,
|
|
17
|
+
type DocumentModelModule,
|
|
18
|
+
} from "@powerhousedao/shared/document-model";
|
|
19
|
+
import {
|
|
20
|
+
createRelationalDb,
|
|
21
|
+
type IRelationalDb,
|
|
22
|
+
} from "@powerhousedao/shared/processors";
|
|
23
|
+
import { Connection } from "@powerhousedao/workflow/document-models/connection";
|
|
24
|
+
import type { ILogger } from "document-model";
|
|
25
|
+
import { documentModelDocumentModelModule } from "document-model";
|
|
26
|
+
import { Kysely } from "kysely";
|
|
27
|
+
import { PGliteDialect } from "kysely-pglite-dialect";
|
|
28
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
29
|
+
import {
|
|
30
|
+
composeWorkflowRuntime,
|
|
31
|
+
type ComposedWorkflowRuntime,
|
|
32
|
+
} from "../src/workflow-runtime.mjs";
|
|
33
|
+
|
|
34
|
+
const READER = "0xreader";
|
|
35
|
+
const WRITER = "0xwriter";
|
|
36
|
+
const OUTSIDER = "0xoutsider";
|
|
37
|
+
const DOCUMENT = "wf-policed";
|
|
38
|
+
const CONNECTION = "conn-policed";
|
|
39
|
+
|
|
40
|
+
// The host's legacy layer under OPEN admits everyone, anonymous included.
|
|
41
|
+
const openAuthorization = {
|
|
42
|
+
config: {
|
|
43
|
+
admins: [],
|
|
44
|
+
defaultProtection: false,
|
|
45
|
+
policy: AuthorizationPolicy.OPEN,
|
|
46
|
+
},
|
|
47
|
+
isSupremeAdmin: () => true,
|
|
48
|
+
canRead: () => Promise.resolve(true),
|
|
49
|
+
canWrite: () => Promise.resolve(true),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Document permissions: everyone reads, only the writer writes.
|
|
53
|
+
const writerOnlyAuthorization = {
|
|
54
|
+
config: {
|
|
55
|
+
admins: [],
|
|
56
|
+
defaultProtection: false,
|
|
57
|
+
policy: AuthorizationPolicy.DOCUMENT_PERMISSIONS,
|
|
58
|
+
},
|
|
59
|
+
isSupremeAdmin: () => false,
|
|
60
|
+
canRead: () => Promise.resolve(true),
|
|
61
|
+
canWrite: (_id: string, address?: string) =>
|
|
62
|
+
Promise.resolve(address === WRITER),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const logger = {
|
|
66
|
+
verbose: vi.fn(),
|
|
67
|
+
debug: vi.fn(),
|
|
68
|
+
info: vi.fn(),
|
|
69
|
+
warn: vi.fn(),
|
|
70
|
+
error: vi.fn(),
|
|
71
|
+
child: () => logger,
|
|
72
|
+
} as unknown as ILogger;
|
|
73
|
+
|
|
74
|
+
const contextFor = (address?: string) =>
|
|
75
|
+
({ headers: {}, db: {}, user: address ? { address } : undefined }) as Context;
|
|
76
|
+
|
|
77
|
+
const readerOnly = (id: string) =>
|
|
78
|
+
initializeAuth({
|
|
79
|
+
version: 1,
|
|
80
|
+
grants: [
|
|
81
|
+
{
|
|
82
|
+
id: `${id}-read`,
|
|
83
|
+
description: "the reader reads the domain",
|
|
84
|
+
effect: "allow",
|
|
85
|
+
principal: { address: READER },
|
|
86
|
+
capability: { can: "read", scope: "global" },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: `${id}-admin`,
|
|
90
|
+
description: "administration stays reachable",
|
|
91
|
+
effect: "allow",
|
|
92
|
+
principal: { anyone: true },
|
|
93
|
+
capability: { can: "execute", scope: "auth" },
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("the workflow runtime's access checks", () => {
|
|
99
|
+
let module: InProcessReactorClientModule | undefined;
|
|
100
|
+
let composed: ComposedWorkflowRuntime | undefined;
|
|
101
|
+
let database: Kysely<unknown> | undefined;
|
|
102
|
+
|
|
103
|
+
afterEach(async () => {
|
|
104
|
+
await composed?.stop();
|
|
105
|
+
await module?.reactor.kill().completed;
|
|
106
|
+
await database?.destroy();
|
|
107
|
+
composed = undefined;
|
|
108
|
+
module = undefined;
|
|
109
|
+
database = undefined;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// The real engine, composed as the host composes it; the runtime it builds
|
|
113
|
+
// is kept so the tests can call it as the subgraph would.
|
|
114
|
+
async function hostRuntime(
|
|
115
|
+
authorizationService: Pick<
|
|
116
|
+
IAuthorizationService,
|
|
117
|
+
"config" | "isSupremeAdmin" | "canRead" | "canWrite"
|
|
118
|
+
>,
|
|
119
|
+
options: { policed: boolean },
|
|
120
|
+
): Promise<WorkflowRuntimeService> {
|
|
121
|
+
module = await new ReactorClientBuilder()
|
|
122
|
+
.withReactorBuilder(
|
|
123
|
+
new ReactorBuilder()
|
|
124
|
+
.withDocumentModelSources([
|
|
125
|
+
documentModelDocumentModelModule as unknown as DocumentModelModule,
|
|
126
|
+
Connection as unknown as DocumentModelModule,
|
|
127
|
+
])
|
|
128
|
+
.withExecutorConfig({
|
|
129
|
+
featureFlags: { documentDecisions: true, authEnforcement: true },
|
|
130
|
+
}),
|
|
131
|
+
)
|
|
132
|
+
.buildModule();
|
|
133
|
+
const client = module.client;
|
|
134
|
+
await client.create(
|
|
135
|
+
withSignaturePolicy(
|
|
136
|
+
documentModelDocumentModelModule.utils.createDocument(),
|
|
137
|
+
"legacy",
|
|
138
|
+
{ id: DOCUMENT },
|
|
139
|
+
),
|
|
140
|
+
);
|
|
141
|
+
await client.create(
|
|
142
|
+
withSignaturePolicy(Connection.utils.createDocument(), "legacy", {
|
|
143
|
+
id: CONNECTION,
|
|
144
|
+
}),
|
|
145
|
+
);
|
|
146
|
+
if (options.policed) {
|
|
147
|
+
await client.execute(DOCUMENT, "main", [readerOnly(DOCUMENT)]);
|
|
148
|
+
await client.execute(CONNECTION, "main", [readerOnly(CONNECTION)]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
database = new Kysely<unknown>({
|
|
152
|
+
dialect: new PGliteDialect(new PGlite()),
|
|
153
|
+
});
|
|
154
|
+
let runtime: WorkflowRuntimeService | undefined;
|
|
155
|
+
composed = await composeWorkflowRuntime({
|
|
156
|
+
reactorClient: client,
|
|
157
|
+
relationalDb: createRelationalDb(database) as IRelationalDb,
|
|
158
|
+
attachments: {} as never,
|
|
159
|
+
authorizationService: authorizationService as never,
|
|
160
|
+
logger,
|
|
161
|
+
load: () =>
|
|
162
|
+
Promise.resolve({
|
|
163
|
+
...engine,
|
|
164
|
+
createWorkflowRuntime: (deps) =>
|
|
165
|
+
(runtime = engine.createWorkflowRuntime(deps)),
|
|
166
|
+
}),
|
|
167
|
+
});
|
|
168
|
+
return runtime!;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
it("refuses under OPEN a document the caller is not served", async () => {
|
|
172
|
+
const runtime = await hostRuntime(openAuthorization, { policed: true });
|
|
173
|
+
|
|
174
|
+
await expect(
|
|
175
|
+
runtime.cancelTriggerTestFor(DOCUMENT, contextFor()),
|
|
176
|
+
).rejects.toThrow();
|
|
177
|
+
await expect(
|
|
178
|
+
runtime.cancelTriggerTestFor(DOCUMENT, contextFor(OUTSIDER)),
|
|
179
|
+
).rejects.toThrow();
|
|
180
|
+
await expect(
|
|
181
|
+
runtime.cancelTriggerTestFor(DOCUMENT, contextFor(READER)),
|
|
182
|
+
).resolves.toBe(false);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("lists a connection only to the callers it is served to", async () => {
|
|
186
|
+
const runtime = await hostRuntime(openAuthorization, { policed: true });
|
|
187
|
+
const listed = async (address?: string) =>
|
|
188
|
+
(await runtime.connections(contextFor(address))).map(({ id }) => id);
|
|
189
|
+
|
|
190
|
+
expect(await listed(READER)).toEqual([CONNECTION]);
|
|
191
|
+
expect(await listed(OUTSIDER)).toEqual([]);
|
|
192
|
+
expect(await listed()).toEqual([]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("refuses a write the host's permissions deny", async () => {
|
|
196
|
+
const runtime = await hostRuntime(writerOnlyAuthorization, {
|
|
197
|
+
policed: false,
|
|
198
|
+
});
|
|
199
|
+
const startOAuth = (address: string) =>
|
|
200
|
+
runtime.startOAuth(CONNECTION, contextFor(address), {
|
|
201
|
+
redirectUri: "https://host/oauth/callback",
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await expect(startOAuth(READER)).rejects.toThrow("to write this document");
|
|
205
|
+
// Past the gate: refused for what the connection is, not who asked.
|
|
206
|
+
await expect(startOAuth(WRITER)).rejects.toThrow(
|
|
207
|
+
"does not sign in with OAuth2",
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
});
|