@arnilo/prism-server 0.0.96 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,11 +1,138 @@
1
1
  # Changelog
2
+
3
+ ## [0.1.0] - 2026-08-09
4
+
5
+ ### Changed
6
+ - Released with exact 0.1.0 graph.
7
+
8
+ ## [0.0.28] - 2026-08-08
9
+
10
+ ### Added
11
+ - `createArtifactService` accepts an optional `bodies: ArtifactBodyStore` (core contract from `@arnilo/prism`): when wired, `deliveryLink` resolves through `bodies.presign` and returns an additional bounded-TTL presigned `url` beside the signed link/token; revisions without a recorded `size` fail closed at delivery. `attach`/`revise` accept an optional `size` (validated non-negative safe integer) recorded on the revision.
12
+ - New `@arnilo/prism-server/artifact-bodies` subpath: `createS3ArtifactBodyStore` — reference S3-compatible `ArtifactBodyStore` (AWS S3, MinIO, Cloudflare R2) with hand-rolled SigV4 presigning over native fetch + WebCrypto (validated against the official AWS sig-v4-test-suite get-vanilla vector), path-style addressing, single-chunk PUT with exact Content-Length and verified `x-amz-content-sha256`, ownership verification on every operation, size/SHA-256/MIME verification on put and get (fail closed), legal-hold-aware idempotent delete (host `isHeld` callback), host-resolved credentials (never inline), optional host-owned client-side KMS callback, bounded concurrent transfers, and no bucket/path/key disclosure in errors. `S3ArtifactBodyError` carries frozen `ERR_PRISM_S3_*` codes; limits `maxBodyBytes` 64 MiB/512 MiB, `maxConcurrentTransfers` 4/16, `presignTtlMs` 10 min/24 h, `maxRefBytes` 256 B/1 KiB.
13
+
14
+ ## [0.0.27] - 2026-08-07
15
+
16
+ ### Changed
17
+ - Released with exact 0.0.27 graph.
18
+
19
+ ## [0.0.26] - 2026-08-06
20
+
21
+ ### Changed
22
+ - Released with exact 0.0.26 graph.
23
+
24
+ ## [0.0.25] - 2026-08-06
25
+
26
+ ### Added
27
+ - Agent resume accepts batch `decisions: RunDecision[]` (exclusive with legacy binary `decision`) with boundary validation.
28
+
29
+ ### Changed
30
+ - Released with exact 0.0.25 graph.
31
+
32
+ See [migration guide](../../docs/migration.md) for the 0.0.24 → 0.0.25 notes.
33
+
34
+ ## [0.0.24] - 2026-08-04
35
+
36
+ ### Added
37
+ - Durable `AgentEventSource` (memory + PostgreSQL LISTEN/NOTIFY), recoverable `ToolEffectStore`, and AG-UI MCP/MCP Apps/A2A fronting for Phase 7.
38
+
39
+ ### Changed
40
+ - Publishable graph remains **47** manifests at **0.0.24**; peers and lockfile move together.
41
+
42
+ See [migration guide](../../docs/migration.md) for the 0.0.23 → 0.0.24 notes.
43
+
44
+ ## [0.0.23] - 2026-08-03
45
+
46
+ ### Changed
47
+ - Released with exact 0.0.23 graph.
48
+
49
+ ## [0.0.22] - 2026-07-31
50
+
51
+ ### Changed
52
+ - Released with exact 0.0.22 graph.
53
+
54
+ ## [0.0.21] - 2026-07-31
55
+
56
+ ### Changed
57
+ - Released with exact 0.0.21 graph.
58
+
59
+ ## [0.0.20] - 2026-07-31
60
+
61
+ ### Changed
62
+ - Released with exact 0.0.20 graph.
63
+
64
+ ## [0.0.19] - 2026-07-30
65
+
66
+ ### Changed
67
+ - Released with exact 0.0.19 graph.
68
+
69
+ ## [0.0.18] - 2026-07-30
70
+
71
+ ### Changed
72
+ - Released with exact 0.0.18 graph.
73
+
74
+ ## [0.0.17] - 2026-07-29
75
+
76
+ ### Changed
77
+ - Released with exact 0.0.17 graph.
78
+
79
+ ## [0.0.16] - 2026-07-26
80
+
81
+ ### Changed
82
+ - Released with exact 0.0.16 graph.
83
+
84
+ ## [0.0.15] - 2026-07-26
85
+
86
+ ### Changed
87
+
88
+ - Released with exact 0.0.15 graph.
89
+
90
+ ## [0.0.14] - 2026-07-26
91
+
92
+ ### Added
93
+
94
+ - Durable user-scoped conversation service (`createConversationService`/`createConversationHandler`) and durable artifact service with review/approval/authorized delivery links (`createArtifactService`/`createArtifactHandler`) (Plan 077 Tasks 1, 3).
95
+
96
+ ## [0.0.13] - 2026-07-24
97
+
98
+ ### Changed
99
+
100
+ - Released with exact 0.0.13 graph.
101
+
102
+ ## [0.0.12] - 2026-07-23
103
+
104
+ ### Added
105
+
106
+ - Optional deployment seams: `createPrismHealthHandler`, `createPrismDrainController`, host `rateLimit` adapter (+ `createMemoryRateLimiter`), ownership-scoped `createPrismEventReplay` / `createPrismReplayHandler`, and `createPrismDeploymentLease` for worker/coordinator election over existing leases. No queue adapter (deferred pending measured Postgres polling need).
107
+
108
+ ## [0.0.12] - 2026-07-22
109
+
110
+ ### Changed
111
+
112
+ - Released with exact 0.0.12 graph.
113
+
114
+ ## [0.0.11] - 2026-07-22
115
+
116
+ ### Changed
117
+
118
+ - Released with exact 0.0.11 graph.
119
+
120
+ ## [0.0.10] - 2026-07-21
121
+
122
+ ### Changed
123
+
124
+ - Released with exact 0.0.10 graph.
125
+
126
+ ### Changed
127
+
128
+ - Released with exact 0.0.10 graph.
129
+
2
130
  ## [0.0.96] - 2026-07-21
3
131
 
4
132
  ### Changed
5
133
 
6
134
  - Released with exact 0.0.96 graph.
7
135
 
8
-
9
136
  ## [0.0.9] - 2026-07-21
10
137
 
11
138
  - Released with the exact 0.0.9 first-party package graph.
package/README.md CHANGED
@@ -24,8 +24,31 @@ const response = await handler(new Request("https://api.example.test/prism/agent
24
24
  }));
25
25
  ```
26
26
 
27
- Routes: direct/SSE agent run, explicitly selected durable agent status/resume through `agentRuns`, direct/SSE workflow run, durable workflow enqueue/status/cancel/resume/replay, and optional ownership-scoped schedule create/list/pause/resume/trigger/delete. `agentRuns` uses core `createAgentRunLifecycle()`; no lifecycle route exists by default. All bodies, responses, events, queues, concurrency, and timeouts are bounded.
27
+ Routes: direct/SSE agent run, cross-replica durable event reconnect through object agent exposures with `events` + `resolveRun`, explicitly selected durable agent status/resume through `agentRuns`, direct/SSE workflow run, durable workflow enqueue/status/cancel/resume/replay, and optional ownership-scoped schedule create/list/pause/resume/trigger/delete. `agentRuns` uses core `createAgentRunLifecycle()`; no lifecycle route exists by default. Agent resume accepts legacy binary `decision` or batch `decisions: RunDecision[]` (exactly one). All bodies, responses, events, queues, concurrency, and timeouts are bounded.
28
28
 
29
- Nothing is exposed by default. Authorization is required; ownership comes only from its result. No listener, framework, auth provider, user database, credential discovery, or hidden package activation ships.
29
+ Optional 0.0.14 co-work handlers (mount beside the API handler): `createConversationHandler` (durable user-scoped conversation threads with reconnectable redacted replay `createConversationService`) and `createArtifactHandler` (artifact revisions, approve/reject review, expiring authorized delivery links — `createArtifactService` over the existing checkpoint store). Both are ownership-scoped and fail closed without authorization.
30
+
31
+ Optional deployment helpers (compose beside the API handler — no embedded listener):
32
+
33
+ ```ts
34
+ import {
35
+ createPrismDrainController,
36
+ createPrismHealthHandler,
37
+ createMemoryRateLimiter,
38
+ createPrismDeploymentLease,
39
+ } from "@arnilo/prism-server";
40
+
41
+ const drain = createPrismDrainController();
42
+ const handler = createPrismHandler({
43
+ agents: { support: agent },
44
+ authorize,
45
+ drain,
46
+ rateLimit: createMemoryRateLimiter({ maxRequests: 60, windowMs: 60_000 }),
47
+ });
48
+ const health = createPrismHealthHandler({ drain, ready: () => store.ping() });
49
+ // Workers: createWorkflowCoordinator(...). Coordinators: createPrismDeploymentLease({ key: "coordinator", ... }).
50
+ ```
51
+
52
+ Nothing is exposed by default. Authorization is required; ownership comes only from its result. No listener, framework, auth provider, user database, credential discovery, queue adapter, or hidden package activation ships.
30
53
 
31
54
  Full API, route, limits, security, and deployment notes: [`docs/server.md`](../../docs/server.md).
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Hand-rolled AWS Signature V4 over native WebCrypto (crypto.subtle) — no SDK dependency.
3
+ * Validated against the official AWS sig-v4-test-suite get-vanilla vector in
4
+ * `src/__tests__/artifact-bodies.test.ts`. S3-specific presigning (query-string auth,
5
+ * UNSIGNED-PAYLOAD for GET/DELETE, signed x-amz-content-sha256 for single-chunk PUT)
6
+ * lives in `artifact-bodies.ts`; this module is the generic algorithm.
7
+ */
8
+ /** RFC 3986 percent-encoding as required by SigV4 (encodeURIComponent leaves !'()* unencoded). */
9
+ export declare function awsUriEncode(value: string): string;
10
+ /** SHA-256 hex digest over WebCrypto. */
11
+ export declare function sha256Hex(data: Uint8Array): Promise<string>;
12
+ /** Canonical query string: sorted keys, RFC 3986 encoded, `key=value` joined with `&`. */
13
+ export declare function canonicalQueryString(query: Readonly<Record<string, string>>): string;
14
+ /** Canonical headers block: lowercase name, trimmed/collapsed value, trailing newline. */
15
+ export declare function canonicalHeaders(headers: Readonly<Record<string, string>>, signedHeaders: readonly string[]): string;
16
+ export interface SigV4SignInput {
17
+ readonly method: string;
18
+ /** Pre-encoded path (e.g. `/bucket/key`); used verbatim. */
19
+ readonly path: string;
20
+ readonly query?: Readonly<Record<string, string>>;
21
+ /** Lowercase header names. */
22
+ readonly headers: Readonly<Record<string, string>>;
23
+ /** Subset of header names, sorted; the caller decides what to sign. */
24
+ readonly signedHeaders: readonly string[];
25
+ /** Hex SHA-256 of the payload, or "UNSIGNED-PAYLOAD". */
26
+ readonly payloadHash: string;
27
+ readonly region: string;
28
+ readonly service: string;
29
+ /** `YYYYMMDDTHHMMSSZ`. */
30
+ readonly amzDate: string;
31
+ readonly accessKeyId: string;
32
+ readonly secretAccessKey: string;
33
+ readonly sessionToken?: string;
34
+ }
35
+ export interface SigV4Signature {
36
+ readonly signature: string;
37
+ readonly canonicalRequest: string;
38
+ }
39
+ /** Full SigV4 pipeline: canonical request -> string-to-sign -> derived-key HMAC. */
40
+ export declare function signV4(input: SigV4SignInput): Promise<SigV4Signature>;
41
+ export interface SigV4PresignInput {
42
+ readonly method: string;
43
+ readonly path: string;
44
+ readonly query?: Readonly<Record<string, string>>;
45
+ /** Lowercase header names; only `host` is signed for presigned URLs. */
46
+ readonly headers: Readonly<Record<string, string>>;
47
+ readonly payloadHash: string;
48
+ readonly region: string;
49
+ readonly service: string;
50
+ readonly amzDate: string;
51
+ readonly expiresSeconds: number;
52
+ readonly accessKeyId: string;
53
+ readonly secretAccessKey: string;
54
+ readonly sessionToken?: string;
55
+ }
56
+ /** Presign: returns the full query string (X-Amz-* params + X-Amz-Signature) for a URL. */
57
+ export declare function presignV4(input: SigV4PresignInput): Promise<string>;
58
+ export interface SigV4RequestSignInput {
59
+ readonly method: string;
60
+ readonly path: string;
61
+ readonly headers: Readonly<Record<string, string>>;
62
+ /** Headers to sign (subset of `headers`, sorted); the caller sends exactly these. */
63
+ readonly signedHeaders: readonly string[];
64
+ readonly payloadHash: string;
65
+ readonly region: string;
66
+ readonly service: string;
67
+ readonly amzDate: string;
68
+ readonly accessKeyId: string;
69
+ readonly secretAccessKey: string;
70
+ readonly sessionToken?: string;
71
+ }
72
+ /** Sign a direct request: returns headers including x-amz-date, x-amz-content-sha256, and Authorization. */
73
+ export declare function signRequestV4(input: SigV4RequestSignInput): Promise<{
74
+ readonly headers: Record<string, string>;
75
+ }>;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Hand-rolled AWS Signature V4 over native WebCrypto (crypto.subtle) — no SDK dependency.
3
+ * Validated against the official AWS sig-v4-test-suite get-vanilla vector in
4
+ * `src/__tests__/artifact-bodies.test.ts`. S3-specific presigning (query-string auth,
5
+ * UNSIGNED-PAYLOAD for GET/DELETE, signed x-amz-content-sha256 for single-chunk PUT)
6
+ * lives in `artifact-bodies.ts`; this module is the generic algorithm.
7
+ */
8
+ /** RFC 3986 percent-encoding as required by SigV4 (encodeURIComponent leaves !'()* unencoded). */
9
+ export function awsUriEncode(value) {
10
+ return encodeURIComponent(value).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
11
+ }
12
+ /** SHA-256 hex digest over WebCrypto. */
13
+ export async function sha256Hex(data) {
14
+ const digest = await crypto.subtle.digest("SHA-256", data);
15
+ return Buffer.from(digest).toString("hex");
16
+ }
17
+ async function hmacSha256(key, data) {
18
+ const imported = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
19
+ const signature = await crypto.subtle.sign("HMAC", imported, data);
20
+ return new Uint8Array(signature);
21
+ }
22
+ /** Canonical query string: sorted keys, RFC 3986 encoded, `key=value` joined with `&`. */
23
+ export function canonicalQueryString(query) {
24
+ return Object.keys(query)
25
+ .sort()
26
+ .map((key) => `${awsUriEncode(key)}=${awsUriEncode(query[key])}`)
27
+ .join("&");
28
+ }
29
+ /** Canonical headers block: lowercase name, trimmed/collapsed value, trailing newline. */
30
+ export function canonicalHeaders(headers, signedHeaders) {
31
+ return signedHeaders.map((name) => `${name}:${(headers[name] ?? "").trim().replace(/\s+/g, " ")}\n`).join("");
32
+ }
33
+ /** Full SigV4 pipeline: canonical request -> string-to-sign -> derived-key HMAC. */
34
+ export async function signV4(input) {
35
+ const date = input.amzDate.slice(0, 8);
36
+ const query = canonicalQueryString(input.query ?? {});
37
+ const headers = canonicalHeaders(input.headers, input.signedHeaders);
38
+ const signedNames = input.signedHeaders.join(";");
39
+ const canonicalRequest = `${input.method}\n${input.path}\n${query}\n${headers}\n${signedNames}\n${input.payloadHash}`;
40
+ const scope = `${date}/${input.region}/${input.service}/aws4_request`;
41
+ const stringToSign = `AWS4-HMAC-SHA256\n${input.amzDate}\n${scope}\n${await sha256Hex(new TextEncoder().encode(canonicalRequest))}`;
42
+ const kDate = await hmacSha256(new TextEncoder().encode(`AWS4${input.secretAccessKey}`), new TextEncoder().encode(date));
43
+ const kRegion = await hmacSha256(kDate, new TextEncoder().encode(input.region));
44
+ const kService = await hmacSha256(kRegion, new TextEncoder().encode(input.service));
45
+ const kSigning = await hmacSha256(kService, new TextEncoder().encode("aws4_request"));
46
+ const signature = Buffer.from(await hmacSha256(kSigning, new TextEncoder().encode(stringToSign))).toString("hex");
47
+ return { signature, canonicalRequest };
48
+ }
49
+ /** Presign: returns the full query string (X-Amz-* params + X-Amz-Signature) for a URL. */
50
+ export async function presignV4(input) {
51
+ const date = input.amzDate.slice(0, 8);
52
+ const scope = `${date}/${input.region}/${input.service}/aws4_request`;
53
+ const signedHeaders = ["host"];
54
+ const query = {
55
+ "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
56
+ "X-Amz-Credential": `${input.accessKeyId}/${scope}`,
57
+ "X-Amz-Date": input.amzDate,
58
+ "X-Amz-Expires": String(input.expiresSeconds),
59
+ "X-Amz-SignedHeaders": signedHeaders.join(";"),
60
+ ...(input.sessionToken === undefined ? {} : { "X-Amz-Security-Token": input.sessionToken }),
61
+ ...input.query,
62
+ };
63
+ const { signature } = await signV4({
64
+ method: input.method,
65
+ path: input.path,
66
+ query,
67
+ headers: input.headers,
68
+ signedHeaders,
69
+ payloadHash: input.payloadHash,
70
+ region: input.region,
71
+ service: input.service,
72
+ amzDate: input.amzDate,
73
+ accessKeyId: input.accessKeyId,
74
+ secretAccessKey: input.secretAccessKey,
75
+ sessionToken: input.sessionToken,
76
+ });
77
+ return canonicalQueryString({ ...query, "X-Amz-Signature": signature });
78
+ }
79
+ /** Sign a direct request: returns headers including x-amz-date, x-amz-content-sha256, and Authorization. */
80
+ export async function signRequestV4(input) {
81
+ const date = input.amzDate.slice(0, 8);
82
+ const scope = `${date}/${input.region}/${input.service}/aws4_request`;
83
+ const headers = {
84
+ ...input.headers,
85
+ "x-amz-date": input.amzDate,
86
+ "x-amz-content-sha256": input.payloadHash,
87
+ };
88
+ const { signature } = await signV4({
89
+ method: input.method,
90
+ path: input.path,
91
+ headers,
92
+ signedHeaders: input.signedHeaders,
93
+ payloadHash: input.payloadHash,
94
+ region: input.region,
95
+ service: input.service,
96
+ amzDate: input.amzDate,
97
+ accessKeyId: input.accessKeyId,
98
+ secretAccessKey: input.secretAccessKey,
99
+ sessionToken: input.sessionToken,
100
+ });
101
+ headers.authorization = `AWS4-HMAC-SHA256 Credential=${input.accessKeyId}/${scope}, SignedHeaders=${input.signedHeaders.join(";")}, Signature=${signature}`;
102
+ return { headers };
103
+ }
104
+ //# sourceMappingURL=artifact-bodies-s3.js.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Reference S3-compatible artifact body store (Phase 11 / 0.0.28): hand-rolled SigV4
3
+ * presigning over native fetch + WebCrypto, path-style addressing, single-chunk PUT with
4
+ * exact Content-Length and x-amz-content-sha256 = verified SHA-256 hex (no chunked transfer).
5
+ * Implements the core `ArtifactBodyStore` contract; hosts may substitute any store.
6
+ *
7
+ * Security posture: ownership verified on every operation; size/hash/MIME verified on put
8
+ * and get (fail closed); delete refuses under legal hold (host `isHeld` callback) and is
9
+ * idempotent; credentials only via the host resolver; bucket/path/key never appear in
10
+ * errors, telemetry, or artifact records (the object key is derived from the ref).
11
+ */
12
+ import { type ArtifactBodyRef, type ArtifactBodyStore } from "@arnilo/prism";
13
+ /** Host-resolved S3 credentials; never inline, never logged. */
14
+ export interface S3Credentials {
15
+ readonly accessKeyId: string;
16
+ readonly secretAccessKey: string;
17
+ /** Optional session token (STS); included in the signature scope when present. */
18
+ readonly sessionToken?: string;
19
+ }
20
+ export interface ArtifactBodyLimits {
21
+ readonly maxBodyBytes?: number;
22
+ readonly maxConcurrentTransfers?: number;
23
+ readonly presignTtlMs?: number;
24
+ readonly maxRefBytes?: number;
25
+ }
26
+ export interface ResolvedArtifactBodyLimits {
27
+ readonly maxBodyBytes: number;
28
+ readonly maxConcurrentTransfers: number;
29
+ readonly presignTtlMs: number;
30
+ readonly maxRefBytes: number;
31
+ }
32
+ /** Phase 11 freeze: body 64 MiB/512 MiB; concurrent transfers 4/16; presign TTL 10 min/24 h; ref 256 B/1 KiB. */
33
+ export declare const DEFAULT_ARTIFACT_BODY_LIMITS: ResolvedArtifactBodyLimits;
34
+ export declare const HARD_ARTIFACT_BODY_LIMITS: ResolvedArtifactBodyLimits;
35
+ export declare function resolveArtifactBodyLimits(input?: ArtifactBodyLimits): ResolvedArtifactBodyLimits;
36
+ export interface S3ArtifactBodyStoreOptions {
37
+ /** Absolute base URL of the S3-compatible endpoint (https; http only for loopback hosts). */
38
+ readonly endpoint: string;
39
+ /** Bucket name (path-style addressing; no slashes). */
40
+ readonly bucket: string;
41
+ /** SigV4 region scope; default "us-east-1". */
42
+ readonly region?: string;
43
+ /** Host resolver for credentials; never inline keys. */
44
+ readonly credentials: () => S3Credentials | Promise<S3Credentials>;
45
+ /**
46
+ * Optional host-owned client-side encryption: `encrypt` runs before upload, `decrypt`
47
+ * after download. The ref hash/size always refer to the plaintext; the stored bytes are
48
+ * the ciphertext (so size is verified on the decrypted plaintext when kms is set).
49
+ */
50
+ readonly kms?: (op: "encrypt" | "decrypt", body: Uint8Array) => Promise<Uint8Array>;
51
+ /** Optional legal-hold check; delete refuses (ERR_PRISM_ARTIFACT_BODY_HELD) when held. */
52
+ readonly isHeld?: (ref: ArtifactBodyRef) => boolean | Promise<boolean>;
53
+ readonly limits?: ArtifactBodyLimits;
54
+ /** Test seam; defaults to global fetch. */
55
+ readonly fetch?: typeof fetch;
56
+ /** Test seam; defaults to Date.now. */
57
+ readonly now?: () => number;
58
+ }
59
+ /** Frozen S3 adapter failure reasons. */
60
+ export type S3ArtifactBodyErrorCode = "PRESIGN" | "UPLOAD" | "DOWNLOAD" | "DELETE" | "CREDENTIALS";
61
+ /** Typed S3 adapter failure; `code` is one of the frozen ERR_PRISM_S3_* codes. */
62
+ export declare class S3ArtifactBodyError extends Error {
63
+ readonly reason: S3ArtifactBodyErrorCode;
64
+ readonly code: `ERR_PRISM_S3_${S3ArtifactBodyErrorCode}`;
65
+ constructor(message: string, reason: S3ArtifactBodyErrorCode);
66
+ }
67
+ /** Deterministic object key derived from the ref; bucket/path/key never enter artifact records. */
68
+ export declare function s3ObjectKey(ref: ArtifactBodyRef): string;
69
+ /** Reference S3-compatible ArtifactBodyStore (AWS S3, MinIO, Cloudflare R2). */
70
+ export declare function createS3ArtifactBodyStore(options: S3ArtifactBodyStoreOptions): ArtifactBodyStore;