@sequenceholdings/artifact-studio 0.5.0 → 0.6.1

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 (40) hide show
  1. package/VERSION-PIN.md +20 -0
  2. package/dist/cli.js +1 -1
  3. package/dist/offline/connector.d.ts +2 -0
  4. package/dist/offline/connector.js +6 -2
  5. package/dist/offline/management-types.d.ts +25 -0
  6. package/dist/offline/management-types.js +1 -0
  7. package/dist/runtime-contracts.js +1 -1
  8. package/dist/sdk.d.ts +8 -1
  9. package/dist/server/api-client.d.ts +26 -0
  10. package/dist/server/api-client.js +38 -0
  11. package/dist/server/client.d.ts +32 -45
  12. package/dist/server/client.js +41 -85
  13. package/dist/server/connection-retry.d.ts +12 -0
  14. package/dist/server/connection-retry.js +40 -0
  15. package/dist/server/errors.d.ts +37 -2
  16. package/dist/server/errors.js +66 -6
  17. package/dist/server/external-context.d.ts +21 -0
  18. package/dist/server/external-context.js +19 -0
  19. package/dist/server/internal/signed-token.d.ts +15 -0
  20. package/dist/server/internal/signed-token.js +67 -0
  21. package/dist/server/internal/v1-context.d.ts +18 -0
  22. package/dist/server/internal/v1-context.js +26 -0
  23. package/dist/server/internal/verify-any.d.ts +22 -0
  24. package/dist/server/internal/verify-any.js +53 -0
  25. package/dist/server/logger.d.ts +1 -1
  26. package/dist/server/principal.d.ts +48 -0
  27. package/dist/server/principal.js +39 -0
  28. package/dist/server/request-context.d.ts +115 -3
  29. package/dist/server/request-context.js +101 -32
  30. package/dist/server/testing.d.ts +40 -0
  31. package/dist/server/testing.js +86 -0
  32. package/dist/server.d.ts +9 -4
  33. package/dist/server.js +5 -2
  34. package/dist/templates/nextjs/package.json +3 -1
  35. package/dist/templates/nextjs/tests/server.test.mjs +25 -0
  36. package/dist/templates/react-vite/package.json +1 -1
  37. package/package.json +11 -3
  38. package/templates/nextjs/package.json +3 -1
  39. package/templates/nextjs/tests/server.test.mjs +25 -0
  40. package/templates/react-vite/package.json +1 -1
@@ -1,6 +1,7 @@
1
- import { createPublicKey, verify } from 'node:crypto';
2
1
  import { z } from 'zod';
3
2
  import { isLoopbackHostname } from '../loopback.js';
3
+ import { ARTIFACT_EXTERNAL_CONTEXT_KID_PREFIX, isExternalContextToken, verifySignedContext } from './internal/signed-token.js';
4
+ import { parseV1Context } from './internal/v1-context.js';
4
5
  export const ARTIFACT_REQUEST_CONTEXT_HEADER = 'x-sequence-artifact-context';
5
6
  export const ARTIFACT_RUNTIME_TOKEN_HEADER = 'x-sequence-artifact-runtime';
6
7
  export const ARTIFACT_SERVER_REQUEST_PATH = '/api/artifact-studio/server/request';
@@ -60,49 +61,117 @@ export const artifactRequestContextSchema = z.object({
60
61
  if (value.mode === 'development' ? !value.devSessionId || value.deploymentId !== null : !value.deploymentId || value.devSessionId !== null)
61
62
  ctx.addIssue({ code: 'custom', message: 'Artifact runtime identity does not match its mode' });
62
63
  });
64
+ export { ARTIFACT_EXTERNAL_CONTEXT_KID_PREFIX };
65
+ /**
66
+ * Prefix of a platform service account's `sub`. Mirrors Atlas's `SERVICE_ACCOUNT_SUB_PREFIX`
67
+ * (atlas/src/server/lib/auth0-sub.ts); Atlas's auth0-sub test pins the two together.
68
+ */
69
+ export const ARTIFACT_SERVICE_ACCOUNT_SUB_PREFIX = 'service-account|';
70
+ /** An external context's lifetime cap. External contexts never carry a Lattice run. */
71
+ export const ARTIFACT_EXTERNAL_CONTEXT_MAX_AGE_SECONDS = 60;
72
+ const ipAddress = z.union([z.ipv4(), z.ipv6()]);
73
+ /** Signed by the gateway after it strips control characters and truncates; refused here otherwise. */
74
+ const observedUserAgent = z.string().max(512).refine(value => !/[\u0000-\u001f\u007f]/.test(value), 'Expected a user agent without control characters');
75
+ /** The external caller as the gateway verified it at pass exchange. Strict: see the v2 key policy below. */
76
+ export const artifactExternalPrincipalSchema = z.object({
77
+ kind: z.literal('external'),
78
+ /** Routing label for the manifest's ingress declaration. Not an authorization attribute. */
79
+ ingress: id,
80
+ provider: id,
81
+ issuer: z.string().min(1).max(2048),
82
+ subject: id,
83
+ clientId: id,
84
+ /** Normalized (trimmed, lowercase) by Atlas at exchange. */
85
+ email: z.string().min(3).max(320).refine(value => value === value.trim().toLowerCase() && value.includes('@'), 'Expected a normalized email'),
86
+ emailVerified: z.literal(true),
87
+ authTime: z.number().int().nonnegative().nullable(),
88
+ amr: z.array(z.string().min(1).max(64)).max(16).nullable(),
89
+ tokenIssuedAt: z.number().int().nonnegative(),
90
+ pass: z.object({ id: z.uuid(), kid: id, issuedAt: z.number().int().nonnegative(), exchangeIp: ipAddress }).strict(),
91
+ client: z.object({
92
+ ip: ipAddress,
93
+ userAgent: observedUserAgent,
94
+ userAgentSha256: z.string().regex(/^[0-9a-f]{64}$/),
95
+ origin: originSchema.nullable(),
96
+ }).strict(),
97
+ }).strict();
98
+ /**
99
+ * External request context (v2): a caller that is not an Atlas user, admitted through a
100
+ * manifest-declared ingress. Key policy: any unknown top-level or `principal` key is refused
101
+ * unless it sits under `ext`. `crit` is reserved for extensions a verifier must understand; this
102
+ * verifier understands none, so it must be empty. Security-relevant additions bump `v`.
103
+ *
104
+ * Public verifiers ({@link verifyArtifactRequestContext}) never accept this version; only the
105
+ * explicit external APIs do.
106
+ */
107
+ export const requestContextV2ExternalSchema = z.object({
108
+ v: z.literal(2),
109
+ iss: z.literal('atlas-artifact-gateway'),
110
+ aud: z.string().min(1).max(300),
111
+ sub: id.refine(value => value.startsWith('ext|'), 'External subjects are namespaced ext|<provider>|<subject>'),
112
+ projectId: id,
113
+ environment: id,
114
+ mode: z.enum(['published', 'development']),
115
+ deploymentId: id.nullable(),
116
+ devSessionId: id.nullable(),
117
+ requestId: id,
118
+ jti: z.uuid(),
119
+ method: artifactRequestContextSchema.shape.method,
120
+ path: artifactRequestContextSchema.shape.path,
121
+ iat: z.number().int().nonnegative(),
122
+ exp: z.number().int().positive(),
123
+ /** The ingress's own service account; external principals act only through service mode. */
124
+ serviceAccountId: id,
125
+ principal: artifactExternalPrincipalSchema,
126
+ crit: z.array(z.string()).length(0),
127
+ ext: z.record(z.string().min(1).max(64), z.unknown()),
128
+ }).strict().superRefine((value, ctx) => {
129
+ if (value.aud !== `artifact:${value.projectId}`)
130
+ ctx.addIssue({ code: 'custom', message: 'Invalid Artifact audience' });
131
+ if (value.sub !== `ext|${value.principal.provider}|${value.principal.subject}`)
132
+ ctx.addIssue({ code: 'custom', message: 'External subject does not match its principal' });
133
+ if (value.exp <= value.iat || value.exp - value.iat > ARTIFACT_EXTERNAL_CONTEXT_MAX_AGE_SECONDS)
134
+ ctx.addIssue({ code: 'custom', message: 'Invalid Artifact context lifetime' });
135
+ if (value.mode === 'development' ? !value.devSessionId || value.deploymentId !== null : !value.deploymentId || value.devSessionId !== null)
136
+ ctx.addIssue({ code: 'custom', message: 'Artifact runtime identity does not match its mode' });
137
+ });
63
138
  export class ArtifactContextError extends Error {
64
- constructor() { super('Missing or invalid Artifact gateway request context'); this.name = 'ArtifactContextError'; }
65
- }
66
- function decode(value) {
67
- if (!/^[A-Za-z0-9_-]+$/.test(value))
68
- throw new ArtifactContextError();
69
- return JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
139
+ reason;
140
+ constructor(reason) {
141
+ super('Missing or invalid Artifact gateway request context');
142
+ this.name = 'ArtifactContextError';
143
+ this.reason = reason;
144
+ }
70
145
  }
71
- /** Only the gateway owns private keys. Applications pin platform-provided public keys. */
146
+ /**
147
+ * Only the gateway owns private keys. Applications pin platform-provided public keys.
148
+ *
149
+ * Accepts v1 (Atlas user and platform service) contexts only. An external (v2) context, or any
150
+ * token signed with an `external-` kid, is refused with reason `external_context_not_accepted`
151
+ * before any other check, so a borrower context can never reach a handler that reads `sub` and
152
+ * `capabilities` as an Atlas user. External contexts are verified only through
153
+ * `verifyArtifactExternalContext` or `createArtifactServerClient({ accept })`.
154
+ */
72
155
  export function verifyArtifactRequestContext({ token, publicKeys, projectId, environment, method, path, now = Math.floor(Date.now() / 1000) }) {
156
+ if (isExternalContextToken(token))
157
+ throw new ArtifactContextError('external_context_not_accepted');
158
+ let payload;
73
159
  try {
74
- if (!token || token.length > 32768)
75
- throw new ArtifactContextError();
76
- const parts = token.split('.');
77
- if (parts.length !== 3)
78
- throw new ArtifactContextError();
79
- const [encodedHeader, encodedPayload, signature] = parts;
80
- const header = z.object({ alg: z.literal('EdDSA'), typ: z.literal('JWT'), kid: id }).strict().parse(decode(encodedHeader));
81
- const pem = Object.hasOwn(publicKeys, header.kid) ? publicKeys[header.kid] : undefined;
82
- if (!pem || !/^[A-Za-z0-9_-]+$/.test(signature))
83
- throw new ArtifactContextError();
84
- const key = createPublicKey(pem);
85
- if (key.asymmetricKeyType !== 'ed25519' ||
86
- !verify(null, Buffer.from(`${encodedHeader}.${encodedPayload}`), key, Buffer.from(signature, 'base64url')))
87
- throw new ArtifactContextError();
88
- const context = artifactRequestContextSchema.parse(decode(encodedPayload));
89
- if (context.projectId !== projectId || context.environment !== environment ||
90
- context.exp <= now || context.iat > now + 5 ||
91
- (method !== undefined && context.method !== method.toUpperCase()) || (path !== undefined && context.path !== path))
160
+ const verified = verifySignedContext({ token, publicKeys });
161
+ if (verified.kid.startsWith(ARTIFACT_EXTERNAL_CONTEXT_KID_PREFIX))
92
162
  throw new ArtifactContextError();
93
- Object.freeze(context.bridge);
94
- Object.freeze(context.lattice);
95
- Object.freeze(context.capabilities);
96
- if (context.taskRoutes)
97
- Object.freeze(context.taskRoutes);
98
- return Object.freeze(context);
163
+ payload = verified.payload;
99
164
  }
100
165
  catch {
101
166
  throw new ArtifactContextError();
102
167
  }
168
+ return parseV1Context({ payload, projectId, environment, method, path, now });
103
169
  }
104
170
  /** Safe props for the client bridge; intentionally excludes actor and runtime credentials. */
105
171
  export function getArtifactBrowserBootstrap(context) {
172
+ // An external caller has no Atlas bridge, theme or Lattice run to hand to a browser.
173
+ if (context.v !== 1)
174
+ throw new ArtifactContextError('external_context_not_accepted');
106
175
  return {
107
176
  parentOrigin: context.bridge.parentOrigin,
108
177
  channelId: context.bridge.channelId,
@@ -0,0 +1,40 @@
1
+ import { type KeyObject } from 'node:crypto';
2
+ import { type RequestContextV2External } from './request-context.js';
3
+ import { type ArtifactRuntimeConfig } from './runtime-config.js';
4
+ /**
5
+ * Test helpers for applications that accept external callers: sign an external (v2) context
6
+ * with a test key, and run code against an explicit runtime configuration. For unit tests and
7
+ * local `next dev` only. A context signed here verifies only against the matching public key,
8
+ * which a hosted runtime never pins.
9
+ */
10
+ type ExternalPrincipalClaims = RequestContextV2External['principal'];
11
+ export interface SignTestExternalContextInput {
12
+ privateKey: KeyObject | string;
13
+ /** Use an `external-` kid; verifiers refuse any other kid on an external context. */
14
+ kid: string;
15
+ projectId: string;
16
+ environment: string;
17
+ method: string;
18
+ path: string;
19
+ /** Overrides on a verified-borrower default principal. */
20
+ principal?: Partial<Omit<ExternalPrincipalClaims, 'kind'>>;
21
+ serviceAccountId: string;
22
+ /** Defaults to `published`. Verifiers refuse `development` until development leases admit it. */
23
+ mode?: RequestContextV2External['mode'];
24
+ deploymentId?: string | null;
25
+ devSessionId?: string | null;
26
+ /** Seconds since the epoch; defaults to now. */
27
+ now?: number;
28
+ /** Defaults to and may not exceed 60. */
29
+ ttlSeconds?: number;
30
+ /** Overrides applied after the defaults, unvalidated, for negative tests. */
31
+ claims?: Record<string, unknown>;
32
+ }
33
+ export declare function signTestExternalContext({ privateKey, kid, projectId, environment, method, path, principal, serviceAccountId, mode, deploymentId, devSessionId, now, ttlSeconds, claims, }: SignTestExternalContextInput): string;
34
+ /**
35
+ * Runs `fn` with the hosted runtime configuration set to `config` (and no runtime file), then
36
+ * restores the previous environment. The override is process-wide: do not run overlapping calls.
37
+ * Refuses to run in a production build.
38
+ */
39
+ export declare function withTestRuntimeConfig<T>(config: ArtifactRuntimeConfig, fn: () => Promise<T> | T): Promise<T>;
40
+ export {};
@@ -0,0 +1,86 @@
1
+ import { createHash, createPrivateKey, randomUUID, sign } from 'node:crypto';
2
+ import { ARTIFACT_EXTERNAL_CONTEXT_MAX_AGE_SECONDS, requestContextV2ExternalSchema } from './request-context.js';
3
+ import { artifactRuntimeConfigSchema } from './runtime-config.js';
4
+ const defaultUserAgent = 'Mozilla/5.0 (test)';
5
+ export function signTestExternalContext({ privateKey, kid, projectId, environment, method, path, principal = {}, serviceAccountId, mode = 'published', deploymentId, devSessionId, now = Math.floor(Date.now() / 1000), ttlSeconds = ARTIFACT_EXTERNAL_CONTEXT_MAX_AGE_SECONDS, claims = {}, }) {
6
+ const userAgent = principal.client?.userAgent ?? defaultUserAgent;
7
+ const resolvedPrincipal = {
8
+ kind: 'external',
9
+ ingress: 'borrower',
10
+ provider: 'test-provider',
11
+ issuer: 'https://idp.example.test/',
12
+ subject: 'test|borrower-1',
13
+ clientId: 'test-client',
14
+ email: 'borrower@example.test',
15
+ emailVerified: true,
16
+ authTime: now - 30,
17
+ amr: ['pwd'],
18
+ tokenIssuedAt: now - 30,
19
+ ...principal,
20
+ pass: { id: randomUUID(), kid: 'pass-1', issuedAt: now - 10, exchangeIp: '203.0.113.10', ...principal.pass },
21
+ client: {
22
+ ip: '203.0.113.10',
23
+ userAgentSha256: createHash('sha256').update(userAgent).digest('hex'),
24
+ origin: 'https://borrower.example.test',
25
+ ...principal.client,
26
+ userAgent,
27
+ },
28
+ };
29
+ const published = mode === 'published';
30
+ const payload = {
31
+ v: 2,
32
+ iss: 'atlas-artifact-gateway',
33
+ aud: `artifact:${projectId}`,
34
+ sub: `ext|${resolvedPrincipal.provider}|${resolvedPrincipal.subject}`,
35
+ projectId,
36
+ environment,
37
+ mode,
38
+ deploymentId: deploymentId !== undefined ? deploymentId : published ? 'test-deployment' : null,
39
+ devSessionId: devSessionId !== undefined ? devSessionId : published ? null : 'test-dev-session',
40
+ requestId: randomUUID(),
41
+ jti: randomUUID(),
42
+ method: method.toUpperCase(),
43
+ path,
44
+ iat: now,
45
+ exp: now + ttlSeconds,
46
+ serviceAccountId,
47
+ principal: resolvedPrincipal,
48
+ crit: [],
49
+ ext: {},
50
+ ...claims,
51
+ };
52
+ // Catch a mistaken default early; `claims` overrides are for deliberately invalid contexts.
53
+ if (Object.keys(claims).length === 0)
54
+ requestContextV2ExternalSchema.parse(payload);
55
+ const key = typeof privateKey === 'string' ? createPrivateKey(privateKey) : privateKey;
56
+ const unsigned = [{ alg: 'EdDSA', typ: 'JWT', kid }, payload]
57
+ .map(part => Buffer.from(JSON.stringify(part)).toString('base64url')).join('.');
58
+ return `${unsigned}.${sign(null, Buffer.from(unsigned), key).toString('base64url')}`;
59
+ }
60
+ const runtimeEnvironmentKeys = ['SEQUENCE_ARTIFACT_RUNTIME_FILE', 'SEQUENCE_ARTIFACT_RUNTIME_CONFIG', 'SEQUENCE_ARTIFACT_RUNTIME_TOKEN'];
61
+ /**
62
+ * Runs `fn` with the hosted runtime configuration set to `config` (and no runtime file), then
63
+ * restores the previous environment. The override is process-wide: do not run overlapping calls.
64
+ * Refuses to run in a production build.
65
+ */
66
+ export async function withTestRuntimeConfig(config, fn) {
67
+ if (process.env.NODE_ENV === 'production')
68
+ throw new Error('withTestRuntimeConfig is for tests and local development only');
69
+ const { sessionToken, ...hosted } = artifactRuntimeConfigSchema.parse(config);
70
+ const previous = Object.fromEntries(runtimeEnvironmentKeys.map(key => [key, process.env[key]]));
71
+ delete process.env.SEQUENCE_ARTIFACT_RUNTIME_FILE;
72
+ process.env.SEQUENCE_ARTIFACT_RUNTIME_CONFIG = JSON.stringify(hosted);
73
+ process.env.SEQUENCE_ARTIFACT_RUNTIME_TOKEN = sessionToken;
74
+ try {
75
+ return await fn();
76
+ }
77
+ finally {
78
+ for (const key of runtimeEnvironmentKeys) {
79
+ const value = previous[key];
80
+ if (value === undefined)
81
+ delete process.env[key];
82
+ else
83
+ process.env[key] = value;
84
+ }
85
+ }
86
+ }
package/dist/server.d.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  import 'server-only';
2
- export { createArtifactServerClient, ArtifactServerError } from './server/client.js';
2
+ export { createArtifactServerClient } from './server/client.js';
3
+ export { ArtifactPrincipalError, ArtifactServerError, INSUFFICIENT_USER_AUTHENTICATION, stepUpResponse } from './server/errors.js';
3
4
  export type { ArtifactAtlasRefusal } from './server/errors.js';
4
- export type { ArtifactAuditEvent, ArtifactOrmOperations, ArtifactOrmOptions, ArtifactProcessAdvance, ArtifactProcessAdvanceResult, ArtifactProcessArtifactRef, ArtifactProcessAssign, ArtifactProcessAssignResult, ArtifactProcessDefinition, ArtifactProcessDescribeQuery, ArtifactProcessEdge, ArtifactProcessHumanNode, ArtifactProcessNode, ArtifactProcessNodeBase, ArtifactProcessNodeState, ArtifactProcessPrincipal, ArtifactProcessPrincipalSearch, ArtifactProcessPrincipals, ArtifactProcessRun, ArtifactProcessRunRecord, ArtifactProcessRunSnapshot, ArtifactProcessRunsQuery, ArtifactProcessRunViewerChange, ArtifactProcessRunViewers, ArtifactProcessStart, ArtifactProcessStepRecord, ArtifactProcessTask, ArtifactProcessTasksQuery, ArtifactServerAudit, ArtifactServerClient, ArtifactServerFetchOptions, ArtifactServerFlags, ArtifactServerProcesses, } from './server/client.js';
5
+ export type { ArtifactAuditEvent, ArtifactOrmOperations, ArtifactOrmOptions, ArtifactProcessAdvance, ArtifactProcessAdvanceResult, ArtifactProcessArtifactRef, ArtifactProcessAssign, ArtifactProcessAssignResult, ArtifactProcessDefinition, ArtifactProcessDescribeQuery, ArtifactProcessEdge, ArtifactProcessHumanNode, ArtifactProcessNode, ArtifactProcessNodeBase, ArtifactProcessNodeState, ArtifactProcessPrincipal, ArtifactProcessPrincipalSearch, ArtifactProcessPrincipals, ArtifactProcessRun, ArtifactProcessRunRecord, ArtifactProcessRunSnapshot, ArtifactProcessRunsQuery, ArtifactProcessRunViewerChange, ArtifactProcessRunViewers, ArtifactProcessStart, ArtifactProcessStepRecord, ArtifactProcessTask, ArtifactProcessTasksQuery, ArtifactServerAudit, ArtifactServerClient, ArtifactServerClientOptions, ArtifactServerContext, ArtifactServerFetchOptions, ArtifactServerFlags, ArtifactServerProcesses, } from './server/client.js';
5
6
  export { ARTIFACT_AUDIT_ACTIONS, ARTIFACT_AUDIT_RESOURCES_PER_EVENT, ARTIFACT_FLAGS_PER_REQUEST, ARTIFACT_PROCESS_PRINCIPAL_SEARCH_LIMIT, ARTIFACT_PROCESS_PRINCIPAL_TYPES, ARTIFACT_PROCESS_PRINCIPALS_PER_CALL, ARTIFACT_PROCESS_RUN_STATUSES, ARTIFACT_PROCESS_RUNS_LIMIT, ARTIFACT_PROCESS_TASK_VIEWS, ARTIFACT_PROCESS_TASKS_LIMIT, } from './server/broker-contract.js';
6
7
  export type { ArtifactAuditAction, ArtifactProcessPrincipalType, ArtifactProcessRunStatus, ArtifactProcessTaskView, } from './server/broker-contract.js';
7
8
  export { getArtifactRequestContext, readArtifactRuntimeConfig } from './server/runtime-config.js';
8
9
  export type { ArtifactRuntimeConfig, ArtifactRequestHeaders } from './server/runtime-config.js';
9
- export { getArtifactBrowserBootstrap } from './server/request-context.js';
10
- export type { ArtifactRequestContext } from './server/request-context.js';
10
+ export { ArtifactContextError, getArtifactBrowserBootstrap } from './server/request-context.js';
11
+ export type { ArtifactContextErrorReason, ArtifactRequestContext, RequestContextV2External } from './server/request-context.js';
12
+ export { verifyArtifactExternalContext } from './server/external-context.js';
13
+ export type { ArtifactExternalAccept } from './server/external-context.js';
14
+ export { externalEvidence } from './server/principal.js';
15
+ export type { ArtifactExternalEvidence, ArtifactExternalPrincipal, ArtifactPrincipal } from './server/principal.js';
11
16
  export type { ArtifactLogMetadata, ArtifactLogRecord } from './server/logger.js';
12
17
  export type { ArtifactOrmJsonScalars } from './orm-json-scalars.js';
package/dist/server.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import 'server-only';
2
- export { createArtifactServerClient, ArtifactServerError } from './server/client.js';
2
+ export { createArtifactServerClient } from './server/client.js';
3
+ export { ArtifactPrincipalError, ArtifactServerError, INSUFFICIENT_USER_AUTHENTICATION, stepUpResponse } from './server/errors.js';
3
4
  export { ARTIFACT_AUDIT_ACTIONS, ARTIFACT_AUDIT_RESOURCES_PER_EVENT, ARTIFACT_FLAGS_PER_REQUEST, ARTIFACT_PROCESS_PRINCIPAL_SEARCH_LIMIT, ARTIFACT_PROCESS_PRINCIPAL_TYPES, ARTIFACT_PROCESS_PRINCIPALS_PER_CALL, ARTIFACT_PROCESS_RUN_STATUSES, ARTIFACT_PROCESS_RUNS_LIMIT, ARTIFACT_PROCESS_TASK_VIEWS, ARTIFACT_PROCESS_TASKS_LIMIT, } from './server/broker-contract.js';
4
5
  export { getArtifactRequestContext, readArtifactRuntimeConfig } from './server/runtime-config.js';
5
- export { getArtifactBrowserBootstrap } from './server/request-context.js';
6
+ export { ArtifactContextError, getArtifactBrowserBootstrap } from './server/request-context.js';
7
+ export { verifyArtifactExternalContext } from './server/external-context.js';
8
+ export { externalEvidence } from './server/principal.js';
@@ -10,7 +10,9 @@
10
10
  "dev": "next dev --webpack --hostname 127.0.0.1",
11
11
  "build": "next build",
12
12
  "start": "next start --hostname 127.0.0.1",
13
- "type-check": "next typegen && tsc --noEmit"
13
+ "lint": "next typegen && tsc --noEmit --noUnusedLocals --noUnusedParameters --noFallthroughCasesInSwitch",
14
+ "type-check": "next typegen && tsc --noEmit",
15
+ "test": "node --test tests/*.test.mjs"
14
16
  },
15
17
  "dependencies": {
16
18
  "@sequenceholdings/artifact-studio": "{{artifactStudioVersion}}",
@@ -0,0 +1,25 @@
1
+ import assert from 'node:assert/strict'
2
+ import { test } from 'node:test'
3
+ import { greet } from '../app/actions.ts'
4
+ import { GET } from '../app/api/health/route.ts'
5
+
6
+ test('greeting validates input and returns the server action result', async () => {
7
+ const data = new FormData()
8
+ data.set('name', ' Atlas ')
9
+ assert.deepEqual(await greet({ message: '', error: null }, data), {
10
+ message: 'Hello, Atlas. Your server action ran successfully.', error: null,
11
+ })
12
+ for (const name of ['', ' '.repeat(3), 'a'.repeat(81)]) {
13
+ data.set('name', name)
14
+ const result = await greet({ message: '', error: null }, data)
15
+ assert.equal(result.message, '')
16
+ assert.equal(result.error, 'Enter a name between 1 and 80 characters.')
17
+ }
18
+ })
19
+
20
+ test('health route returns uncached JSON without any runtime bindings', async () => {
21
+ const response = GET()
22
+ assert.equal(response.status, 200)
23
+ assert.equal(response.headers.get('cache-control'), 'no-store')
24
+ assert.deepEqual(await response.json(), { ok: true, framework: 'nextjs' })
25
+ })
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "dependencies": {},
11
11
  "devDependencies": {
12
- "@sequenceholdings/artifact-studio": "^0.5.0",
12
+ "@sequenceholdings/artifact-studio": "^0.6.0",
13
13
  "@sequenceholdings/atlas-ui": "^3.1.1",
14
14
  "@tailwindcss/postcss": "^4.2.2",
15
15
  "@tailwindcss/vite": "^4.2.2",
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "publishConfig": {
10
10
  "access": "public"
11
11
  },
12
- "version": "0.5.0",
12
+ "version": "0.6.1",
13
13
  "description": "SDK types and CLI library for building Artifact Studio apps. Driven via `seq-studio artifact <sub>` (@sequenceholdings/studio-cli), which runs the `./cli` runCli export.",
14
14
  "type": "module",
15
15
  "exports": {
@@ -105,6 +105,10 @@
105
105
  "types": "./dist/server/release-forms.d.ts",
106
106
  "default": "./dist/server/release-forms.js"
107
107
  },
108
+ "./server/testing": {
109
+ "types": "./dist/server/testing.d.ts",
110
+ "default": "./dist/server/testing.js"
111
+ },
108
112
  "./next": {
109
113
  "types": "./dist/next.d.ts",
110
114
  "default": "./dist/next.js"
@@ -124,6 +128,10 @@
124
128
  "./tunnel": {
125
129
  "types": "./dist/tunnel/index.d.ts",
126
130
  "default": "./dist/tunnel/index.js"
131
+ },
132
+ "./offline-management": {
133
+ "types": "./dist/offline/management-types.d.ts",
134
+ "default": "./dist/offline/management-types.js"
127
135
  }
128
136
  },
129
137
  "files": [
@@ -150,9 +158,9 @@
150
158
  "google-auth-library": "^10.5.0",
151
159
  "server-only": "0.0.1",
152
160
  "parse5": "7.3.0",
161
+ "@sequenceholdings/lattice-form-renderer": "0.2.0",
153
162
  "@sequenceholdings/storage": "0.1.0",
154
- "@sequenceholdings/orm": "2.2.0",
155
- "@sequenceholdings/lattice-form-renderer": "0.2.0"
163
+ "@sequenceholdings/orm": "2.3.0"
156
164
  },
157
165
  "devDependencies": {
158
166
  "@types/js-yaml": "^4.0.9",
@@ -10,7 +10,9 @@
10
10
  "dev": "next dev --webpack --hostname 127.0.0.1",
11
11
  "build": "next build",
12
12
  "start": "next start --hostname 127.0.0.1",
13
- "type-check": "next typegen && tsc --noEmit"
13
+ "lint": "next typegen && tsc --noEmit --noUnusedLocals --noUnusedParameters --noFallthroughCasesInSwitch",
14
+ "type-check": "next typegen && tsc --noEmit",
15
+ "test": "node --test tests/*.test.mjs"
14
16
  },
15
17
  "dependencies": {
16
18
  "@sequenceholdings/artifact-studio": "{{artifactStudioVersion}}",
@@ -0,0 +1,25 @@
1
+ import assert from 'node:assert/strict'
2
+ import { test } from 'node:test'
3
+ import { greet } from '../app/actions.ts'
4
+ import { GET } from '../app/api/health/route.ts'
5
+
6
+ test('greeting validates input and returns the server action result', async () => {
7
+ const data = new FormData()
8
+ data.set('name', ' Atlas ')
9
+ assert.deepEqual(await greet({ message: '', error: null }, data), {
10
+ message: 'Hello, Atlas. Your server action ran successfully.', error: null,
11
+ })
12
+ for (const name of ['', ' '.repeat(3), 'a'.repeat(81)]) {
13
+ data.set('name', name)
14
+ const result = await greet({ message: '', error: null }, data)
15
+ assert.equal(result.message, '')
16
+ assert.equal(result.error, 'Enter a name between 1 and 80 characters.')
17
+ }
18
+ })
19
+
20
+ test('health route returns uncached JSON without any runtime bindings', async () => {
21
+ const response = GET()
22
+ assert.equal(response.status, 200)
23
+ assert.equal(response.headers.get('cache-control'), 'no-store')
24
+ assert.deepEqual(await response.json(), { ok: true, framework: 'nextjs' })
25
+ })
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "dependencies": {},
11
11
  "devDependencies": {
12
- "@sequenceholdings/artifact-studio": "^0.5.0",
12
+ "@sequenceholdings/artifact-studio": "^0.6.0",
13
13
  "@sequenceholdings/atlas-ui": "^3.1.1",
14
14
  "@tailwindcss/postcss": "^4.2.2",
15
15
  "@tailwindcss/vite": "^4.2.2",