@synap-core/sdk 1.3.0 → 1.5.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/CHANGELOG.md +133 -0
- package/README.md +212 -12
- package/dist/{chunk-J7XHAAFJ.js → chunk-UK2U6SMA.js} +14 -2
- package/dist/chunk-UK2U6SMA.js.map +1 -0
- package/dist/{client-CVUT1Mcf.d.cts → client-B50KC2aJ.d.cts} +14 -9
- package/dist/{client-CVUT1Mcf.d.ts → client-B50KC2aJ.d.ts} +14 -9
- package/dist/connect.cjs +790 -161
- package/dist/connect.cjs.map +1 -1
- package/dist/connect.d.cts +610 -37
- package/dist/connect.d.ts +610 -37
- package/dist/connect.js +777 -162
- package/dist/connect.js.map +1 -1
- package/dist/public.cjs +9 -2
- package/dist/public.cjs.map +1 -1
- package/dist/public.d.cts +19 -4
- package/dist/public.d.ts +19 -4
- package/dist/public.js +8 -2
- package/dist/public.js.map +1 -1
- package/dist/testing.cjs +366 -17
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +190 -7
- package/dist/testing.d.ts +190 -7
- package/dist/testing.js +358 -17
- package/dist/testing.js.map +1 -1
- package/dist/{types-DpqPNaaa.d.cts → types-DZYNBCq7.d.cts} +47 -5
- package/dist/{types-DpqPNaaa.d.ts → types-DZYNBCq7.d.ts} +47 -5
- package/package.json +36 -12
- package/src/connect/auth.ts +314 -55
- package/src/connect/client.ts +472 -26
- package/src/connect/cloud.ts +210 -0
- package/src/connect/discovery.ts +79 -44
- package/src/connect/encoding.ts +1 -1
- package/src/connect/errors.ts +8 -1
- package/src/connect/flow.ts +80 -12
- package/src/connect/http.ts +42 -0
- package/src/connect/hub-types.ts +254 -0
- package/src/connect/index.ts +69 -23
- package/src/connect/rules.ts +18 -0
- package/src/connect/seal.ts +1 -1
- package/src/connect/store.ts +50 -8
- package/src/connect/types.ts +48 -4
- package/src/public/client.ts +14 -2
- package/src/public/safe-markdown.ts +13 -0
- package/src/public/share-model.ts +6 -2
- package/src/public/site-model.ts +5 -3
- package/src/testing/auth-server.ts +377 -20
- package/src/testing/cloud.ts +83 -0
- package/src/testing/fixtures.ts +26 -0
- package/src/testing/index.ts +10 -6
- package/src/testing/seed.ts +120 -0
- package/dist/chunk-J7XHAAFJ.js.map +0 -1
package/dist/testing.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PublicForm,
|
|
2
|
-
import { P as PodGrant, a as PodUser } from './types-
|
|
1
|
+
import { P as PublicForm, h as PublicShare, f as PublicRecord } from './client-B50KC2aJ.cjs';
|
|
2
|
+
import { P as PodGrant, a as PodUser, b as PodActsAs, C as ClientMetadataDocument } from './types-DZYNBCq7.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Demo data for the mock pod. Shapes are the `@synap-core/sdk/public` wire
|
|
@@ -8,6 +8,12 @@ import { P as PodGrant, a as PodUser } from './types-DpqPNaaa.cjs';
|
|
|
8
8
|
|
|
9
9
|
declare const MOCK_FORM: PublicForm;
|
|
10
10
|
declare const MOCK_SHARE: PublicShare;
|
|
11
|
+
/**
|
|
12
|
+
* A published HTML document (an estimate), as the pod serves an `html` body: a
|
|
13
|
+
* page renders it only inside `<iframe sandbox="" srcdoc>`, so the script below
|
|
14
|
+
* never runs.
|
|
15
|
+
*/
|
|
16
|
+
declare const MOCK_HTML_SHARE: PublicShare;
|
|
11
17
|
/** One collection of a mock site: its door config and its live records. */
|
|
12
18
|
interface MockCollection {
|
|
13
19
|
/** Default sort keys (`-` = descending); also the sort keys the door accepts. */
|
|
@@ -29,6 +35,90 @@ interface MockSite {
|
|
|
29
35
|
*/
|
|
30
36
|
declare const MOCK_SITE: MockSite;
|
|
31
37
|
|
|
38
|
+
/**
|
|
39
|
+
* The mock pod's data as ONE JSON-serialisable shape, so an app's mock mode,
|
|
40
|
+
* smoke and screenshots replay a recorded snapshot (one file) instead of
|
|
41
|
+
* fixtures built in app code:
|
|
42
|
+
*
|
|
43
|
+
* import { readFileSync } from "node:fs";
|
|
44
|
+
* const seed = parseMockPodSeed(readFileSync("mock/pod-seed.json", "utf8"));
|
|
45
|
+
* const pod = createMockAuthServer({ seed });
|
|
46
|
+
*
|
|
47
|
+
* Rows are the pod's wire shapes (see `@synap-core/sdk/connect` hub-types):
|
|
48
|
+
* `entities` as `GET /entities` answers them, `sessions` as `GET
|
|
49
|
+
* /focus-sessions` rows (missing columns get the pod's defaults), `documents`
|
|
50
|
+
* as `GET /documents/:id` (`sections` and `diagnostics` answered empty),
|
|
51
|
+
* `relations` as graph edges for `GET /entities/:id/connections`, `proposals`
|
|
52
|
+
* as `GET /proposals?view=basic` rows, `spaces` name each granted space's
|
|
53
|
+
* template (`synap_grant[].templateSlug` on the token response).
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
interface MockSeedEntity {
|
|
57
|
+
id: string;
|
|
58
|
+
profileSlug?: string;
|
|
59
|
+
workspaceId?: string | null;
|
|
60
|
+
title?: string | null;
|
|
61
|
+
properties?: Record<string, unknown>;
|
|
62
|
+
[key: string]: unknown;
|
|
63
|
+
}
|
|
64
|
+
interface MockSeedSession {
|
|
65
|
+
id: string;
|
|
66
|
+
workspaceId?: string | null;
|
|
67
|
+
projectId?: string | null;
|
|
68
|
+
goal?: string;
|
|
69
|
+
status?: string;
|
|
70
|
+
/** Slots; `owner: "human"` ones are what waits on the person. */
|
|
71
|
+
expectedOutputs?: Array<Record<string, unknown>>;
|
|
72
|
+
/** The entity it is about: listed among that entity's connections. */
|
|
73
|
+
subjectEntityId?: string | null;
|
|
74
|
+
/** ISO 8601; the list is newest first by it. */
|
|
75
|
+
startedAt?: string;
|
|
76
|
+
[key: string]: unknown;
|
|
77
|
+
}
|
|
78
|
+
interface MockSeedDocument {
|
|
79
|
+
id: string;
|
|
80
|
+
title: string;
|
|
81
|
+
content: string;
|
|
82
|
+
type?: string;
|
|
83
|
+
language?: string | null;
|
|
84
|
+
workspaceId?: string | null;
|
|
85
|
+
revision?: number;
|
|
86
|
+
version?: number;
|
|
87
|
+
createdAt?: string;
|
|
88
|
+
updatedAt?: string;
|
|
89
|
+
}
|
|
90
|
+
interface MockSeedRelation {
|
|
91
|
+
id: string;
|
|
92
|
+
sourceEntityId: string;
|
|
93
|
+
targetEntityId: string;
|
|
94
|
+
type: string;
|
|
95
|
+
createdAt?: string;
|
|
96
|
+
}
|
|
97
|
+
interface MockSeedSpace {
|
|
98
|
+
id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
/** The template it came from (e.g. `content-os`); absent = made by hand. */
|
|
101
|
+
templateSlug?: string;
|
|
102
|
+
}
|
|
103
|
+
interface MockPodSeed {
|
|
104
|
+
entities?: MockSeedEntity[];
|
|
105
|
+
sessions?: MockSeedSession[];
|
|
106
|
+
documents?: MockSeedDocument[];
|
|
107
|
+
relations?: MockSeedRelation[];
|
|
108
|
+
/** Missing fields default to a pending `create` of an `entity`, with null ids and the seed's load time. */
|
|
109
|
+
proposals?: Array<Partial<MockProposal> & {
|
|
110
|
+
id: string;
|
|
111
|
+
}>;
|
|
112
|
+
spaces?: MockSeedSpace[];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Read a seed (JSON text or an already-parsed value) and check its shape:
|
|
116
|
+
* every collection optional, each an array of objects carrying its required
|
|
117
|
+
* string fields; unknown top-level keys are refused (a typo would otherwise
|
|
118
|
+
* seed nothing). Throws `TypeError` naming the first bad path.
|
|
119
|
+
*/
|
|
120
|
+
declare function parseMockPodSeed(input: string | unknown): MockPodSeed;
|
|
121
|
+
|
|
32
122
|
/** A placeholder pod origin; never contacted. */
|
|
33
123
|
declare const MOCK_AUTH_ISSUER = "https://pod.mock-synap.test";
|
|
34
124
|
declare const MOCK_GRANT: PodGrant[];
|
|
@@ -36,7 +126,7 @@ declare const MOCK_USER: PodUser;
|
|
|
36
126
|
type MockHubFailure = "rate_limited" | "forbidden" | "door_unsupported" | "unreachable" | "server" | "unavailable";
|
|
37
127
|
interface MockAuthServerOptions {
|
|
38
128
|
issuer?: string;
|
|
39
|
-
/** Issue rotating refresh tokens
|
|
129
|
+
/** Issue rotating refresh tokens. Default true; false = a pod without refresh tokens. */
|
|
40
130
|
refresh?: boolean;
|
|
41
131
|
/** Default 3600 (the pod's 1 h). */
|
|
42
132
|
accessTtlSec?: number;
|
|
@@ -44,12 +134,38 @@ interface MockAuthServerOptions {
|
|
|
44
134
|
refreshIdleSec?: number;
|
|
45
135
|
/** Send `refresh_token_expires_in` with each refresh token. Default false (the pod does not say). */
|
|
46
136
|
advertiseRefreshExpiry?: boolean;
|
|
137
|
+
/** The reuse grace, seconds. Default 30 (the pod's); 0 = off (a pod without the grace). */
|
|
138
|
+
refreshGraceSec?: number;
|
|
139
|
+
/**
|
|
140
|
+
* `synap_app` on token responses: the connection's id and its page on the
|
|
141
|
+
* pod console. Default `MOCK_APP`; null = a pod that does not send it.
|
|
142
|
+
*/
|
|
143
|
+
app?: {
|
|
144
|
+
id: string;
|
|
145
|
+
manageUrl: string | null;
|
|
146
|
+
} | null;
|
|
147
|
+
/** Entities `GET /entities` answers (with `workspaceId` / `profileSlug` filters). */
|
|
148
|
+
entities?: Array<Record<string, unknown>>;
|
|
47
149
|
grant?: PodGrant[];
|
|
48
150
|
user?: PodUser;
|
|
49
151
|
/** Override or remove (undefined) metadata fields: a pod too old, a lying issuer. */
|
|
50
152
|
metadata?: Record<string, unknown>;
|
|
51
|
-
/**
|
|
153
|
+
/**
|
|
154
|
+
* Hub writes: "proposed" (ask-first, the default for an agent token) or
|
|
155
|
+
* "applied" (the default for `actsAs: "person"`).
|
|
156
|
+
*/
|
|
52
157
|
writeMode?: "proposed" | "applied";
|
|
158
|
+
/**
|
|
159
|
+
* `synap_acts_as` on token responses. Omitted = not sent (a pod that does
|
|
160
|
+
* not name it; the SDK reads an agent connection).
|
|
161
|
+
*/
|
|
162
|
+
actsAs?: PodActsAs;
|
|
163
|
+
/**
|
|
164
|
+
* The pod's data as one JSON shape (`parseMockPodSeed`). `seed.entities` come
|
|
165
|
+
* before `entities`; `seed.spaces` put a `templateSlug` on the grant rows of
|
|
166
|
+
* those spaces.
|
|
167
|
+
*/
|
|
168
|
+
seed?: MockPodSeed;
|
|
53
169
|
/** Hub paths (after /api/hub) that answer one failure. */
|
|
54
170
|
failures?: Record<string, MockHubFailure>;
|
|
55
171
|
now?: () => number;
|
|
@@ -70,6 +186,27 @@ interface AuthorizeDecision {
|
|
|
70
186
|
/** Override `state` on the answer: a forged answer. */
|
|
71
187
|
state?: string;
|
|
72
188
|
}
|
|
189
|
+
/** A proposal a governed write filed (`GET /proposals` lists the pending ones). */
|
|
190
|
+
interface MockProposal {
|
|
191
|
+
id: string;
|
|
192
|
+
proposalType: string;
|
|
193
|
+
targetType: string;
|
|
194
|
+
targetId: string | null;
|
|
195
|
+
status: "pending" | "approved" | "rejected";
|
|
196
|
+
workspaceId: string | null;
|
|
197
|
+
createdAt: string;
|
|
198
|
+
correlationId: string | null;
|
|
199
|
+
sessionId: string | null;
|
|
200
|
+
agentUserId: string | null;
|
|
201
|
+
summary?: string;
|
|
202
|
+
}
|
|
203
|
+
/** The connection's `synap_app` the mock pod answers by default (backend `appTokenResponse`). */
|
|
204
|
+
declare const MOCK_APP: {
|
|
205
|
+
id: string;
|
|
206
|
+
manageUrl: string;
|
|
207
|
+
};
|
|
208
|
+
/** The app's agent on the mock pod: who files its proposals. */
|
|
209
|
+
declare const MOCK_APP_AGENT_ID = "agent_mock_app";
|
|
73
210
|
interface MockAuthServer {
|
|
74
211
|
issuer: string;
|
|
75
212
|
fetch: typeof fetch;
|
|
@@ -80,6 +217,20 @@ interface MockAuthServer {
|
|
|
80
217
|
path: string;
|
|
81
218
|
body: unknown;
|
|
82
219
|
}>;
|
|
220
|
+
/** Proposals: the seeded ones, then those filed by proposed writes, in order. */
|
|
221
|
+
proposals: MockProposal[];
|
|
222
|
+
/** Proposals approved through `POST /proposals/:id/approve`, in order. */
|
|
223
|
+
approvals: string[];
|
|
224
|
+
/** The person approved or rejected one: it leaves the pending list. */
|
|
225
|
+
settleProposal(id: string, status: "approved" | "rejected"): void;
|
|
226
|
+
/** Another instance of the app rotated `token` (its own refresh): returns the pair the pod answered it. */
|
|
227
|
+
redeemElsewhere(token: string): Promise<{
|
|
228
|
+
access_token: string;
|
|
229
|
+
refresh_token: string;
|
|
230
|
+
expires_in: number;
|
|
231
|
+
}>;
|
|
232
|
+
/** Refresh redeems answered within the reuse grace (the same pair again). */
|
|
233
|
+
graceAnswers: number;
|
|
83
234
|
/** Stand-in for the consent screen: validates the request like /authorize, returns where the pod redirects. */
|
|
84
235
|
authorize(authorizeUrl: string, decision?: AuthorizeDecision): string;
|
|
85
236
|
/** Mark a refresh token used elsewhere: presenting it now is a REUSE (family + live key revoked). */
|
|
@@ -90,12 +241,43 @@ interface MockAuthServer {
|
|
|
90
241
|
reuses: number;
|
|
91
242
|
/** The person revoked the app: every token stops working. */
|
|
92
243
|
revokeAll(): void;
|
|
244
|
+
/**
|
|
245
|
+
* The live access keys stop working, the refresh tokens do not: what a
|
|
246
|
+
* narrow (the slot's key is revoked at once) or a pod-side "Revoke key"
|
|
247
|
+
* does. The next refresh mints a new key.
|
|
248
|
+
*/
|
|
249
|
+
revokeAccessKeys(): void;
|
|
93
250
|
/** Live (unrevoked) refresh tokens. */
|
|
94
251
|
liveRefreshTokens(): string[];
|
|
95
252
|
reset(): void;
|
|
96
253
|
}
|
|
97
254
|
declare function createMockAuthServer(options?: MockAuthServerOptions): MockAuthServer;
|
|
98
255
|
|
|
256
|
+
/** A placeholder Control Plane origin; never contacted. */
|
|
257
|
+
declare const MOCK_CLOUD_URL = "https://cp.mock-synap.test";
|
|
258
|
+
interface MockCloud {
|
|
259
|
+
url: string;
|
|
260
|
+
/** The chooser URLs the app sent browsers to. */
|
|
261
|
+
requests: string[];
|
|
262
|
+
/**
|
|
263
|
+
* The person picks `pod` on the chooser: validates the request like the CP,
|
|
264
|
+
* returns where the CP redirects the browser. Throws on a request the CP
|
|
265
|
+
* refuses (shown as a page there, never redirected).
|
|
266
|
+
*/
|
|
267
|
+
choose(location: string, opts: {
|
|
268
|
+
pod: string;
|
|
269
|
+
metadata: Pick<ClientMetadataDocument, "client_id" | "initiate_login_uri">;
|
|
270
|
+
}): string;
|
|
271
|
+
/** The person cancels on the chooser: `<initiate_login_uri>?state=…&error=access_denied`, no `iss`. */
|
|
272
|
+
cancel(location: string, opts: {
|
|
273
|
+
metadata: Pick<ClientMetadataDocument, "client_id" | "initiate_login_uri">;
|
|
274
|
+
}): string;
|
|
275
|
+
}
|
|
276
|
+
declare function createMockCloud(options?: {
|
|
277
|
+
url?: string;
|
|
278
|
+
allowInsecureLoopback?: boolean;
|
|
279
|
+
}): MockCloud;
|
|
280
|
+
|
|
99
281
|
/**
|
|
100
282
|
* @synap-core/sdk/testing — a mock pod: a fake `fetch` that answers the pod's
|
|
101
283
|
* public doors (forms, shares, sites) from fixtures, with no network. Inject
|
|
@@ -110,12 +292,12 @@ declare function createMockAuthServer(options?: MockAuthServerOptions): MockAuth
|
|
|
110
292
|
* `credentials: "include"` request is refused (it rejects, as a CORS failure
|
|
111
293
|
* does); with `{ browser: true }` a non-safelisted request header is too.
|
|
112
294
|
*
|
|
113
|
-
* tokens MOCK_TOKENS.form / .share / .site the demo form,
|
|
295
|
+
* tokens MOCK_TOKENS.form / .share / .htmlShare / .site the demo form, shares (markdown, html) and site
|
|
114
296
|
* tokens MOCK_FAILURE_TOKENS.* one failure on every door
|
|
115
297
|
* anything else the uniform 404
|
|
116
298
|
*
|
|
117
299
|
* Pod-app sign-in (`@synap-core/sdk/connect`): `createMockAuthServer()`, see
|
|
118
|
-
* ./auth-server.ts.
|
|
300
|
+
* ./auth-server.ts, seeded from one JSON shape (`parseMockPodSeed`, ./seed.ts); Synap Cloud's pod chooser: `createMockCloud()`, ./cloud.ts.
|
|
119
301
|
*/
|
|
120
302
|
|
|
121
303
|
/** A placeholder origin; never contacted. */
|
|
@@ -123,6 +305,7 @@ declare const MOCK_POD_URL = "https://mock.synap.invalid";
|
|
|
123
305
|
declare const MOCK_TOKENS: {
|
|
124
306
|
readonly form: "demo-form";
|
|
125
307
|
readonly share: "demo";
|
|
308
|
+
readonly htmlShare: "demo-html";
|
|
126
309
|
readonly site: "demo-site";
|
|
127
310
|
};
|
|
128
311
|
/** The `Retry-After` the `rateLimitedRetryAfter` token sends. */
|
|
@@ -178,4 +361,4 @@ interface MockPod {
|
|
|
178
361
|
}
|
|
179
362
|
declare function createMockPod(options?: MockPodOptions): MockPod;
|
|
180
363
|
|
|
181
|
-
export { type AuthorizeDecision, MOCK_AUTH_ISSUER, MOCK_FAILURE_TOKENS, MOCK_FORM, MOCK_GRANT, MOCK_POD_URL, MOCK_RETRY_AFTER_SEC, MOCK_SHARE, MOCK_SITE, MOCK_TOKENS, MOCK_USER, type MockAuthRequest, type MockAuthServer, type MockAuthServerOptions, type MockCollection, type MockHubFailure, type MockPod, type MockPodOptions, type MockRequest, type MockSite, type MockSubmission, createMockAuthServer, createMockPod };
|
|
364
|
+
export { type AuthorizeDecision, MOCK_APP, MOCK_APP_AGENT_ID, MOCK_AUTH_ISSUER, MOCK_CLOUD_URL, MOCK_FAILURE_TOKENS, MOCK_FORM, MOCK_GRANT, MOCK_HTML_SHARE, MOCK_POD_URL, MOCK_RETRY_AFTER_SEC, MOCK_SHARE, MOCK_SITE, MOCK_TOKENS, MOCK_USER, type MockAuthRequest, type MockAuthServer, type MockAuthServerOptions, type MockCloud, type MockCollection, type MockHubFailure, type MockPod, type MockPodOptions, type MockPodSeed, type MockProposal, type MockRequest, type MockSeedDocument, type MockSeedEntity, type MockSeedRelation, type MockSeedSession, type MockSeedSpace, type MockSite, type MockSubmission, createMockAuthServer, createMockCloud, createMockPod, parseMockPodSeed };
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PublicForm,
|
|
2
|
-
import { P as PodGrant, a as PodUser } from './types-
|
|
1
|
+
import { P as PublicForm, h as PublicShare, f as PublicRecord } from './client-B50KC2aJ.js';
|
|
2
|
+
import { P as PodGrant, a as PodUser, b as PodActsAs, C as ClientMetadataDocument } from './types-DZYNBCq7.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Demo data for the mock pod. Shapes are the `@synap-core/sdk/public` wire
|
|
@@ -8,6 +8,12 @@ import { P as PodGrant, a as PodUser } from './types-DpqPNaaa.js';
|
|
|
8
8
|
|
|
9
9
|
declare const MOCK_FORM: PublicForm;
|
|
10
10
|
declare const MOCK_SHARE: PublicShare;
|
|
11
|
+
/**
|
|
12
|
+
* A published HTML document (an estimate), as the pod serves an `html` body: a
|
|
13
|
+
* page renders it only inside `<iframe sandbox="" srcdoc>`, so the script below
|
|
14
|
+
* never runs.
|
|
15
|
+
*/
|
|
16
|
+
declare const MOCK_HTML_SHARE: PublicShare;
|
|
11
17
|
/** One collection of a mock site: its door config and its live records. */
|
|
12
18
|
interface MockCollection {
|
|
13
19
|
/** Default sort keys (`-` = descending); also the sort keys the door accepts. */
|
|
@@ -29,6 +35,90 @@ interface MockSite {
|
|
|
29
35
|
*/
|
|
30
36
|
declare const MOCK_SITE: MockSite;
|
|
31
37
|
|
|
38
|
+
/**
|
|
39
|
+
* The mock pod's data as ONE JSON-serialisable shape, so an app's mock mode,
|
|
40
|
+
* smoke and screenshots replay a recorded snapshot (one file) instead of
|
|
41
|
+
* fixtures built in app code:
|
|
42
|
+
*
|
|
43
|
+
* import { readFileSync } from "node:fs";
|
|
44
|
+
* const seed = parseMockPodSeed(readFileSync("mock/pod-seed.json", "utf8"));
|
|
45
|
+
* const pod = createMockAuthServer({ seed });
|
|
46
|
+
*
|
|
47
|
+
* Rows are the pod's wire shapes (see `@synap-core/sdk/connect` hub-types):
|
|
48
|
+
* `entities` as `GET /entities` answers them, `sessions` as `GET
|
|
49
|
+
* /focus-sessions` rows (missing columns get the pod's defaults), `documents`
|
|
50
|
+
* as `GET /documents/:id` (`sections` and `diagnostics` answered empty),
|
|
51
|
+
* `relations` as graph edges for `GET /entities/:id/connections`, `proposals`
|
|
52
|
+
* as `GET /proposals?view=basic` rows, `spaces` name each granted space's
|
|
53
|
+
* template (`synap_grant[].templateSlug` on the token response).
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
interface MockSeedEntity {
|
|
57
|
+
id: string;
|
|
58
|
+
profileSlug?: string;
|
|
59
|
+
workspaceId?: string | null;
|
|
60
|
+
title?: string | null;
|
|
61
|
+
properties?: Record<string, unknown>;
|
|
62
|
+
[key: string]: unknown;
|
|
63
|
+
}
|
|
64
|
+
interface MockSeedSession {
|
|
65
|
+
id: string;
|
|
66
|
+
workspaceId?: string | null;
|
|
67
|
+
projectId?: string | null;
|
|
68
|
+
goal?: string;
|
|
69
|
+
status?: string;
|
|
70
|
+
/** Slots; `owner: "human"` ones are what waits on the person. */
|
|
71
|
+
expectedOutputs?: Array<Record<string, unknown>>;
|
|
72
|
+
/** The entity it is about: listed among that entity's connections. */
|
|
73
|
+
subjectEntityId?: string | null;
|
|
74
|
+
/** ISO 8601; the list is newest first by it. */
|
|
75
|
+
startedAt?: string;
|
|
76
|
+
[key: string]: unknown;
|
|
77
|
+
}
|
|
78
|
+
interface MockSeedDocument {
|
|
79
|
+
id: string;
|
|
80
|
+
title: string;
|
|
81
|
+
content: string;
|
|
82
|
+
type?: string;
|
|
83
|
+
language?: string | null;
|
|
84
|
+
workspaceId?: string | null;
|
|
85
|
+
revision?: number;
|
|
86
|
+
version?: number;
|
|
87
|
+
createdAt?: string;
|
|
88
|
+
updatedAt?: string;
|
|
89
|
+
}
|
|
90
|
+
interface MockSeedRelation {
|
|
91
|
+
id: string;
|
|
92
|
+
sourceEntityId: string;
|
|
93
|
+
targetEntityId: string;
|
|
94
|
+
type: string;
|
|
95
|
+
createdAt?: string;
|
|
96
|
+
}
|
|
97
|
+
interface MockSeedSpace {
|
|
98
|
+
id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
/** The template it came from (e.g. `content-os`); absent = made by hand. */
|
|
101
|
+
templateSlug?: string;
|
|
102
|
+
}
|
|
103
|
+
interface MockPodSeed {
|
|
104
|
+
entities?: MockSeedEntity[];
|
|
105
|
+
sessions?: MockSeedSession[];
|
|
106
|
+
documents?: MockSeedDocument[];
|
|
107
|
+
relations?: MockSeedRelation[];
|
|
108
|
+
/** Missing fields default to a pending `create` of an `entity`, with null ids and the seed's load time. */
|
|
109
|
+
proposals?: Array<Partial<MockProposal> & {
|
|
110
|
+
id: string;
|
|
111
|
+
}>;
|
|
112
|
+
spaces?: MockSeedSpace[];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Read a seed (JSON text or an already-parsed value) and check its shape:
|
|
116
|
+
* every collection optional, each an array of objects carrying its required
|
|
117
|
+
* string fields; unknown top-level keys are refused (a typo would otherwise
|
|
118
|
+
* seed nothing). Throws `TypeError` naming the first bad path.
|
|
119
|
+
*/
|
|
120
|
+
declare function parseMockPodSeed(input: string | unknown): MockPodSeed;
|
|
121
|
+
|
|
32
122
|
/** A placeholder pod origin; never contacted. */
|
|
33
123
|
declare const MOCK_AUTH_ISSUER = "https://pod.mock-synap.test";
|
|
34
124
|
declare const MOCK_GRANT: PodGrant[];
|
|
@@ -36,7 +126,7 @@ declare const MOCK_USER: PodUser;
|
|
|
36
126
|
type MockHubFailure = "rate_limited" | "forbidden" | "door_unsupported" | "unreachable" | "server" | "unavailable";
|
|
37
127
|
interface MockAuthServerOptions {
|
|
38
128
|
issuer?: string;
|
|
39
|
-
/** Issue rotating refresh tokens
|
|
129
|
+
/** Issue rotating refresh tokens. Default true; false = a pod without refresh tokens. */
|
|
40
130
|
refresh?: boolean;
|
|
41
131
|
/** Default 3600 (the pod's 1 h). */
|
|
42
132
|
accessTtlSec?: number;
|
|
@@ -44,12 +134,38 @@ interface MockAuthServerOptions {
|
|
|
44
134
|
refreshIdleSec?: number;
|
|
45
135
|
/** Send `refresh_token_expires_in` with each refresh token. Default false (the pod does not say). */
|
|
46
136
|
advertiseRefreshExpiry?: boolean;
|
|
137
|
+
/** The reuse grace, seconds. Default 30 (the pod's); 0 = off (a pod without the grace). */
|
|
138
|
+
refreshGraceSec?: number;
|
|
139
|
+
/**
|
|
140
|
+
* `synap_app` on token responses: the connection's id and its page on the
|
|
141
|
+
* pod console. Default `MOCK_APP`; null = a pod that does not send it.
|
|
142
|
+
*/
|
|
143
|
+
app?: {
|
|
144
|
+
id: string;
|
|
145
|
+
manageUrl: string | null;
|
|
146
|
+
} | null;
|
|
147
|
+
/** Entities `GET /entities` answers (with `workspaceId` / `profileSlug` filters). */
|
|
148
|
+
entities?: Array<Record<string, unknown>>;
|
|
47
149
|
grant?: PodGrant[];
|
|
48
150
|
user?: PodUser;
|
|
49
151
|
/** Override or remove (undefined) metadata fields: a pod too old, a lying issuer. */
|
|
50
152
|
metadata?: Record<string, unknown>;
|
|
51
|
-
/**
|
|
153
|
+
/**
|
|
154
|
+
* Hub writes: "proposed" (ask-first, the default for an agent token) or
|
|
155
|
+
* "applied" (the default for `actsAs: "person"`).
|
|
156
|
+
*/
|
|
52
157
|
writeMode?: "proposed" | "applied";
|
|
158
|
+
/**
|
|
159
|
+
* `synap_acts_as` on token responses. Omitted = not sent (a pod that does
|
|
160
|
+
* not name it; the SDK reads an agent connection).
|
|
161
|
+
*/
|
|
162
|
+
actsAs?: PodActsAs;
|
|
163
|
+
/**
|
|
164
|
+
* The pod's data as one JSON shape (`parseMockPodSeed`). `seed.entities` come
|
|
165
|
+
* before `entities`; `seed.spaces` put a `templateSlug` on the grant rows of
|
|
166
|
+
* those spaces.
|
|
167
|
+
*/
|
|
168
|
+
seed?: MockPodSeed;
|
|
53
169
|
/** Hub paths (after /api/hub) that answer one failure. */
|
|
54
170
|
failures?: Record<string, MockHubFailure>;
|
|
55
171
|
now?: () => number;
|
|
@@ -70,6 +186,27 @@ interface AuthorizeDecision {
|
|
|
70
186
|
/** Override `state` on the answer: a forged answer. */
|
|
71
187
|
state?: string;
|
|
72
188
|
}
|
|
189
|
+
/** A proposal a governed write filed (`GET /proposals` lists the pending ones). */
|
|
190
|
+
interface MockProposal {
|
|
191
|
+
id: string;
|
|
192
|
+
proposalType: string;
|
|
193
|
+
targetType: string;
|
|
194
|
+
targetId: string | null;
|
|
195
|
+
status: "pending" | "approved" | "rejected";
|
|
196
|
+
workspaceId: string | null;
|
|
197
|
+
createdAt: string;
|
|
198
|
+
correlationId: string | null;
|
|
199
|
+
sessionId: string | null;
|
|
200
|
+
agentUserId: string | null;
|
|
201
|
+
summary?: string;
|
|
202
|
+
}
|
|
203
|
+
/** The connection's `synap_app` the mock pod answers by default (backend `appTokenResponse`). */
|
|
204
|
+
declare const MOCK_APP: {
|
|
205
|
+
id: string;
|
|
206
|
+
manageUrl: string;
|
|
207
|
+
};
|
|
208
|
+
/** The app's agent on the mock pod: who files its proposals. */
|
|
209
|
+
declare const MOCK_APP_AGENT_ID = "agent_mock_app";
|
|
73
210
|
interface MockAuthServer {
|
|
74
211
|
issuer: string;
|
|
75
212
|
fetch: typeof fetch;
|
|
@@ -80,6 +217,20 @@ interface MockAuthServer {
|
|
|
80
217
|
path: string;
|
|
81
218
|
body: unknown;
|
|
82
219
|
}>;
|
|
220
|
+
/** Proposals: the seeded ones, then those filed by proposed writes, in order. */
|
|
221
|
+
proposals: MockProposal[];
|
|
222
|
+
/** Proposals approved through `POST /proposals/:id/approve`, in order. */
|
|
223
|
+
approvals: string[];
|
|
224
|
+
/** The person approved or rejected one: it leaves the pending list. */
|
|
225
|
+
settleProposal(id: string, status: "approved" | "rejected"): void;
|
|
226
|
+
/** Another instance of the app rotated `token` (its own refresh): returns the pair the pod answered it. */
|
|
227
|
+
redeemElsewhere(token: string): Promise<{
|
|
228
|
+
access_token: string;
|
|
229
|
+
refresh_token: string;
|
|
230
|
+
expires_in: number;
|
|
231
|
+
}>;
|
|
232
|
+
/** Refresh redeems answered within the reuse grace (the same pair again). */
|
|
233
|
+
graceAnswers: number;
|
|
83
234
|
/** Stand-in for the consent screen: validates the request like /authorize, returns where the pod redirects. */
|
|
84
235
|
authorize(authorizeUrl: string, decision?: AuthorizeDecision): string;
|
|
85
236
|
/** Mark a refresh token used elsewhere: presenting it now is a REUSE (family + live key revoked). */
|
|
@@ -90,12 +241,43 @@ interface MockAuthServer {
|
|
|
90
241
|
reuses: number;
|
|
91
242
|
/** The person revoked the app: every token stops working. */
|
|
92
243
|
revokeAll(): void;
|
|
244
|
+
/**
|
|
245
|
+
* The live access keys stop working, the refresh tokens do not: what a
|
|
246
|
+
* narrow (the slot's key is revoked at once) or a pod-side "Revoke key"
|
|
247
|
+
* does. The next refresh mints a new key.
|
|
248
|
+
*/
|
|
249
|
+
revokeAccessKeys(): void;
|
|
93
250
|
/** Live (unrevoked) refresh tokens. */
|
|
94
251
|
liveRefreshTokens(): string[];
|
|
95
252
|
reset(): void;
|
|
96
253
|
}
|
|
97
254
|
declare function createMockAuthServer(options?: MockAuthServerOptions): MockAuthServer;
|
|
98
255
|
|
|
256
|
+
/** A placeholder Control Plane origin; never contacted. */
|
|
257
|
+
declare const MOCK_CLOUD_URL = "https://cp.mock-synap.test";
|
|
258
|
+
interface MockCloud {
|
|
259
|
+
url: string;
|
|
260
|
+
/** The chooser URLs the app sent browsers to. */
|
|
261
|
+
requests: string[];
|
|
262
|
+
/**
|
|
263
|
+
* The person picks `pod` on the chooser: validates the request like the CP,
|
|
264
|
+
* returns where the CP redirects the browser. Throws on a request the CP
|
|
265
|
+
* refuses (shown as a page there, never redirected).
|
|
266
|
+
*/
|
|
267
|
+
choose(location: string, opts: {
|
|
268
|
+
pod: string;
|
|
269
|
+
metadata: Pick<ClientMetadataDocument, "client_id" | "initiate_login_uri">;
|
|
270
|
+
}): string;
|
|
271
|
+
/** The person cancels on the chooser: `<initiate_login_uri>?state=…&error=access_denied`, no `iss`. */
|
|
272
|
+
cancel(location: string, opts: {
|
|
273
|
+
metadata: Pick<ClientMetadataDocument, "client_id" | "initiate_login_uri">;
|
|
274
|
+
}): string;
|
|
275
|
+
}
|
|
276
|
+
declare function createMockCloud(options?: {
|
|
277
|
+
url?: string;
|
|
278
|
+
allowInsecureLoopback?: boolean;
|
|
279
|
+
}): MockCloud;
|
|
280
|
+
|
|
99
281
|
/**
|
|
100
282
|
* @synap-core/sdk/testing — a mock pod: a fake `fetch` that answers the pod's
|
|
101
283
|
* public doors (forms, shares, sites) from fixtures, with no network. Inject
|
|
@@ -110,12 +292,12 @@ declare function createMockAuthServer(options?: MockAuthServerOptions): MockAuth
|
|
|
110
292
|
* `credentials: "include"` request is refused (it rejects, as a CORS failure
|
|
111
293
|
* does); with `{ browser: true }` a non-safelisted request header is too.
|
|
112
294
|
*
|
|
113
|
-
* tokens MOCK_TOKENS.form / .share / .site the demo form,
|
|
295
|
+
* tokens MOCK_TOKENS.form / .share / .htmlShare / .site the demo form, shares (markdown, html) and site
|
|
114
296
|
* tokens MOCK_FAILURE_TOKENS.* one failure on every door
|
|
115
297
|
* anything else the uniform 404
|
|
116
298
|
*
|
|
117
299
|
* Pod-app sign-in (`@synap-core/sdk/connect`): `createMockAuthServer()`, see
|
|
118
|
-
* ./auth-server.ts.
|
|
300
|
+
* ./auth-server.ts, seeded from one JSON shape (`parseMockPodSeed`, ./seed.ts); Synap Cloud's pod chooser: `createMockCloud()`, ./cloud.ts.
|
|
119
301
|
*/
|
|
120
302
|
|
|
121
303
|
/** A placeholder origin; never contacted. */
|
|
@@ -123,6 +305,7 @@ declare const MOCK_POD_URL = "https://mock.synap.invalid";
|
|
|
123
305
|
declare const MOCK_TOKENS: {
|
|
124
306
|
readonly form: "demo-form";
|
|
125
307
|
readonly share: "demo";
|
|
308
|
+
readonly htmlShare: "demo-html";
|
|
126
309
|
readonly site: "demo-site";
|
|
127
310
|
};
|
|
128
311
|
/** The `Retry-After` the `rateLimitedRetryAfter` token sends. */
|
|
@@ -178,4 +361,4 @@ interface MockPod {
|
|
|
178
361
|
}
|
|
179
362
|
declare function createMockPod(options?: MockPodOptions): MockPod;
|
|
180
363
|
|
|
181
|
-
export { type AuthorizeDecision, MOCK_AUTH_ISSUER, MOCK_FAILURE_TOKENS, MOCK_FORM, MOCK_GRANT, MOCK_POD_URL, MOCK_RETRY_AFTER_SEC, MOCK_SHARE, MOCK_SITE, MOCK_TOKENS, MOCK_USER, type MockAuthRequest, type MockAuthServer, type MockAuthServerOptions, type MockCollection, type MockHubFailure, type MockPod, type MockPodOptions, type MockRequest, type MockSite, type MockSubmission, createMockAuthServer, createMockPod };
|
|
364
|
+
export { type AuthorizeDecision, MOCK_APP, MOCK_APP_AGENT_ID, MOCK_AUTH_ISSUER, MOCK_CLOUD_URL, MOCK_FAILURE_TOKENS, MOCK_FORM, MOCK_GRANT, MOCK_HTML_SHARE, MOCK_POD_URL, MOCK_RETRY_AFTER_SEC, MOCK_SHARE, MOCK_SITE, MOCK_TOKENS, MOCK_USER, type MockAuthRequest, type MockAuthServer, type MockAuthServerOptions, type MockCloud, type MockCollection, type MockHubFailure, type MockPod, type MockPodOptions, type MockPodSeed, type MockProposal, type MockRequest, type MockSeedDocument, type MockSeedEntity, type MockSeedRelation, type MockSeedSession, type MockSeedSpace, type MockSite, type MockSubmission, createMockAuthServer, createMockCloud, createMockPod, parseMockPodSeed };
|