@sequenceholdings/artifact-studio 0.6.4 → 0.6.7
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/README.md +254 -10
- package/VERSION-PIN.md +78 -1
- package/dist/gateway/admission.d.ts +23 -1
- package/dist/gateway/admission.js +30 -13
- package/dist/gateway/control.d.ts +40 -0
- package/dist/gateway/control.js +76 -7
- package/dist/gateway/hosted-proxy.d.ts +11 -8
- package/dist/gateway/hosted-proxy.js +99 -91
- package/dist/gateway/ingress-admission.d.ts +77 -0
- package/dist/gateway/ingress-admission.js +150 -0
- package/dist/gateway/ingress-audit.d.ts +46 -0
- package/dist/gateway/ingress-audit.js +84 -0
- package/dist/gateway/ingress-context.d.ts +57 -0
- package/dist/gateway/ingress-context.js +57 -0
- package/dist/gateway/ingress-development.d.ts +12 -0
- package/dist/gateway/ingress-development.js +23 -0
- package/dist/gateway/ingress-handler.d.ts +44 -0
- package/dist/gateway/ingress-handler.js +320 -0
- package/dist/gateway/ingress-pass.d.ts +83 -0
- package/dist/gateway/ingress-pass.js +143 -0
- package/dist/gateway/ingress-response.d.ts +74 -0
- package/dist/gateway/ingress-response.js +98 -0
- package/dist/gateway/ingress-runtime.d.ts +86 -0
- package/dist/gateway/ingress-runtime.js +126 -0
- package/dist/gateway/ingress-snapshot.d.ts +54 -0
- package/dist/gateway/ingress-snapshot.js +63 -0
- package/dist/gateway/ingress-stages.d.ts +60 -0
- package/dist/gateway/ingress-stages.js +387 -0
- package/dist/gateway/ingress.d.ts +259 -0
- package/dist/gateway/ingress.js +204 -0
- package/dist/gateway/lattice-invoke.js +2 -0
- package/dist/gateway/lattice-upstream.d.ts +3 -4
- package/dist/gateway/lattice-upstream.js +11 -9
- package/dist/gateway/runtime-transport.d.ts +17 -0
- package/dist/gateway/runtime-transport.js +26 -0
- package/dist/gateway/server.d.ts +3 -0
- package/dist/gateway/server.js +21 -0
- package/dist/gateway/session-response.d.ts +15 -0
- package/dist/gateway/session-response.js +40 -0
- package/dist/gateway/usage.d.ts +8 -1
- package/dist/gateway-main.js +32 -8
- package/dist/manifest-next-browser-data.d.ts +17 -0
- package/dist/manifest-next-browser-data.js +46 -0
- package/dist/manifest-next-ingress.d.ts +5 -2
- package/dist/manifest-next-ingress.js +27 -2
- package/dist/manifest-next.d.ts +27 -10
- package/dist/manifest-next.js +16 -5
- package/dist/manifest.d.ts +13 -6
- package/dist/next-project.d.ts +4 -0
- package/dist/sdk.d.ts +12 -3
- package/dist/server/broker-contract.d.ts +62 -0
- package/dist/server/broker-contract.js +43 -1
- package/dist/server/client.js +38 -13
- package/dist/server/external-context.d.ts +8 -1
- package/dist/server/external-context.js +7 -2
- package/dist/server/internal/v1-context.js +17 -1
- package/dist/server/internal/verify-any.d.ts +25 -2
- package/dist/server/internal/verify-any.js +38 -4
- package/dist/server/process-run-sharing.d.ts +86 -0
- package/dist/server/process-run-sharing.js +29 -0
- package/dist/server/process-runs-page.d.ts +39 -0
- package/dist/server/process-runs-page.js +10 -0
- package/dist/server/processes.d.ts +8 -9
- package/dist/server/processes.js +4 -0
- package/dist/server/request-context.js +10 -3
- package/dist/server/runtime-config.d.ts +10 -1
- package/dist/server/runtime-config.js +11 -1
- package/dist/server/testing.d.ts +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.js +1 -1
- package/dist/tunnel/broker.d.ts +18 -0
- package/dist/tunnel/broker.js +175 -17
- package/dist/tunnel/client.d.ts +13 -0
- package/dist/tunnel/client.js +40 -2
- package/dist/tunnel/external.d.ts +51 -0
- package/dist/tunnel/external.js +10 -0
- package/dist/tunnel/index.d.ts +1 -1
- package/dist/tunnel/protocol.d.ts +20 -2
- package/dist/tunnel/protocol.js +42 -6
- package/package.json +5 -5
|
@@ -58,6 +58,7 @@ export declare const artifactNextExternalIngressSchema: z.ZodObject<{
|
|
|
58
58
|
data: z.ZodPrefault<z.ZodObject<{
|
|
59
59
|
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
60
60
|
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
61
|
+
operations: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
61
62
|
}, z.core.$strict>>;
|
|
62
63
|
api: z.ZodPrefault<z.ZodObject<{
|
|
63
64
|
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -123,6 +124,7 @@ export declare const artifactNextIngressSchema: z.ZodObject<{
|
|
|
123
124
|
data: z.ZodPrefault<z.ZodObject<{
|
|
124
125
|
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
125
126
|
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
127
|
+
operations: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
126
128
|
}, z.core.$strict>>;
|
|
127
129
|
api: z.ZodPrefault<z.ZodObject<{
|
|
128
130
|
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -203,8 +205,9 @@ interface ServerForIngress {
|
|
|
203
205
|
/**
|
|
204
206
|
* Cross-field rules between `server.ingress` and the rest of `server`: one ingress per provider,
|
|
205
207
|
* a service account distinct from the app's, capabilities inside the server's own (verbatim list
|
|
206
|
-
* membership; for storage, the same workspace at a role no higher than the server's
|
|
207
|
-
* the ingress
|
|
208
|
+
* membership; for storage, the same workspace at a role no higher than the server's; for
|
|
209
|
+
* `data.operations`, a namespace the ingress's own data reaches), and room for the ingress share of
|
|
210
|
+
* concurrency. Called from the server schema's refinement.
|
|
208
211
|
*/
|
|
209
212
|
export declare function validateIngressAgainstServer({ server, ctx }: {
|
|
210
213
|
server: ServerForIngress;
|
|
@@ -62,11 +62,17 @@ const evidenceEntrySchema = z.strictObject({
|
|
|
62
62
|
// external caller run any mutation the ORM role allows over that table, and evidence binding and
|
|
63
63
|
// freshness are keyed per named operation (D28; design C-3, "by name, no globs").
|
|
64
64
|
const ingressDataWriteRef = boundedRef.refine((ref) => ormOperationRef.test(ref), 'Ingress ORM writes name each operation: namespace/ops/Operation');
|
|
65
|
+
// The ingress service account holds grants that span every row its role reaches, so an external
|
|
66
|
+
// caller runs only the ORM operations its ingress names: these (reads) and `write`. The broker
|
|
67
|
+
// refuses any other persisted operation, query or mutation, before the data plane's table check
|
|
68
|
+
// (design §(g.3), "Operation allow-list"). By name, no globs, like `write`.
|
|
69
|
+
const ingressDataOperationRef = boundedRef.refine((ref) => ormOperationRef.test(ref), 'Ingress ORM operations name each operation: namespace/ops/Operation');
|
|
65
70
|
const ingressCapabilitiesSchema = z.strictObject({
|
|
66
71
|
storage: storageScopesSchema.optional(),
|
|
67
72
|
data: z.strictObject({
|
|
68
73
|
read: z.array(dataReadRef).max(256).default([]),
|
|
69
74
|
write: z.array(ingressDataWriteRef).max(256).default([]),
|
|
75
|
+
operations: z.array(ingressDataOperationRef).max(256).refine(unique, 'Ingress ORM operations must be unique').default([]),
|
|
70
76
|
}).prefault({}),
|
|
71
77
|
api: reachShape.api,
|
|
72
78
|
functions: reachShape.functions,
|
|
@@ -130,11 +136,29 @@ function requireMembers({ ctx, path, entries, allowed, label }) {
|
|
|
130
136
|
ctx.addIssue({ code: 'custom', path: [...path, index], message: `${entry} must also be declared in server.capabilities.${label}` });
|
|
131
137
|
}
|
|
132
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* An ingress operation ref names its namespace, and the ingress's own data capabilities (read or
|
|
141
|
+
* write) must reach it: an external caller's grant is built from those, so an operation in any other
|
|
142
|
+
* namespace could never run (the grant refuses its namespace before the allow-list is consulted).
|
|
143
|
+
* The ingress's read and write already sit inside the server's, so this also keeps the namespace
|
|
144
|
+
* inside the server's data. The operation itself need not be listed anywhere else: the data plane
|
|
145
|
+
* still checks its tables against the grant.
|
|
146
|
+
*/
|
|
147
|
+
function requireReachedNamespaces({ ctx, path, entries, reached }) {
|
|
148
|
+
const namespaces = new Set([...reached.read, ...reached.write].map((ref) => ref.split('/', 1)[0]));
|
|
149
|
+
for (const [index, entry] of entries.entries()) {
|
|
150
|
+
const namespace = entry.split('/', 1)[0];
|
|
151
|
+
if (!namespaces.has(namespace)) {
|
|
152
|
+
ctx.addIssue({ code: 'custom', path: [...path, index], message: `${entry} names namespace ${namespace}, which this ingress's capabilities.data does not reach` });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
133
156
|
/**
|
|
134
157
|
* Cross-field rules between `server.ingress` and the rest of `server`: one ingress per provider,
|
|
135
158
|
* a service account distinct from the app's, capabilities inside the server's own (verbatim list
|
|
136
|
-
* membership; for storage, the same workspace at a role no higher than the server's
|
|
137
|
-
* the ingress
|
|
159
|
+
* membership; for storage, the same workspace at a role no higher than the server's; for
|
|
160
|
+
* `data.operations`, a namespace the ingress's own data reaches), and room for the ingress share of
|
|
161
|
+
* concurrency. Called from the server schema's refinement.
|
|
138
162
|
*/
|
|
139
163
|
export function validateIngressAgainstServer({ server, ctx }) {
|
|
140
164
|
const external = server.ingress?.external;
|
|
@@ -171,6 +195,7 @@ export function validateIngressAgainstServer({ server, ctx }) {
|
|
|
171
195
|
const capabilities = [...at, 'capabilities'];
|
|
172
196
|
requireMembers({ ctx, path: [...capabilities, 'data', 'read'], entries: own.data.read, allowed: declared.data.read, label: 'data.read' });
|
|
173
197
|
requireMembers({ ctx, path: [...capabilities, 'data', 'write'], entries: own.data.write, allowed: declared.data.write, label: 'data.write' });
|
|
198
|
+
requireReachedNamespaces({ ctx, path: [...capabilities, 'data', 'operations'], entries: own.data.operations, reached: own.data });
|
|
174
199
|
requireMembers({ ctx, path: [...capabilities, 'api', 'read'], entries: own.api.read, allowed: declared.api.read, label: 'api.read' });
|
|
175
200
|
requireMembers({ ctx, path: [...capabilities, 'api', 'write'], entries: own.api.write, allowed: declared.api.write, label: 'api.write' });
|
|
176
201
|
requireMembers({ ctx, path: [...capabilities, 'functions', 'invoke'], entries: own.functions.invoke, allowed: declared.functions.invoke, label: 'functions.invoke' });
|
package/dist/manifest-next.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare const ARTIFACT_NEXT_RESOURCE_DEFAULTS: {
|
|
|
22
22
|
timeout_seconds: number;
|
|
23
23
|
};
|
|
24
24
|
export { artifactNextAuditCapabilitySchema };
|
|
25
|
+
export type { ArtifactNextBrowserData } from './manifest-next-browser-data.js';
|
|
25
26
|
export * from './manifest-next-ingress.js';
|
|
26
27
|
export declare const artifactNextServerCapabilitiesSchema: z.ZodPrefault<z.ZodObject<{
|
|
27
28
|
audit: z.ZodOptional<z.ZodObject<{
|
|
@@ -59,11 +60,15 @@ export declare const artifactNextResourceLimitsSchema: z.ZodPrefault<z.ZodObject
|
|
|
59
60
|
timeout_seconds: z.ZodDefault<z.ZodNumber>;
|
|
60
61
|
}, z.core.$strict>>;
|
|
61
62
|
export declare const artifactNextSecretNameSchema: z.ZodString;
|
|
62
|
-
export declare const artifactNextSecretBindingSchema: z.ZodObject<{
|
|
63
|
+
export declare const artifactNextSecretBindingSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
63
64
|
name: z.ZodString;
|
|
64
65
|
secret_id: z.ZodUUID;
|
|
65
66
|
version: z.ZodNumber;
|
|
66
|
-
}, z.core.$strict
|
|
67
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
68
|
+
name: z.ZodString;
|
|
69
|
+
secret_name: z.ZodString;
|
|
70
|
+
version: z.ZodNumber;
|
|
71
|
+
}, z.core.$strict>]>;
|
|
67
72
|
/** Pinned public assets fetched by the trusted preparation stage before offline compilation. */
|
|
68
73
|
export declare const artifactNextPublicFontSchema: z.ZodObject<{
|
|
69
74
|
path: z.ZodString;
|
|
@@ -107,11 +112,15 @@ export declare const artifactNextServerSchema: z.ZodPrefault<z.ZodObject<{
|
|
|
107
112
|
}, z.core.$strict>>;
|
|
108
113
|
}, z.core.$strict>>;
|
|
109
114
|
service_account: z.ZodOptional<z.ZodString>;
|
|
110
|
-
secrets: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
115
|
+
secrets: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
111
116
|
name: z.ZodString;
|
|
112
117
|
secret_id: z.ZodUUID;
|
|
113
118
|
version: z.ZodNumber;
|
|
114
|
-
}, z.core.$strict
|
|
119
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
120
|
+
name: z.ZodString;
|
|
121
|
+
secret_name: z.ZodString;
|
|
122
|
+
version: z.ZodNumber;
|
|
123
|
+
}, z.core.$strict>]>>>;
|
|
115
124
|
egress: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
116
125
|
resources: z.ZodPrefault<z.ZodObject<{
|
|
117
126
|
cpu: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<4>, z.ZodLiteral<8>]>>;
|
|
@@ -142,6 +151,7 @@ export declare const artifactNextServerSchema: z.ZodPrefault<z.ZodObject<{
|
|
|
142
151
|
data: z.ZodPrefault<z.ZodObject<{
|
|
143
152
|
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
144
153
|
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
154
|
+
operations: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
145
155
|
}, z.core.$strict>>;
|
|
146
156
|
api: z.ZodPrefault<z.ZodObject<{
|
|
147
157
|
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -219,6 +229,12 @@ export declare const artifactStudioNextManifestSchema: z.ZodObject<{
|
|
|
219
229
|
next: z.ZodDefault<z.ZodLiteral<"16.3">>;
|
|
220
230
|
}, z.core.$strict>>;
|
|
221
231
|
capabilities: z.ZodPrefault<z.ZodObject<{
|
|
232
|
+
data: z.ZodPrefault<z.ZodObject<{
|
|
233
|
+
operations: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
234
|
+
required_capabilities: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>>;
|
|
235
|
+
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
236
|
+
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
237
|
+
}, z.core.$strict>>;
|
|
222
238
|
allow_downloads: z.ZodDefault<z.ZodBoolean>;
|
|
223
239
|
allow_popups: z.ZodDefault<z.ZodBoolean>;
|
|
224
240
|
roles: z.ZodOptional<z.ZodObject<{
|
|
@@ -238,10 +254,6 @@ export declare const artifactStudioNextManifestSchema: z.ZodObject<{
|
|
|
238
254
|
editor: "editor";
|
|
239
255
|
admin: "admin";
|
|
240
256
|
}>>>;
|
|
241
|
-
data: z.ZodPrefault<z.ZodObject<{
|
|
242
|
-
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
243
|
-
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
244
|
-
}, z.core.$strict>>;
|
|
245
257
|
api: z.ZodPrefault<z.ZodObject<{
|
|
246
258
|
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
247
259
|
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -291,11 +303,15 @@ export declare const artifactStudioNextManifestSchema: z.ZodObject<{
|
|
|
291
303
|
}, z.core.$strict>>;
|
|
292
304
|
}, z.core.$strict>>;
|
|
293
305
|
service_account: z.ZodOptional<z.ZodString>;
|
|
294
|
-
secrets: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
306
|
+
secrets: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
295
307
|
name: z.ZodString;
|
|
296
308
|
secret_id: z.ZodUUID;
|
|
297
309
|
version: z.ZodNumber;
|
|
298
|
-
}, z.core.$strict
|
|
310
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
311
|
+
name: z.ZodString;
|
|
312
|
+
secret_name: z.ZodString;
|
|
313
|
+
version: z.ZodNumber;
|
|
314
|
+
}, z.core.$strict>]>>>;
|
|
299
315
|
egress: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
300
316
|
resources: z.ZodPrefault<z.ZodObject<{
|
|
301
317
|
cpu: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<4>, z.ZodLiteral<8>]>>;
|
|
@@ -326,6 +342,7 @@ export declare const artifactStudioNextManifestSchema: z.ZodObject<{
|
|
|
326
342
|
data: z.ZodPrefault<z.ZodObject<{
|
|
327
343
|
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
328
344
|
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
345
|
+
operations: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
329
346
|
}, z.core.$strict>>;
|
|
330
347
|
api: z.ZodPrefault<z.ZodObject<{
|
|
331
348
|
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
package/dist/manifest-next.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ontologyContractSchema } from '@sequenceholdings/orm/canonical';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { capabilityPinsSchema, customRolesBlockSchema, validateCapabilityPins } from './manifest-custom-roles.js';
|
|
4
|
+
import { artifactNextBrowserDataSchema, validateBrowserDataAdmission } from './manifest-next-browser-data.js';
|
|
4
5
|
import { isManagedServerInvokePath } from './server/managed-server-path.js';
|
|
5
6
|
import { artifactNextIngressSchema, validateIngressAgainstServer } from './manifest-next-ingress.js';
|
|
6
7
|
import { artifactNextAuditCapabilitySchema, reachShape, serviceAccountRef } from './manifest-next-reach.js';
|
|
@@ -25,6 +26,7 @@ const strictCustomRoles = customRolesBlockSchema.safeExtend({
|
|
|
25
26
|
}).strict();
|
|
26
27
|
const browserCapabilities = z.strictObject({
|
|
27
28
|
...reachShape,
|
|
29
|
+
data: artifactNextBrowserDataSchema,
|
|
28
30
|
allow_downloads: z.boolean().default(false),
|
|
29
31
|
allow_popups: z.boolean().default(false),
|
|
30
32
|
roles: strictCustomRoles.optional(),
|
|
@@ -47,11 +49,19 @@ export const artifactNextResourceLimitsSchema = z.strictObject({
|
|
|
47
49
|
const envName = z.string().max(128).regex(/^[A-Z][A-Z0-9_]*$/);
|
|
48
50
|
const reservedServerEnv = /^(NEXT_PUBLIC_|ARTIFACT_|SEQUENCE_|GOOGLE_|GCP_|GCLOUD_|DOPPLER_|MODAL_|AUTH0_|NPM_CONFIG_|npm_config_)/;
|
|
49
51
|
export const artifactNextSecretNameSchema = envName.refine((name) => !reservedServerEnv.test(name) && !['NODE_OPTIONS', 'NODE_ENV', 'PORT', 'HOSTNAME', 'PATH', 'HOME', 'LD_PRELOAD', 'LD_LIBRARY_PATH'].includes(name), 'Secret names cannot override platform/runtime environment variables or expose NEXT_PUBLIC_ values');
|
|
50
|
-
export const artifactNextSecretBindingSchema = z.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
export const artifactNextSecretBindingSchema = z.union([
|
|
53
|
+
z.strictObject({
|
|
54
|
+
name: artifactNextSecretNameSchema,
|
|
55
|
+
secret_id: z.uuid(),
|
|
56
|
+
version: z.number().int().positive(),
|
|
57
|
+
}),
|
|
58
|
+
z.strictObject({
|
|
59
|
+
name: artifactNextSecretNameSchema,
|
|
60
|
+
// A registry name can differ from the mounted env name, including its reserved prefixes.
|
|
61
|
+
secret_name: z.string().max(255).regex(/^[A-Z][A-Z0-9_]*$/),
|
|
62
|
+
version: z.number().int().positive(),
|
|
63
|
+
}),
|
|
64
|
+
]);
|
|
55
65
|
const publicEnvironmentName = envName.refine((name) => name.startsWith('NEXT_PUBLIC_') && !/(^|_)(SECRET|TOKEN|PASSWORD|CREDENTIALS?|PRIVATE_KEY|API_KEY)(_|$)/.test(name), 'Public build variables require NEXT_PUBLIC_ and must not contain secret, token, password, credential, or private/API key names');
|
|
56
66
|
/** Pinned public assets fetched by the trusted preparation stage before offline compilation. */
|
|
57
67
|
export const artifactNextPublicFontSchema = z.strictObject({
|
|
@@ -117,6 +127,7 @@ export const artifactStudioNextManifestSchema = z.strictObject({
|
|
|
117
127
|
sidebarIcon: z.string().max(64).optional(),
|
|
118
128
|
sidebarBadge: sidebarBadgeSchema.strict().optional(),
|
|
119
129
|
}).superRefine((manifest, ctx) => {
|
|
130
|
+
validateBrowserDataAdmission({ ...manifest.capabilities, ctx });
|
|
120
131
|
for (const message of validateCapabilityPins({ uses: manifest.capabilities.uses, roles: manifest.capabilities.roles })) {
|
|
121
132
|
ctx.addIssue({ code: 'custom', path: ['capabilities', 'uses'], message });
|
|
122
133
|
}
|
package/dist/manifest.d.ts
CHANGED
|
@@ -112,6 +112,12 @@ export declare const artifactStudioManifestSchema: z.ZodUnion<readonly [z.ZodObj
|
|
|
112
112
|
next: z.ZodDefault<z.ZodLiteral<"16.3">>;
|
|
113
113
|
}, z.core.$strict>>;
|
|
114
114
|
capabilities: z.ZodPrefault<z.ZodObject<{
|
|
115
|
+
data: z.ZodPrefault<z.ZodObject<{
|
|
116
|
+
operations: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
117
|
+
required_capabilities: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>>;
|
|
118
|
+
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
119
|
+
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
120
|
+
}, z.core.$strict>>;
|
|
115
121
|
allow_downloads: z.ZodDefault<z.ZodBoolean>;
|
|
116
122
|
allow_popups: z.ZodDefault<z.ZodBoolean>;
|
|
117
123
|
roles: z.ZodOptional<z.ZodObject<{
|
|
@@ -131,10 +137,6 @@ export declare const artifactStudioManifestSchema: z.ZodUnion<readonly [z.ZodObj
|
|
|
131
137
|
editor: "editor";
|
|
132
138
|
admin: "admin";
|
|
133
139
|
}>>>;
|
|
134
|
-
data: z.ZodPrefault<z.ZodObject<{
|
|
135
|
-
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
136
|
-
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
137
|
-
}, z.core.$strict>>;
|
|
138
140
|
api: z.ZodPrefault<z.ZodObject<{
|
|
139
141
|
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
140
142
|
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -184,11 +186,15 @@ export declare const artifactStudioManifestSchema: z.ZodUnion<readonly [z.ZodObj
|
|
|
184
186
|
}, z.core.$strict>>;
|
|
185
187
|
}, z.core.$strict>>;
|
|
186
188
|
service_account: z.ZodOptional<z.ZodString>;
|
|
187
|
-
secrets: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
189
|
+
secrets: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
188
190
|
name: z.ZodString;
|
|
189
191
|
secret_id: z.ZodUUID;
|
|
190
192
|
version: z.ZodNumber;
|
|
191
|
-
}, z.core.$strict
|
|
193
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
194
|
+
name: z.ZodString;
|
|
195
|
+
secret_name: z.ZodString;
|
|
196
|
+
version: z.ZodNumber;
|
|
197
|
+
}, z.core.$strict>]>>>;
|
|
192
198
|
egress: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
193
199
|
resources: z.ZodPrefault<z.ZodObject<{
|
|
194
200
|
cpu: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<4>, z.ZodLiteral<8>]>>;
|
|
@@ -219,6 +225,7 @@ export declare const artifactStudioManifestSchema: z.ZodUnion<readonly [z.ZodObj
|
|
|
219
225
|
data: z.ZodPrefault<z.ZodObject<{
|
|
220
226
|
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
221
227
|
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
228
|
+
operations: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
222
229
|
}, z.core.$strict>>;
|
|
223
230
|
api: z.ZodPrefault<z.ZodObject<{
|
|
224
231
|
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
package/dist/next-project.d.ts
CHANGED
|
@@ -17,6 +17,10 @@ export declare function createNextChildEnv({ homeDir, publicEnv, publicOrigin, e
|
|
|
17
17
|
publicEnv: Record<string, string>;
|
|
18
18
|
publicOrigin: string;
|
|
19
19
|
egressHosts?: readonly string[];
|
|
20
|
+
/**
|
|
21
|
+
* The development lease file. Its path in SEQUENCE_ARTIFACT_RUNTIME_FILE is what marks the
|
|
22
|
+
* runtime as a development lease (`runtimeConfig.development`); a hosted runtime never sets it.
|
|
23
|
+
*/
|
|
20
24
|
runtimeFile?: string;
|
|
21
25
|
source?: NodeJS.ProcessEnv;
|
|
22
26
|
}): NodeJS.ProcessEnv;
|
package/dist/sdk.d.ts
CHANGED
|
@@ -13,6 +13,17 @@ export interface SequenceStorageOptions {
|
|
|
13
13
|
}) => void;
|
|
14
14
|
}
|
|
15
15
|
export interface SequenceStorage {
|
|
16
|
+
/** Host-owned, caller-authorized PDF/image dialog. Requires a pinned version and declared Storage scope.
|
|
17
|
+
* Older Atlas builds omit this method; feature-detect before calling. */
|
|
18
|
+
preview?(input: {
|
|
19
|
+
workspaceId: string;
|
|
20
|
+
fileId: string;
|
|
21
|
+
versionId: string;
|
|
22
|
+
filename: string;
|
|
23
|
+
mime: string;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
opened: true;
|
|
26
|
+
}>;
|
|
16
27
|
upload(workspaceId: string, input: StartStorageUpload, file: Blob, options?: SequenceStorageOptions): Promise<StorageUploadSession>;
|
|
17
28
|
resume(workspaceId: string, uploadId: string, file: Blob, options?: SequenceStorageOptions): Promise<StorageUploadSession>;
|
|
18
29
|
}
|
|
@@ -400,9 +411,7 @@ export declare function createArtifactStorageClient(sdk?: SequenceArtifactSdk):
|
|
|
400
411
|
size: number | null;
|
|
401
412
|
contentType: string | null;
|
|
402
413
|
}>;
|
|
403
|
-
createFolder: (name: string, parentId
|
|
404
|
-
/** The specific step run (the assignee's task) — the step-scoped id reviewers hold access to. */
|
|
405
|
-
? /** The specific step run (the assignee's task) — the step-scoped id reviewers hold access to. */: string | null, { idempotencyKey, }?: {
|
|
414
|
+
createFolder: (name: string, parentId?: string | null, { idempotencyKey, }?: {
|
|
406
415
|
idempotencyKey?: string;
|
|
407
416
|
}) => Promise<{
|
|
408
417
|
id: string;
|
|
@@ -18,6 +18,12 @@ export declare function isArtifactApiPath(path: string): boolean;
|
|
|
18
18
|
export declare const ARTIFACT_PROCESS_RUN_STATUSES: readonly ["pending", "running", "completed", "failed", "cancelled", "timed_out"];
|
|
19
19
|
/** Lattice lists at most this many runs per call. */
|
|
20
20
|
export declare const ARTIFACT_PROCESS_RUNS_LIMIT = 500;
|
|
21
|
+
/** A run page can filter to at most this many entities of one type. */
|
|
22
|
+
export declare const ARTIFACT_PROCESS_RUN_ENTITIES_LIMIT = 500;
|
|
23
|
+
/** A run share adds collaborators to at most this many explicitly assigned human nodes. */
|
|
24
|
+
export declare const ARTIFACT_PROCESS_SHARE_NODES_LIMIT = 100;
|
|
25
|
+
/** An owner may list at most this many sharing authorities per page. */
|
|
26
|
+
export declare const ARTIFACT_PROCESS_SHARING_AUTHORITIES_LIMIT = 50;
|
|
21
27
|
/** A run-events read returns at most this many events per call, as the run's stream drains them. */
|
|
22
28
|
export declare const ARTIFACT_PROCESS_RUN_EVENTS_LIMIT = 1000;
|
|
23
29
|
/** Which of the user's human steps to list: parked and waiting on them, or already done. */
|
|
@@ -31,6 +37,8 @@ export declare const ARTIFACT_PROCESS_PRINCIPAL_SEARCH_LIMIT = 50;
|
|
|
31
37
|
export declare const ARTIFACT_PROCESS_PRINCIPAL_TYPES: readonly ["user", "group"];
|
|
32
38
|
/** A start seeds assignments for at most this many human nodes. */
|
|
33
39
|
export declare const ARTIFACT_PROCESS_START_ASSIGNMENT_NODES = 64;
|
|
40
|
+
/** The data plane's own idempotency-key rule (`orm-data-plane/src/runtime.ts`), so a key the SDK sends is one it accepts. */
|
|
41
|
+
export declare const artifactOrmIdempotencyKeySchema: z.ZodString;
|
|
34
42
|
export declare const ARTIFACT_FLAGS_PER_REQUEST = 32;
|
|
35
43
|
/** The audit actions an application may record. Sign-in, identity merges and webhooks stay Atlas's. */
|
|
36
44
|
export declare const ARTIFACT_AUDIT_ACTIONS: readonly ["view", "search", "create", "update", "delete", "download", "upload", "export", "import", "share", "submit", "approve", "reject", "execute"];
|
|
@@ -64,6 +72,7 @@ export declare const artifactServerRequestSchema: z.ZodDiscriminatedUnion<[z.Zod
|
|
|
64
72
|
op: z.ZodString;
|
|
65
73
|
variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
66
74
|
jsonScalars: z.ZodOptional<z.ZodLiteral<"lossless-v1">>;
|
|
75
|
+
idempotencyKey: z.ZodOptional<z.ZodString>;
|
|
67
76
|
}, z.core.$strict>, z.ZodObject<{
|
|
68
77
|
operation: z.ZodLiteral<"storage">;
|
|
69
78
|
mode: z.ZodDefault<z.ZodEnum<{
|
|
@@ -128,6 +137,29 @@ export declare const artifactServerRequestSchema: z.ZodDiscriminatedUnion<[z.Zod
|
|
|
128
137
|
}, z.core.$strict>>;
|
|
129
138
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
130
139
|
archived: z.ZodOptional<z.ZodBoolean>;
|
|
140
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
141
|
+
operation: z.ZodLiteral<"lattice_runs_page">;
|
|
142
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
143
|
+
service: "service";
|
|
144
|
+
user: "user";
|
|
145
|
+
}>>;
|
|
146
|
+
process: z.ZodString;
|
|
147
|
+
statuses: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
148
|
+
running: "running";
|
|
149
|
+
failed: "failed";
|
|
150
|
+
cancelled: "cancelled";
|
|
151
|
+
pending: "pending";
|
|
152
|
+
completed: "completed";
|
|
153
|
+
timed_out: "timed_out";
|
|
154
|
+
}>>>;
|
|
155
|
+
entity: z.ZodOptional<z.ZodObject<{
|
|
156
|
+
type: z.ZodString;
|
|
157
|
+
id: z.ZodOptional<z.ZodString>;
|
|
158
|
+
ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
159
|
+
}, z.core.$strict>>;
|
|
160
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
161
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
162
|
+
archived: z.ZodOptional<z.ZodBoolean>;
|
|
131
163
|
}, z.core.$strict>, z.ZodObject<{
|
|
132
164
|
operation: z.ZodLiteral<"lattice_run">;
|
|
133
165
|
mode: z.ZodDefault<z.ZodEnum<{
|
|
@@ -184,6 +216,36 @@ export declare const artifactServerRequestSchema: z.ZodDiscriminatedUnion<[z.Zod
|
|
|
184
216
|
}>>;
|
|
185
217
|
runId: z.ZodString;
|
|
186
218
|
stepId: z.ZodString;
|
|
219
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
220
|
+
delegateSharing: z.ZodOptional<z.ZodObject<{
|
|
221
|
+
audience: z.ZodLiteral<"current_app">;
|
|
222
|
+
}, z.core.$strict>>;
|
|
223
|
+
users: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
224
|
+
groups: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
225
|
+
operation: z.ZodLiteral<"lattice_share_run">;
|
|
226
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
227
|
+
service: "service";
|
|
228
|
+
user: "user";
|
|
229
|
+
}>>;
|
|
230
|
+
runId: z.ZodString;
|
|
231
|
+
nodeIds: z.ZodArray<z.ZodString>;
|
|
232
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
233
|
+
operation: z.ZodLiteral<"lattice_sharing_authorities">;
|
|
234
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
235
|
+
service: "service";
|
|
236
|
+
user: "user";
|
|
237
|
+
}>>;
|
|
238
|
+
runId: z.ZodString;
|
|
239
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
240
|
+
cursor: z.ZodOptional<z.ZodUUID>;
|
|
241
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
242
|
+
operation: z.ZodLiteral<"lattice_sharing_authority_revoke">;
|
|
243
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
244
|
+
service: "service";
|
|
245
|
+
user: "user";
|
|
246
|
+
}>>;
|
|
247
|
+
runId: z.ZodString;
|
|
248
|
+
authorityId: z.ZodUUID;
|
|
187
249
|
}, z.core.$strict>, z.ZodObject<{
|
|
188
250
|
operation: z.ZodLiteral<"lattice_cancel">;
|
|
189
251
|
mode: z.ZodDefault<z.ZodEnum<{
|
|
@@ -31,6 +31,12 @@ const latticeVersion = z.string().min(1).max(128).regex(/^[A-Za-z0-9._-]+$/);
|
|
|
31
31
|
export const ARTIFACT_PROCESS_RUN_STATUSES = ['pending', 'running', 'completed', 'failed', 'cancelled', 'timed_out'];
|
|
32
32
|
/** Lattice lists at most this many runs per call. */
|
|
33
33
|
export const ARTIFACT_PROCESS_RUNS_LIMIT = 500;
|
|
34
|
+
/** A run page can filter to at most this many entities of one type. */
|
|
35
|
+
export const ARTIFACT_PROCESS_RUN_ENTITIES_LIMIT = 500;
|
|
36
|
+
/** A run share adds collaborators to at most this many explicitly assigned human nodes. */
|
|
37
|
+
export const ARTIFACT_PROCESS_SHARE_NODES_LIMIT = 100;
|
|
38
|
+
/** An owner may list at most this many sharing authorities per page. */
|
|
39
|
+
export const ARTIFACT_PROCESS_SHARING_AUTHORITIES_LIMIT = 50;
|
|
34
40
|
/** A run-events read returns at most this many events per call, as the run's stream drains them. */
|
|
35
41
|
export const ARTIFACT_PROCESS_RUN_EVENTS_LIMIT = 1000;
|
|
36
42
|
/** Which of the user's human steps to list: parked and waiting on them, or already done. */
|
|
@@ -65,8 +71,14 @@ const assignmentNodeKey = z.string().min(1).max(255).regex(/^[A-Za-z0-9._-]+(?::
|
|
|
65
71
|
const runAssignments = z.record(assignmentNodeKey, z.object(principalSet).strict().refine(namesPrincipals, PRINCIPAL_SET_MESSAGE))
|
|
66
72
|
.refine((value) => Object.keys(value).length > 0 && Object.keys(value).length <= ARTIFACT_PROCESS_START_ASSIGNMENT_NODES, `Between 1 and ${ARTIFACT_PROCESS_START_ASSIGNMENT_NODES} assigned nodes`);
|
|
67
73
|
const entityRef = z.string().min(1).max(255);
|
|
74
|
+
const runPageEntity = z.object({
|
|
75
|
+
type: entityRef, id: entityRef.optional(),
|
|
76
|
+
ids: z.array(entityRef).min(1).max(ARTIFACT_PROCESS_RUN_ENTITIES_LIMIT).optional(),
|
|
77
|
+
}).strict().refine(value => value.id === undefined || value.ids === undefined, 'Name one entity id or a set of ids, not both');
|
|
68
78
|
// Unleash feature names are URL-safe.
|
|
69
79
|
const flagName = z.string().min(1).max(255).regex(/^[A-Za-z0-9._~-]+$/);
|
|
80
|
+
/** The data plane's own idempotency-key rule (`orm-data-plane/src/runtime.ts`), so a key the SDK sends is one it accepts. */
|
|
81
|
+
export const artifactOrmIdempotencyKeySchema = z.string().regex(/^[A-Za-z0-9._:-]{1,128}$/);
|
|
70
82
|
export const ARTIFACT_FLAGS_PER_REQUEST = 32;
|
|
71
83
|
/** The audit actions an application may record. Sign-in, identity merges and webhooks stay Atlas's. */
|
|
72
84
|
export const ARTIFACT_AUDIT_ACTIONS = [
|
|
@@ -97,10 +109,13 @@ const auditEvent = {
|
|
|
97
109
|
export const artifactServerRequestSchema = z.discriminatedUnion('operation', [
|
|
98
110
|
z.object({ operation: z.literal('api'), mode, method, path, body: z.unknown().optional() }).strict(),
|
|
99
111
|
// jsonScalars asks the data plane for exact numeric and bigint strings; Atlas forwards it as
|
|
100
|
-
// the data plane's own header and passes the acknowledgment back.
|
|
112
|
+
// the data plane's own header and passes the acknowledgment back. idempotencyKey names one
|
|
113
|
+
// logical mutation so a retry returns its receipt; Atlas forwards it in the data plane body
|
|
114
|
+
// (re-scoped per caller for external principals) and the data plane refuses it on a query.
|
|
101
115
|
z.object({
|
|
102
116
|
operation: z.literal('orm'), mode, namespace: id, op: z.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
103
117
|
variables: z.record(z.string(), z.unknown()).optional(), jsonScalars: z.literal(ORM_LOSSLESS_JSON_SCALARS).optional(),
|
|
118
|
+
idempotencyKey: artifactOrmIdempotencyKeySchema.optional(),
|
|
104
119
|
}).strict(),
|
|
105
120
|
z.object({ operation: z.literal('storage'), mode, method, path: path.refine(value => {
|
|
106
121
|
const pathname = value.split('?')[0];
|
|
@@ -128,6 +143,17 @@ export const artifactServerRequestSchema = z.discriminatedUnion('operation', [
|
|
|
128
143
|
limit: z.number().int().min(1).max(ARTIFACT_PROCESS_RUNS_LIMIT).optional(),
|
|
129
144
|
archived: z.boolean().optional(),
|
|
130
145
|
}).strict(),
|
|
146
|
+
// Keyset pagination selects a bounded window from the principal's authorized runs.
|
|
147
|
+
// Continuation is returned only when another visible run exists; denied rows do not expose a cursor.
|
|
148
|
+
z.object({
|
|
149
|
+
operation: z.literal('lattice_runs_page'), mode, process: latticeId,
|
|
150
|
+
statuses: z.array(z.enum(ARTIFACT_PROCESS_RUN_STATUSES)).min(1).max(ARTIFACT_PROCESS_RUN_STATUSES.length)
|
|
151
|
+
.refine(value => new Set(value).size === value.length, 'Statuses must be unique').optional(),
|
|
152
|
+
entity: runPageEntity.optional(),
|
|
153
|
+
limit: z.number().int().min(1).max(ARTIFACT_PROCESS_RUNS_LIMIT).optional(),
|
|
154
|
+
cursor: z.string().min(1).max(2048).optional(),
|
|
155
|
+
archived: z.boolean().optional(),
|
|
156
|
+
}).strict(),
|
|
131
157
|
// Reads one run the user may view, as `GET /api/lattice/runs/[runId]` does: the run, its steps, the
|
|
132
158
|
// collapsed per-node states and the event cursor to resume its stream from.
|
|
133
159
|
z.object({ operation: z.literal('lattice_run'), mode, runId: latticeId }).strict(),
|
|
@@ -162,6 +188,22 @@ export const artifactServerRequestSchema = z.discriminatedUnion('operation', [
|
|
|
162
188
|
// The user's run `owner` relation decides; the node's assignee allowlist still applies.
|
|
163
189
|
z.object({ operation: z.literal('lattice_assign'), mode, runId: latticeId, stepId: latticeId, ...principalSet }).strict()
|
|
164
190
|
.refine(namesPrincipals, PRINCIPAL_SET_MESSAGE),
|
|
191
|
+
// Adds collaborators to selected, explicitly assigned human nodes and grants run viewing.
|
|
192
|
+
// Existing assignees remain. Onward sharing is explicit and scoped to the admitted application.
|
|
193
|
+
z.object({
|
|
194
|
+
operation: z.literal('lattice_share_run'), mode, runId: latticeId,
|
|
195
|
+
nodeIds: z.array(z.string().min(1).max(255).refine(value => !value.includes('::'), 'Node ids cannot contain the reserved :: separator'))
|
|
196
|
+
.min(1).max(ARTIFACT_PROCESS_SHARE_NODES_LIMIT),
|
|
197
|
+
...principalSet,
|
|
198
|
+
delegateSharing: z.object({ audience: z.literal('current_app') }).strict().optional(),
|
|
199
|
+
}).strict().refine(namesPrincipals, PRINCIPAL_SET_MESSAGE)
|
|
200
|
+
.refine(value => !value.delegateSharing || ((value.users?.length ?? 0) > 0 && (value.groups?.length ?? 0) === 0), 'Delegated sharing names individual users only'),
|
|
201
|
+
z.object({
|
|
202
|
+
operation: z.literal('lattice_sharing_authorities'), mode, runId: latticeId,
|
|
203
|
+
limit: z.number().int().min(1).max(ARTIFACT_PROCESS_SHARING_AUTHORITIES_LIMIT).optional(),
|
|
204
|
+
cursor: z.uuid().optional(),
|
|
205
|
+
}).strict(),
|
|
206
|
+
z.object({ operation: z.literal('lattice_sharing_authority_revoke'), mode, runId: latticeId, authorityId: z.uuid() }).strict(),
|
|
165
207
|
// Cancels a run of a process declared under `processes.act`, with its forked lineage, as
|
|
166
208
|
// `POST /api/lattice/runs/[runId]/cancel` does. The user's Invoker relation on the run decides.
|
|
167
209
|
z.object({ operation: z.literal('lattice_cancel'), mode, runId: latticeId }).strict(),
|
package/dist/server/client.js
CHANGED
|
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { StorageApiError, storageClient, storageErrorSchema } from '@sequenceholdings/storage';
|
|
3
3
|
import { OrmOperationError } from '@sequenceholdings/orm/client';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { artifactServerRequestSchema } from './broker-contract.js';
|
|
5
|
+
import { artifactOrmIdempotencyKeySchema, artifactServerRequestSchema } from './broker-contract.js';
|
|
6
6
|
import { createArtifactProcessesClient } from './processes.js';
|
|
7
7
|
import { assertJsonScalarsAcknowledged, ORM_JSON_SCALARS_HEADER } from '../orm-json-scalars.js';
|
|
8
8
|
import { createArtifactLogger } from './logger.js';
|
|
@@ -24,6 +24,7 @@ const maxBodyBytes = 1024 * 1024;
|
|
|
24
24
|
/** A read whose connection failed is sent at most three times, pausing up to 100 ms, then up to 200 ms. */
|
|
25
25
|
const reconnect = { attempts: 3, baseDelayMs: 100 };
|
|
26
26
|
export async function createArtifactServerClient({ headers, runtime, fetch: fetcher = globalThis.fetch, method, path, logSink, accept }) {
|
|
27
|
+
// An explicit runtime is re-parsed, so it never carries the development-lease marker.
|
|
27
28
|
const config = runtime ? artifactRuntimeConfigSchema.parse(runtime) : await readArtifactRuntimeConfig();
|
|
28
29
|
if (!config)
|
|
29
30
|
throw new ArtifactServerError(401, 'runtime_not_connected', 'Run seq-studio artifact dev to connect platform services, or use a hosted preview.');
|
|
@@ -32,7 +33,11 @@ export async function createArtifactServerClient({ headers, runtime, fetch: fetc
|
|
|
32
33
|
if (accept && isExternalContextToken(token)) {
|
|
33
34
|
if (!method || !path)
|
|
34
35
|
throw new ArtifactServerError(401, 'invalid_request_context', 'An external request context is verified only against the route method and path');
|
|
35
|
-
context = verifyArtifactExternalContext({
|
|
36
|
+
context = verifyArtifactExternalContext({
|
|
37
|
+
token, publicKeys: config.publicKeys, projectId: config.projectId, environment: config.environment, method, path, accept,
|
|
38
|
+
// A development context only under a development lease (never a hosted or explicit runtime).
|
|
39
|
+
developmentSessionId: config.development === true ? config.devSessionId : undefined,
|
|
40
|
+
});
|
|
36
41
|
}
|
|
37
42
|
else {
|
|
38
43
|
context = verifyArtifactRequestContext({ token, publicKeys: config.publicKeys, projectId: config.projectId, environment: config.environment, method, path });
|
|
@@ -42,7 +47,7 @@ export async function createArtifactServerClient({ headers, runtime, fetch: fetc
|
|
|
42
47
|
throw new ArtifactServerError(401, 'invalid_runtime_session', 'Artifact request does not belong to this runtime');
|
|
43
48
|
const getRuntime = async () => {
|
|
44
49
|
const current = runtime ? config : await readArtifactRuntimeConfig();
|
|
45
|
-
if (!current || current.projectId !== config.projectId || current.environment !== config.environment || current.atlasUrl !== config.atlasUrl || current.devSessionId !== config.devSessionId || current.deploymentId !== config.deploymentId)
|
|
50
|
+
if (!current || current.projectId !== config.projectId || current.environment !== config.environment || current.atlasUrl !== config.atlasUrl || current.devSessionId !== config.devSessionId || current.deploymentId !== config.deploymentId || current.development !== config.development)
|
|
46
51
|
throw new ArtifactServerError(401, 'runtime_changed', 'Artifact runtime disconnected or changed; reload the preview');
|
|
47
52
|
return current;
|
|
48
53
|
};
|
|
@@ -97,14 +102,9 @@ function buildClient(args) {
|
|
|
97
102
|
},
|
|
98
103
|
audit: { record: (event) => recordAuditEvent({ request, mode, event }) },
|
|
99
104
|
orm: ({ namespace, manifest, jsonScalars }) => ({
|
|
100
|
-
async execute(op, variables = {}) {
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
if (!hash)
|
|
104
|
-
throw new ArtifactServerError(400, 'unknown_orm_operation', 'ORM operation is not in the generated manifest; run seq-studio orm generate');
|
|
105
|
-
const response = await request(artifactServerRequestSchema.parse({ operation: 'orm', mode, namespace, op: hash, variables, ...(jsonScalars ? { jsonScalars } : {}) }),
|
|
106
|
-
// Only an operation the manifest lists as a query is known to change nothing.
|
|
107
|
-
{ replaySafe: listed?.kind === 'query' });
|
|
105
|
+
async execute(op, variables = {}, options = {}) {
|
|
106
|
+
const { operation, replaySafe } = ormOperationRequest({ mode, namespace, manifest, op, variables, jsonScalars, idempotencyKey: options.idempotencyKey });
|
|
107
|
+
const response = await request(operation, { replaySafe });
|
|
108
108
|
const body = await response.json().catch(() => null);
|
|
109
109
|
const stepUp = stepUpError(response.status, body);
|
|
110
110
|
if (stepUp)
|
|
@@ -130,6 +130,30 @@ function buildClient(args) {
|
|
|
130
130
|
},
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* The broker `orm` request for one persisted operation, and whether a lost connection may resend
|
|
135
|
+
* it: an operation the manifest lists as a query changes nothing, and a mutation sent with an
|
|
136
|
+
* idempotency key is answered from its stored receipt when it arrives twice (the same body carries
|
|
137
|
+
* the same key and fingerprint).
|
|
138
|
+
*/
|
|
139
|
+
function ormOperationRequest({ mode, namespace, manifest, op, variables, jsonScalars, idempotencyKey }) {
|
|
140
|
+
const listed = op.startsWith('sha256:') ? Object.values(manifest.operations).find(entry => entry.hash === op) : manifest.operations[op];
|
|
141
|
+
const hash = op.startsWith('sha256:') ? op : listed?.hash;
|
|
142
|
+
if (!hash)
|
|
143
|
+
throw new ArtifactServerError(400, 'unknown_orm_operation', 'ORM operation is not in the generated manifest; run seq-studio orm generate');
|
|
144
|
+
const query = listed?.kind === 'query';
|
|
145
|
+
// A key names one mutation's receipt; the data plane refuses one on a query, so refuse it before the call.
|
|
146
|
+
if (idempotencyKey !== undefined && query)
|
|
147
|
+
throw new ArtifactServerError(400, 'idempotency_key_on_query', 'An idempotency key applies only to a mutation');
|
|
148
|
+
if (idempotencyKey !== undefined && !artifactOrmIdempotencyKeySchema.safeParse(idempotencyKey).success) {
|
|
149
|
+
throw new ArtifactServerError(400, 'invalid_idempotency_key', 'Idempotency keys are 1-128 letters, digits, dots, underscores, colons or hyphens');
|
|
150
|
+
}
|
|
151
|
+
const operation = artifactServerRequestSchema.parse({
|
|
152
|
+
operation: 'orm', mode, namespace, op: hash, variables,
|
|
153
|
+
...(jsonScalars ? { jsonScalars } : {}), ...(idempotencyKey === undefined ? {} : { idempotencyKey }),
|
|
154
|
+
});
|
|
155
|
+
return { operation, replaySafe: query || idempotencyKey !== undefined };
|
|
156
|
+
}
|
|
133
157
|
/**
|
|
134
158
|
* Sends one audit event, and once more with the same event id when Atlas couldn't confirm delivery
|
|
135
159
|
* (503, or no answer): Atlas derives the stored event id from it and the request, so the audit sink
|
|
@@ -231,8 +255,9 @@ function createBrokerTransport({ context, principal, token, getRuntime, fetcher,
|
|
|
231
255
|
/**
|
|
232
256
|
* Sends a read again when its connection to Atlas failed before Atlas answered: such failures
|
|
233
257
|
* clear within moments, and a read changes nothing by arriving twice. A write is never sent
|
|
234
|
-
* again, because a reset can come after Atlas acted on it
|
|
235
|
-
*
|
|
258
|
+
* again, because a reset can come after Atlas acted on it, unless its caller marks it replay-safe
|
|
259
|
+
* (an ORM mutation carrying an idempotency key). A response, whatever its status, is Atlas's
|
|
260
|
+
* answer and is returned as is.
|
|
236
261
|
*/
|
|
237
262
|
return async (input, { signal, replaySafe = isReadOperation(input), stream = false } = {}) => {
|
|
238
263
|
for (let attempt = 1;; attempt++) {
|
|
@@ -9,7 +9,7 @@ export interface ArtifactExternalAccept {
|
|
|
9
9
|
* bound to the one route it was signed for. Refuses a v1 context (`external_context_required`)
|
|
10
10
|
* and a context from an ingress the handler did not name (`external_ingress_not_accepted`).
|
|
11
11
|
*/
|
|
12
|
-
export declare function verifyArtifactExternalContext({ token, publicKeys, projectId, environment, method, path, accept, now }: {
|
|
12
|
+
export declare function verifyArtifactExternalContext({ token, publicKeys, projectId, environment, method, path, accept, developmentSessionId, now }: {
|
|
13
13
|
token: string | null;
|
|
14
14
|
publicKeys: ArtifactGatewayPublicKeys;
|
|
15
15
|
projectId: string;
|
|
@@ -17,5 +17,12 @@ export declare function verifyArtifactExternalContext({ token, publicKeys, proje
|
|
|
17
17
|
method: string;
|
|
18
18
|
path: string;
|
|
19
19
|
accept: ArtifactExternalAccept;
|
|
20
|
+
/**
|
|
21
|
+
* Set only when this runtime runs under a development lease (`runtimeConfig.development`): its
|
|
22
|
+
* session id. A `development`-mode context is accepted only then, and only for that session; a
|
|
23
|
+
* hosted runtime leaves it unset and refuses every development context
|
|
24
|
+
* (`external_mode_not_accepted`).
|
|
25
|
+
*/
|
|
26
|
+
developmentSessionId?: string;
|
|
20
27
|
now?: number;
|
|
21
28
|
}): RequestContextV2External;
|