@sequenceholdings/artifact-studio 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -0
- package/VERSION-PIN.md +15 -0
- package/dist/api.d.ts +5 -0
- package/dist/api.js +7 -0
- package/dist/artifact-env-bindings.d.ts +1 -1
- package/dist/artifact-env-bindings.js +5 -4
- package/dist/build-input-guards.d.ts +8 -0
- package/dist/build-input-guards.js +178 -0
- package/dist/build.js +14 -5
- package/dist/cli-content-readers.d.ts +8 -0
- package/dist/cli-content-readers.js +28 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +38 -6
- package/dist/{env-binding-scan.d.ts → env-binding-manifest.d.ts} +1 -4
- package/dist/env-binding-manifest.js +56 -0
- package/dist/env-bindings.js +2 -34
- package/dist/git-service-client.d.ts +11 -0
- package/dist/git-service-client.js +10 -0
- package/dist/manifest-next.d.ts +6 -0
- package/dist/manifest-next.js +3 -0
- package/dist/manifest.d.ts +4 -0
- package/dist/manifest.js +1 -0
- package/dist/next/bridge.d.ts +15 -1
- package/dist/next/bridge.js +18 -1
- package/dist/next-source-includes.d.ts +50 -0
- package/dist/next-source-includes.js +68 -0
- package/dist/prepare-build.d.ts +10 -2
- package/dist/prepare-build.js +33 -3
- package/dist/repository-identity.d.ts +31 -0
- package/dist/repository-identity.js +41 -0
- package/dist/runtime-contracts.d.ts +1 -4
- package/dist/runtime-contracts.js +2 -15
- package/dist/server/api-client.d.ts +1 -0
- package/dist/server/api-client.js +13 -7
- package/dist/server/group-directory.d.ts +90 -0
- package/dist/server/group-directory.js +17 -0
- package/dist/server/groups.d.ts +2 -1
- package/dist/server/groups.js +2 -0
- package/dist/server/human-completion.d.ts +44 -0
- package/dist/server/human-completion.js +38 -0
- package/dist/server/processes.d.ts +19 -0
- package/dist/server/processes.js +11 -0
- package/dist/server/task-assignments.d.ts +81 -0
- package/dist/server/task-assignments.js +26 -0
- package/dist/server.d.ts +4 -0
- package/dist/source-boundary.d.ts +27 -6
- package/dist/source-boundary.js +44 -30
- package/dist/source-exclusions.d.ts +4 -0
- package/dist/source-exclusions.js +16 -0
- package/dist/source-resolver.js +2 -1
- package/dist/templates/react-vite/package.json +1 -1
- package/package.json +6 -2
- package/templates/react-vite/package.json +1 -1
- package/dist/env-binding-scan.js +0 -179
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Internal wire contract shared by Atlas and seq-studio. Not deployment authorization. */
|
|
3
|
+
export const REPOSITORY_IDENTITY_PATH = '.seq/identity.json';
|
|
4
|
+
export const RepositoryIdentitySchema = z.object({
|
|
5
|
+
tenantId: z.string().max(32).regex(/^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/),
|
|
6
|
+
repositoryId: z.string().uuid(),
|
|
7
|
+
generation: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
8
|
+
}).strict();
|
|
9
|
+
export const RepositoryReceiptSchema = z.object({
|
|
10
|
+
schemaVersion: z.literal(1),
|
|
11
|
+
identity: RepositoryIdentitySchema,
|
|
12
|
+
// Extensible, non-authoritative annotations. Never use metadata for auth or routing.
|
|
13
|
+
metadata: z.record(z.string(), z.json()).default({}),
|
|
14
|
+
}).strict();
|
|
15
|
+
export function parseRepositoryReceipt({ text }) {
|
|
16
|
+
if (new TextEncoder().encode(text).byteLength > 65_536)
|
|
17
|
+
throw new Error('Repository identity exceeds 64 KiB');
|
|
18
|
+
const value = JSON.parse(text);
|
|
19
|
+
const visit = (entry, depth) => {
|
|
20
|
+
if (depth > 32)
|
|
21
|
+
throw new Error('Repository identity exceeds the metadata depth limit');
|
|
22
|
+
if (entry !== null && typeof entry === 'object') {
|
|
23
|
+
for (const child of Object.values(entry))
|
|
24
|
+
visit(child, depth + 1);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
visit(value, 0);
|
|
28
|
+
return RepositoryReceiptSchema.parse(value);
|
|
29
|
+
}
|
|
30
|
+
export function renderRepositoryReceipt({ identity, metadata = {} }) {
|
|
31
|
+
const text = JSON.stringify({ schemaVersion: 1, identity, metadata }, null, 2) + '\n';
|
|
32
|
+
parseRepositoryReceipt({ text });
|
|
33
|
+
return text;
|
|
34
|
+
}
|
|
35
|
+
export function assertRepositoryIdentity({ receipt, expected }) {
|
|
36
|
+
if (receipt.identity.tenantId !== expected.tenantId ||
|
|
37
|
+
receipt.identity.repositoryId !== expected.repositoryId ||
|
|
38
|
+
receipt.identity.generation !== expected.generation) {
|
|
39
|
+
throw new Error('Repository identity does not match the platform record; do not edit identity fields to change ownership');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -17,10 +17,7 @@ export declare const ARTIFACT_NEXT_SOURCE_LIMITS: Readonly<{
|
|
|
17
17
|
export declare const artifactSha256DigestSchema: z.ZodString;
|
|
18
18
|
/** Immutable Next deploymentId from compiled output; distinct from the Atlas activation ID. */
|
|
19
19
|
export declare const artifactNextFrameworkBuildIdSchema: z.ZodString;
|
|
20
|
-
|
|
21
|
-
export declare function isExcludedArtifactSourcePath({ path }: {
|
|
22
|
-
path: string;
|
|
23
|
-
}): boolean;
|
|
20
|
+
export { isExcludedArtifactSourcePath } from './source-exclusions.js';
|
|
24
21
|
/** POSIX source paths only; filesystem readers must additionally reject symlink escapes. */
|
|
25
22
|
export declare const artifactSourcePathSchema: z.ZodString;
|
|
26
23
|
/** An exact browser origin, not a URL prefix or an allowlist wildcard. */
|
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { ARTIFACT_NEXT_RUNTIME, artifactNextSecretNameSchema } from './manifest-next.js';
|
|
3
3
|
import { MAX_STUDIO_PATH_LENGTH } from './paths.js';
|
|
4
4
|
import { isLoopbackHostname } from './loopback.js';
|
|
5
|
+
import { isExcludedArtifactSourcePath } from './source-exclusions.js';
|
|
5
6
|
export { resolveArtifactNextImages } from './next-image-config.js';
|
|
6
7
|
/** Atlas routes the gateway calls; every one authenticates the gateway itself. */
|
|
7
8
|
export const ARTIFACT_NEXT_CONTROL_PATH = '/api/artifact-studio/next/control';
|
|
@@ -27,21 +28,7 @@ export const artifactSha256DigestSchema = z.string().regex(/^[a-f0-9]{64}$/, 'Ex
|
|
|
27
28
|
export const artifactNextFrameworkBuildIdSchema = z.string().min(1).max(128).refine((value) => !/[^A-Za-z0-9_-]/.test(value), 'Expected a canonical Next.js deploymentId containing only letters, digits, underscores and hyphens');
|
|
28
29
|
const imageDigestSchema = z.string().regex(/^sha256:[a-f0-9]{64}$/, 'Expected a SHA-256 image digest');
|
|
29
30
|
const imageReferenceSchema = z.string().max(1024).regex(/^[a-z0-9]+(?:[.-][a-z0-9]+)*(?::[1-9][0-9]{0,4})?(?:\/[a-z0-9]+(?:[._-][a-z0-9]+)*)+@sha256:[a-f0-9]{64}$/, 'Expected a registry image reference pinned by digest');
|
|
30
|
-
|
|
31
|
-
'.git', 'node_modules', '.next', '.npmrc', '.netrc', '.aws', '.ssh', '.config',
|
|
32
|
-
'dist', '.artifact-studio', '.git-credentials', '.pypirc', '.azure', '.gcloud', '.doppler',
|
|
33
|
-
'.turbo', 'coverage', '.docker', '.seq-next-output.json',
|
|
34
|
-
]);
|
|
35
|
-
/** Shared upload exclusion policy. Content scanning and filesystem containment remain separate checks. */
|
|
36
|
-
export function isExcludedArtifactSourcePath({ path }) {
|
|
37
|
-
return path.split('/').some((segment) => {
|
|
38
|
-
const normalized = segment.toLowerCase();
|
|
39
|
-
return excludedSourceSegments.has(normalized)
|
|
40
|
-
|| normalized.startsWith('.env') || normalized.startsWith('.yarnrc')
|
|
41
|
-
|| /(?:^|[-_.])(?:credentials?|service[-_]?account)(?:[-_.].*)?\.json$/.test(normalized)
|
|
42
|
-
|| /\.(pem|key|p12|pfx|jks)$/.test(normalized);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
31
|
+
export { isExcludedArtifactSourcePath } from './source-exclusions.js';
|
|
45
32
|
/** POSIX source paths only; filesystem readers must additionally reject symlink escapes. */
|
|
46
33
|
export const artifactSourcePathSchema = z.string().min(1).max(MAX_STUDIO_PATH_LENGTH).refine((path) => {
|
|
47
34
|
if (path !== path.trim() || /[\\%:\u0000-\u001f\u007f]/.test(path))
|
|
@@ -18,6 +18,7 @@ export declare function createApiClient({ mode, request, json }: {
|
|
|
18
18
|
}): {
|
|
19
19
|
request: (path: string, options?: ArtifactServerFetchOptions) => Promise<Response>;
|
|
20
20
|
fetch<TBody = unknown>(path: string, options?: ArtifactServerFetchOptions): Promise<SequenceApiFetchResponse<TBody>>;
|
|
21
|
+
streamResponse(path: string, options?: ArtifactServerFetchOptions): Promise<Response>;
|
|
21
22
|
stream(path: string, options?: ArtifactServerFetchOptions): Promise<ReadableStream<Uint8Array<ArrayBuffer>>>;
|
|
22
23
|
get: <T = unknown>(path: string) => Promise<T>;
|
|
23
24
|
post: <T = unknown>(path: string, body?: unknown) => Promise<T>;
|
|
@@ -9,6 +9,15 @@ export function createApiClient({ mode, request, json }) {
|
|
|
9
9
|
// Explicit caller result types match the existing browser SDK; the server validates endpoint schemas.
|
|
10
10
|
return await json(operation(path, options), options.signal);
|
|
11
11
|
};
|
|
12
|
+
const readableResponse = async (path, options) => {
|
|
13
|
+
// Only headers are subject to the broker answer deadline. The stream retains
|
|
14
|
+
// cancellation and server byte/deadline limits without losing response metadata.
|
|
15
|
+
const response = await request(operation(path, options), { signal: options.signal, stream: true });
|
|
16
|
+
if (!response.ok || !response.body)
|
|
17
|
+
throw await stepUpErrorFromResponse(response) ??
|
|
18
|
+
new ArtifactServerError(response.status, 'stream_unavailable', 'Atlas did not return a readable response');
|
|
19
|
+
return response;
|
|
20
|
+
};
|
|
12
21
|
return {
|
|
13
22
|
request: fetchResponse,
|
|
14
23
|
async fetch(path, options = {}) {
|
|
@@ -21,14 +30,11 @@ export function createApiClient({ mode, request, json }) {
|
|
|
21
30
|
return value === null ? [] : [[key, value]];
|
|
22
31
|
})) };
|
|
23
32
|
},
|
|
33
|
+
async streamResponse(path, options = {}) {
|
|
34
|
+
return readableResponse(path, options);
|
|
35
|
+
},
|
|
24
36
|
async stream(path, options = {}) {
|
|
25
|
-
|
|
26
|
-
const response = await request(operation(path, options), { signal: options.signal, stream: true });
|
|
27
|
-
if (!response.ok || !response.body) {
|
|
28
|
-
throw await stepUpErrorFromResponse(response) ??
|
|
29
|
-
new ArtifactServerError(response.status, 'stream_unavailable', 'Atlas did not return a readable response');
|
|
30
|
-
}
|
|
31
|
-
return response.body;
|
|
37
|
+
return (await readableResponse(path, options)).body;
|
|
32
38
|
},
|
|
33
39
|
get: (path) => typedJson(path),
|
|
34
40
|
post: (path, body) => typedJson(path, { method: 'POST', body }),
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ArtifactServerMode, type ArtifactServerRequest } from './broker-contract.js';
|
|
3
|
+
declare const detail: z.ZodObject<{
|
|
4
|
+
id: z.ZodString;
|
|
5
|
+
name: z.ZodString;
|
|
6
|
+
description: z.ZodNullable<z.ZodString>;
|
|
7
|
+
email: z.ZodNullable<z.ZodString>;
|
|
8
|
+
source: z.ZodString;
|
|
9
|
+
managedBy: z.ZodNullable<z.ZodString>;
|
|
10
|
+
memberCount: z.ZodNullable<z.ZodNumber>;
|
|
11
|
+
userRole: z.ZodNullable<z.ZodEnum<{
|
|
12
|
+
admin: "admin";
|
|
13
|
+
member: "member";
|
|
14
|
+
}>>;
|
|
15
|
+
members: z.ZodArray<z.ZodObject<{
|
|
16
|
+
id: z.ZodString;
|
|
17
|
+
sub: z.ZodNullable<z.ZodString>;
|
|
18
|
+
name: z.ZodString;
|
|
19
|
+
email: z.ZodString;
|
|
20
|
+
role: z.ZodEnum<{
|
|
21
|
+
admin: "admin";
|
|
22
|
+
member: "member";
|
|
23
|
+
}>;
|
|
24
|
+
}, z.core.$strip>>;
|
|
25
|
+
added: z.ZodOptional<z.ZodNumber>;
|
|
26
|
+
skippedExisting: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
declare const page: z.ZodObject<{
|
|
29
|
+
groups: z.ZodArray<z.ZodObject<{
|
|
30
|
+
id: z.ZodString;
|
|
31
|
+
name: z.ZodString;
|
|
32
|
+
description: z.ZodNullable<z.ZodString>;
|
|
33
|
+
email: z.ZodNullable<z.ZodString>;
|
|
34
|
+
source: z.ZodString;
|
|
35
|
+
managedBy: z.ZodNullable<z.ZodString>;
|
|
36
|
+
memberCount: z.ZodNullable<z.ZodNumber>;
|
|
37
|
+
userRole: z.ZodNullable<z.ZodEnum<{
|
|
38
|
+
admin: "admin";
|
|
39
|
+
member: "member";
|
|
40
|
+
}>>;
|
|
41
|
+
}, z.core.$strip>>;
|
|
42
|
+
pagination: z.ZodObject<{
|
|
43
|
+
page: z.ZodNumber;
|
|
44
|
+
limit: z.ZodNumber;
|
|
45
|
+
totalCount: z.ZodNumber;
|
|
46
|
+
totalPages: z.ZodNumber;
|
|
47
|
+
}, z.core.$strip>;
|
|
48
|
+
}, z.core.$strip>;
|
|
49
|
+
export type ArtifactGroupDetail = z.infer<typeof detail>;
|
|
50
|
+
export type ArtifactGroupDirectoryPage = z.infer<typeof page>;
|
|
51
|
+
export type ArtifactGroupDirectoryQuery = {
|
|
52
|
+
page?: number;
|
|
53
|
+
limit?: number;
|
|
54
|
+
search?: string;
|
|
55
|
+
adminView?: boolean;
|
|
56
|
+
};
|
|
57
|
+
export type ArtifactGroupMemberChange = {
|
|
58
|
+
action: 'add';
|
|
59
|
+
members: Array<{
|
|
60
|
+
userId: string;
|
|
61
|
+
role?: 'admin' | 'member';
|
|
62
|
+
}>;
|
|
63
|
+
} | {
|
|
64
|
+
action: 'remove';
|
|
65
|
+
userId: string;
|
|
66
|
+
} | {
|
|
67
|
+
action: 'role';
|
|
68
|
+
userId: string;
|
|
69
|
+
role: 'admin' | 'member';
|
|
70
|
+
};
|
|
71
|
+
export interface ArtifactGroupDirectory {
|
|
72
|
+
/** Exact live group labels visible through membership or discoverability, without rosters. */
|
|
73
|
+
resolve(groupIds: string[]): Promise<{
|
|
74
|
+
groups: Array<{
|
|
75
|
+
id: string;
|
|
76
|
+
name: string;
|
|
77
|
+
}>;
|
|
78
|
+
}>;
|
|
79
|
+
/** Caller memberships; adminView requires platform administration. Declare POST /api/groups/directory in api.write. */
|
|
80
|
+
directory(query?: ArtifactGroupDirectoryQuery): Promise<ArtifactGroupDirectoryPage>;
|
|
81
|
+
/** Requires group membership or platform administration and api.read declaration. */
|
|
82
|
+
get(groupId: string): Promise<ArtifactGroupDetail>;
|
|
83
|
+
/** Requires group administration and the exact api.write path. App roles do not confer group authority. */
|
|
84
|
+
changeMembers(groupId: string, change: ArtifactGroupMemberChange): Promise<ArtifactGroupDetail>;
|
|
85
|
+
}
|
|
86
|
+
export declare function createGroupDirectory({ mode, json }: {
|
|
87
|
+
mode: ArtifactServerMode;
|
|
88
|
+
json: (operation: ArtifactServerRequest) => Promise<unknown>;
|
|
89
|
+
}): ArtifactGroupDirectory;
|
|
90
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { artifactServerRequestSchema } from './broker-contract.js';
|
|
3
|
+
import { ArtifactServerError } from './errors.js';
|
|
4
|
+
const summary = z.object({ id: z.string(), name: z.string(), description: z.string().nullable(), email: z.string().nullable(), source: z.string(), managedBy: z.string().nullable(), memberCount: z.number().nullable(), userRole: z.enum(['admin', 'member']).nullable() });
|
|
5
|
+
const detail = summary.extend({ members: z.array(z.object({ id: z.string(), sub: z.string().nullable(), name: z.string(), email: z.string(), role: z.enum(['admin', 'member']) })), added: z.number().optional(), skippedExisting: z.array(z.string()).optional() });
|
|
6
|
+
const page = z.object({ groups: z.array(summary), pagination: z.object({ page: z.number(), limit: z.number(), totalCount: z.number(), totalPages: z.number() }) });
|
|
7
|
+
export function createGroupDirectory({ mode, json }) {
|
|
8
|
+
const segment = (value) => { if (!value || value.length > 255 || /[/?#%\\]/.test(value) || value === '.' || value === '..')
|
|
9
|
+
throw new ArtifactServerError(400, 'invalid_request', 'Invalid group id'); return value; };
|
|
10
|
+
const call = async (schema, path, method, body) => {
|
|
11
|
+
const parsed = schema.safeParse(await json(artifactServerRequestSchema.parse({ operation: 'api', mode, path, method, body })));
|
|
12
|
+
if (!parsed.success)
|
|
13
|
+
throw new ArtifactServerError(502, 'invalid_response', 'Atlas returned an invalid group response');
|
|
14
|
+
return parsed.data;
|
|
15
|
+
};
|
|
16
|
+
return { resolve: groupIds => call(z.object({ groups: z.array(z.object({ id: z.string(), name: z.string() })) }), '/api/groups/resolve', 'POST', { groupIds }), directory: (query = {}) => call(page, '/api/groups/directory', 'POST', query), get: groupId => call(detail, `/api/groups/${segment(groupId)}`, 'GET'), changeMembers: (groupId, change) => call(detail, `/api/groups/${segment(groupId)}/members/change`, 'POST', change) };
|
|
17
|
+
}
|
package/dist/server/groups.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ArtifactGroupDirectory } from './group-directory.js';
|
|
1
2
|
import { type ArtifactServerMode, type ArtifactServerRequest } from './broker-contract.js';
|
|
2
3
|
/** One group the signed-in user belongs to. `userRole` is `admin` when they administer it. */
|
|
3
4
|
export interface ArtifactGroupMembership {
|
|
@@ -18,7 +19,7 @@ export interface ArtifactGroupMembershipsPage {
|
|
|
18
19
|
totalPages: number;
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
|
-
export interface ArtifactServerGroups {
|
|
22
|
+
export interface ArtifactServerGroups extends ArtifactGroupDirectory {
|
|
22
23
|
/**
|
|
23
24
|
* The signed-in user's group memberships. Requires `server.capabilities.groups.read`.
|
|
24
25
|
* Atlas decides which groups they are in; this call cannot name groups, search the directory,
|
package/dist/server/groups.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createGroupDirectory } from './group-directory.js';
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { ARTIFACT_GROUP_MEMBERSHIPS_LIMIT, artifactServerRequestSchema, } from './broker-contract.js';
|
|
3
4
|
import { ArtifactServerError } from './errors.js';
|
|
@@ -16,6 +17,7 @@ const membershipsPageSchema = z.object({
|
|
|
16
17
|
}).strict();
|
|
17
18
|
export function createArtifactGroupsClient({ mode, json }) {
|
|
18
19
|
return {
|
|
20
|
+
...createGroupDirectory({ mode, json }),
|
|
19
21
|
async list(query = {}) {
|
|
20
22
|
const parsed = membershipsPageSchema.safeParse(await json(artifactServerRequestSchema.parse({
|
|
21
23
|
operation: 'groups_memberships', mode, ...query,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ArtifactServerMode, type ArtifactServerRequest } from './broker-contract.js';
|
|
3
|
+
declare const querySchema: z.ZodObject<{
|
|
4
|
+
runId: z.ZodString;
|
|
5
|
+
stepId: z.ZodString;
|
|
6
|
+
}, z.core.$strict>;
|
|
7
|
+
declare const receiptSchema: z.ZodObject<{
|
|
8
|
+
runId: z.ZodString;
|
|
9
|
+
processId: z.ZodString;
|
|
10
|
+
processVersion: z.ZodString;
|
|
11
|
+
bundleHash: z.ZodString;
|
|
12
|
+
stepId: z.ZodString;
|
|
13
|
+
nodeId: z.ZodString;
|
|
14
|
+
attempt: z.ZodNumber;
|
|
15
|
+
actorSub: z.ZodString;
|
|
16
|
+
edgeId: z.ZodNullable<z.ZodString>;
|
|
17
|
+
payloadHash: z.ZodString;
|
|
18
|
+
completedAt: z.ZodString;
|
|
19
|
+
}, z.core.$strict>;
|
|
20
|
+
export type ArtifactHumanCompletionQuery = z.infer<typeof querySchema>;
|
|
21
|
+
export type ArtifactHumanCompletion = z.infer<typeof receiptSchema>;
|
|
22
|
+
/** Read verified, committed human evidence. No payload, token, or mutation is returned.
|
|
23
|
+
* Requires the API path declaration and the caller's Viewer on a declared process run. */
|
|
24
|
+
export declare function createHumanCompletionReader({ mode, json }: {
|
|
25
|
+
mode: ArtifactServerMode;
|
|
26
|
+
json: (operation: ArtifactServerRequest) => Promise<unknown>;
|
|
27
|
+
}): (query: ArtifactHumanCompletionQuery) => Promise<ArtifactHumanCompletion>;
|
|
28
|
+
declare const continuationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
29
|
+
kind: z.ZodLiteral<"waiting">;
|
|
30
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
31
|
+
kind: z.ZodLiteral<"terminal">;
|
|
32
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
33
|
+
kind: z.ZodLiteral<"human">;
|
|
34
|
+
stepId: z.ZodString;
|
|
35
|
+
nodeId: z.ZodString;
|
|
36
|
+
users: z.ZodArray<z.ZodString>;
|
|
37
|
+
groups: z.ZodArray<z.ZodString>;
|
|
38
|
+
}, z.core.$strict>], "kind">;
|
|
39
|
+
export type ArtifactHumanContinuation = z.infer<typeof continuationSchema>;
|
|
40
|
+
export declare function createHumanContinuationReader({ mode, json }: {
|
|
41
|
+
mode: ArtifactServerMode;
|
|
42
|
+
json: (operation: ArtifactServerRequest) => Promise<unknown>;
|
|
43
|
+
}): (query: ArtifactHumanCompletionQuery) => Promise<ArtifactHumanContinuation>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { artifactServerRequestSchema } from './broker-contract.js';
|
|
3
|
+
import { ArtifactServerError } from './errors.js';
|
|
4
|
+
const querySchema = z.object({ runId: z.string().uuid(), stepId: z.string().uuid() }).strict();
|
|
5
|
+
const receiptSchema = z.object({
|
|
6
|
+
runId: z.string().uuid(), processId: z.string().min(1), processVersion: z.string().min(1), bundleHash: z.string().min(1),
|
|
7
|
+
stepId: z.string().uuid(), nodeId: z.string().min(1), attempt: z.number().int().positive(), actorSub: z.string().min(1),
|
|
8
|
+
edgeId: z.string().nullable(), payloadHash: z.string().regex(/^[a-f0-9]{64}$/), completedAt: z.string().datetime(),
|
|
9
|
+
}).strict();
|
|
10
|
+
/** Read verified, committed human evidence. No payload, token, or mutation is returned.
|
|
11
|
+
* Requires the API path declaration and the caller's Viewer on a declared process run. */
|
|
12
|
+
export function createHumanCompletionReader({ mode, json }) {
|
|
13
|
+
return async (query) => {
|
|
14
|
+
const input = querySchema.parse(query);
|
|
15
|
+
const parsed = receiptSchema.safeParse(await json(artifactServerRequestSchema.parse({
|
|
16
|
+
operation: 'api', mode, method: 'POST', path: '/api/lattice/human-completion/read', body: input,
|
|
17
|
+
})));
|
|
18
|
+
if (!parsed.success || parsed.data.runId !== input.runId || parsed.data.stepId !== input.stepId)
|
|
19
|
+
throw new ArtifactServerError(502, 'invalid_response', 'Atlas returned invalid human completion evidence');
|
|
20
|
+
return parsed.data;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const continuationSchema = z.discriminatedUnion('kind', [
|
|
24
|
+
z.object({ kind: z.literal('waiting') }).strict(),
|
|
25
|
+
z.object({ kind: z.literal('terminal') }).strict(),
|
|
26
|
+
z.object({ kind: z.literal('human'), stepId: z.string().uuid(), nodeId: z.string().min(1), users: z.array(z.string().min(1)).max(500), groups: z.array(z.string().min(1)).max(500) }).strict(),
|
|
27
|
+
]);
|
|
28
|
+
export function createHumanContinuationReader({ mode, json }) {
|
|
29
|
+
return async (query) => {
|
|
30
|
+
const input = querySchema.parse(query);
|
|
31
|
+
const parsed = continuationSchema.safeParse(await json(artifactServerRequestSchema.parse({
|
|
32
|
+
operation: 'api', mode, method: 'POST', path: '/api/lattice/human-continuation/read', body: input,
|
|
33
|
+
})));
|
|
34
|
+
if (!parsed.success || (parsed.data.kind === 'human' && parsed.data.stepId === input.stepId))
|
|
35
|
+
throw new ArtifactServerError(502, 'invalid_response', 'Atlas returned invalid human continuation evidence');
|
|
36
|
+
return parsed.data;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type ArtifactHumanContinuation, type ArtifactHumanCompletionQuery, type ArtifactHumanCompletion } from './human-completion.js';
|
|
2
|
+
import { type ArtifactGroupTasksQuery, type ArtifactGroupTasksPage, type ArtifactTaskAssignments } from './task-assignments.js';
|
|
1
3
|
import { type ArtifactProcessPrincipalType, type ArtifactProcessRunStatus, type ArtifactProcessTaskView, type ArtifactServerMode, type ArtifactServerRequest } from './broker-contract.js';
|
|
2
4
|
import { type ArtifactProcessRunsPage, type ArtifactProcessRunsPageQuery } from './process-runs-page.js';
|
|
3
5
|
import { type ArtifactProcessPrincipals, type ArtifactProcessRunViewerChange, type ArtifactRunSharingOperations } from './process-run-sharing.js';
|
|
@@ -318,6 +320,21 @@ export interface ArtifactProcessPrincipal {
|
|
|
318
320
|
memberCount?: number;
|
|
319
321
|
}
|
|
320
322
|
export interface ArtifactServerProcesses extends ArtifactRunSharingOperations {
|
|
323
|
+
/** Parked tasks and materialized assignees; run Viewer checks and declared process scopes apply. */
|
|
324
|
+
listGroupTasksPage(query: ArtifactGroupTasksQuery): Promise<ArtifactGroupTasksPage>;
|
|
325
|
+
readTaskAssignments(query: {
|
|
326
|
+
runIds: string[];
|
|
327
|
+
}): Promise<ArtifactTaskAssignments>;
|
|
328
|
+
/** Batch current definitions, each gated by process Viewer and the declared read set. */
|
|
329
|
+
describeMany(query: {
|
|
330
|
+
processes: Array<string | ArtifactProcessDescribeQuery>;
|
|
331
|
+
}): Promise<{
|
|
332
|
+
processes: Array<{
|
|
333
|
+
process: string;
|
|
334
|
+
version?: string;
|
|
335
|
+
definition: ArtifactProcessDefinition;
|
|
336
|
+
}>;
|
|
337
|
+
}>;
|
|
321
338
|
/** Start a run of a process declared under `processes.start`; the user's Invoker grant decides. */
|
|
322
339
|
start(input: ArtifactProcessStart): Promise<ArtifactProcessRun>;
|
|
323
340
|
/** List the runs of a process declared under `processes.read` that the user may view. */
|
|
@@ -331,6 +348,8 @@ export interface ArtifactServerProcesses extends ArtifactRunSharingOperations {
|
|
|
331
348
|
/** The user's own human steps across processes declared under `processes.read`. */
|
|
332
349
|
listTasks(query?: ArtifactProcessTasksQuery): Promise<ArtifactProcessTask[]>;
|
|
333
350
|
/** Complete a parked human step on a run of a process declared under `processes.act`; the user's own `advancer` grant decides. */
|
|
351
|
+
getHumanContinuation(query: ArtifactHumanCompletionQuery): Promise<ArtifactHumanContinuation>;
|
|
352
|
+
getHumanCompletion(query: ArtifactHumanCompletionQuery): Promise<ArtifactHumanCompletion>;
|
|
334
353
|
advance(input: ArtifactProcessAdvance): Promise<ArtifactProcessAdvanceResult>;
|
|
335
354
|
/** Reassign a parked human step; the user's `owner` relation on the run decides. */
|
|
336
355
|
assign(input: ArtifactProcessAssign): Promise<ArtifactProcessAssignResult>;
|
package/dist/server/processes.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createHumanCompletionReader, createHumanContinuationReader } from './human-completion.js';
|
|
2
|
+
import { createTaskAssignmentsReader, createGroupTasksReader } from './task-assignments.js';
|
|
1
3
|
import { artifactServerRequestSchema, } from './broker-contract.js';
|
|
2
4
|
import { ArtifactServerError } from './errors.js';
|
|
3
5
|
import { readArtifactProcessRunsPage } from './process-runs-page.js';
|
|
@@ -5,6 +7,13 @@ import { createArtifactProcessSharingClient } from './process-run-sharing.js';
|
|
|
5
7
|
/** `client.processes`: Lattice through the broker's typed `lattice*` operations. Atlas owns validation and redaction. */
|
|
6
8
|
export function createArtifactProcessesClient({ mode, json }) {
|
|
7
9
|
return {
|
|
10
|
+
listGroupTasksPage: createGroupTasksReader({ mode, json }),
|
|
11
|
+
readTaskAssignments: createTaskAssignmentsReader({ mode, json }),
|
|
12
|
+
describeMany: async ({ processes }) => {
|
|
13
|
+
if (!processes.length || processes.length > 50 || processes.some(value => { const id = typeof value === 'string' ? value : value.process; return !id || id.length > 255 || (typeof value !== 'string' && value.version !== undefined && (!value.version || value.version.length > 255)); }))
|
|
14
|
+
throw new ArtifactServerError(400, 'invalid_request', 'Expected between one and fifty process IDs');
|
|
15
|
+
return await json(artifactServerRequestSchema.parse({ operation: 'api', mode, method: 'POST', path: '/api/lattice/process-definitions/read', body: { processes } }));
|
|
16
|
+
},
|
|
8
17
|
start: async ({ process, ...run }) => {
|
|
9
18
|
// Atlas returns the started run's identifiers; the shape is the Lattice start response.
|
|
10
19
|
return await json(artifactServerRequestSchema.parse({ operation: 'lattice', mode, process, ...run }));
|
|
@@ -32,6 +41,8 @@ export function createArtifactProcessesClient({ mode, json }) {
|
|
|
32
41
|
throw new ArtifactServerError(502, 'malformed_response', 'Atlas returned a malformed task list');
|
|
33
42
|
return body.items;
|
|
34
43
|
},
|
|
44
|
+
getHumanContinuation: createHumanContinuationReader({ mode, json }),
|
|
45
|
+
getHumanCompletion: createHumanCompletionReader({ mode, json }),
|
|
35
46
|
advance: async (input) => {
|
|
36
47
|
// The Lattice advance response; Atlas owns the step resolution, the ACL and the walk.
|
|
37
48
|
return await json(artifactServerRequestSchema.parse({ operation: 'lattice_advance', mode, ...input }));
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ArtifactServerMode, type ArtifactServerRequest } from './broker-contract.js';
|
|
3
|
+
declare const result: z.ZodObject<{
|
|
4
|
+
runs: z.ZodArray<z.ZodObject<{
|
|
5
|
+
runId: z.ZodString;
|
|
6
|
+
processId: z.ZodString;
|
|
7
|
+
truncated: z.ZodBoolean;
|
|
8
|
+
tasks: z.ZodArray<z.ZodObject<{
|
|
9
|
+
stepId: z.ZodString;
|
|
10
|
+
nodeId: z.ZodString;
|
|
11
|
+
startedAt: z.ZodString;
|
|
12
|
+
dueAt: z.ZodNullable<z.ZodString>;
|
|
13
|
+
users: z.ZodArray<z.ZodObject<{
|
|
14
|
+
type: z.ZodEnum<{
|
|
15
|
+
user: "user";
|
|
16
|
+
group: "group";
|
|
17
|
+
}>;
|
|
18
|
+
id: z.ZodString;
|
|
19
|
+
name: z.ZodString;
|
|
20
|
+
email: z.ZodOptional<z.ZodString>;
|
|
21
|
+
picture: z.ZodOptional<z.ZodString>;
|
|
22
|
+
memberCount: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
unavailable: z.ZodOptional<z.ZodBoolean>;
|
|
24
|
+
}, z.core.$strip>>;
|
|
25
|
+
groups: z.ZodArray<z.ZodObject<{
|
|
26
|
+
type: z.ZodEnum<{
|
|
27
|
+
user: "user";
|
|
28
|
+
group: "group";
|
|
29
|
+
}>;
|
|
30
|
+
id: z.ZodString;
|
|
31
|
+
name: z.ZodString;
|
|
32
|
+
email: z.ZodOptional<z.ZodString>;
|
|
33
|
+
picture: z.ZodOptional<z.ZodString>;
|
|
34
|
+
memberCount: z.ZodOptional<z.ZodNumber>;
|
|
35
|
+
unavailable: z.ZodOptional<z.ZodBoolean>;
|
|
36
|
+
}, z.core.$strip>>;
|
|
37
|
+
}, z.core.$strip>>;
|
|
38
|
+
}, z.core.$strip>>;
|
|
39
|
+
}, z.core.$strip>;
|
|
40
|
+
export type ArtifactTaskAssignments = z.infer<typeof result>;
|
|
41
|
+
/** Read at most fifty runs' parked assignments. Requires api.write path declaration and processes.read; every run must be viewable. */
|
|
42
|
+
export declare function createTaskAssignmentsReader({ mode, json }: {
|
|
43
|
+
mode: ArtifactServerMode;
|
|
44
|
+
json: (operation: ArtifactServerRequest) => Promise<unknown>;
|
|
45
|
+
}): ({ runIds }: {
|
|
46
|
+
runIds: string[];
|
|
47
|
+
}) => Promise<ArtifactTaskAssignments>;
|
|
48
|
+
declare const groupTaskQuery: z.ZodObject<{
|
|
49
|
+
groupId: z.ZodString;
|
|
50
|
+
processes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
51
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
52
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
53
|
+
}, z.core.$strict>;
|
|
54
|
+
declare const groupTaskPage: z.ZodObject<{
|
|
55
|
+
tasks: z.ZodArray<z.ZodObject<{
|
|
56
|
+
stepId: z.ZodString;
|
|
57
|
+
runId: z.ZodString;
|
|
58
|
+
nodeId: z.ZodString;
|
|
59
|
+
processId: z.ZodString;
|
|
60
|
+
processVersion: z.ZodString;
|
|
61
|
+
title: z.ZodNullable<z.ZodString>;
|
|
62
|
+
entity: z.ZodNullable<z.ZodObject<{
|
|
63
|
+
type: z.ZodString;
|
|
64
|
+
id: z.ZodString;
|
|
65
|
+
}, z.core.$strip>>;
|
|
66
|
+
startedAt: z.ZodString;
|
|
67
|
+
dueAt: z.ZodNullable<z.ZodString>;
|
|
68
|
+
users: z.ZodArray<z.ZodString>;
|
|
69
|
+
memberUsers: z.ZodArray<z.ZodString>;
|
|
70
|
+
groups: z.ZodArray<z.ZodString>;
|
|
71
|
+
}, z.core.$strip>>;
|
|
72
|
+
nextCursor: z.ZodNullable<z.ZodString>;
|
|
73
|
+
}, z.core.$strip>;
|
|
74
|
+
export type ArtifactGroupTasksQuery = z.infer<typeof groupTaskQuery>;
|
|
75
|
+
export type ArtifactGroupTasksPage = z.infer<typeof groupTaskPage>;
|
|
76
|
+
/** Group membership and each run's Viewer scope are checked before paging. */
|
|
77
|
+
export declare function createGroupTasksReader({ mode, json }: {
|
|
78
|
+
mode: ArtifactServerMode;
|
|
79
|
+
json: (operation: ArtifactServerRequest) => Promise<unknown>;
|
|
80
|
+
}): (query: ArtifactGroupTasksQuery) => Promise<ArtifactGroupTasksPage>;
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { artifactServerRequestSchema } from './broker-contract.js';
|
|
3
|
+
import { ArtifactServerError } from './errors.js';
|
|
4
|
+
const principal = z.object({ type: z.enum(['user', 'group']), id: z.string(), name: z.string(), email: z.string().optional(), picture: z.string().optional(), memberCount: z.number().optional(), unavailable: z.boolean().optional() });
|
|
5
|
+
const result = z.object({ runs: z.array(z.object({ runId: z.string(), processId: z.string(), truncated: z.boolean(), tasks: z.array(z.object({ stepId: z.string(), nodeId: z.string(), startedAt: z.string(), dueAt: z.string().nullable(), users: z.array(principal), groups: z.array(principal) })) })) });
|
|
6
|
+
/** Read at most fifty runs' parked assignments. Requires api.write path declaration and processes.read; every run must be viewable. */
|
|
7
|
+
export function createTaskAssignmentsReader({ mode, json }) {
|
|
8
|
+
return async ({ runIds }) => {
|
|
9
|
+
const input = z.array(z.string().uuid()).min(1).max(50).parse(runIds);
|
|
10
|
+
const parsed = result.safeParse(await json(artifactServerRequestSchema.parse({ operation: 'api', mode, method: 'POST', path: '/api/lattice/task-assignments/read', body: { runIds: input } })));
|
|
11
|
+
if (!parsed.success)
|
|
12
|
+
throw new ArtifactServerError(502, 'invalid_response', 'Atlas returned invalid task assignments');
|
|
13
|
+
return parsed.data;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const groupTaskQuery = z.object({ groupId: z.string().min(1).max(255), processes: z.array(z.string().min(1).max(255)).min(1).max(200).optional(), limit: z.number().int().min(1).max(100).optional(), cursor: z.string().max(2048).optional() }).strict();
|
|
17
|
+
const groupTaskPage = z.object({ tasks: z.array(z.object({ stepId: z.string(), runId: z.string(), nodeId: z.string(), processId: z.string(), processVersion: z.string(), title: z.string().nullable(), entity: z.object({ type: z.string(), id: z.string() }).nullable(), startedAt: z.string(), dueAt: z.string().nullable(), users: z.array(z.string()), memberUsers: z.array(z.string()), groups: z.array(z.string()) })), nextCursor: z.string().nullable() });
|
|
18
|
+
/** Group membership and each run's Viewer scope are checked before paging. */
|
|
19
|
+
export function createGroupTasksReader({ mode, json }) {
|
|
20
|
+
return async (query) => {
|
|
21
|
+
const parsed = groupTaskPage.safeParse(await json(artifactServerRequestSchema.parse({ operation: 'api', mode, method: 'POST', path: '/api/lattice/group-tasks/read', body: groupTaskQuery.parse(query) })));
|
|
22
|
+
if (!parsed.success)
|
|
23
|
+
throw new ArtifactServerError(502, 'invalid_response', 'Atlas returned an invalid group task page');
|
|
24
|
+
return parsed.data;
|
|
25
|
+
};
|
|
26
|
+
}
|
package/dist/server.d.ts
CHANGED
|
@@ -16,3 +16,7 @@ export { externalEvidence } from './server/principal.js';
|
|
|
16
16
|
export type { ArtifactExternalEvidence, ArtifactExternalPrincipal, ArtifactPrincipal } from './server/principal.js';
|
|
17
17
|
export type { ArtifactLogMetadata, ArtifactLogRecord } from './server/logger.js';
|
|
18
18
|
export type { ArtifactOrmJsonScalars } from './orm-json-scalars.js';
|
|
19
|
+
export type { ArtifactGroupDetail, ArtifactGroupDirectoryPage, ArtifactGroupDirectoryQuery, ArtifactGroupMemberChange } from './server/group-directory.js';
|
|
20
|
+
export type { ArtifactTaskAssignments } from './server/task-assignments.js';
|
|
21
|
+
export type { ArtifactGroupTasksQuery, ArtifactGroupTasksPage } from './server/task-assignments.js';
|
|
22
|
+
export type { ArtifactHumanCompletionQuery, ArtifactHumanCompletion, ArtifactHumanContinuation } from './server/human-completion.js';
|
|
@@ -4,16 +4,37 @@ export interface ArtifactSourceBoundaryOptions {
|
|
|
4
4
|
trustedExternalRoots: readonly string[];
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* the build root would let
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* Which files may feed an artifact build. Artifact source must be
|
|
8
|
+
* self-contained: a relative path that escapes the build root would let files
|
|
9
|
+
* elsewhere on the build machine (uncommitted checkout files, or the caller's
|
|
10
|
+
* credentials) enter the official bundle. Published dependencies and the
|
|
11
|
+
* CLI-pinned platform peers are intentionally allowed because they are external
|
|
12
|
+
* to the artifact by design.
|
|
12
13
|
*/
|
|
13
|
-
export
|
|
14
|
+
export interface SourceBoundary {
|
|
15
|
+
/** Physical source root. */
|
|
16
|
+
root: string;
|
|
17
|
+
/**
|
|
18
|
+
* Throw unless `filePath` may enter the build when `importer` references it.
|
|
19
|
+
* `bareSpecifier` marks a package reference rather than a path. Paths are
|
|
20
|
+
* compared physically, so an in-root symlink cannot admit an outside file.
|
|
21
|
+
*/
|
|
22
|
+
assertBuildInput(input: {
|
|
23
|
+
filePath: string;
|
|
24
|
+
importer?: string;
|
|
25
|
+
bareSpecifier: boolean;
|
|
26
|
+
reference: string;
|
|
27
|
+
}): void;
|
|
28
|
+
/** Whether `filePath` is part of the CLI-pinned platform toolchain. */
|
|
29
|
+
isTrusted(filePath: string): boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare function createSourceBoundary({ root, trustedExternalRoots }: ArtifactSourceBoundaryOptions): SourceBoundary;
|
|
32
|
+
/** Reject module imports that resolve outside the source boundary. */
|
|
33
|
+
export declare function rejectOutsideSourcePlugin(boundary: SourceBoundary): Plugin;
|
|
14
34
|
/**
|
|
15
35
|
* Whether `filePath` is inside a `node_modules` directory. Bundlers pass
|
|
16
36
|
* Windows paths with forward slashes, so compare in that form whatever the
|
|
17
37
|
* platform separator is.
|
|
18
38
|
*/
|
|
19
39
|
export declare function isNodeModulesPath(filePath: string): boolean;
|
|
40
|
+
export declare function isBareImport(source: string): boolean;
|