@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.
Files changed (80) hide show
  1. package/README.md +254 -10
  2. package/VERSION-PIN.md +78 -1
  3. package/dist/gateway/admission.d.ts +23 -1
  4. package/dist/gateway/admission.js +30 -13
  5. package/dist/gateway/control.d.ts +40 -0
  6. package/dist/gateway/control.js +76 -7
  7. package/dist/gateway/hosted-proxy.d.ts +11 -8
  8. package/dist/gateway/hosted-proxy.js +99 -91
  9. package/dist/gateway/ingress-admission.d.ts +77 -0
  10. package/dist/gateway/ingress-admission.js +150 -0
  11. package/dist/gateway/ingress-audit.d.ts +46 -0
  12. package/dist/gateway/ingress-audit.js +84 -0
  13. package/dist/gateway/ingress-context.d.ts +57 -0
  14. package/dist/gateway/ingress-context.js +57 -0
  15. package/dist/gateway/ingress-development.d.ts +12 -0
  16. package/dist/gateway/ingress-development.js +23 -0
  17. package/dist/gateway/ingress-handler.d.ts +44 -0
  18. package/dist/gateway/ingress-handler.js +320 -0
  19. package/dist/gateway/ingress-pass.d.ts +83 -0
  20. package/dist/gateway/ingress-pass.js +143 -0
  21. package/dist/gateway/ingress-response.d.ts +74 -0
  22. package/dist/gateway/ingress-response.js +98 -0
  23. package/dist/gateway/ingress-runtime.d.ts +86 -0
  24. package/dist/gateway/ingress-runtime.js +126 -0
  25. package/dist/gateway/ingress-snapshot.d.ts +54 -0
  26. package/dist/gateway/ingress-snapshot.js +63 -0
  27. package/dist/gateway/ingress-stages.d.ts +60 -0
  28. package/dist/gateway/ingress-stages.js +387 -0
  29. package/dist/gateway/ingress.d.ts +259 -0
  30. package/dist/gateway/ingress.js +204 -0
  31. package/dist/gateway/lattice-invoke.js +2 -0
  32. package/dist/gateway/lattice-upstream.d.ts +3 -4
  33. package/dist/gateway/lattice-upstream.js +11 -9
  34. package/dist/gateway/runtime-transport.d.ts +17 -0
  35. package/dist/gateway/runtime-transport.js +26 -0
  36. package/dist/gateway/server.d.ts +3 -0
  37. package/dist/gateway/server.js +21 -0
  38. package/dist/gateway/session-response.d.ts +15 -0
  39. package/dist/gateway/session-response.js +40 -0
  40. package/dist/gateway/usage.d.ts +8 -1
  41. package/dist/gateway-main.js +32 -8
  42. package/dist/manifest-next-browser-data.d.ts +17 -0
  43. package/dist/manifest-next-browser-data.js +46 -0
  44. package/dist/manifest-next-ingress.d.ts +5 -2
  45. package/dist/manifest-next-ingress.js +27 -2
  46. package/dist/manifest-next.d.ts +27 -10
  47. package/dist/manifest-next.js +16 -5
  48. package/dist/manifest.d.ts +13 -6
  49. package/dist/next-project.d.ts +4 -0
  50. package/dist/sdk.d.ts +12 -3
  51. package/dist/server/broker-contract.d.ts +62 -0
  52. package/dist/server/broker-contract.js +43 -1
  53. package/dist/server/client.js +38 -13
  54. package/dist/server/external-context.d.ts +8 -1
  55. package/dist/server/external-context.js +7 -2
  56. package/dist/server/internal/v1-context.js +17 -1
  57. package/dist/server/internal/verify-any.d.ts +25 -2
  58. package/dist/server/internal/verify-any.js +38 -4
  59. package/dist/server/process-run-sharing.d.ts +86 -0
  60. package/dist/server/process-run-sharing.js +29 -0
  61. package/dist/server/process-runs-page.d.ts +39 -0
  62. package/dist/server/process-runs-page.js +10 -0
  63. package/dist/server/processes.d.ts +8 -9
  64. package/dist/server/processes.js +4 -0
  65. package/dist/server/request-context.js +10 -3
  66. package/dist/server/runtime-config.d.ts +10 -1
  67. package/dist/server/runtime-config.js +11 -1
  68. package/dist/server/testing.d.ts +1 -1
  69. package/dist/server.d.ts +2 -2
  70. package/dist/server.js +1 -1
  71. package/dist/tunnel/broker.d.ts +18 -0
  72. package/dist/tunnel/broker.js +175 -17
  73. package/dist/tunnel/client.d.ts +13 -0
  74. package/dist/tunnel/client.js +40 -2
  75. package/dist/tunnel/external.d.ts +51 -0
  76. package/dist/tunnel/external.js +10 -0
  77. package/dist/tunnel/index.d.ts +1 -1
  78. package/dist/tunnel/protocol.d.ts +20 -2
  79. package/dist/tunnel/protocol.js +42 -6
  80. 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), and room for
207
- * the ingress share of concurrency. Called from the server schema's refinement.
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), and room for
137
- * the ingress share of concurrency. Called from the server schema's refinement.
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' });
@@ -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>>;
@@ -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.strictObject({
51
- name: artifactNextSecretNameSchema,
52
- secret_id: z.uuid(),
53
- version: z.number().int().positive(),
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
  }
@@ -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>>;
@@ -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(),
@@ -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({ token, publicKeys: config.publicKeys, projectId: config.projectId, environment: config.environment, method, path, accept });
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 listed = op.startsWith('sha256:') ? Object.values(manifest.operations).find(entry => entry.hash === op) : manifest.operations[op];
102
- const hash = op.startsWith('sha256:') ? op : listed?.hash;
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. A response, whatever its status, is
235
- * Atlas's answer and is returned as is.
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;