@sequenceholdings/artifact-studio 0.9.2 → 0.10.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.
package/README.md CHANGED
@@ -475,6 +475,13 @@ const body = await client.api.stream('/api/agents/chat', {
475
475
  return new Response(body, { headers: { 'content-type': 'text/event-stream' } })
476
476
  ```
477
477
 
478
+ `client.api.streamResponse(path, options)` uses the same streaming transport and
479
+ returns the full `Response`, retaining the broker-approved content type, length,
480
+ and download filename. It does not buffer the body. Forward `request.signal` to
481
+ cancel upstream work when the user disconnects. Governed content readers impose
482
+ their own stricter byte and whole-stream deadlines; this method does not extend
483
+ them. The method is prepared for 0.10.0 and awaits publication.
484
+
478
485
  ## Grouping calls into a trace
479
486
 
480
487
  The Artifact host supplies tracing context automatically for supported platform
@@ -514,3 +521,215 @@ Upload with `api.upload` option `{ fieldName: 'audio' }`; ordinary uploads still
514
521
  default to `file`. Atlas transcription trims silence by default; multipart
515
522
  `trimSilence=false` opts out. Speech uses the declared host paths without requiring
516
523
  the separate general Next host API rollout flag.
524
+
525
+ ## Next.js task completion and app navigation access (awaiting publication)
526
+
527
+ These methods require the matching Atlas host deployment. The SDK conveniences
528
+ are prepared for Artifact Studio 0.10.0; source availability does not mean the
529
+ package has been published.
530
+
531
+ ```ts
532
+ const appAccess = await bridge.auth.getAppAccess()
533
+ // Show links only when the destination's value is true. This is a UI hint;
534
+ // the destination still authorizes the user independently.
535
+
536
+ // After your app-owned route successfully advances its native process step:
537
+ const { notified } = await bridge.lattice.notifyAdvance()
538
+ // false means the server has not yet completed this human step. Retry this
539
+ // read after the accepted transition settles; never replay the mutation.
540
+ ```
541
+
542
+ `auth.getAppAccess` reads only the current viewer's boolean navigation map from
543
+ one fixed, authenticated host endpoint. It accepts no principal or URL,
544
+ requires no arbitrary `capabilities.api` declaration, and does not depend on
545
+ `artifact-next-browser-api`. Missing or malformed permission responses fail
546
+ closed. The generic bridge equivalent is
547
+ `bridge.request({ method: 'auth.getAppAccess' })`.
548
+
549
+ `lattice.notifyAdvance` accepts no run or step arguments. The host uses its own
550
+ trusted embed context and checks the native authorized run snapshot. Only the
551
+ exact completed human step produces an advance notification; pending, failed,
552
+ replaced or unauthorized tasks cannot. Repeated notifications are deduplicated
553
+ for that host instance. It never advances a process or changes assignments.
554
+ An app can call it before submitting as a host-support check: a parked step
555
+ returns `{ notified: false }`; an old host rejects the method. The generic
556
+ bridge equivalent is `bridge.request({ method: 'lattice.notifyAdvance' })`.
557
+ Applications outside a task embed have no completion context and cannot use it.
558
+
559
+
560
+ ### Bounded lifecycle reads and group management discovery
561
+
562
+ A server application can read exact run lifecycles in batches of at most 500
563
+ through `api.post('/api/lattice/runs/read', { runIds })`. Declare that POST path
564
+ under `server.capabilities.api.write` and each process under `processes.read`.
565
+ The adapter has no write effects. It requires the signed-in user, a declared
566
+ process (including development bindings), and Viewer on every run before
567
+ returning any result. Missing or denied runs fail the entire batch. Results
568
+ contain only `id`, `processId`, `status`, `startedAt`, `completedAt`, and
569
+ `archivedAt`; no process metadata, step context, or credentials are exposed.
570
+
571
+ The authorized group directory and detail responses include `canManage`,
572
+ derived from existing group administration or platform administration, and
573
+ `externalId`. Applications cannot confer group authority through their own
574
+ roles. `POST /api/groups/directory` accepts `manageableOnly: true`; it uses all
575
+ groups only for a verified platform administrator, and otherwise filters the
576
+ caller's existing accessible pages. In that mode the response is
577
+ `{ groups, nextPage: number | null }`, with no filtered-count claim. Follow
578
+ `nextPage` even when a page is empty. This read grants no memberships and changes no group.
579
+
580
+ ### Recoverable group task assignment
581
+
582
+ Human callers can use these declared `server.capabilities.api.write` adapters:
583
+
584
+ - `POST /api/lattice/group-task-assignment/prepare`: `{groupId, runId, stepId,
585
+ targetSub}` returns `{runId, stepId, nodeId, processId, processVersion,
586
+ expectedAssignmentToken, intentId, preparationProof}`. The server mints the
587
+ intent UUID. The signed proof binds the exact caller, project, group, command,
588
+ pinned process/step and actual SQL environment. The application must declare `processes.act` for
589
+ the mapped process and `groups.read`. The caller must independently administer
590
+ the live group and hold run Viewer. The task must be parked and unsubmitted,
591
+ currently assigned to that group or a member, and the target must be an active
592
+ group member permitted by the run's pinned node allowlist. Application roles
593
+ do not grant group administration.
594
+ - `POST /api/lattice/group-task-assignment/commit`: the same fields plus
595
+ `{intentId, expectedAssignmentToken, preparationProof}` return `{receipt}`. The UUID intent is
596
+ unique within the project. Native assignment, its authorization outbox, and
597
+ the immutable `applied` receipt commit in one transaction. Reusing an intent
598
+ with different actor, run, step, target, or token returns 409; an exact retry
599
+ never reapplies an old assignment.
600
+ - `POST /api/lattice/group-task-assignment/cancel`: the commit body returns the
601
+ stable receipt. Cancellation and commit serialize on the same intent key.
602
+ A `cancelled` receipt prevents any later assignment; an already `applied`
603
+ receipt is returned unchanged. Current group authority and run Viewer remain
604
+ required, but a disabled target or completed task does not prevent cancellation.
605
+ - `POST /api/lattice/group-task-assignment/receipt`: `{runId, intentId}` returns
606
+ `{receipt: object | null}` under the project's exact identity, declared
607
+ `processes.read`, and current run Viewer. It never returns another project's
608
+ receipt or exposes task state, context, or wait credentials.
609
+
610
+ A receipt contains project/intent/run/step/node identities, actor, principals,
611
+ `outcome: applied | cancelled`, and `committedAt`. Its timestamp is the database
612
+ transaction timestamp; the receipt becomes visible only after commit. A null
613
+ receipt is **not** proof that a command cannot still apply. App-owned ownership
614
+ reservations must finalize on `applied` and release only on `cancelled`. They
615
+ must not expire an ambiguous reservation or issue another intent as a retry.
616
+ The preparation proof has no expiry and grants no permission by itself. Signing
617
+ key rotation requires explicit operator recovery for pending commands. If the
618
+ caller loses group authority, recovery needs an explicitly authorized
619
+ operator; these endpoints never impersonate the original caller. The first
620
+ release provides caller-driven recovery, not a background service reconciler.
621
+
622
+ Deployment requires the host adapters and the journaled assignment receipt
623
+ migration. The app's ownership transaction and notification outbox remain
624
+ application-owned and require their own reviewed service grants.
625
+
626
+ `client.processes.getHumanCompletion({ runId, stepId })` reads a committed,
627
+ server-verified human completion receipt. Declare the exact
628
+ `/api/lattice/human-completion/read` path under `server.capabilities.api.write`
629
+ (the POST carries a body but has no write effects), plus the process under
630
+ `processes.read`. Only a verified human caller with current Run Viewer access can
631
+ read it. The result binds actor, immutable process version/bundle, exact step
632
+ attempt, edge and canonical payload hash; it never returns the payload, signing
633
+ token or wait credentials. The payload hash covers canonical JSON of the actual
634
+ submitted payload, including the server-stamped `advanced_by`, excluding the
635
+ reserved `_sequence_human_completion` receipt key. HTTP409
636
+ `HUMAN_COMPLETION_UNAVAILABLE` means no verified committed evidence is available;
637
+ it is not permission to replay a mutation. This method does not establish a
638
+ successor step or grant authority to assign one. The same adapter is callable
639
+ through `client.api.post` by earlier SDK clients, once the target platform release
640
+ is active. Policy-backed application writes still independently require the
641
+ reviewed ORM runtime capability and admission checks.
642
+
643
+ `client.processes.getHumanContinuation({ runId, stepId })` uses the corresponding
644
+ `/api/lattice/human-continuation/read` POST adapter with the same caller and process
645
+ read permissions. It returns `waiting`, `terminal`, or the first still-parked human
646
+ step and its materialized user/group assignments. The platform verifies the signed
647
+ source completion and every actual edge in a bounded contiguous native serial walk
648
+ against the pinned bundle. Ambiguous or truncated history, parallel/subprocess
649
+ paths, resets, intervening human completions and repeated nodes are refused with
650
+ HTTP409 `HUMAN_CONTINUATION_UNAVAILABLE`; timestamps or graph reachability alone
651
+ never establish a successor. This read grants no authority to advance or reassign.
652
+
653
+ `bridge.lattice.prepareAdvance()` verifies the host-owned embedded task and its caller Viewer admission without emitting a completion signal, including when the native step already completed. Apps with a separate durable handoff must first verify that handoff, then call `notifyAdvance()`. Both methods accept no task identifiers; the host supplies its own signed context.
654
+
655
+ ### Coordinating a human submission with application ownership (awaiting publication)
656
+
657
+ Official Next.js servers can coordinate a native human completion with a separate
658
+ application transaction through `/api/lattice/human-handoff/*`. These are fixed
659
+ `client.api.post` adapters: declare each exact path under `server.capabilities.api.write`
660
+ and the process under `processes.act` and `processes.read`. They require a verified
661
+ runtime credential, the gateway's authenticated human, current Run Viewer, and the
662
+ same application and original actor on every retry. Browser delegation, service
663
+ mode, a copied request context, and caller-supplied identity are refused. The
664
+ application server is trusted to acknowledge only its own committed transaction;
665
+ applications must not proxy acknowledgement directly from browser input.
666
+
667
+ The protocol is deliberately ordered:
668
+
669
+ 1. `prepare` takes `{runId, stepId, edgeId, payload}` and performs read-only support,
670
+ current Advancer and immutable graph checks. It returns an immutable command
671
+ with `id`, source pins, `payloadHash`, `assignmentToken` and `preparationProof`.
672
+ Store this complete command with the application intent and original payload.
673
+ 2. Reserve the application intent with its own CAS, then call `reserve` with that
674
+ exact prepared command. Native reservation checks the source assignment,
675
+ submission and mutation admissions under the run lock. A failure does not
676
+ authorize clearing the application reservation.
677
+ 3. `advance` takes `{id, runId, stepId, payload}`. Only the original immutable
678
+ command may signal the workflow. Admission is durable before dispatch; an HTTP
679
+ failure, Temporal signal ACK, or absent completion receipt is not cancellation.
680
+ 4. Use the verified completion and continuation readers. `reconcile` takes
681
+ `{id, runId, stepId, users, groups}` and binds the proved immediate successor.
682
+ Identical native assignments need no Owner grant. A real change retains native
683
+ Run Owner, destination grantability and allowlist requirements, with readback.
684
+ Terminal continuations require empty user/group arrays.
685
+ 5. Commit the application's atomic ownership/watchers/outbox transaction. Only
686
+ then call `acknowledge` with `{id, runId, stepId}`. `released` is required before
687
+ telling the browser the whole action succeeded. If acknowledgement is lost,
688
+ retry it; it never replays the application transaction.
689
+
690
+ `read` takes `{runId, stepId, id?}` and returns only fence identity/status and the
691
+ bound successor. Include `id` to recover an exact cancelled attempt. Without it,
692
+ only a non-cancelled source attempt is returned. It exposes no payload or token.
693
+
694
+ When the source assignment changes between preparation and reservation, `cancel`
695
+ accepts the exact prepared command and verifies its server signature, actor,
696
+ application and actual SQL environment. Under the same run lock it writes a
697
+ permanent cancellation receipt only if no matching native command was admitted
698
+ or unexplained settlement observed. A different immutable native admission proves
699
+ this command did not settle, even if that other command already completed.
700
+ Protocol-1 workers always require matching admission before consuming a signal.
701
+ A worker-only timeout receipt, written atomically with the timeout result and
702
+ pinned to the source attempt, also proves an unaccepted prepared command did
703
+ not complete when the timeout won before reservation. Payload fields and imported
704
+ runner output are never timeout evidence. The application may clear its own exact pending CAS only
705
+ after observing `status: 'cancelled'`. The cancelled ID can never reserve or
706
+ advance; a fresh prepared ID can retry the still-parked source. There is no timeout
707
+ unlock. A late request using a cancelled permit is refused before signal delivery.
708
+
709
+ **Activation boundaries:** this initial protocol admits only top-level Temporal
710
+ human waiters that advertise the patched native handoff protocol. Trigger wait
711
+ credentials and older in-flight waiters are unsupported. Existing human timeout durations remain in force. A held fence pauses settlement
712
+ of both its source timeout and any successor timeout until reconciliation or
713
+ exact cancellation; it does not remove the configured timeout. Every possible selected continuation must reach a first human or
714
+ terminal through at most 128 serial automation, managed-function, managed-server
715
+ or delay nodes. Parallel/subprocess/agent routes, loops, duplicate or missing
716
+ edges are rejected before reserving application state. Deploy migrations first,
717
+ then roll out the admission-enforcing API to every instance, then deploy patched
718
+ workers, and only then enable application use. Deploying workers before all API
719
+ callers admit signals can discard unadmitted old-version signals; an API rollout
720
+ alone is insufficient. Existing unsupported waiters need an independently
721
+ reviewed migration or must finish on their prior application path.
722
+
723
+ While held, native advance, assignment, sharing, cancellation, reset and fork
724
+ mutations cannot cross the fence. The worker discards stale unadmitted signals
725
+ and holds successor timeouts until release. Ordinary native commands are admitted
726
+ before dispatch, and a prior admitted command prevents fence acquisition. A
727
+ causally verified terminal automation failure can reconcile and release the
728
+ handoff; terminal settlement does not imply a successful ticket outcome. An
729
+ unexplained run failure still requires investigation.
730
+ Nontransactional reset/fork operations retain per-attempt admissions after an
731
+ uncertain mutating failure. **Those admissions currently require operator recovery
732
+ and are an explicit operability/release gate for affected runs:** a later retry or
733
+ current SQL outcome alone cannot prove an older provider call has stopped. No API
734
+ silently expires, impersonates, or clears those attempts. Review provider-specific
735
+ reconciliation before enabling affected-run recovery.
package/VERSION-PIN.md CHANGED
@@ -94,6 +94,21 @@ before publication; an ordinary released CLI intentionally cannot.
94
94
  The React/Vite starter therefore uses released primitives and responsive CSS
95
95
  until that pin advances. Its build test exercises the ordinary CLI toolchain.
96
96
 
97
+ ## Planned team directory and embedded-task release
98
+
99
+ Artifact Studio 0.10.0 and Studio CLI 0.6.6 are prepared for the separate
100
+ chain-release workflow and remain awaiting publication. This change adds
101
+ user-authorized directory/group methods, parked human-task assignment reads,
102
+ and a host-verified completion notification for embedded Next.js tasks. The
103
+ narrow app-access bridge reads only the signed-in viewer's app access.
104
+
105
+ Deploy the matching platform handlers before activating consumers. An SDK
106
+ build or candidate version is not evidence that the registry or target host
107
+ supports these methods. Group administration still requires authority over the
108
+ group; an application's manage capability does not grant it. Completion
109
+ notification verifies the host's own run/step and does not execute or replay a
110
+ workflow transition. Existing package export classifications stay unchanged.
111
+
97
112
  ## Unreleased server process additions
98
113
 
99
114
  Workspace source adds `processes.listRunsPage` (visible, paginated run summaries)
package/dist/api.d.ts CHANGED
@@ -19,6 +19,11 @@ export declare function postJson<T>({ baseUrl, token, path, body, retry503, sign
19
19
  signal?: AbortSignal;
20
20
  headers?: Record<string, string>;
21
21
  }): Promise<T>;
22
+ /** Binding updates are idempotent; normal authenticated target transport applies. */
23
+ export declare function putJson<T>({ baseUrl, token, path, body }: ApiClientOptions & {
24
+ path: string;
25
+ body: unknown;
26
+ }): Promise<T>;
22
27
  export declare function deleteJson({ baseUrl, token, path, signal }: ApiClientOptions & {
23
28
  path: string;
24
29
  signal?: AbortSignal;
package/dist/api.js CHANGED
@@ -111,6 +111,13 @@ export async function postJson({ baseUrl, token, path, body, retry503 = true, si
111
111
  throw new ArtifactApiError(await responseError('POST', path, response), response.status);
112
112
  return response.json();
113
113
  }
114
+ /** Binding updates are idempotent; normal authenticated target transport applies. */
115
+ export async function putJson({ baseUrl, token, path, body }) {
116
+ const response = await authenticatedFetch({ baseUrl, token, path, init: { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) } });
117
+ if (!response.ok)
118
+ throw new ArtifactApiError(await responseError('PUT', path, response), response.status);
119
+ return response.json();
120
+ }
114
121
  export async function deleteJson({ baseUrl, token, path, signal }) {
115
122
  const response = await authenticatedFetch({ baseUrl, token, path, init: { method: 'DELETE', ...(signal ? { signal } : {}) } });
116
123
  if (!response.ok)
@@ -0,0 +1,8 @@
1
+ /** Public CLI for reviewed target bindings; never creates provider registrations. */
2
+ export declare function runContentReadersCommand(input: {
3
+ action: string;
4
+ projectId?: string | true;
5
+ file?: string | true;
6
+ baseUrl: string;
7
+ token: string;
8
+ }): Promise<number>;
@@ -0,0 +1,28 @@
1
+ import { readFile, stat } from 'node:fs/promises';
2
+ import { z } from 'zod';
3
+ import { getJson, putJson } from './api.js';
4
+ const bindings = z.record(z.string().regex(/^[a-z][a-z0-9-]{0,62}$/), z.strictObject({ enabled: z.boolean(), revision: z.string().regex(/^[a-f0-9]{64}$/) }))
5
+ .refine(value => Object.keys(value).length >= 1 && Object.keys(value).length <= 16);
6
+ /** Public CLI for reviewed target bindings; never creates provider registrations. */
7
+ export async function runContentReadersCommand(input) {
8
+ const projectId = z.uuid().parse(input.projectId);
9
+ if (input.action !== 'show' && input.action !== 'apply')
10
+ throw new Error('content-readers requires show or apply');
11
+ if (input.action === 'show' && input.file !== undefined)
12
+ throw new Error('show does not accept --file');
13
+ const path = `/api/artifact-studio/projects/${projectId}/next/content-readers`;
14
+ if (input.action === 'show') {
15
+ console.log(JSON.stringify(await getJson({ baseUrl: input.baseUrl, token: input.token, path }), null, 2));
16
+ return 0;
17
+ }
18
+ if (typeof input.file !== 'string' || !input.file)
19
+ throw new Error('apply requires --file with reviewed bindings');
20
+ if ((await stat(input.file)).size > 65536)
21
+ throw new Error('Content-reader binding file exceeds 64 KiB');
22
+ const raw = await readFile(input.file, 'utf8');
23
+ if (Buffer.byteLength(raw) > 65536)
24
+ throw new Error('Content-reader binding file exceeds 64 KiB');
25
+ const body = bindings.parse(JSON.parse(raw));
26
+ console.log(JSON.stringify(await putJson({ baseUrl: input.baseUrl, token: input.token, path, body }), null, 2));
27
+ return 0;
28
+ }
package/dist/cli.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { runContentReadersCommand } from './cli-content-readers.js';
1
2
  import { copyFile, cp, mkdir, writeFile } from 'node:fs/promises';
2
3
  import { constants, existsSync } from 'node:fs';
3
4
  import { dirname, join, relative, resolve } from 'node:path';
@@ -22,7 +23,7 @@ import yaml from 'js-yaml';
22
23
  import { warnOnRoleDefinitions } from './role-definitions-notice.js';
23
24
  const COMMANDS = [
24
25
  'init', 'whoami', 'link', 'env', 'status', 'list', 'show',
25
- 'validate', 'build', 'plan', 'deploy', 'dev', 'promote', 'pull', 'rollback',
26
+ 'content-readers', 'validate', 'build', 'plan', 'deploy', 'dev', 'promote', 'pull', 'rollback',
26
27
  ];
27
28
  const USAGE = `usage:
28
29
  seq-studio artifact init <dir> [--template react-vite|nextjs]
@@ -40,6 +41,7 @@ const USAGE = `usage:
40
41
  seq-studio artifact promote <deployment-id> --env <env>
41
42
  seq-studio artifact pull <project-id> --env <env> --out <dir>
42
43
  seq-studio artifact rollback <deployment-id> --env <env>
44
+ seq-studio artifact content-readers show|apply --project <id> --env <env> [--file bindings.json]
43
45
 
44
46
  Add --help or -h to any artifact command to print this usage without running it.
45
47
  init includes AGENTS.md pointing to your environment's live docs, with a
@@ -111,6 +113,10 @@ export async function runCli(argv = process.argv.slice(2)) {
111
113
  case 'promote': return promoteCommand(parsed);
112
114
  case 'pull': return pullCommand(parsed);
113
115
  case 'rollback': return rollbackCommand(parsed);
116
+ case 'content-readers': {
117
+ const env = await envFromArgs(parsed);
118
+ return runContentReadersCommand({ action: parsed.positional.join(' '), projectId: parsed.flags.project, file: parsed.flags.file, baseUrl: env.url, token: await getRequiredToken(parsed) });
119
+ }
114
120
  }
115
121
  }
116
122
  function validateKnownOptions(args) {
@@ -125,6 +131,7 @@ function validateKnownOptions(args) {
125
131
  dev: { values: ['env', 'key', 'link', 'port'], booleans: ['once'] },
126
132
  promote: { values: ['env'], booleans: [] }, pull: { values: ['env', 'out'], booleans: [] },
127
133
  rollback: { values: ['env'], booleans: [] },
134
+ 'content-readers': { values: ['env', 'project', 'file'], booleans: [] },
128
135
  };
129
136
  const { values, booleans } = options[args.command];
130
137
  const allowed = new Set([...values, ...booleans]);
@@ -37,7 +37,21 @@ export interface NextArtifactBridge {
37
37
  reportRuntimeLog(input: NextArtifactRuntimeLog): void;
38
38
  forwardHostShortcut(input: Pick<KeyboardEvent, 'key' | 'code' | 'metaKey' | 'ctrlKey' | 'shiftKey' | 'altKey'>): boolean;
39
39
  host: SequenceHost;
40
- lattice: SequenceLattice;
40
+ /** Read only the current viewer's app navigation decisions. This is not an authorization grant. */
41
+ auth: {
42
+ getAppAccess(): Promise<Record<string, boolean>>;
43
+ };
44
+ lattice: SequenceLattice & {
45
+ /** Ask the host to verify its own task completed after an app-owned mutation.
46
+ * Returns false while the native step is pending; retry this read, never the mutation. */
47
+ notifyAdvance(): Promise<{
48
+ notified: boolean;
49
+ }>;
50
+ /** Verify current host task admission without notifying or completing it. */
51
+ prepareAdvance(): Promise<{
52
+ supported: true;
53
+ }>;
54
+ };
41
55
  }
42
56
  /**
43
57
  * An SSR-safe external store and DOM transport. Call connect from a client effect,
@@ -1,3 +1,4 @@
1
+ import { z } from 'zod';
1
2
  import { createNextArtifactBridgeEnvelope, EMPTY_NEXT_ARTIFACT_LATTICE_CONTEXT, isNextArtifactRelativePath, nextArtifactInitialContextSchema, nextArtifactThemeSchema, parseNextArtifactBridgeEnvelope, validateNextArtifactBootstrap, } from './protocol.js';
2
3
  import { createNextArtifactRequests } from './requests.js';
3
4
  import { createNextArtifactStreams } from './streams.js';
@@ -203,7 +204,23 @@ export function createNextArtifactBridge(options) {
203
204
  send({ type: 'artifact-studio-host-shortcut', key: input.key, code: input.code, metaKey: input.metaKey, ctrlKey: input.ctrlKey, shiftKey: input.shiftKey, altKey: input.altKey });
204
205
  return true;
205
206
  },
206
- lattice: { context: () => ({ ...(snapshot.lattice ?? EMPTY_NEXT_ARTIFACT_LATTICE_CONTEXT) }) },
207
+ auth: {
208
+ async getAppAccess() {
209
+ const result = await bridge.request({ method: 'auth.getAppAccess' });
210
+ return z.record(z.string(), z.boolean()).parse(result);
211
+ },
212
+ },
213
+ lattice: {
214
+ context: () => ({ ...(snapshot.lattice ?? EMPTY_NEXT_ARTIFACT_LATTICE_CONTEXT) }),
215
+ async prepareAdvance() {
216
+ const result = await bridge.request({ method: 'lattice.prepareAdvance' });
217
+ return z.object({ supported: z.literal(true) }).strict().parse(result);
218
+ },
219
+ async notifyAdvance() {
220
+ const result = await bridge.request({ method: 'lattice.notifyAdvance' });
221
+ return z.object({ notified: z.boolean() }).strict().parse(result);
222
+ },
223
+ },
207
224
  host: {
208
225
  navigate(path) {
209
226
  requireReady();
package/dist/sdk.d.ts CHANGED
@@ -413,6 +413,40 @@ export declare function createArtifactStorageClient(sdk?: SequenceArtifactSdk):
413
413
  size: number | null;
414
414
  contentType: string | null;
415
415
  }>;
416
+ getNodeWithAncestors: (id: string) => Promise<{
417
+ id: string;
418
+ workspaceId: string;
419
+ parentId: string | null;
420
+ name: string;
421
+ kind: "file" | "folder";
422
+ state: "active" | "trashed" | "purging";
423
+ restricted: boolean;
424
+ revision: number;
425
+ currentVersionId: string | null;
426
+ metadata: Record<string, string>;
427
+ createdAt: string;
428
+ updatedAt: string;
429
+ role: "viewer" | "editor" | "admin";
430
+ size: number | null;
431
+ contentType: string | null;
432
+ ancestors: {
433
+ id: string;
434
+ workspaceId: string;
435
+ parentId: string | null;
436
+ name: string;
437
+ kind: "file" | "folder";
438
+ state: "active" | "trashed" | "purging";
439
+ restricted: boolean;
440
+ revision: number;
441
+ currentVersionId: string | null;
442
+ metadata: Record<string, string>;
443
+ createdAt: string;
444
+ updatedAt: string;
445
+ role: "viewer" | "editor" | "admin";
446
+ size: number | null;
447
+ contentType: string | null;
448
+ }[];
449
+ }>;
416
450
  createFolder: (name: string, parentId?: string | null, { idempotencyKey, }?: {
417
451
  idempotencyKey?: string;
418
452
  }) => Promise<{
@@ -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
- // A stream outlives the broker's answer deadline: only its headers must arrive in time.
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
+ }
@@ -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,
@@ -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>;
@@ -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';
@@ -186,6 +186,10 @@ own origin rather than a neighbour's. When the application origin is on a
186
186
  different site from its Atlas frame, a cookie it needs there must also be
187
187
  `SameSite=None; Partitioned`, or the browser drops it.
188
188
 
189
+ For the same reason, the gateway refuses any `POST`, `PUT`, `PATCH`, `DELETE` or
190
+ WebSocket upgrade whose `Origin` is not your application's own. It does not guard
191
+ `GET`, `HEAD` or `OPTIONS`, so never change state in response to them.
192
+
189
193
  The local process is ordinary code running as your operating-system user.
190
194
  Atlas governs access to platform resources; it does not sandbox your laptop's
191
195
  filesystem or all outbound network connections.
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "dependencies": {},
12
12
  "devDependencies": {
13
- "@sequenceholdings/artifact-studio": "^0.9.0",
13
+ "@sequenceholdings/artifact-studio": "^0.10.0",
14
14
  "@sequenceholdings/atlas-ui": "^3.1.1",
15
15
  "@tailwindcss/postcss": "^4.2.2",
16
16
  "@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.9.2",
12
+ "version": "0.10.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": {
@@ -171,9 +171,9 @@
171
171
  "google-auth-library": "^10.5.0",
172
172
  "server-only": "0.0.1",
173
173
  "parse5": "7.3.0",
174
- "@sequenceholdings/lattice-form-renderer": "0.2.0",
175
- "@sequenceholdings/orm": "2.6.2",
176
- "@sequenceholdings/storage": "0.1.2"
174
+ "@sequenceholdings/storage": "0.1.2",
175
+ "@sequenceholdings/orm": "2.7.1",
176
+ "@sequenceholdings/lattice-form-renderer": "0.2.0"
177
177
  },
178
178
  "devDependencies": {
179
179
  "@types/js-yaml": "^4.0.9",
@@ -186,6 +186,10 @@ own origin rather than a neighbour's. When the application origin is on a
186
186
  different site from its Atlas frame, a cookie it needs there must also be
187
187
  `SameSite=None; Partitioned`, or the browser drops it.
188
188
 
189
+ For the same reason, the gateway refuses any `POST`, `PUT`, `PATCH`, `DELETE` or
190
+ WebSocket upgrade whose `Origin` is not your application's own. It does not guard
191
+ `GET`, `HEAD` or `OPTIONS`, so never change state in response to them.
192
+
189
193
  The local process is ordinary code running as your operating-system user.
190
194
  Atlas governs access to platform resources; it does not sandbox your laptop's
191
195
  filesystem or all outbound network connections.
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "dependencies": {},
12
12
  "devDependencies": {
13
- "@sequenceholdings/artifact-studio": "^0.9.0",
13
+ "@sequenceholdings/artifact-studio": "^0.10.0",
14
14
  "@sequenceholdings/atlas-ui": "^3.1.1",
15
15
  "@tailwindcss/postcss": "^4.2.2",
16
16
  "@tailwindcss/vite": "^4.2.2",