@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 +219 -0
- package/VERSION-PIN.md +15 -0
- package/dist/api.d.ts +5 -0
- package/dist/api.js +7 -0
- package/dist/cli-content-readers.d.ts +8 -0
- package/dist/cli-content-readers.js +28 -0
- package/dist/cli.js +8 -1
- package/dist/next/bridge.d.ts +15 -1
- package/dist/next/bridge.js +18 -1
- package/dist/sdk.d.ts +34 -0
- 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/templates/nextjs/README.md +4 -0
- package/dist/templates/react-vite/package.json +1 -1
- package/package.json +4 -4
- package/templates/nextjs/README.md +4 -0
- package/templates/react-vite/package.json +1 -1
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]);
|
package/dist/next/bridge.d.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|
package/dist/next/bridge.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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';
|
|
@@ -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.
|
|
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.
|
|
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/
|
|
175
|
-
"@sequenceholdings/orm": "2.
|
|
176
|
-
"@sequenceholdings/
|
|
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.
|
|
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",
|