@sequenceholdings/artifact-studio 0.6.4 → 0.6.6

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 (78) hide show
  1. package/README.md +183 -6
  2. package/VERSION-PIN.md +56 -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-ingress.d.ts +5 -2
  43. package/dist/manifest-next-ingress.js +27 -2
  44. package/dist/manifest-next.d.ts +20 -6
  45. package/dist/manifest-next.js +13 -5
  46. package/dist/manifest.d.ts +7 -2
  47. package/dist/next-project.d.ts +4 -0
  48. package/dist/sdk.d.ts +12 -3
  49. package/dist/server/broker-contract.d.ts +62 -0
  50. package/dist/server/broker-contract.js +43 -1
  51. package/dist/server/client.js +38 -13
  52. package/dist/server/external-context.d.ts +8 -1
  53. package/dist/server/external-context.js +7 -2
  54. package/dist/server/internal/v1-context.js +17 -1
  55. package/dist/server/internal/verify-any.d.ts +11 -2
  56. package/dist/server/internal/verify-any.js +11 -3
  57. package/dist/server/process-run-sharing.d.ts +86 -0
  58. package/dist/server/process-run-sharing.js +29 -0
  59. package/dist/server/process-runs-page.d.ts +39 -0
  60. package/dist/server/process-runs-page.js +10 -0
  61. package/dist/server/processes.d.ts +8 -9
  62. package/dist/server/processes.js +4 -0
  63. package/dist/server/request-context.js +10 -3
  64. package/dist/server/runtime-config.d.ts +10 -1
  65. package/dist/server/runtime-config.js +11 -1
  66. package/dist/server/testing.d.ts +1 -1
  67. package/dist/server.d.ts +2 -2
  68. package/dist/server.js +1 -1
  69. package/dist/tunnel/broker.d.ts +18 -0
  70. package/dist/tunnel/broker.js +175 -17
  71. package/dist/tunnel/client.d.ts +13 -0
  72. package/dist/tunnel/client.js +40 -2
  73. package/dist/tunnel/external.d.ts +51 -0
  74. package/dist/tunnel/external.js +10 -0
  75. package/dist/tunnel/index.d.ts +1 -1
  76. package/dist/tunnel/protocol.d.ts +20 -2
  77. package/dist/tunnel/protocol.js +42 -6
  78. package/package.json +4 -4
@@ -13,6 +13,9 @@ import { gatewayClientAddress } from './control-admission.js';
13
13
  import { gatewayAuthorizationKey, PositiveDecisionCache, tunnelAuthorizationKey } from './authorization-cache.js';
14
14
  import { GatewayUsageReporter } from './usage.js';
15
15
  import { artifactNextFrameworkBuildIdSchema } from '../runtime-contracts.js';
16
+ import { createDevelopmentIngressProxy } from './ingress-development.js';
17
+ import { createIngressHandler, handleClientErrors } from './ingress-handler.js';
18
+ import { resemblesIngressHost } from './ingress.js';
16
19
  function json(response, status, error) {
17
20
  if (response.headersSent) {
18
21
  response.destroy();
@@ -115,6 +118,7 @@ export function createArtifactGateway(options) {
115
118
  const tunnelAuthorizations = new PositiveDecisionCache({ ttlMs: options.authorizationCacheTtlMs });
116
119
  const usage = new GatewayUsageReporter({ send: events => options.control.usage(events), onFailure: failure => options.onError?.(failure) });
117
120
  let draining = false;
121
+ const isIngressRequest = (request) => resemblesIngressHost({ rawHost: header(request, 'host'), runtimeDomain: options.runtimeDomain });
118
122
  /** Admission for the unauthenticated endpoints; the release frees the in-flight permit. */
119
123
  async function admitControlCall(endpoint, request) {
120
124
  if (!options.controlAdmission)
@@ -254,7 +258,16 @@ export function createArtifactGateway(options) {
254
258
  onTunnelClosed: lease => { if (leases.get(lease.sessionId)?.lease.epoch === lease.epoch)
255
259
  leases.delete(lease.sessionId); },
256
260
  });
261
+ // A development target is proxied over its session tunnel only by a development gateway (design §(j.3) step 3).
262
+ const proxyDevelopment = options.ingress?.appEnv === 'development' ? createDevelopmentIngressProxy({ broker }) : undefined;
263
+ const ingress = createIngressHandler({ options: options.ingress, runtimeDomain: options.runtimeDomain, protocol: new URL(gatewayOrigin).protocol, isDraining: () => draining, onError: options.onError, usage, proxyDevelopment });
257
264
  const server = createServer({ maxHeaderSize: 32 * 1024, requestTimeout: 60_000, headersTimeout: 15_000 }, (request, response) => {
265
+ // Before readiness, bootstrap and requestOrigin(): an ingress host, canonical or not, never
266
+ // reaches a session-path handler or the 401/404→403 remap below (design §(e) "Branch placement").
267
+ if (isIngressRequest(request)) {
268
+ void ingress.handle(request, response);
269
+ return;
270
+ }
258
271
  const startedAt = new Date().toISOString(), started = performance.now();
259
272
  void (async () => {
260
273
  if (isReadinessRequest({ request, gatewayOrigin, runtimeDomain: options.runtimeDomain })) {
@@ -350,6 +363,11 @@ export function createArtifactGateway(options) {
350
363
  rejectUpgrade(socket, 503);
351
364
  return;
352
365
  }
366
+ // An ingress answers data requests only: no WebSocket, no tunnel.
367
+ if (isIngressRequest(request)) {
368
+ rejectUpgrade(socket, 404);
369
+ return;
370
+ }
353
371
  if (request.url === '/_sequence/tunnel') {
354
372
  await broker.handleTunnelUpgrade(request, socket, head);
355
373
  return;
@@ -362,6 +380,9 @@ export function createArtifactGateway(options) {
362
380
  await broker.handleWebSocketUpgrade(request, socket, head, authorized.target.sessionId);
363
381
  })().catch(() => rejectUpgrade(socket));
364
382
  });
383
+ // Only with the ingress branch configured: without it, Node's own clientError replies stay untouched.
384
+ if (options.ingress)
385
+ handleClientErrors({ server, ingress });
365
386
  return {
366
387
  server,
367
388
  /** Observations recorded but not yet confirmed by Atlas: what a forced exit would lose. */
@@ -0,0 +1,15 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ /** Response-header rules for the iframe session path of the hosted proxy (an ingress uses `ingress-response.ts`). */
3
+ export declare function rewriteLocation({ location, upstream, publicOrigin }: {
4
+ location: string;
5
+ upstream: URL;
6
+ publicOrigin: string;
7
+ }): string;
8
+ export declare function mergeFramePolicy({ application, trusted }: {
9
+ application: string | string[];
10
+ trusted: string | string[];
11
+ }): string[];
12
+ export declare function enforceBrowserCachePolicy({ request, response }: {
13
+ request: IncomingMessage;
14
+ response: ServerResponse;
15
+ }): void;
@@ -0,0 +1,40 @@
1
+ import { isSharedCacheDirective, SHARED_CACHE_REFUSAL } from '../tunnel/protocol.js';
2
+ /** Response-header rules for the iframe session path of the hosted proxy (an ingress uses `ingress-response.ts`). */
3
+ export function rewriteLocation({ location, upstream, publicOrigin }) {
4
+ if (!/^(?:https?:)?\/\//i.test(location))
5
+ return location;
6
+ const url = new URL(location, upstream);
7
+ return url.origin === upstream.origin ? `${publicOrigin}${url.pathname}${url.search}${url.hash}` : location;
8
+ }
9
+ export function mergeFramePolicy({ application, trusted }) {
10
+ const policies = Array.isArray(application) ? application : [application];
11
+ const frames = Array.isArray(trusted) ? trusted : [trusted];
12
+ return [...policies.map((policy) => policy.split(';')
13
+ .filter((directive) => directive.trim().split(/\s/, 1)[0].toLowerCase() !== 'frame-ancestors')
14
+ .map((directive) => directive.trim()).filter(Boolean).join('; ')).filter(Boolean), ...frames];
15
+ }
16
+ export function enforceBrowserCachePolicy({ request, response }) {
17
+ const path = (request.url ?? '/').split('?', 1)[0];
18
+ let staticAsset = false;
19
+ try {
20
+ const segments = path.slice(1).split('/').map((segment) => decodeURIComponent(segment));
21
+ staticAsset = path.startsWith('/_next/static/') && /\.(?:js|css)$/.test(path) &&
22
+ segments.every((segment) => segment !== '' && segment !== '.' && segment !== '..' && !/[\\/]/.test(segment));
23
+ }
24
+ catch { /* Malformed or ambiguous asset paths retain the default no-store policy. */ }
25
+ const contentType = response.getHeader('content-type');
26
+ const mediaType = typeof contentType === 'string' ? contentType.split(';', 1)[0].trim().toLowerCase() : undefined;
27
+ const assetType = path.endsWith('.css') ? mediaType === 'text/css' : mediaType === 'text/javascript' || mediaType === 'application/javascript';
28
+ const revalidateAsset = staticAsset && ['GET', 'HEAD'].includes(request.method ?? 'GET') &&
29
+ (response.statusCode === 200 || response.statusCode === 304) &&
30
+ (assetType || (response.statusCode === 304 && contentType === undefined));
31
+ // Every protected reuse must reach authorization. Retain only private static
32
+ // JS/CSS bytes and validators; app output may never opt into browser/CDN TTLs.
33
+ for (const name of response.getHeaderNames()) {
34
+ if (isSharedCacheDirective(name))
35
+ response.removeHeader(name);
36
+ }
37
+ response.setHeader('cache-control', revalidateAsset ? 'private, no-cache' : 'private, no-store');
38
+ for (const [name, value] of Object.entries(SHARED_CACHE_REFUSAL))
39
+ response.setHeader(name, value);
40
+ }
@@ -1,8 +1,15 @@
1
1
  export interface GatewayUsageEvent {
2
2
  eventId: string;
3
3
  requestId: string;
4
- /** The user the gateway acted for: the session owner Atlas authorized for this request. */
4
+ /**
5
+ * Who the gateway acted for: the session owner Atlas authorized for this request, or for an
6
+ * external ingress request the namespaced external subject (`ext|<provider>|<subject>`).
7
+ */
5
8
  actorId: string;
9
+ /** Present only for an external ingress request. */
10
+ principalKind?: 'external';
11
+ /** The ingress an external request arrived through. */
12
+ ingressId?: string;
6
13
  projectId: string;
7
14
  deploymentId?: string;
8
15
  devSessionId?: string;
@@ -7,6 +7,7 @@ import { gatewaySessionSameSite } from './gateway/session.js';
7
7
  import { RedisTunnelOwnership, RedisTunnelRelay } from './tunnel/redis.js';
8
8
  import { ArtifactRequestAdmission } from './gateway/admission.js';
9
9
  import { GatewayControlAdmission } from './gateway/control-admission.js';
10
+ import { createIngressRuntime, IngressSecretsError } from './gateway/ingress-runtime.js';
10
11
  function required(name) {
11
12
  const value = process.env[name];
12
13
  if (!value)
@@ -40,6 +41,8 @@ async function main() {
40
41
  // A flood of refused requests or forged frames must not become a log flood: count and report every 10 s.
41
42
  const refusals = new Map();
42
43
  const refuse = (kind) => { refusals.set(kind, (refusals.get(kind) ?? 0) + 1); };
44
+ // Kinds a caller can produce at request rate: counted, never logged one by one.
45
+ const counted = new Set(['admission_rejected', 'ingress_pass_rejected', 'ingress_token_rejected', 'ingress_edge_header_invalid', 'ingress_client_ip_invalid']);
43
46
  setInterval(() => {
44
47
  for (const [kind, count] of refusals)
45
48
  log({ event: 'artifact_gateway.request', kind, count });
@@ -56,26 +59,43 @@ async function main() {
56
59
  subscriber.on('ready', () => { if (!stopping)
57
60
  void relay.start().catch(() => undefined); });
58
61
  const controlUrl = required('ARTIFACT_NEXT_CONTROL_PLANE_URL');
62
+ const controlToken = required('ARTIFACT_NEXT_CONTROL_TOKEN');
63
+ const control = new ArtifactGatewayControl({ url: controlUrl, token: controlToken });
64
+ const contextPrivateKey = required('ARTIFACT_NEXT_CONTEXT_PRIVATE_KEY');
65
+ const contextKeyId = required('ARTIFACT_NEXT_CONTEXT_KEY_ID');
66
+ const admission = new ArtifactRequestAdmission({ redis: command, namespace });
67
+ // The external ingress branch: off (every ingress host 404) unless all four of its secrets are
68
+ // set; the process refuses to boot when they are set but unusable or not independent.
69
+ const ingress = createIngressRuntime({
70
+ env: process.env, control, controlToken, sessionKey, contextPrivateKey, contextKeyId, redis: command, namespace, admission, log,
71
+ onFailure: failure => { if ('count' in failure)
72
+ log({ event: 'artifact_gateway.request', ...failure });
73
+ else
74
+ refuse(failure.kind); },
75
+ });
59
76
  const gateway = createArtifactGateway({
60
- control: new ArtifactGatewayControl({ url: controlUrl, token: required('ARTIFACT_NEXT_CONTROL_TOKEN') }),
77
+ control,
61
78
  runtimeDomain: required('ARTIFACT_NEXT_RUNTIME_DOMAIN'),
62
79
  parentOrigin: process.env.ARTIFACT_NEXT_PARENT_ORIGIN ?? new URL(controlUrl).origin,
63
80
  gatewayOrigin: required('ARTIFACT_NEXT_GATEWAY_URL'),
64
81
  sessionKey,
65
82
  sessionCookieSameSite: gatewaySessionSameSite({ value: process.env.ARTIFACT_NEXT_SESSION_COOKIE_SAMESITE, appEnv: namespace }),
66
- contextPrivateKey: required('ARTIFACT_NEXT_CONTEXT_PRIVATE_KEY'),
67
- contextKeyId: required('ARTIFACT_NEXT_CONTEXT_KEY_ID'),
83
+ contextPrivateKey,
84
+ contextKeyId,
68
85
  nodeId, routing: ownership, relay,
69
- admission: new ArtifactRequestAdmission({ redis: command, namespace }),
86
+ admission,
87
+ ingress: ingress.ingress,
70
88
  controlAdmission: new GatewayControlAdmission({ redis: command, namespace }),
71
89
  resolveLease: sessionId => ownership.resolveLease(sessionId),
72
90
  renewLease: lease => ownership.renew(lease, nodeId),
73
91
  ready: () => clients.every(client => client.status === 'ready') && relay.isReady(),
74
- onError: event => { if (event.kind === 'admission_rejected')
92
+ onError: event => { if (counted.has(event.kind))
75
93
  refuse(event.kind);
76
94
  else
77
95
  log({ event: 'artifact_gateway.request', ...event }); },
78
96
  });
97
+ // The first snapshot pull starts before traffic; until it lands, ingress hosts answer 503.
98
+ ingress.start();
79
99
  const port = Number(process.env.PORT ?? '8080');
80
100
  if (!Number.isSafeInteger(port) || port < 1 || port > 65535)
81
101
  throw new Error('Invalid gateway PORT');
@@ -102,7 +122,8 @@ async function main() {
102
122
  }, 9_000);
103
123
  deadline.unref();
104
124
  await gateway.drain();
105
- await relay.close();
125
+ // Audit delivery runs beside tunnel cleanup, never in front of it.
126
+ await Promise.allSettled([ingress.close(), relay.close()]);
106
127
  for (const client of clients)
107
128
  client.disconnect();
108
129
  clearTimeout(deadline);
@@ -110,7 +131,10 @@ async function main() {
110
131
  process.once('SIGTERM', () => { void stop(); });
111
132
  process.once('SIGINT', () => { void stop(); });
112
133
  }
113
- void main().catch(() => {
114
- process.stderr.write(`${JSON.stringify({ event: 'artifact_gateway.start_failed' })}\n`);
134
+ void main().catch((error) => {
135
+ // Only an ingress-secrets refusal is described: its message names variables, never values, by
136
+ // construction. Other errors (Redis, URLs) may carry connection strings.
137
+ const reason = error instanceof IngressSecretsError ? error.message : undefined;
138
+ process.stderr.write(`${JSON.stringify({ event: 'artifact_gateway.start_failed', ...(reason ? { reason } : {}) })}\n`);
115
139
  process.exit(1);
116
140
  });
@@ -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' });
@@ -59,11 +59,15 @@ export declare const artifactNextResourceLimitsSchema: z.ZodPrefault<z.ZodObject
59
59
  timeout_seconds: z.ZodDefault<z.ZodNumber>;
60
60
  }, z.core.$strict>>;
61
61
  export declare const artifactNextSecretNameSchema: z.ZodString;
62
- export declare const artifactNextSecretBindingSchema: z.ZodObject<{
62
+ export declare const artifactNextSecretBindingSchema: z.ZodUnion<readonly [z.ZodObject<{
63
63
  name: z.ZodString;
64
64
  secret_id: z.ZodUUID;
65
65
  version: z.ZodNumber;
66
- }, z.core.$strict>;
66
+ }, z.core.$strict>, z.ZodObject<{
67
+ name: z.ZodString;
68
+ secret_name: z.ZodString;
69
+ version: z.ZodNumber;
70
+ }, z.core.$strict>]>;
67
71
  /** Pinned public assets fetched by the trusted preparation stage before offline compilation. */
68
72
  export declare const artifactNextPublicFontSchema: z.ZodObject<{
69
73
  path: z.ZodString;
@@ -107,11 +111,15 @@ export declare const artifactNextServerSchema: z.ZodPrefault<z.ZodObject<{
107
111
  }, z.core.$strict>>;
108
112
  }, z.core.$strict>>;
109
113
  service_account: z.ZodOptional<z.ZodString>;
110
- secrets: z.ZodDefault<z.ZodArray<z.ZodObject<{
114
+ secrets: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
111
115
  name: z.ZodString;
112
116
  secret_id: z.ZodUUID;
113
117
  version: z.ZodNumber;
114
- }, z.core.$strict>>>;
118
+ }, z.core.$strict>, z.ZodObject<{
119
+ name: z.ZodString;
120
+ secret_name: z.ZodString;
121
+ version: z.ZodNumber;
122
+ }, z.core.$strict>]>>>;
115
123
  egress: z.ZodDefault<z.ZodArray<z.ZodString>>;
116
124
  resources: z.ZodPrefault<z.ZodObject<{
117
125
  cpu: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<4>, z.ZodLiteral<8>]>>;
@@ -142,6 +150,7 @@ export declare const artifactNextServerSchema: z.ZodPrefault<z.ZodObject<{
142
150
  data: z.ZodPrefault<z.ZodObject<{
143
151
  read: z.ZodDefault<z.ZodArray<z.ZodString>>;
144
152
  write: z.ZodDefault<z.ZodArray<z.ZodString>>;
153
+ operations: z.ZodDefault<z.ZodArray<z.ZodString>>;
145
154
  }, z.core.$strict>>;
146
155
  api: z.ZodPrefault<z.ZodObject<{
147
156
  read: z.ZodDefault<z.ZodArray<z.ZodString>>;
@@ -291,11 +300,15 @@ export declare const artifactStudioNextManifestSchema: z.ZodObject<{
291
300
  }, z.core.$strict>>;
292
301
  }, z.core.$strict>>;
293
302
  service_account: z.ZodOptional<z.ZodString>;
294
- secrets: z.ZodDefault<z.ZodArray<z.ZodObject<{
303
+ secrets: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
295
304
  name: z.ZodString;
296
305
  secret_id: z.ZodUUID;
297
306
  version: z.ZodNumber;
298
- }, z.core.$strict>>>;
307
+ }, z.core.$strict>, z.ZodObject<{
308
+ name: z.ZodString;
309
+ secret_name: z.ZodString;
310
+ version: z.ZodNumber;
311
+ }, z.core.$strict>]>>>;
299
312
  egress: z.ZodDefault<z.ZodArray<z.ZodString>>;
300
313
  resources: z.ZodPrefault<z.ZodObject<{
301
314
  cpu: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<4>, z.ZodLiteral<8>]>>;
@@ -326,6 +339,7 @@ export declare const artifactStudioNextManifestSchema: z.ZodObject<{
326
339
  data: z.ZodPrefault<z.ZodObject<{
327
340
  read: z.ZodDefault<z.ZodArray<z.ZodString>>;
328
341
  write: z.ZodDefault<z.ZodArray<z.ZodString>>;
342
+ operations: z.ZodDefault<z.ZodArray<z.ZodString>>;
329
343
  }, z.core.$strict>>;
330
344
  api: z.ZodPrefault<z.ZodObject<{
331
345
  read: z.ZodDefault<z.ZodArray<z.ZodString>>;
@@ -47,11 +47,19 @@ export const artifactNextResourceLimitsSchema = z.strictObject({
47
47
  const envName = z.string().max(128).regex(/^[A-Z][A-Z0-9_]*$/);
48
48
  const reservedServerEnv = /^(NEXT_PUBLIC_|ARTIFACT_|SEQUENCE_|GOOGLE_|GCP_|GCLOUD_|DOPPLER_|MODAL_|AUTH0_|NPM_CONFIG_|npm_config_)/;
49
49
  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
- });
50
+ export const artifactNextSecretBindingSchema = z.union([
51
+ z.strictObject({
52
+ name: artifactNextSecretNameSchema,
53
+ secret_id: z.uuid(),
54
+ version: z.number().int().positive(),
55
+ }),
56
+ z.strictObject({
57
+ name: artifactNextSecretNameSchema,
58
+ // A registry name can differ from the mounted env name, including its reserved prefixes.
59
+ secret_name: z.string().max(255).regex(/^[A-Z][A-Z0-9_]*$/),
60
+ version: z.number().int().positive(),
61
+ }),
62
+ ]);
55
63
  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
64
  /** Pinned public assets fetched by the trusted preparation stage before offline compilation. */
57
65
  export const artifactNextPublicFontSchema = z.strictObject({
@@ -184,11 +184,15 @@ export declare const artifactStudioManifestSchema: z.ZodUnion<readonly [z.ZodObj
184
184
  }, z.core.$strict>>;
185
185
  }, z.core.$strict>>;
186
186
  service_account: z.ZodOptional<z.ZodString>;
187
- secrets: z.ZodDefault<z.ZodArray<z.ZodObject<{
187
+ secrets: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
188
188
  name: z.ZodString;
189
189
  secret_id: z.ZodUUID;
190
190
  version: z.ZodNumber;
191
- }, z.core.$strict>>>;
191
+ }, z.core.$strict>, z.ZodObject<{
192
+ name: z.ZodString;
193
+ secret_name: z.ZodString;
194
+ version: z.ZodNumber;
195
+ }, z.core.$strict>]>>>;
192
196
  egress: z.ZodDefault<z.ZodArray<z.ZodString>>;
193
197
  resources: z.ZodPrefault<z.ZodObject<{
194
198
  cpu: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<4>, z.ZodLiteral<8>]>>;
@@ -219,6 +223,7 @@ export declare const artifactStudioManifestSchema: z.ZodUnion<readonly [z.ZodObj
219
223
  data: z.ZodPrefault<z.ZodObject<{
220
224
  read: z.ZodDefault<z.ZodArray<z.ZodString>>;
221
225
  write: z.ZodDefault<z.ZodArray<z.ZodString>>;
226
+ operations: z.ZodDefault<z.ZodArray<z.ZodString>>;
222
227
  }, z.core.$strict>>;
223
228
  api: z.ZodPrefault<z.ZodObject<{
224
229
  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<{