every-plugin 3.0.0-rc.16 → 3.0.0-rc.17

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/dist/client.cjs CHANGED
@@ -3,69 +3,21 @@ let _orpc_client = require("@orpc/client");
3
3
  let _orpc_client_fetch = require("@orpc/client/fetch");
4
4
 
5
5
  //#region src/client.ts
6
- const RATE_LIMIT_MAX_RETRIES = 3;
7
- const RATE_LIMIT_RETRY_CAP_MS = 2e3;
8
- const browserClients = /* @__PURE__ */ new Map();
9
- async function fetchWithRateLimitRetry(fetchUrl, options) {
10
- let response = await fetch(fetchUrl, options);
11
- for (let attempt = 1; attempt <= RATE_LIMIT_MAX_RETRIES && response.status === 429; attempt++) {
12
- const retryAfterSeconds = Number(response.headers.get("retry-after"));
13
- const delay = Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0 ? Math.min(retryAfterSeconds * 1e3, RATE_LIMIT_RETRY_CAP_MS) : 100 * attempt;
14
- await new Promise((resolve) => setTimeout(resolve, delay));
15
- response = await fetch(fetchUrl, options);
16
- }
17
- return response;
18
- }
19
- function mergeHeaders(base, overrides) {
20
- const merged = new Headers(base);
21
- new Headers(overrides).forEach((value, key) => {
22
- merged.set(key, value);
23
- });
24
- return merged;
25
- }
26
- function createRpcLink(config, headers) {
27
- return new _orpc_client_fetch.RPCLink({
28
- origin: config.hostUrl,
29
- url: config.path,
30
- interceptors: [(0, _orpc_client.onError)((error) => {
31
- if (typeof globalThis.window === "undefined") return;
32
- if (error && typeof error === "object" && "message" in error) {
33
- const message = String(error.message).toLowerCase();
34
- if (message.includes("fetch") || message.includes("network") || message.includes("failed to fetch")) import("sonner").then(({ toast }) => {
35
- const message = config.connectionError?.() ?? {
36
- title: "Unable to connect to API",
37
- description: "The API is currently unavailable. Please try again later."
38
- };
39
- toast.error(message.title, {
40
- id: "api-connection-error",
41
- description: message.description
42
- });
43
- }).catch(() => {});
44
- }
45
- })],
46
- fetch(fetchUrl, options) {
47
- return fetchWithRateLimitRetry(fetchUrl, {
48
- ...options,
49
- credentials: "include",
50
- headers: headers ? mergeHeaders(headers, options?.headers) : options?.headers
51
- });
52
- }
53
- });
54
- }
55
- function buildRpcClient(config, headers) {
56
- if (!config.hostUrl) throw new Error("Missing runtime host URL");
57
- return (0, _orpc_client.createORPCClient)(createRpcLink(config, headers));
58
- }
59
- function createRpcClient(config, headers) {
60
- if (typeof globalThis.window !== "undefined" && !headers) {
61
- const key = `${config.hostUrl}${config.path}`;
62
- const memoized = browserClients.get(key);
63
- if (memoized) return memoized;
64
- const client = buildRpcClient(config, headers);
65
- browserClients.set(key, client);
66
- return client;
67
- }
68
- return buildRpcClient(config, headers);
6
+ /**
7
+ * Bare typed oRPC client over the RPC protocol — a thin convenience over
8
+ * `createORPCClient(new RPCLink({ origin, url }))`.
9
+ *
10
+ * Promise-native, so async/await callers call it directly and Effect callers
11
+ * lift calls with `Effect.tryPromise` (owning timeout/retry/cache in their
12
+ * service layer). Transport customization (headers, timeouts, retries, test
13
+ * doubles) is RPCLink's native surface — construct the link yourself and pass
14
+ * it to `createORPCClient` instead of growing config here.
15
+ */
16
+ function createRpcClient(hostUrl, path = "/api/rpc") {
17
+ return (0, _orpc_client.createORPCClient)(new _orpc_client_fetch.RPCLink({
18
+ origin: hostUrl,
19
+ url: path
20
+ }));
69
21
  }
70
22
 
71
23
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"client.cjs","names":["RPCLink","onError","createORPCClient"],"sources":["../src/client.ts"],"sourcesContent":["import { createORPCClient, onError } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport type { ContractRouterClient, RouterContract } from \"@orpc/contract\";\n\nexport type RpcConnectionErrorCopy = () => { title: string; description: string };\n\nexport interface RpcClientConfig {\n hostUrl: string;\n path: `/${string}`;\n connectionError?: RpcConnectionErrorCopy;\n}\n\nconst RATE_LIMIT_MAX_RETRIES = 3;\nconst RATE_LIMIT_RETRY_CAP_MS = 2_000;\nconst browserClients = new Map<string, unknown>();\ntype FetchInput = Parameters<typeof fetch>[0];\n\nasync function fetchWithRateLimitRetry(\n fetchUrl: FetchInput,\n options?: RequestInit,\n): Promise<Response> {\n let response = await fetch(fetchUrl, options);\n for (let attempt = 1; attempt <= RATE_LIMIT_MAX_RETRIES && response.status === 429; attempt++) {\n const retryAfterSeconds = Number(response.headers.get(\"retry-after\"));\n const delay =\n Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0\n ? Math.min(retryAfterSeconds * 1000, RATE_LIMIT_RETRY_CAP_MS)\n : 100 * attempt;\n await new Promise((resolve) => setTimeout(resolve, delay));\n response = await fetch(fetchUrl, options);\n }\n\n return response;\n}\n\nfunction mergeHeaders(base: Headers, overrides?: RequestInit[\"headers\"]): Headers {\n const merged = new Headers(base);\n new Headers(overrides).forEach((value, key) => {\n merged.set(key, value);\n });\n return merged;\n}\n\nfunction createRpcLink(config: RpcClientConfig, headers?: Headers) {\n return new RPCLink({\n origin: config.hostUrl,\n url: config.path,\n interceptors: [\n onError((error: unknown) => {\n const browserWindow = (globalThis as { window?: unknown }).window;\n if (typeof browserWindow === \"undefined\") {\n return;\n }\n\n if (error && typeof error === \"object\" && \"message\" in error) {\n const message = String(error.message).toLowerCase();\n if (\n message.includes(\"fetch\") ||\n message.includes(\"network\") ||\n message.includes(\"failed to fetch\")\n ) {\n void import(\"sonner\")\n .then(({ toast }) => {\n const message = config.connectionError?.() ?? {\n title: \"Unable to connect to API\",\n description: \"The API is currently unavailable. Please try again later.\",\n };\n toast.error(message.title, {\n id: \"api-connection-error\",\n description: message.description,\n });\n })\n .catch(() => {});\n }\n }\n }),\n ],\n fetch(fetchUrl: FetchInput, options?: RequestInit) {\n return fetchWithRateLimitRetry(fetchUrl, {\n ...options,\n credentials: \"include\",\n headers: headers ? mergeHeaders(headers, options?.headers) : options?.headers,\n });\n },\n });\n}\n\nfunction buildRpcClient<T extends RouterContract>(\n config: RpcClientConfig,\n headers?: Headers,\n): ContractRouterClient<T> {\n if (!config.hostUrl) {\n throw new Error(\"Missing runtime host URL\");\n }\n\n return createORPCClient(createRpcLink(config, headers)) as ContractRouterClient<T>;\n}\n\nexport function createRpcClient<T extends RouterContract = RouterContract>(\n config: RpcClientConfig,\n headers?: Headers,\n): ContractRouterClient<T> {\n const browserWindow = (globalThis as { window?: unknown }).window;\n if (typeof browserWindow !== \"undefined\" && !headers) {\n const key = `${config.hostUrl}${config.path}`;\n const memoized = browserClients.get(key);\n if (memoized) {\n return memoized as ContractRouterClient<T>;\n }\n\n const client = buildRpcClient<T>(config, headers);\n browserClients.set(key, client);\n return client;\n }\n\n return buildRpcClient<T>(config, headers);\n}\n"],"mappings":";;;;;AAYA,MAAM,yBAAyB;AAC/B,MAAM,0BAA0B;AAChC,MAAM,iCAAiB,IAAI,IAAqB;AAGhD,eAAe,wBACb,UACA,SACmB;CACnB,IAAI,WAAW,MAAM,MAAM,UAAU,OAAO;CAC5C,KAAK,IAAI,UAAU,GAAG,WAAW,0BAA0B,SAAS,WAAW,KAAK,WAAW;EAC7F,MAAM,oBAAoB,OAAO,SAAS,QAAQ,IAAI,aAAa,CAAC;EACpE,MAAM,QACJ,OAAO,SAAS,iBAAiB,KAAK,oBAAoB,IACtD,KAAK,IAAI,oBAAoB,KAAM,uBAAuB,IAC1D,MAAM;EACZ,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,KAAK,CAAC;EACzD,WAAW,MAAM,MAAM,UAAU,OAAO;CAC1C;CAEA,OAAO;AACT;AAEA,SAAS,aAAa,MAAe,WAA6C;CAChF,MAAM,SAAS,IAAI,QAAQ,IAAI;CAC/B,IAAI,QAAQ,SAAS,CAAC,CAAC,SAAS,OAAO,QAAQ;EAC7C,OAAO,IAAI,KAAK,KAAK;CACvB,CAAC;CACD,OAAO;AACT;AAEA,SAAS,cAAc,QAAyB,SAAmB;CACjE,OAAO,IAAIA,2BAAQ;EACjB,QAAQ,OAAO;EACf,KAAK,OAAO;EACZ,cAAc,KACZC,uBAAS,UAAmB;GAE1B,IAAI,OADmB,WAAoC,WAC9B,aAC3B;GAGF,IAAI,SAAS,OAAO,UAAU,YAAY,aAAa,OAAO;IAC5D,MAAM,UAAU,OAAO,MAAM,OAAO,CAAC,CAAC,YAAY;IAClD,IACE,QAAQ,SAAS,OAAO,KACxB,QAAQ,SAAS,SAAS,KAC1B,QAAQ,SAAS,iBAAiB,GAElC,AAAK,OAAO,SAAS,CAClB,MAAM,EAAE,YAAY;KACnB,MAAM,UAAU,OAAO,kBAAkB,KAAK;MAC5C,OAAO;MACP,aAAa;KACf;KACA,MAAM,MAAM,QAAQ,OAAO;MACzB,IAAI;MACJ,aAAa,QAAQ;KACvB,CAAC;IACH,CAAC,CAAC,CACD,YAAY,CAAC,CAAC;GAErB;EACF,CAAC,CACH;EACA,MAAM,UAAsB,SAAuB;GACjD,OAAO,wBAAwB,UAAU;IACvC,GAAG;IACH,aAAa;IACb,SAAS,UAAU,aAAa,SAAS,SAAS,OAAO,IAAI,SAAS;GACxE,CAAC;EACH;CACF,CAAC;AACH;AAEA,SAAS,eACP,QACA,SACyB;CACzB,IAAI,CAAC,OAAO,SACV,MAAM,IAAI,MAAM,0BAA0B;CAG5C,WAAOC,+BAAiB,cAAc,QAAQ,OAAO,CAAC;AACxD;AAEA,SAAgB,gBACd,QACA,SACyB;CAEzB,IAAI,OADmB,WAAoC,WAC9B,eAAe,CAAC,SAAS;EACpD,MAAM,MAAM,GAAG,OAAO,UAAU,OAAO;EACvC,MAAM,WAAW,eAAe,IAAI,GAAG;EACvC,IAAI,UACF,OAAO;EAGT,MAAM,SAAS,eAAkB,QAAQ,OAAO;EAChD,eAAe,IAAI,KAAK,MAAM;EAC9B,OAAO;CACT;CAEA,OAAO,eAAkB,QAAQ,OAAO;AAC1C"}
1
+ {"version":3,"file":"client.cjs","names":["createORPCClient","RPCLink"],"sources":["../src/client.ts"],"sourcesContent":["import { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport type { ContractRouterClient, RouterContract } from \"@orpc/contract\";\n\n/**\n * Bare typed oRPC client over the RPC protocol — a thin convenience over\n * `createORPCClient(new RPCLink({ origin, url }))`.\n *\n * Promise-native, so async/await callers call it directly and Effect callers\n * lift calls with `Effect.tryPromise` (owning timeout/retry/cache in their\n * service layer). Transport customization (headers, timeouts, retries, test\n * doubles) is RPCLink's native surface — construct the link yourself and pass\n * it to `createORPCClient` instead of growing config here.\n */\nexport function createRpcClient<T extends RouterContract = RouterContract>(\n hostUrl: string,\n path: `/${string}` = \"/api/rpc\",\n): ContractRouterClient<T> {\n return createORPCClient(new RPCLink({ origin: hostUrl, url: path })) as ContractRouterClient<T>;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAcA,SAAgB,gBACd,SACA,OAAqB,YACI;CACzB,WAAOA,+BAAiB,IAAIC,2BAAQ;EAAE,QAAQ;EAAS,KAAK;CAAK,CAAC,CAAC;AACrE"}
package/dist/client.d.cts CHANGED
@@ -1,15 +1,16 @@
1
1
  import { ContractRouterClient, RouterContract } from "@orpc/contract";
2
2
  //#region src/client.d.ts
3
- type RpcConnectionErrorCopy = () => {
4
- title: string;
5
- description: string;
6
- };
7
- interface RpcClientConfig {
8
- hostUrl: string;
9
- path: `/${string}`;
10
- connectionError?: RpcConnectionErrorCopy;
11
- }
12
- declare function createRpcClient<T extends RouterContract = RouterContract>(config: RpcClientConfig, headers?: Headers): ContractRouterClient<T>;
3
+ /**
4
+ * Bare typed oRPC client over the RPC protocol — a thin convenience over
5
+ * `createORPCClient(new RPCLink({ origin, url }))`.
6
+ *
7
+ * Promise-native, so async/await callers call it directly and Effect callers
8
+ * lift calls with `Effect.tryPromise` (owning timeout/retry/cache in their
9
+ * service layer). Transport customization (headers, timeouts, retries, test
10
+ * doubles) is RPCLink's native surface — construct the link yourself and pass
11
+ * it to `createORPCClient` instead of growing config here.
12
+ */
13
+ declare function createRpcClient<T extends RouterContract = RouterContract>(hostUrl: string, path?: `/${string}`): ContractRouterClient<T>;
13
14
  //#endregion
14
- export { RpcClientConfig, RpcConnectionErrorCopy, createRpcClient };
15
+ export { createRpcClient };
15
16
  //# sourceMappingURL=client.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.cts","names":[],"sources":["../src/client.ts"],"mappings":";;KAIY;EAAiC;EAAe;;UAE3C;EACf;EACA;EACA,kBAAkB;;iBAyFJ,gBAAgB,UAAU,iBAAiB,gBACzD,QAAQ,iBACR,UAAU,UACT,qBAAqB"}
1
+ {"version":3,"file":"client.d.cts","names":[],"sources":["../src/client.ts"],"mappings":";;;;;;;;;;;;iBAcgB,gBAAgB,UAAU,iBAAiB,gBACzD,iBACA,sBACC,qBAAqB"}
package/dist/client.d.mts CHANGED
@@ -1,15 +1,16 @@
1
1
  import { ContractRouterClient, RouterContract } from "@orpc/contract";
2
2
  //#region src/client.d.ts
3
- type RpcConnectionErrorCopy = () => {
4
- title: string;
5
- description: string;
6
- };
7
- interface RpcClientConfig {
8
- hostUrl: string;
9
- path: `/${string}`;
10
- connectionError?: RpcConnectionErrorCopy;
11
- }
12
- declare function createRpcClient<T extends RouterContract = RouterContract>(config: RpcClientConfig, headers?: Headers): ContractRouterClient<T>;
3
+ /**
4
+ * Bare typed oRPC client over the RPC protocol — a thin convenience over
5
+ * `createORPCClient(new RPCLink({ origin, url }))`.
6
+ *
7
+ * Promise-native, so async/await callers call it directly and Effect callers
8
+ * lift calls with `Effect.tryPromise` (owning timeout/retry/cache in their
9
+ * service layer). Transport customization (headers, timeouts, retries, test
10
+ * doubles) is RPCLink's native surface — construct the link yourself and pass
11
+ * it to `createORPCClient` instead of growing config here.
12
+ */
13
+ declare function createRpcClient<T extends RouterContract = RouterContract>(hostUrl: string, path?: `/${string}`): ContractRouterClient<T>;
13
14
  //#endregion
14
- export { RpcClientConfig, RpcConnectionErrorCopy, createRpcClient };
15
+ export { createRpcClient };
15
16
  //# sourceMappingURL=client.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;KAIY;EAAiC;EAAe;;UAE3C;EACf;EACA;EACA,kBAAkB;;iBAyFJ,gBAAgB,UAAU,iBAAiB,gBACzD,QAAQ,iBACR,UAAU,UACT,qBAAqB"}
1
+ {"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;;;;;;;;;;;iBAcgB,gBAAgB,UAAU,iBAAiB,gBACzD,iBACA,sBACC,qBAAqB"}
package/dist/client.mjs CHANGED
@@ -1,70 +1,22 @@
1
- import { createORPCClient, onError } from "@orpc/client";
1
+ import { createORPCClient } from "@orpc/client";
2
2
  import { RPCLink } from "@orpc/client/fetch";
3
3
 
4
4
  //#region src/client.ts
5
- const RATE_LIMIT_MAX_RETRIES = 3;
6
- const RATE_LIMIT_RETRY_CAP_MS = 2e3;
7
- const browserClients = /* @__PURE__ */ new Map();
8
- async function fetchWithRateLimitRetry(fetchUrl, options) {
9
- let response = await fetch(fetchUrl, options);
10
- for (let attempt = 1; attempt <= RATE_LIMIT_MAX_RETRIES && response.status === 429; attempt++) {
11
- const retryAfterSeconds = Number(response.headers.get("retry-after"));
12
- const delay = Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0 ? Math.min(retryAfterSeconds * 1e3, RATE_LIMIT_RETRY_CAP_MS) : 100 * attempt;
13
- await new Promise((resolve) => setTimeout(resolve, delay));
14
- response = await fetch(fetchUrl, options);
15
- }
16
- return response;
17
- }
18
- function mergeHeaders(base, overrides) {
19
- const merged = new Headers(base);
20
- new Headers(overrides).forEach((value, key) => {
21
- merged.set(key, value);
22
- });
23
- return merged;
24
- }
25
- function createRpcLink(config, headers) {
26
- return new RPCLink({
27
- origin: config.hostUrl,
28
- url: config.path,
29
- interceptors: [onError((error) => {
30
- if (typeof globalThis.window === "undefined") return;
31
- if (error && typeof error === "object" && "message" in error) {
32
- const message = String(error.message).toLowerCase();
33
- if (message.includes("fetch") || message.includes("network") || message.includes("failed to fetch")) import("sonner").then(({ toast }) => {
34
- const message = config.connectionError?.() ?? {
35
- title: "Unable to connect to API",
36
- description: "The API is currently unavailable. Please try again later."
37
- };
38
- toast.error(message.title, {
39
- id: "api-connection-error",
40
- description: message.description
41
- });
42
- }).catch(() => {});
43
- }
44
- })],
45
- fetch(fetchUrl, options) {
46
- return fetchWithRateLimitRetry(fetchUrl, {
47
- ...options,
48
- credentials: "include",
49
- headers: headers ? mergeHeaders(headers, options?.headers) : options?.headers
50
- });
51
- }
52
- });
53
- }
54
- function buildRpcClient(config, headers) {
55
- if (!config.hostUrl) throw new Error("Missing runtime host URL");
56
- return createORPCClient(createRpcLink(config, headers));
57
- }
58
- function createRpcClient(config, headers) {
59
- if (typeof globalThis.window !== "undefined" && !headers) {
60
- const key = `${config.hostUrl}${config.path}`;
61
- const memoized = browserClients.get(key);
62
- if (memoized) return memoized;
63
- const client = buildRpcClient(config, headers);
64
- browserClients.set(key, client);
65
- return client;
66
- }
67
- return buildRpcClient(config, headers);
5
+ /**
6
+ * Bare typed oRPC client over the RPC protocol — a thin convenience over
7
+ * `createORPCClient(new RPCLink({ origin, url }))`.
8
+ *
9
+ * Promise-native, so async/await callers call it directly and Effect callers
10
+ * lift calls with `Effect.tryPromise` (owning timeout/retry/cache in their
11
+ * service layer). Transport customization (headers, timeouts, retries, test
12
+ * doubles) is RPCLink's native surface — construct the link yourself and pass
13
+ * it to `createORPCClient` instead of growing config here.
14
+ */
15
+ function createRpcClient(hostUrl, path = "/api/rpc") {
16
+ return createORPCClient(new RPCLink({
17
+ origin: hostUrl,
18
+ url: path
19
+ }));
68
20
  }
69
21
 
70
22
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import { createORPCClient, onError } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport type { ContractRouterClient, RouterContract } from \"@orpc/contract\";\n\nexport type RpcConnectionErrorCopy = () => { title: string; description: string };\n\nexport interface RpcClientConfig {\n hostUrl: string;\n path: `/${string}`;\n connectionError?: RpcConnectionErrorCopy;\n}\n\nconst RATE_LIMIT_MAX_RETRIES = 3;\nconst RATE_LIMIT_RETRY_CAP_MS = 2_000;\nconst browserClients = new Map<string, unknown>();\ntype FetchInput = Parameters<typeof fetch>[0];\n\nasync function fetchWithRateLimitRetry(\n fetchUrl: FetchInput,\n options?: RequestInit,\n): Promise<Response> {\n let response = await fetch(fetchUrl, options);\n for (let attempt = 1; attempt <= RATE_LIMIT_MAX_RETRIES && response.status === 429; attempt++) {\n const retryAfterSeconds = Number(response.headers.get(\"retry-after\"));\n const delay =\n Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0\n ? Math.min(retryAfterSeconds * 1000, RATE_LIMIT_RETRY_CAP_MS)\n : 100 * attempt;\n await new Promise((resolve) => setTimeout(resolve, delay));\n response = await fetch(fetchUrl, options);\n }\n\n return response;\n}\n\nfunction mergeHeaders(base: Headers, overrides?: RequestInit[\"headers\"]): Headers {\n const merged = new Headers(base);\n new Headers(overrides).forEach((value, key) => {\n merged.set(key, value);\n });\n return merged;\n}\n\nfunction createRpcLink(config: RpcClientConfig, headers?: Headers) {\n return new RPCLink({\n origin: config.hostUrl,\n url: config.path,\n interceptors: [\n onError((error: unknown) => {\n const browserWindow = (globalThis as { window?: unknown }).window;\n if (typeof browserWindow === \"undefined\") {\n return;\n }\n\n if (error && typeof error === \"object\" && \"message\" in error) {\n const message = String(error.message).toLowerCase();\n if (\n message.includes(\"fetch\") ||\n message.includes(\"network\") ||\n message.includes(\"failed to fetch\")\n ) {\n void import(\"sonner\")\n .then(({ toast }) => {\n const message = config.connectionError?.() ?? {\n title: \"Unable to connect to API\",\n description: \"The API is currently unavailable. Please try again later.\",\n };\n toast.error(message.title, {\n id: \"api-connection-error\",\n description: message.description,\n });\n })\n .catch(() => {});\n }\n }\n }),\n ],\n fetch(fetchUrl: FetchInput, options?: RequestInit) {\n return fetchWithRateLimitRetry(fetchUrl, {\n ...options,\n credentials: \"include\",\n headers: headers ? mergeHeaders(headers, options?.headers) : options?.headers,\n });\n },\n });\n}\n\nfunction buildRpcClient<T extends RouterContract>(\n config: RpcClientConfig,\n headers?: Headers,\n): ContractRouterClient<T> {\n if (!config.hostUrl) {\n throw new Error(\"Missing runtime host URL\");\n }\n\n return createORPCClient(createRpcLink(config, headers)) as ContractRouterClient<T>;\n}\n\nexport function createRpcClient<T extends RouterContract = RouterContract>(\n config: RpcClientConfig,\n headers?: Headers,\n): ContractRouterClient<T> {\n const browserWindow = (globalThis as { window?: unknown }).window;\n if (typeof browserWindow !== \"undefined\" && !headers) {\n const key = `${config.hostUrl}${config.path}`;\n const memoized = browserClients.get(key);\n if (memoized) {\n return memoized as ContractRouterClient<T>;\n }\n\n const client = buildRpcClient<T>(config, headers);\n browserClients.set(key, client);\n return client;\n }\n\n return buildRpcClient<T>(config, headers);\n}\n"],"mappings":";;;;AAYA,MAAM,yBAAyB;AAC/B,MAAM,0BAA0B;AAChC,MAAM,iCAAiB,IAAI,IAAqB;AAGhD,eAAe,wBACb,UACA,SACmB;CACnB,IAAI,WAAW,MAAM,MAAM,UAAU,OAAO;CAC5C,KAAK,IAAI,UAAU,GAAG,WAAW,0BAA0B,SAAS,WAAW,KAAK,WAAW;EAC7F,MAAM,oBAAoB,OAAO,SAAS,QAAQ,IAAI,aAAa,CAAC;EACpE,MAAM,QACJ,OAAO,SAAS,iBAAiB,KAAK,oBAAoB,IACtD,KAAK,IAAI,oBAAoB,KAAM,uBAAuB,IAC1D,MAAM;EACZ,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,KAAK,CAAC;EACzD,WAAW,MAAM,MAAM,UAAU,OAAO;CAC1C;CAEA,OAAO;AACT;AAEA,SAAS,aAAa,MAAe,WAA6C;CAChF,MAAM,SAAS,IAAI,QAAQ,IAAI;CAC/B,IAAI,QAAQ,SAAS,CAAC,CAAC,SAAS,OAAO,QAAQ;EAC7C,OAAO,IAAI,KAAK,KAAK;CACvB,CAAC;CACD,OAAO;AACT;AAEA,SAAS,cAAc,QAAyB,SAAmB;CACjE,OAAO,IAAI,QAAQ;EACjB,QAAQ,OAAO;EACf,KAAK,OAAO;EACZ,cAAc,CACZ,SAAS,UAAmB;GAE1B,IAAI,OADmB,WAAoC,WAC9B,aAC3B;GAGF,IAAI,SAAS,OAAO,UAAU,YAAY,aAAa,OAAO;IAC5D,MAAM,UAAU,OAAO,MAAM,OAAO,CAAC,CAAC,YAAY;IAClD,IACE,QAAQ,SAAS,OAAO,KACxB,QAAQ,SAAS,SAAS,KAC1B,QAAQ,SAAS,iBAAiB,GAElC,AAAK,OAAO,SAAS,CAClB,MAAM,EAAE,YAAY;KACnB,MAAM,UAAU,OAAO,kBAAkB,KAAK;MAC5C,OAAO;MACP,aAAa;KACf;KACA,MAAM,MAAM,QAAQ,OAAO;MACzB,IAAI;MACJ,aAAa,QAAQ;KACvB,CAAC;IACH,CAAC,CAAC,CACD,YAAY,CAAC,CAAC;GAErB;EACF,CAAC,CACH;EACA,MAAM,UAAsB,SAAuB;GACjD,OAAO,wBAAwB,UAAU;IACvC,GAAG;IACH,aAAa;IACb,SAAS,UAAU,aAAa,SAAS,SAAS,OAAO,IAAI,SAAS;GACxE,CAAC;EACH;CACF,CAAC;AACH;AAEA,SAAS,eACP,QACA,SACyB;CACzB,IAAI,CAAC,OAAO,SACV,MAAM,IAAI,MAAM,0BAA0B;CAG5C,OAAO,iBAAiB,cAAc,QAAQ,OAAO,CAAC;AACxD;AAEA,SAAgB,gBACd,QACA,SACyB;CAEzB,IAAI,OADmB,WAAoC,WAC9B,eAAe,CAAC,SAAS;EACpD,MAAM,MAAM,GAAG,OAAO,UAAU,OAAO;EACvC,MAAM,WAAW,eAAe,IAAI,GAAG;EACvC,IAAI,UACF,OAAO;EAGT,MAAM,SAAS,eAAkB,QAAQ,OAAO;EAChD,eAAe,IAAI,KAAK,MAAM;EAC9B,OAAO;CACT;CAEA,OAAO,eAAkB,QAAQ,OAAO;AAC1C"}
1
+ {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport type { ContractRouterClient, RouterContract } from \"@orpc/contract\";\n\n/**\n * Bare typed oRPC client over the RPC protocol — a thin convenience over\n * `createORPCClient(new RPCLink({ origin, url }))`.\n *\n * Promise-native, so async/await callers call it directly and Effect callers\n * lift calls with `Effect.tryPromise` (owning timeout/retry/cache in their\n * service layer). Transport customization (headers, timeouts, retries, test\n * doubles) is RPCLink's native surface — construct the link yourself and pass\n * it to `createORPCClient` instead of growing config here.\n */\nexport function createRpcClient<T extends RouterContract = RouterContract>(\n hostUrl: string,\n path: `/${string}` = \"/api/rpc\",\n): ContractRouterClient<T> {\n return createORPCClient(new RPCLink({ origin: hostUrl, url: path })) as ContractRouterClient<T>;\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAgB,gBACd,SACA,OAAqB,YACI;CACzB,OAAO,iBAAiB,IAAI,QAAQ;EAAE,QAAQ;EAAS,KAAK;CAAK,CAAC,CAAC;AACrE"}
package/dist/index.d.cts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { remoteName } from "./identity.cjs";
2
- import { RpcClientConfig, RpcConnectionErrorCopy, createRpcClient } from "./client.cjs";
2
+ import { createRpcClient } from "./client.cjs";
3
3
  import { ModuleFederationError, PluginLoadFailureInfo, PluginRuntimeError, PluginWarningInfo, ValidationError, classifyPluginFailure, defaultMissingSecretWarnings, extractFromFiberFailure, formatORPCError, toPluginRuntimeError, wrapORPCError } from "./runtime/errors.cjs";
4
4
  import { BAD_REQUEST, CONFLICT, CONNECTION_ERROR, FORBIDDEN, NOT_FOUND, PLUGIN_ERROR_STATUS_MAP, PluginErrors, RATE_LIMITED, SERVICE_UNAVAILABLE, TIMEOUT, UNAUTHORIZED } from "./errors.cjs";
5
5
  import { buildScoped, buildScopedContext } from "./effect-helpers.cjs";
6
6
  import { CreatePluginFn, CreatePluginWithPlugins, LoadedPluginWithBinding, Plugin, PluginConfigFor, PluginEnv, PluginIdTag, PluginInitializeInput, PluginWarningsFn, createPlugin, withPlugins } from "./plugin.cjs";
7
- import { AnyPlugin, EveryPlugin, InitializedPlugin, LoadedPlugin, ModuleClientEntry, ModuleEndpoint, ModuleSurface, ModuleTransport, PluginClientEntry, PluginClientType, PluginConfigInput, PluginContext, PluginContract, PluginEndpoint, PluginInstance, PluginMetadata, PluginRegistry, PluginRouterType, PluginRuntimeConfig, PluginSecrets, PluginServicesEntry, PluginSurface, PluginTransport, PluginVariables, RegisteredPlugins, SecretsConfig, UsePluginResult } from "./types.cjs";
7
+ import { AnyPlugin, EveryPlugin, InferRegistryFromEntries, InitializedPlugin, LoadedPlugin, ModuleClientEntry, ModuleEndpoint, ModuleSurface, ModuleTransport, PluginClientEntry, PluginClientType, PluginConfigInput, PluginContext, PluginContract, PluginEndpoint, PluginInstance, PluginMetadata, PluginRegistry, PluginRegistryEntry, PluginRouterType, PluginRuntimeConfig, PluginSecrets, PluginServicesEntry, PluginSurface, PluginTransport, PluginVariables, RegisteredPlugins, SecretsConfig, UsePluginResult } from "./types.cjs";
8
8
  import { PluginRuntime, createPluginRuntime } from "./runtime/index.cjs";
9
9
  import "@orpc/openapi/extensions/route";
10
- export { type AnyPlugin, BAD_REQUEST, CONFLICT, CONNECTION_ERROR, CreatePluginFn, CreatePluginWithPlugins, type EveryPlugin, FORBIDDEN, type InitializedPlugin, type LoadedPlugin, LoadedPluginWithBinding, type ModuleClientEntry, type ModuleEndpoint, ModuleFederationError, type ModuleSurface, type ModuleTransport, NOT_FOUND, PLUGIN_ERROR_STATUS_MAP, Plugin, type PluginClientEntry, type PluginClientType, PluginConfigFor, type PluginConfigInput, type PluginContext, type PluginContract, type PluginEndpoint, PluginEnv, PluginErrors, PluginIdTag, PluginInitializeInput, type PluginInstance, type PluginLoadFailureInfo, type PluginMetadata, type PluginRegistry, type PluginRouterType, PluginRuntime, type PluginRuntimeConfig, PluginRuntimeError, type PluginSecrets, type PluginServicesEntry, type PluginSurface, type PluginTransport, type PluginVariables, type PluginWarningInfo, PluginWarningsFn, RATE_LIMITED, type RegisteredPlugins, RpcClientConfig, RpcConnectionErrorCopy, SERVICE_UNAVAILABLE, type SecretsConfig, TIMEOUT, UNAUTHORIZED, type UsePluginResult, ValidationError, buildScoped, buildScopedContext, classifyPluginFailure, createPlugin, createPluginRuntime, createRpcClient, defaultMissingSecretWarnings, extractFromFiberFailure, formatORPCError, remoteName as getNormalizedRemoteName, toPluginRuntimeError, withPlugins, wrapORPCError };
10
+ export { type AnyPlugin, BAD_REQUEST, CONFLICT, CONNECTION_ERROR, CreatePluginFn, CreatePluginWithPlugins, type EveryPlugin, FORBIDDEN, type InferRegistryFromEntries, type InitializedPlugin, type LoadedPlugin, LoadedPluginWithBinding, type ModuleClientEntry, type ModuleEndpoint, ModuleFederationError, type ModuleSurface, type ModuleTransport, NOT_FOUND, PLUGIN_ERROR_STATUS_MAP, Plugin, type PluginClientEntry, type PluginClientType, PluginConfigFor, type PluginConfigInput, type PluginContext, type PluginContract, type PluginEndpoint, PluginEnv, PluginErrors, PluginIdTag, PluginInitializeInput, type PluginInstance, type PluginLoadFailureInfo, type PluginMetadata, type PluginRegistry, type PluginRegistryEntry, type PluginRouterType, PluginRuntime, type PluginRuntimeConfig, PluginRuntimeError, type PluginSecrets, type PluginServicesEntry, type PluginSurface, type PluginTransport, type PluginVariables, type PluginWarningInfo, PluginWarningsFn, RATE_LIMITED, type RegisteredPlugins, SERVICE_UNAVAILABLE, type SecretsConfig, TIMEOUT, UNAUTHORIZED, type UsePluginResult, ValidationError, buildScoped, buildScopedContext, classifyPluginFailure, createPlugin, createPluginRuntime, createRpcClient, defaultMissingSecretWarnings, extractFromFiberFailure, formatORPCError, remoteName as getNormalizedRemoteName, toPluginRuntimeError, withPlugins, wrapORPCError };
package/dist/index.d.mts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { remoteName } from "./identity.mjs";
2
- import { RpcClientConfig, RpcConnectionErrorCopy, createRpcClient } from "./client.mjs";
2
+ import { createRpcClient } from "./client.mjs";
3
3
  import { ModuleFederationError, PluginLoadFailureInfo, PluginRuntimeError, PluginWarningInfo, ValidationError, classifyPluginFailure, defaultMissingSecretWarnings, extractFromFiberFailure, formatORPCError, toPluginRuntimeError, wrapORPCError } from "./runtime/errors.mjs";
4
4
  import { BAD_REQUEST, CONFLICT, CONNECTION_ERROR, FORBIDDEN, NOT_FOUND, PLUGIN_ERROR_STATUS_MAP, PluginErrors, RATE_LIMITED, SERVICE_UNAVAILABLE, TIMEOUT, UNAUTHORIZED } from "./errors.mjs";
5
5
  import { buildScoped, buildScopedContext } from "./effect-helpers.mjs";
6
6
  import { CreatePluginFn, CreatePluginWithPlugins, LoadedPluginWithBinding, Plugin, PluginConfigFor, PluginEnv, PluginIdTag, PluginInitializeInput, PluginWarningsFn, createPlugin, withPlugins } from "./plugin.mjs";
7
- import { AnyPlugin, EveryPlugin, InitializedPlugin, LoadedPlugin, ModuleClientEntry, ModuleEndpoint, ModuleSurface, ModuleTransport, PluginClientEntry, PluginClientType, PluginConfigInput, PluginContext, PluginContract, PluginEndpoint, PluginInstance, PluginMetadata, PluginRegistry, PluginRouterType, PluginRuntimeConfig, PluginSecrets, PluginServicesEntry, PluginSurface, PluginTransport, PluginVariables, RegisteredPlugins, SecretsConfig, UsePluginResult } from "./types.mjs";
7
+ import { AnyPlugin, EveryPlugin, InferRegistryFromEntries, InitializedPlugin, LoadedPlugin, ModuleClientEntry, ModuleEndpoint, ModuleSurface, ModuleTransport, PluginClientEntry, PluginClientType, PluginConfigInput, PluginContext, PluginContract, PluginEndpoint, PluginInstance, PluginMetadata, PluginRegistry, PluginRegistryEntry, PluginRouterType, PluginRuntimeConfig, PluginSecrets, PluginServicesEntry, PluginSurface, PluginTransport, PluginVariables, RegisteredPlugins, SecretsConfig, UsePluginResult } from "./types.mjs";
8
8
  import { PluginRuntime, createPluginRuntime } from "./runtime/index.mjs";
9
9
  import "@orpc/openapi/extensions/route";
10
- export { type AnyPlugin, BAD_REQUEST, CONFLICT, CONNECTION_ERROR, CreatePluginFn, CreatePluginWithPlugins, type EveryPlugin, FORBIDDEN, type InitializedPlugin, type LoadedPlugin, LoadedPluginWithBinding, type ModuleClientEntry, type ModuleEndpoint, ModuleFederationError, type ModuleSurface, type ModuleTransport, NOT_FOUND, PLUGIN_ERROR_STATUS_MAP, Plugin, type PluginClientEntry, type PluginClientType, PluginConfigFor, type PluginConfigInput, type PluginContext, type PluginContract, type PluginEndpoint, PluginEnv, PluginErrors, PluginIdTag, PluginInitializeInput, type PluginInstance, type PluginLoadFailureInfo, type PluginMetadata, type PluginRegistry, type PluginRouterType, PluginRuntime, type PluginRuntimeConfig, PluginRuntimeError, type PluginSecrets, type PluginServicesEntry, type PluginSurface, type PluginTransport, type PluginVariables, type PluginWarningInfo, PluginWarningsFn, RATE_LIMITED, type RegisteredPlugins, RpcClientConfig, RpcConnectionErrorCopy, SERVICE_UNAVAILABLE, type SecretsConfig, TIMEOUT, UNAUTHORIZED, type UsePluginResult, ValidationError, buildScoped, buildScopedContext, classifyPluginFailure, createPlugin, createPluginRuntime, createRpcClient, defaultMissingSecretWarnings, extractFromFiberFailure, formatORPCError, remoteName as getNormalizedRemoteName, toPluginRuntimeError, withPlugins, wrapORPCError };
10
+ export { type AnyPlugin, BAD_REQUEST, CONFLICT, CONNECTION_ERROR, CreatePluginFn, CreatePluginWithPlugins, type EveryPlugin, FORBIDDEN, type InferRegistryFromEntries, type InitializedPlugin, type LoadedPlugin, LoadedPluginWithBinding, type ModuleClientEntry, type ModuleEndpoint, ModuleFederationError, type ModuleSurface, type ModuleTransport, NOT_FOUND, PLUGIN_ERROR_STATUS_MAP, Plugin, type PluginClientEntry, type PluginClientType, PluginConfigFor, type PluginConfigInput, type PluginContext, type PluginContract, type PluginEndpoint, PluginEnv, PluginErrors, PluginIdTag, PluginInitializeInput, type PluginInstance, type PluginLoadFailureInfo, type PluginMetadata, type PluginRegistry, type PluginRegistryEntry, type PluginRouterType, PluginRuntime, type PluginRuntimeConfig, PluginRuntimeError, type PluginSecrets, type PluginServicesEntry, type PluginSurface, type PluginTransport, type PluginVariables, type PluginWarningInfo, PluginWarningsFn, RATE_LIMITED, type RegisteredPlugins, SERVICE_UNAVAILABLE, type SecretsConfig, TIMEOUT, UNAUTHORIZED, type UsePluginResult, ValidationError, buildScoped, buildScopedContext, classifyPluginFailure, createPlugin, createPluginRuntime, createRpcClient, defaultMissingSecretWarnings, extractFromFiberFailure, formatORPCError, remoteName as getNormalizedRemoteName, toPluginRuntimeError, withPlugins, wrapORPCError };
@@ -3,7 +3,7 @@ const require_shared_deps_spec = require("../shared-deps-spec.cjs");
3
3
  let node_fs = require("node:fs");
4
4
 
5
5
  //#region src/runtime/mf-config.ts
6
- const PLUGIN_VERSION = "3.0.0-rc.16";
6
+ const PLUGIN_VERSION = "3.0.0-rc.17";
7
7
  const MF_CORE_SHARED_DEPS = require_shared_deps_spec.buildMfCoreSharedDependencies({ ownVersion: PLUGIN_VERSION });
8
8
 
9
9
  //#endregion
@@ -2,7 +2,7 @@ import { buildMfCoreSharedDependencies } from "../shared-deps-spec.mjs";
2
2
  import { readFileSync } from "node:fs";
3
3
 
4
4
  //#region src/runtime/mf-config.ts
5
- const PLUGIN_VERSION = "3.0.0-rc.16";
5
+ const PLUGIN_VERSION = "3.0.0-rc.17";
6
6
  const MF_CORE_SHARED_DEPS = buildMfCoreSharedDependencies({ ownVersion: PLUGIN_VERSION });
7
7
 
8
8
  //#endregion
@@ -14,6 +14,18 @@ let node_crypto = require("node:crypto");
14
14
  const SRI = zod.z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);
15
15
  /** Slot-relative directory holding one deploy's immutable file copies (#826). */
16
16
  const VERSION_DIR_PATTERN = /^versions\/[0-9a-f]{16}$/;
17
+ /** The attribution block stored in the manifest's `signature` field. The
18
+ * verifying key rides along so a verifier can distinguish "wrong signature"
19
+ * from "signed by a key the config never registered" — both are
20
+ * `SignatureInvalid`, but the reasons name different compromises. */
21
+ const ManifestSignatureSchema = zod.z.object({
22
+ algorithm: zod.z.literal("ed25519"),
23
+ /** NEAR-format verifying key (`ed25519:<base58>`) — must equal the
24
+ * config's `publish.verification.publicKey`. */
25
+ publicKey: zod.z.string().min(1),
26
+ /** base64 ed25519 signature over the canonical payload bytes. */
27
+ value: zod.z.string().min(1)
28
+ });
17
29
  const WorkspaceVersionManifestSchema = zod.z.object({
18
30
  version: zod.z.string().regex(/^[0-9a-f]{16}$/),
19
31
  builtAt: zod.z.string().optional(),
@@ -61,8 +73,38 @@ const WorkspaceVersionManifestSchema = zod.z.object({
61
73
  source: zod.z.object({
62
74
  file: zod.z.string().min(1),
63
75
  integrity: SRI
64
- }).optional()
76
+ }).optional(),
77
+ /**
78
+ * Attribution (ADR 0046 ticket 05): the deploy train's ed25519 signature
79
+ * over `signaturePayloadOf(this document)` — SRI attests WHAT the bytes
80
+ * are, the signature attests WHO published them, with the verifying key
81
+ * anchored in the on-chain config (`publish.verification`). Absent on
82
+ * rollouts with attribution off; readers that predate the field ignore it
83
+ * (zod strips unknown keys). Excluded from the version-id derivation —
84
+ * like `builtAt`, it is not a coordinate of the build.
85
+ */
86
+ signature: ManifestSignatureSchema.optional()
65
87
  });
88
+ /** Canonical key-sorted JSON serialization — the same form the version-id
89
+ * derivation hashes (undefined-valued entries dropped). Exported so the
90
+ * signature payload and the version id can never disagree about what
91
+ * "canonical" means. */
92
+ function stableStringify(value) {
93
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
94
+ if (value && typeof value === "object") return `{${Object.entries(value).filter(([, v]) => v !== void 0).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).sort().join(",")}}`;
95
+ return JSON.stringify(value) ?? "null";
96
+ }
97
+ /**
98
+ * The bytes a manifest signature attests: the document minus its reserved
99
+ * `signature` field, canonically serialized (key-sorted — the version-id
100
+ * rule). The signer signs this before the field exists; the verifier
101
+ * re-derives it from the parsed document, so the stored document's
102
+ * whitespace never matters.
103
+ */
104
+ function signaturePayloadOf(manifest) {
105
+ const { signature: _signature, ...payload } = manifest;
106
+ return stableStringify(payload);
107
+ }
66
108
  /**
67
109
  * Compose the manifest with its `version` derived from the canonical content
68
110
  * of the artifact coordinates alone (key-sorted, `builtAt` excluded) —
@@ -88,15 +130,13 @@ function composeVersionManifest(input) {
88
130
  function versionDirPath(files) {
89
131
  return `versions/${(0, node_crypto.createHash)("sha256").update(stableStringify(files)).digest("hex").slice(0, 16)}`;
90
132
  }
91
- function stableStringify(value) {
92
- if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
93
- if (value && typeof value === "object") return `{${Object.entries(value).filter(([, v]) => v !== void 0).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).sort().join(",")}}`;
94
- return JSON.stringify(value) ?? "null";
95
- }
96
133
 
97
134
  //#endregion
135
+ exports.ManifestSignatureSchema = ManifestSignatureSchema;
98
136
  exports.VERSION_DIR_PATTERN = VERSION_DIR_PATTERN;
99
137
  exports.WorkspaceVersionManifestSchema = WorkspaceVersionManifestSchema;
100
138
  exports.composeVersionManifest = composeVersionManifest;
139
+ exports.signaturePayloadOf = signaturePayloadOf;
140
+ exports.stableStringify = stableStringify;
101
141
  exports.versionDirPath = versionDirPath;
102
142
  //# sourceMappingURL=version-manifest.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-manifest.cjs","names":["z","createHash"],"sources":["../src/version-manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n/**\n * WorkspaceVersionManifest — the immutable per-deploy document at\n * `bundles/<account>/<gateway>/<workspace>/versions/<version>.json`\n * (ADR 0020, as amended; atomic-deploys MAP decision 1). The published\n * config's slot pins this document via its `pin` (`manifest` + the\n * manifest's own SRI); every artifact coordinate lives here, so the config\n * stops growing with artifact kinds and old versions stay fully servable.\n */\n\nconst SRI = z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);\n\n/** Slot-relative directory holding one deploy's immutable file copies (#826). */\nexport const VERSION_DIR_PATTERN = /^versions\\/[0-9a-f]{16}$/;\n\nexport const WorkspaceVersionManifestSchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n builtAt: z.string().optional(),\n gitSha: z.string().optional(),\n /**\n * Slot-relative directory (`versions/<dirId>`) holding this deploy's\n * byte-immutable copies of every dist file (#826). When present, all\n * artifact coordinates below resolve against `<slotBase>/<dir>/` — so a\n * pinned child's entry fetches its expose chunk and chunks from the same\n * frozen directory instead of the mutable slot root. Absent on legacy\n * manifests: coordinates resolve against the slot root exactly as before.\n * Derived from the `files` map (see `versionDirPath`), so it participates\n * in the version hash without circularity.\n */\n dir: z.string().regex(VERSION_DIR_PATTERN).optional(),\n entry: z.string().min(1),\n entryIntegrity: SRI,\n ssr: z\n .object({\n entry: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n browserManifest: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n /** extra assets (css, icons) — logical name → served-object SRI */\n assets: z.record(z.string(), SRI).optional(),\n /** shared-dependency versions this build was built against */\n shared: z.record(z.string(), z.string()).optional(),\n /**\n * Full per-file digest map of the dist (object path → served-object SRI).\n * Written by `every-plugin`-based deploys so the next deploy can diff against\n * it and skip re-uploading unchanged files; readers that predate the field\n * ignore it (zod strips unknown keys). Participates in the version hash.\n */\n files: z.record(z.string(), SRI).optional(),\n /**\n * Source archive of the workspace subtree (object path → served-object SRI)\n * published beside the dists when the runtime opts in via\n * `publish.sourcePublish` — lets `bos import` dereference a published\n * config slot to the exact source bytes that build came from, with no\n * GitHub dependency. Readers that predate the field ignore it (zod strips\n * unknown keys). Participates in the version hash.\n */\n source: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n});\nexport type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;\n\n/** A published-artifact coordinate: object path within the bundle namespace + its served SRI. */\nexport type SourceRef = NonNullable<WorkspaceVersionManifest[\"source\"]>;\n\nexport interface VersionManifestInput {\n /** Omit: a per-build timestamp makes the same version id map to different\n * manifest bytes per machine, breaking the pin's SRI against the image's\n * staged copy and the CDN's immutability for republished versions. */\n builtAt?: string;\n gitSha?: string;\n dir?: string;\n entry: string;\n entryIntegrity: string;\n ssr?: { entry: string; integrity: string };\n browserManifest?: { file: string; integrity: string };\n assets?: Record<string, string>;\n shared?: Record<string, string>;\n files?: Record<string, string>;\n source?: { file: string; integrity: string };\n}\n\n/**\n * Compose the manifest with its `version` derived from the canonical content\n * of the artifact coordinates alone (key-sorted, `builtAt` excluded) —\n * identical bytes → identical version id regardless of when they were built,\n * so republishing unchanged content is a pointer no-op. Any changed\n * coordinate → a new version (and a new file).\n */\nexport function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest {\n const { builtAt: _builtAt, ...hashedInputs } = input;\n const version = createHash(\"sha256\")\n .update(stableStringify(hashedInputs))\n .digest(\"hex\")\n .slice(0, 16);\n return WorkspaceVersionManifestSchema.parse({ ...input, version });\n}\n\n/**\n * The content-addressed directory name for one dist's file set (#826): a\n * 16-hex hash over the canonical files map. Identical bytes → identical\n * directory → re-deploying unchanged content is a server-side no-op, and a\n * published pin's directory can never be rewritten by a later deploy\n * (the upload service refuses conflicting writes under `versions/`).\n */\nexport function versionDirPath(files: Record<string, string>): string {\n const id = createHash(\"sha256\").update(stableStringify(files)).digest(\"hex\").slice(0, 16);\n return `versions/${id}`;\n}\n\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n .sort()\n .join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,MAAM,MAAMA,MAAE,OAAO,CAAC,CAAC,MAAM,0BAA0B;;AAGvD,MAAa,sBAAsB;AAEnC,MAAa,iCAAiCA,MAAE,OAAO;CACrD,SAASA,MAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB;CAC1C,SAASA,MAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,QAAQA,MAAE,OAAO,CAAC,CAAC,SAAS;;;;;;;;;;;CAW5B,KAAKA,MAAE,OAAO,CAAC,CAAC,MAAM,mBAAmB,CAAC,CAAC,SAAS;CACpD,OAAOA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,gBAAgB;CAChB,KAAKA,MACF,OAAO;EACN,OAAOA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACvB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;CACZ,iBAAiBA,MACd,OAAO;EACN,MAAMA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;;CAEZ,QAAQA,MAAE,OAAOA,MAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;CAE3C,QAAQA,MAAE,OAAOA,MAAE,OAAO,GAAGA,MAAE,OAAO,CAAC,CAAC,CAAC,SAAS;;;;;;;CAOlD,OAAOA,MAAE,OAAOA,MAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;;;;;;;;CAS1C,QAAQA,MACL,OAAO;EACN,MAAMA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;AACd,CAAC;;;;;;;;AA8BD,SAAgB,uBAAuB,OAAuD;CAC5F,MAAM,EAAE,SAAS,UAAU,GAAG,iBAAiB;CAC/C,MAAM,cAAUC,wBAAW,QAAQ,CAAC,CACjC,OAAO,gBAAgB,YAAY,CAAC,CAAC,CACrC,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EAAE;CACd,OAAO,+BAA+B,MAAM;EAAE,GAAG;EAAO;CAAQ,CAAC;AACnE;;;;;;;;AASA,SAAgB,eAAe,OAAuC;CAEpE,OAAO,gBADIA,wBAAW,QAAQ,CAAC,CAAC,OAAO,gBAAgB,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAClE;AACtB;AAEA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAC1E,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,IAAI,OAAO,QAAQ,KAAK,CAAC,CAC7B,QAAQ,GAAG,OAAO,MAAM,MAAS,CAAC,CAClC,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAC7D,KAAK,CAAC,CACN,KAAK,GAAG,EAAE;CAEf,OAAO,KAAK,UAAU,KAAK,KAAK;AAClC"}
1
+ {"version":3,"file":"version-manifest.cjs","names":["z","createHash"],"sources":["../src/version-manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n/**\n * WorkspaceVersionManifest — the immutable per-deploy document at\n * `bundles/<account>/<gateway>/<workspace>/versions/<version>.json`\n * (ADR 0020, as amended; atomic-deploys MAP decision 1). The published\n * config's slot pins this document via its `pin` (`manifest` + the\n * manifest's own SRI); every artifact coordinate lives here, so the config\n * stops growing with artifact kinds and old versions stay fully servable.\n */\n\nconst SRI = z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);\n\n/** Slot-relative directory holding one deploy's immutable file copies (#826). */\nexport const VERSION_DIR_PATTERN = /^versions\\/[0-9a-f]{16}$/;\n\n/** The attribution block stored in the manifest's `signature` field. The\n * verifying key rides along so a verifier can distinguish \"wrong signature\"\n * from \"signed by a key the config never registered\" — both are\n * `SignatureInvalid`, but the reasons name different compromises. */\nexport const ManifestSignatureSchema = z.object({\n algorithm: z.literal(\"ed25519\"),\n /** NEAR-format verifying key (`ed25519:<base58>`) — must equal the\n * config's `publish.verification.publicKey`. */\n publicKey: z.string().min(1),\n /** base64 ed25519 signature over the canonical payload bytes. */\n value: z.string().min(1),\n});\nexport type ManifestSignature = z.infer<typeof ManifestSignatureSchema>;\n\nexport const WorkspaceVersionManifestSchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n builtAt: z.string().optional(),\n gitSha: z.string().optional(),\n /**\n * Slot-relative directory (`versions/<dirId>`) holding this deploy's\n * byte-immutable copies of every dist file (#826). When present, all\n * artifact coordinates below resolve against `<slotBase>/<dir>/` — so a\n * pinned child's entry fetches its expose chunk and chunks from the same\n * frozen directory instead of the mutable slot root. Absent on legacy\n * manifests: coordinates resolve against the slot root exactly as before.\n * Derived from the `files` map (see `versionDirPath`), so it participates\n * in the version hash without circularity.\n */\n dir: z.string().regex(VERSION_DIR_PATTERN).optional(),\n entry: z.string().min(1),\n entryIntegrity: SRI,\n ssr: z\n .object({\n entry: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n browserManifest: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n /** extra assets (css, icons) — logical name → served-object SRI */\n assets: z.record(z.string(), SRI).optional(),\n /** shared-dependency versions this build was built against */\n shared: z.record(z.string(), z.string()).optional(),\n /**\n * Full per-file digest map of the dist (object path → served-object SRI).\n * Written by `every-plugin`-based deploys so the next deploy can diff against\n * it and skip re-uploading unchanged files; readers that predate the field\n * ignore it (zod strips unknown keys). Participates in the version hash.\n */\n files: z.record(z.string(), SRI).optional(),\n /**\n * Source archive of the workspace subtree (object path → served-object SRI)\n * published beside the dists when the runtime opts in via\n * `publish.sourcePublish` — lets `bos import` dereference a published\n * config slot to the exact source bytes that build came from, with no\n * GitHub dependency. Readers that predate the field ignore it (zod strips\n * unknown keys). Participates in the version hash.\n */\n source: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n /**\n * Attribution (ADR 0046 ticket 05): the deploy train's ed25519 signature\n * over `signaturePayloadOf(this document)` — SRI attests WHAT the bytes\n * are, the signature attests WHO published them, with the verifying key\n * anchored in the on-chain config (`publish.verification`). Absent on\n * rollouts with attribution off; readers that predate the field ignore it\n * (zod strips unknown keys). Excluded from the version-id derivation —\n * like `builtAt`, it is not a coordinate of the build.\n */\n signature: ManifestSignatureSchema.optional(),\n});\nexport type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;\n\n/** Canonical key-sorted JSON serialization — the same form the version-id\n * derivation hashes (undefined-valued entries dropped). Exported so the\n * signature payload and the version id can never disagree about what\n * \"canonical\" means. */\nexport function stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n .sort()\n .join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n\n/**\n * The bytes a manifest signature attests: the document minus its reserved\n * `signature` field, canonically serialized (key-sorted — the version-id\n * rule). The signer signs this before the field exists; the verifier\n * re-derives it from the parsed document, so the stored document's\n * whitespace never matters.\n */\nexport function signaturePayloadOf(manifest: WorkspaceVersionManifest): string {\n const { signature: _signature, ...payload } = manifest;\n return stableStringify(payload);\n}\n\n/** A published-artifact coordinate: object path within the bundle namespace + its served SRI. */\nexport type SourceRef = NonNullable<WorkspaceVersionManifest[\"source\"]>;\n\nexport interface VersionManifestInput {\n /** Omit: a per-build timestamp makes the same version id map to different\n * manifest bytes per machine, breaking the pin's SRI against the image's\n * staged copy and the CDN's immutability for republished versions. */\n builtAt?: string;\n gitSha?: string;\n dir?: string;\n entry: string;\n entryIntegrity: string;\n ssr?: { entry: string; integrity: string };\n browserManifest?: { file: string; integrity: string };\n assets?: Record<string, string>;\n shared?: Record<string, string>;\n files?: Record<string, string>;\n source?: { file: string; integrity: string };\n}\n\n/**\n * Compose the manifest with its `version` derived from the canonical content\n * of the artifact coordinates alone (key-sorted, `builtAt` excluded) —\n * identical bytes → identical version id regardless of when they were built,\n * so republishing unchanged content is a pointer no-op. Any changed\n * coordinate → a new version (and a new file).\n */\nexport function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest {\n const { builtAt: _builtAt, ...hashedInputs } = input;\n const version = createHash(\"sha256\")\n .update(stableStringify(hashedInputs))\n .digest(\"hex\")\n .slice(0, 16);\n return WorkspaceVersionManifestSchema.parse({ ...input, version });\n}\n\n/**\n * The content-addressed directory name for one dist's file set (#826): a\n * 16-hex hash over the canonical files map. Identical bytes → identical\n * directory → re-deploying unchanged content is a server-side no-op, and a\n * published pin's directory can never be rewritten by a later deploy\n * (the upload service refuses conflicting writes under `versions/`).\n */\nexport function versionDirPath(files: Record<string, string>): string {\n const id = createHash(\"sha256\").update(stableStringify(files)).digest(\"hex\").slice(0, 16);\n return `versions/${id}`;\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,MAAM,MAAMA,MAAE,OAAO,CAAC,CAAC,MAAM,0BAA0B;;AAGvD,MAAa,sBAAsB;;;;;AAMnC,MAAa,0BAA0BA,MAAE,OAAO;CAC9C,WAAWA,MAAE,QAAQ,SAAS;;;CAG9B,WAAWA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;;CAE3B,OAAOA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;AACzB,CAAC;AAGD,MAAa,iCAAiCA,MAAE,OAAO;CACrD,SAASA,MAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB;CAC1C,SAASA,MAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,QAAQA,MAAE,OAAO,CAAC,CAAC,SAAS;;;;;;;;;;;CAW5B,KAAKA,MAAE,OAAO,CAAC,CAAC,MAAM,mBAAmB,CAAC,CAAC,SAAS;CACpD,OAAOA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,gBAAgB;CAChB,KAAKA,MACF,OAAO;EACN,OAAOA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACvB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;CACZ,iBAAiBA,MACd,OAAO;EACN,MAAMA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;;CAEZ,QAAQA,MAAE,OAAOA,MAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;CAE3C,QAAQA,MAAE,OAAOA,MAAE,OAAO,GAAGA,MAAE,OAAO,CAAC,CAAC,CAAC,SAAS;;;;;;;CAOlD,OAAOA,MAAE,OAAOA,MAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;;;;;;;;CAS1C,QAAQA,MACL,OAAO;EACN,MAAMA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;;;;;;;;;;CAUZ,WAAW,wBAAwB,SAAS;AAC9C,CAAC;;;;;AAOD,SAAgB,gBAAgB,OAAwB;CACtD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAC1E,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,IAAI,OAAO,QAAQ,KAAK,CAAC,CAC7B,QAAQ,GAAG,OAAO,MAAM,MAAS,CAAC,CAClC,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAC7D,KAAK,CAAC,CACN,KAAK,GAAG,EAAE;CAEf,OAAO,KAAK,UAAU,KAAK,KAAK;AAClC;;;;;;;;AASA,SAAgB,mBAAmB,UAA4C;CAC7E,MAAM,EAAE,WAAW,YAAY,GAAG,YAAY;CAC9C,OAAO,gBAAgB,OAAO;AAChC;;;;;;;;AA6BA,SAAgB,uBAAuB,OAAuD;CAC5F,MAAM,EAAE,SAAS,UAAU,GAAG,iBAAiB;CAC/C,MAAM,cAAUC,wBAAW,QAAQ,CAAC,CACjC,OAAO,gBAAgB,YAAY,CAAC,CAAC,CACrC,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EAAE;CACd,OAAO,+BAA+B,MAAM;EAAE,GAAG;EAAO;CAAQ,CAAC;AACnE;;;;;;;;AASA,SAAgB,eAAe,OAAuC;CAEpE,OAAO,gBADIA,wBAAW,QAAQ,CAAC,CAAC,OAAO,gBAAgB,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAClE;AACtB"}
@@ -2,6 +2,16 @@ import { z } from "zod";
2
2
  //#region src/version-manifest.d.ts
3
3
  /** Slot-relative directory holding one deploy's immutable file copies (#826). */
4
4
  declare const VERSION_DIR_PATTERN: RegExp;
5
+ /** The attribution block stored in the manifest's `signature` field. The
6
+ * verifying key rides along so a verifier can distinguish "wrong signature"
7
+ * from "signed by a key the config never registered" — both are
8
+ * `SignatureInvalid`, but the reasons name different compromises. */
9
+ declare const ManifestSignatureSchema: z.ZodObject<{
10
+ algorithm: z.ZodLiteral<"ed25519">;
11
+ publicKey: z.ZodString;
12
+ value: z.ZodString;
13
+ }, z.core.$strip>;
14
+ type ManifestSignature = z.infer<typeof ManifestSignatureSchema>;
5
15
  declare const WorkspaceVersionManifestSchema: z.ZodObject<{
6
16
  version: z.ZodString;
7
17
  builtAt: z.ZodOptional<z.ZodString>;
@@ -24,8 +34,26 @@ declare const WorkspaceVersionManifestSchema: z.ZodObject<{
24
34
  file: z.ZodString;
25
35
  integrity: z.ZodString;
26
36
  }, z.core.$strip>>;
37
+ signature: z.ZodOptional<z.ZodObject<{
38
+ algorithm: z.ZodLiteral<"ed25519">;
39
+ publicKey: z.ZodString;
40
+ value: z.ZodString;
41
+ }, z.core.$strip>>;
27
42
  }, z.core.$strip>;
28
43
  type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;
44
+ /** Canonical key-sorted JSON serialization — the same form the version-id
45
+ * derivation hashes (undefined-valued entries dropped). Exported so the
46
+ * signature payload and the version id can never disagree about what
47
+ * "canonical" means. */
48
+ declare function stableStringify(value: unknown): string;
49
+ /**
50
+ * The bytes a manifest signature attests: the document minus its reserved
51
+ * `signature` field, canonically serialized (key-sorted — the version-id
52
+ * rule). The signer signs this before the field exists; the verifier
53
+ * re-derives it from the parsed document, so the stored document's
54
+ * whitespace never matters.
55
+ */
56
+ declare function signaturePayloadOf(manifest: WorkspaceVersionManifest): string;
29
57
  /** A published-artifact coordinate: object path within the bundle namespace + its served SRI. */
30
58
  type SourceRef = NonNullable<WorkspaceVersionManifest["source"]>;
31
59
  interface VersionManifestInput {
@@ -70,5 +98,5 @@ declare function composeVersionManifest(input: VersionManifestInput): WorkspaceV
70
98
  */
71
99
  declare function versionDirPath(files: Record<string, string>): string;
72
100
  //#endregion
73
- export { SourceRef, VERSION_DIR_PATTERN, VersionManifestInput, WorkspaceVersionManifest, WorkspaceVersionManifestSchema, composeVersionManifest, versionDirPath };
101
+ export { ManifestSignature, ManifestSignatureSchema, SourceRef, VERSION_DIR_PATTERN, VersionManifestInput, WorkspaceVersionManifest, WorkspaceVersionManifestSchema, composeVersionManifest, signaturePayloadOf, stableStringify, versionDirPath };
74
102
  //# sourceMappingURL=version-manifest.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-manifest.d.cts","names":[],"sources":["../src/version-manifest.ts"],"mappings":";;;cAea,qBAAmB;cAEnB,gCAA8B,EAAA;;;;;;;;;;;;;;;;;;;;;;GAsDzC,EAAA,KAAA;KACU,2BAA2B,EAAE,aAAa;;KAG1C,YAAY,YAAY;UAEnB;;;;EAIf;EACA;EACA;EACA;EACA;EACA;IAAQ;IAAe;;EACvB;IAAoB;IAAc;;EAClC,SAAS;EACT,SAAS;EACT,QAAQ;EACR;IAAW;IAAc;;;;;;;;;;iBAUX,uBAAuB,OAAO,uBAAuB;;;;;;;;iBAgBrD,eAAe,OAAO"}
1
+ {"version":3,"file":"version-manifest.d.cts","names":[],"sources":["../src/version-manifest.ts"],"mappings":";;;cAea,qBAAmB;;;;;cAMnB,yBAAuB,EAAA;;;;GAOlC,EAAA,KAAA;KACU,oBAAoB,EAAE,aAAa;cAElC,gCAA8B,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEzC,EAAA,KAAA;KACU,2BAA2B,EAAE,aAAa;;;;;iBAMtC,gBAAgB;;;;;;;;iBAmBhB,mBAAmB,UAAU;;KAMjC,YAAY,YAAY;UAEnB;;;;EAIf;EACA;EACA;EACA;EACA;EACA;IAAQ;IAAe;;EACvB;IAAoB;IAAc;;EAClC,SAAS;EACT,SAAS;EACT,QAAQ;EACR;IAAW;IAAc;;;;;;;;;;iBAUX,uBAAuB,OAAO,uBAAuB;;;;;;;;iBAgBrD,eAAe,OAAO"}
@@ -2,6 +2,16 @@ import { z } from "zod";
2
2
  //#region src/version-manifest.d.ts
3
3
  /** Slot-relative directory holding one deploy's immutable file copies (#826). */
4
4
  declare const VERSION_DIR_PATTERN: RegExp;
5
+ /** The attribution block stored in the manifest's `signature` field. The
6
+ * verifying key rides along so a verifier can distinguish "wrong signature"
7
+ * from "signed by a key the config never registered" — both are
8
+ * `SignatureInvalid`, but the reasons name different compromises. */
9
+ declare const ManifestSignatureSchema: z.ZodObject<{
10
+ algorithm: z.ZodLiteral<"ed25519">;
11
+ publicKey: z.ZodString;
12
+ value: z.ZodString;
13
+ }, z.core.$strip>;
14
+ type ManifestSignature = z.infer<typeof ManifestSignatureSchema>;
5
15
  declare const WorkspaceVersionManifestSchema: z.ZodObject<{
6
16
  version: z.ZodString;
7
17
  builtAt: z.ZodOptional<z.ZodString>;
@@ -24,8 +34,26 @@ declare const WorkspaceVersionManifestSchema: z.ZodObject<{
24
34
  file: z.ZodString;
25
35
  integrity: z.ZodString;
26
36
  }, z.core.$strip>>;
37
+ signature: z.ZodOptional<z.ZodObject<{
38
+ algorithm: z.ZodLiteral<"ed25519">;
39
+ publicKey: z.ZodString;
40
+ value: z.ZodString;
41
+ }, z.core.$strip>>;
27
42
  }, z.core.$strip>;
28
43
  type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;
44
+ /** Canonical key-sorted JSON serialization — the same form the version-id
45
+ * derivation hashes (undefined-valued entries dropped). Exported so the
46
+ * signature payload and the version id can never disagree about what
47
+ * "canonical" means. */
48
+ declare function stableStringify(value: unknown): string;
49
+ /**
50
+ * The bytes a manifest signature attests: the document minus its reserved
51
+ * `signature` field, canonically serialized (key-sorted — the version-id
52
+ * rule). The signer signs this before the field exists; the verifier
53
+ * re-derives it from the parsed document, so the stored document's
54
+ * whitespace never matters.
55
+ */
56
+ declare function signaturePayloadOf(manifest: WorkspaceVersionManifest): string;
29
57
  /** A published-artifact coordinate: object path within the bundle namespace + its served SRI. */
30
58
  type SourceRef = NonNullable<WorkspaceVersionManifest["source"]>;
31
59
  interface VersionManifestInput {
@@ -70,5 +98,5 @@ declare function composeVersionManifest(input: VersionManifestInput): WorkspaceV
70
98
  */
71
99
  declare function versionDirPath(files: Record<string, string>): string;
72
100
  //#endregion
73
- export { SourceRef, VERSION_DIR_PATTERN, VersionManifestInput, WorkspaceVersionManifest, WorkspaceVersionManifestSchema, composeVersionManifest, versionDirPath };
101
+ export { ManifestSignature, ManifestSignatureSchema, SourceRef, VERSION_DIR_PATTERN, VersionManifestInput, WorkspaceVersionManifest, WorkspaceVersionManifestSchema, composeVersionManifest, signaturePayloadOf, stableStringify, versionDirPath };
74
102
  //# sourceMappingURL=version-manifest.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-manifest.d.mts","names":[],"sources":["../src/version-manifest.ts"],"mappings":";;;cAea,qBAAmB;cAEnB,gCAA8B,EAAA;;;;;;;;;;;;;;;;;;;;;;GAsDzC,EAAA,KAAA;KACU,2BAA2B,EAAE,aAAa;;KAG1C,YAAY,YAAY;UAEnB;;;;EAIf;EACA;EACA;EACA;EACA;EACA;IAAQ;IAAe;;EACvB;IAAoB;IAAc;;EAClC,SAAS;EACT,SAAS;EACT,QAAQ;EACR;IAAW;IAAc;;;;;;;;;;iBAUX,uBAAuB,OAAO,uBAAuB;;;;;;;;iBAgBrD,eAAe,OAAO"}
1
+ {"version":3,"file":"version-manifest.d.mts","names":[],"sources":["../src/version-manifest.ts"],"mappings":";;;cAea,qBAAmB;;;;;cAMnB,yBAAuB,EAAA;;;;GAOlC,EAAA,KAAA;KACU,oBAAoB,EAAE,aAAa;cAElC,gCAA8B,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEzC,EAAA,KAAA;KACU,2BAA2B,EAAE,aAAa;;;;;iBAMtC,gBAAgB;;;;;;;;iBAmBhB,mBAAmB,UAAU;;KAMjC,YAAY,YAAY;UAEnB;;;;EAIf;EACA;EACA;EACA;EACA;EACA;IAAQ;IAAe;;EACvB;IAAoB;IAAc;;EAClC,SAAS;EACT,SAAS;EACT,QAAQ;EACR;IAAW;IAAc;;;;;;;;;;iBAUX,uBAAuB,OAAO,uBAAuB;;;;;;;;iBAgBrD,eAAe,OAAO"}
@@ -13,6 +13,18 @@ import { createHash } from "node:crypto";
13
13
  const SRI = z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);
14
14
  /** Slot-relative directory holding one deploy's immutable file copies (#826). */
15
15
  const VERSION_DIR_PATTERN = /^versions\/[0-9a-f]{16}$/;
16
+ /** The attribution block stored in the manifest's `signature` field. The
17
+ * verifying key rides along so a verifier can distinguish "wrong signature"
18
+ * from "signed by a key the config never registered" — both are
19
+ * `SignatureInvalid`, but the reasons name different compromises. */
20
+ const ManifestSignatureSchema = z.object({
21
+ algorithm: z.literal("ed25519"),
22
+ /** NEAR-format verifying key (`ed25519:<base58>`) — must equal the
23
+ * config's `publish.verification.publicKey`. */
24
+ publicKey: z.string().min(1),
25
+ /** base64 ed25519 signature over the canonical payload bytes. */
26
+ value: z.string().min(1)
27
+ });
16
28
  const WorkspaceVersionManifestSchema = z.object({
17
29
  version: z.string().regex(/^[0-9a-f]{16}$/),
18
30
  builtAt: z.string().optional(),
@@ -60,8 +72,38 @@ const WorkspaceVersionManifestSchema = z.object({
60
72
  source: z.object({
61
73
  file: z.string().min(1),
62
74
  integrity: SRI
63
- }).optional()
75
+ }).optional(),
76
+ /**
77
+ * Attribution (ADR 0046 ticket 05): the deploy train's ed25519 signature
78
+ * over `signaturePayloadOf(this document)` — SRI attests WHAT the bytes
79
+ * are, the signature attests WHO published them, with the verifying key
80
+ * anchored in the on-chain config (`publish.verification`). Absent on
81
+ * rollouts with attribution off; readers that predate the field ignore it
82
+ * (zod strips unknown keys). Excluded from the version-id derivation —
83
+ * like `builtAt`, it is not a coordinate of the build.
84
+ */
85
+ signature: ManifestSignatureSchema.optional()
64
86
  });
87
+ /** Canonical key-sorted JSON serialization — the same form the version-id
88
+ * derivation hashes (undefined-valued entries dropped). Exported so the
89
+ * signature payload and the version id can never disagree about what
90
+ * "canonical" means. */
91
+ function stableStringify(value) {
92
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
93
+ if (value && typeof value === "object") return `{${Object.entries(value).filter(([, v]) => v !== void 0).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).sort().join(",")}}`;
94
+ return JSON.stringify(value) ?? "null";
95
+ }
96
+ /**
97
+ * The bytes a manifest signature attests: the document minus its reserved
98
+ * `signature` field, canonically serialized (key-sorted — the version-id
99
+ * rule). The signer signs this before the field exists; the verifier
100
+ * re-derives it from the parsed document, so the stored document's
101
+ * whitespace never matters.
102
+ */
103
+ function signaturePayloadOf(manifest) {
104
+ const { signature: _signature, ...payload } = manifest;
105
+ return stableStringify(payload);
106
+ }
65
107
  /**
66
108
  * Compose the manifest with its `version` derived from the canonical content
67
109
  * of the artifact coordinates alone (key-sorted, `builtAt` excluded) —
@@ -87,12 +129,7 @@ function composeVersionManifest(input) {
87
129
  function versionDirPath(files) {
88
130
  return `versions/${createHash("sha256").update(stableStringify(files)).digest("hex").slice(0, 16)}`;
89
131
  }
90
- function stableStringify(value) {
91
- if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
92
- if (value && typeof value === "object") return `{${Object.entries(value).filter(([, v]) => v !== void 0).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).sort().join(",")}}`;
93
- return JSON.stringify(value) ?? "null";
94
- }
95
132
 
96
133
  //#endregion
97
- export { VERSION_DIR_PATTERN, WorkspaceVersionManifestSchema, composeVersionManifest, versionDirPath };
134
+ export { ManifestSignatureSchema, VERSION_DIR_PATTERN, WorkspaceVersionManifestSchema, composeVersionManifest, signaturePayloadOf, stableStringify, versionDirPath };
98
135
  //# sourceMappingURL=version-manifest.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-manifest.mjs","names":[],"sources":["../src/version-manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n/**\n * WorkspaceVersionManifest — the immutable per-deploy document at\n * `bundles/<account>/<gateway>/<workspace>/versions/<version>.json`\n * (ADR 0020, as amended; atomic-deploys MAP decision 1). The published\n * config's slot pins this document via its `pin` (`manifest` + the\n * manifest's own SRI); every artifact coordinate lives here, so the config\n * stops growing with artifact kinds and old versions stay fully servable.\n */\n\nconst SRI = z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);\n\n/** Slot-relative directory holding one deploy's immutable file copies (#826). */\nexport const VERSION_DIR_PATTERN = /^versions\\/[0-9a-f]{16}$/;\n\nexport const WorkspaceVersionManifestSchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n builtAt: z.string().optional(),\n gitSha: z.string().optional(),\n /**\n * Slot-relative directory (`versions/<dirId>`) holding this deploy's\n * byte-immutable copies of every dist file (#826). When present, all\n * artifact coordinates below resolve against `<slotBase>/<dir>/` — so a\n * pinned child's entry fetches its expose chunk and chunks from the same\n * frozen directory instead of the mutable slot root. Absent on legacy\n * manifests: coordinates resolve against the slot root exactly as before.\n * Derived from the `files` map (see `versionDirPath`), so it participates\n * in the version hash without circularity.\n */\n dir: z.string().regex(VERSION_DIR_PATTERN).optional(),\n entry: z.string().min(1),\n entryIntegrity: SRI,\n ssr: z\n .object({\n entry: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n browserManifest: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n /** extra assets (css, icons) — logical name → served-object SRI */\n assets: z.record(z.string(), SRI).optional(),\n /** shared-dependency versions this build was built against */\n shared: z.record(z.string(), z.string()).optional(),\n /**\n * Full per-file digest map of the dist (object path → served-object SRI).\n * Written by `every-plugin`-based deploys so the next deploy can diff against\n * it and skip re-uploading unchanged files; readers that predate the field\n * ignore it (zod strips unknown keys). Participates in the version hash.\n */\n files: z.record(z.string(), SRI).optional(),\n /**\n * Source archive of the workspace subtree (object path → served-object SRI)\n * published beside the dists when the runtime opts in via\n * `publish.sourcePublish` — lets `bos import` dereference a published\n * config slot to the exact source bytes that build came from, with no\n * GitHub dependency. Readers that predate the field ignore it (zod strips\n * unknown keys). Participates in the version hash.\n */\n source: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n});\nexport type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;\n\n/** A published-artifact coordinate: object path within the bundle namespace + its served SRI. */\nexport type SourceRef = NonNullable<WorkspaceVersionManifest[\"source\"]>;\n\nexport interface VersionManifestInput {\n /** Omit: a per-build timestamp makes the same version id map to different\n * manifest bytes per machine, breaking the pin's SRI against the image's\n * staged copy and the CDN's immutability for republished versions. */\n builtAt?: string;\n gitSha?: string;\n dir?: string;\n entry: string;\n entryIntegrity: string;\n ssr?: { entry: string; integrity: string };\n browserManifest?: { file: string; integrity: string };\n assets?: Record<string, string>;\n shared?: Record<string, string>;\n files?: Record<string, string>;\n source?: { file: string; integrity: string };\n}\n\n/**\n * Compose the manifest with its `version` derived from the canonical content\n * of the artifact coordinates alone (key-sorted, `builtAt` excluded) —\n * identical bytes → identical version id regardless of when they were built,\n * so republishing unchanged content is a pointer no-op. Any changed\n * coordinate → a new version (and a new file).\n */\nexport function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest {\n const { builtAt: _builtAt, ...hashedInputs } = input;\n const version = createHash(\"sha256\")\n .update(stableStringify(hashedInputs))\n .digest(\"hex\")\n .slice(0, 16);\n return WorkspaceVersionManifestSchema.parse({ ...input, version });\n}\n\n/**\n * The content-addressed directory name for one dist's file set (#826): a\n * 16-hex hash over the canonical files map. Identical bytes → identical\n * directory → re-deploying unchanged content is a server-side no-op, and a\n * published pin's directory can never be rewritten by a later deploy\n * (the upload service refuses conflicting writes under `versions/`).\n */\nexport function versionDirPath(files: Record<string, string>): string {\n const id = createHash(\"sha256\").update(stableStringify(files)).digest(\"hex\").slice(0, 16);\n return `versions/${id}`;\n}\n\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n .sort()\n .join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n"],"mappings":";;;;;;;;;;;;AAYA,MAAM,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,0BAA0B;;AAGvD,MAAa,sBAAsB;AAEnC,MAAa,iCAAiC,EAAE,OAAO;CACrD,SAAS,EAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB;CAC1C,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;;;;;;;;;;;CAW5B,KAAK,EAAE,OAAO,CAAC,CAAC,MAAM,mBAAmB,CAAC,CAAC,SAAS;CACpD,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,gBAAgB;CAChB,KAAK,EACF,OAAO;EACN,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACvB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;CACZ,iBAAiB,EACd,OAAO;EACN,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;;CAEZ,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;CAE3C,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS;;;;;;;CAOlD,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;;;;;;;;CAS1C,QAAQ,EACL,OAAO;EACN,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;AACd,CAAC;;;;;;;;AA8BD,SAAgB,uBAAuB,OAAuD;CAC5F,MAAM,EAAE,SAAS,UAAU,GAAG,iBAAiB;CAC/C,MAAM,UAAU,WAAW,QAAQ,CAAC,CACjC,OAAO,gBAAgB,YAAY,CAAC,CAAC,CACrC,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EAAE;CACd,OAAO,+BAA+B,MAAM;EAAE,GAAG;EAAO;CAAQ,CAAC;AACnE;;;;;;;;AASA,SAAgB,eAAe,OAAuC;CAEpE,OAAO,YADI,WAAW,QAAQ,CAAC,CAAC,OAAO,gBAAgB,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAClE;AACtB;AAEA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAC1E,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,IAAI,OAAO,QAAQ,KAAK,CAAC,CAC7B,QAAQ,GAAG,OAAO,MAAM,MAAS,CAAC,CAClC,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAC7D,KAAK,CAAC,CACN,KAAK,GAAG,EAAE;CAEf,OAAO,KAAK,UAAU,KAAK,KAAK;AAClC"}
1
+ {"version":3,"file":"version-manifest.mjs","names":[],"sources":["../src/version-manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n/**\n * WorkspaceVersionManifest — the immutable per-deploy document at\n * `bundles/<account>/<gateway>/<workspace>/versions/<version>.json`\n * (ADR 0020, as amended; atomic-deploys MAP decision 1). The published\n * config's slot pins this document via its `pin` (`manifest` + the\n * manifest's own SRI); every artifact coordinate lives here, so the config\n * stops growing with artifact kinds and old versions stay fully servable.\n */\n\nconst SRI = z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);\n\n/** Slot-relative directory holding one deploy's immutable file copies (#826). */\nexport const VERSION_DIR_PATTERN = /^versions\\/[0-9a-f]{16}$/;\n\n/** The attribution block stored in the manifest's `signature` field. The\n * verifying key rides along so a verifier can distinguish \"wrong signature\"\n * from \"signed by a key the config never registered\" — both are\n * `SignatureInvalid`, but the reasons name different compromises. */\nexport const ManifestSignatureSchema = z.object({\n algorithm: z.literal(\"ed25519\"),\n /** NEAR-format verifying key (`ed25519:<base58>`) — must equal the\n * config's `publish.verification.publicKey`. */\n publicKey: z.string().min(1),\n /** base64 ed25519 signature over the canonical payload bytes. */\n value: z.string().min(1),\n});\nexport type ManifestSignature = z.infer<typeof ManifestSignatureSchema>;\n\nexport const WorkspaceVersionManifestSchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n builtAt: z.string().optional(),\n gitSha: z.string().optional(),\n /**\n * Slot-relative directory (`versions/<dirId>`) holding this deploy's\n * byte-immutable copies of every dist file (#826). When present, all\n * artifact coordinates below resolve against `<slotBase>/<dir>/` — so a\n * pinned child's entry fetches its expose chunk and chunks from the same\n * frozen directory instead of the mutable slot root. Absent on legacy\n * manifests: coordinates resolve against the slot root exactly as before.\n * Derived from the `files` map (see `versionDirPath`), so it participates\n * in the version hash without circularity.\n */\n dir: z.string().regex(VERSION_DIR_PATTERN).optional(),\n entry: z.string().min(1),\n entryIntegrity: SRI,\n ssr: z\n .object({\n entry: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n browserManifest: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n /** extra assets (css, icons) — logical name → served-object SRI */\n assets: z.record(z.string(), SRI).optional(),\n /** shared-dependency versions this build was built against */\n shared: z.record(z.string(), z.string()).optional(),\n /**\n * Full per-file digest map of the dist (object path → served-object SRI).\n * Written by `every-plugin`-based deploys so the next deploy can diff against\n * it and skip re-uploading unchanged files; readers that predate the field\n * ignore it (zod strips unknown keys). Participates in the version hash.\n */\n files: z.record(z.string(), SRI).optional(),\n /**\n * Source archive of the workspace subtree (object path → served-object SRI)\n * published beside the dists when the runtime opts in via\n * `publish.sourcePublish` — lets `bos import` dereference a published\n * config slot to the exact source bytes that build came from, with no\n * GitHub dependency. Readers that predate the field ignore it (zod strips\n * unknown keys). Participates in the version hash.\n */\n source: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n /**\n * Attribution (ADR 0046 ticket 05): the deploy train's ed25519 signature\n * over `signaturePayloadOf(this document)` — SRI attests WHAT the bytes\n * are, the signature attests WHO published them, with the verifying key\n * anchored in the on-chain config (`publish.verification`). Absent on\n * rollouts with attribution off; readers that predate the field ignore it\n * (zod strips unknown keys). Excluded from the version-id derivation —\n * like `builtAt`, it is not a coordinate of the build.\n */\n signature: ManifestSignatureSchema.optional(),\n});\nexport type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;\n\n/** Canonical key-sorted JSON serialization — the same form the version-id\n * derivation hashes (undefined-valued entries dropped). Exported so the\n * signature payload and the version id can never disagree about what\n * \"canonical\" means. */\nexport function stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n .sort()\n .join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n\n/**\n * The bytes a manifest signature attests: the document minus its reserved\n * `signature` field, canonically serialized (key-sorted — the version-id\n * rule). The signer signs this before the field exists; the verifier\n * re-derives it from the parsed document, so the stored document's\n * whitespace never matters.\n */\nexport function signaturePayloadOf(manifest: WorkspaceVersionManifest): string {\n const { signature: _signature, ...payload } = manifest;\n return stableStringify(payload);\n}\n\n/** A published-artifact coordinate: object path within the bundle namespace + its served SRI. */\nexport type SourceRef = NonNullable<WorkspaceVersionManifest[\"source\"]>;\n\nexport interface VersionManifestInput {\n /** Omit: a per-build timestamp makes the same version id map to different\n * manifest bytes per machine, breaking the pin's SRI against the image's\n * staged copy and the CDN's immutability for republished versions. */\n builtAt?: string;\n gitSha?: string;\n dir?: string;\n entry: string;\n entryIntegrity: string;\n ssr?: { entry: string; integrity: string };\n browserManifest?: { file: string; integrity: string };\n assets?: Record<string, string>;\n shared?: Record<string, string>;\n files?: Record<string, string>;\n source?: { file: string; integrity: string };\n}\n\n/**\n * Compose the manifest with its `version` derived from the canonical content\n * of the artifact coordinates alone (key-sorted, `builtAt` excluded) —\n * identical bytes → identical version id regardless of when they were built,\n * so republishing unchanged content is a pointer no-op. Any changed\n * coordinate → a new version (and a new file).\n */\nexport function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest {\n const { builtAt: _builtAt, ...hashedInputs } = input;\n const version = createHash(\"sha256\")\n .update(stableStringify(hashedInputs))\n .digest(\"hex\")\n .slice(0, 16);\n return WorkspaceVersionManifestSchema.parse({ ...input, version });\n}\n\n/**\n * The content-addressed directory name for one dist's file set (#826): a\n * 16-hex hash over the canonical files map. Identical bytes → identical\n * directory → re-deploying unchanged content is a server-side no-op, and a\n * published pin's directory can never be rewritten by a later deploy\n * (the upload service refuses conflicting writes under `versions/`).\n */\nexport function versionDirPath(files: Record<string, string>): string {\n const id = createHash(\"sha256\").update(stableStringify(files)).digest(\"hex\").slice(0, 16);\n return `versions/${id}`;\n}\n"],"mappings":";;;;;;;;;;;;AAYA,MAAM,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,0BAA0B;;AAGvD,MAAa,sBAAsB;;;;;AAMnC,MAAa,0BAA0B,EAAE,OAAO;CAC9C,WAAW,EAAE,QAAQ,SAAS;;;CAG9B,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;;CAE3B,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;AACzB,CAAC;AAGD,MAAa,iCAAiC,EAAE,OAAO;CACrD,SAAS,EAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB;CAC1C,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;;;;;;;;;;;CAW5B,KAAK,EAAE,OAAO,CAAC,CAAC,MAAM,mBAAmB,CAAC,CAAC,SAAS;CACpD,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,gBAAgB;CAChB,KAAK,EACF,OAAO;EACN,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACvB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;CACZ,iBAAiB,EACd,OAAO;EACN,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;;CAEZ,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;CAE3C,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS;;;;;;;CAOlD,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;;;;;;;;CAS1C,QAAQ,EACL,OAAO;EACN,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;;;;;;;;;;CAUZ,WAAW,wBAAwB,SAAS;AAC9C,CAAC;;;;;AAOD,SAAgB,gBAAgB,OAAwB;CACtD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAC1E,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,IAAI,OAAO,QAAQ,KAAK,CAAC,CAC7B,QAAQ,GAAG,OAAO,MAAM,MAAS,CAAC,CAClC,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAC7D,KAAK,CAAC,CACN,KAAK,GAAG,EAAE;CAEf,OAAO,KAAK,UAAU,KAAK,KAAK;AAClC;;;;;;;;AASA,SAAgB,mBAAmB,UAA4C;CAC7E,MAAM,EAAE,WAAW,YAAY,GAAG,YAAY;CAC9C,OAAO,gBAAgB,OAAO;AAChC;;;;;;;;AA6BA,SAAgB,uBAAuB,OAAuD;CAC5F,MAAM,EAAE,SAAS,UAAU,GAAG,iBAAiB;CAC/C,MAAM,UAAU,WAAW,QAAQ,CAAC,CACjC,OAAO,gBAAgB,YAAY,CAAC,CAAC,CACrC,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EAAE;CACd,OAAO,+BAA+B,MAAM;EAAE,GAAG;EAAO;CAAQ,CAAC;AACnE;;;;;;;;AASA,SAAgB,eAAe,OAAuC;CAEpE,OAAO,YADI,WAAW,QAAQ,CAAC,CAAC,OAAO,gBAAgB,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAClE;AACtB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "every-plugin",
3
- "version": "3.0.0-rc.16",
3
+ "version": "3.0.0-rc.17",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -470,6 +470,6 @@ See the `extends-config` skill for deep merge semantics, per-environment extends
470
470
  | 401 `UNAUTHORIZED` "API key required" with `data: { authType: "apiKey", hint: "Provide a valid API key via x-api-key header" }` | the route is `requireApiKey(...)`-protected and your key was absent — usually `Authorization: Bearer` was used instead | switch to the `x-api-key` header |
471
471
  | 403 `FORBIDDEN` "API key lacks permission: <resource>:<action>" with `data: { requiredPermissions, keyPermissions }` | the key's `permissions` map does not cover every required resource/action — partial matches are rejected | re-create (server-side) or update the key with the needed permissions |
472
472
  | 403 `FORBIDDEN` "Requires role: ..." / "Requires organization role: ..." | session role gate | sign in with an account holding the role; a key cannot satisfy these |
473
- | 403 `FORBIDDEN` "Organization requires platform-admin approval" | the org's `status` is not `"active"` | stop — needs platform-admin approval, no client-side fix |
473
+ | 403 `FORBIDDEN` "Organization requires platform-admin approval" | a downstream app still runs its own approval middleware (this platform ships none) | app-level policy — surface the org's approval state to the user |
474
474
  | `authClient.apiKey.list` / `update` return only `prefix` and metadata | expected — the full secret is returned once at `create` | create a new key; the old secret is unrecoverable |
475
475
  | Browser request blocked by CORS despite a valid key | session cookies not sent (`credentials: "include"` missing) or origin not allowed by `CORS_ORIGIN` | add `credentials: "include"` to the fetch wrapper, or fix `CORS_ORIGIN` |
package/src/client.ts CHANGED
@@ -1,117 +1,20 @@
1
- import { createORPCClient, onError } from "@orpc/client";
1
+ import { createORPCClient } from "@orpc/client";
2
2
  import { RPCLink } from "@orpc/client/fetch";
3
3
  import type { ContractRouterClient, RouterContract } from "@orpc/contract";
4
4
 
5
- export type RpcConnectionErrorCopy = () => { title: string; description: string };
6
-
7
- export interface RpcClientConfig {
8
- hostUrl: string;
9
- path: `/${string}`;
10
- connectionError?: RpcConnectionErrorCopy;
11
- }
12
-
13
- const RATE_LIMIT_MAX_RETRIES = 3;
14
- const RATE_LIMIT_RETRY_CAP_MS = 2_000;
15
- const browserClients = new Map<string, unknown>();
16
- type FetchInput = Parameters<typeof fetch>[0];
17
-
18
- async function fetchWithRateLimitRetry(
19
- fetchUrl: FetchInput,
20
- options?: RequestInit,
21
- ): Promise<Response> {
22
- let response = await fetch(fetchUrl, options);
23
- for (let attempt = 1; attempt <= RATE_LIMIT_MAX_RETRIES && response.status === 429; attempt++) {
24
- const retryAfterSeconds = Number(response.headers.get("retry-after"));
25
- const delay =
26
- Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0
27
- ? Math.min(retryAfterSeconds * 1000, RATE_LIMIT_RETRY_CAP_MS)
28
- : 100 * attempt;
29
- await new Promise((resolve) => setTimeout(resolve, delay));
30
- response = await fetch(fetchUrl, options);
31
- }
32
-
33
- return response;
34
- }
35
-
36
- function mergeHeaders(base: Headers, overrides?: RequestInit["headers"]): Headers {
37
- const merged = new Headers(base);
38
- new Headers(overrides).forEach((value, key) => {
39
- merged.set(key, value);
40
- });
41
- return merged;
42
- }
43
-
44
- function createRpcLink(config: RpcClientConfig, headers?: Headers) {
45
- return new RPCLink({
46
- origin: config.hostUrl,
47
- url: config.path,
48
- interceptors: [
49
- onError((error: unknown) => {
50
- const browserWindow = (globalThis as { window?: unknown }).window;
51
- if (typeof browserWindow === "undefined") {
52
- return;
53
- }
54
-
55
- if (error && typeof error === "object" && "message" in error) {
56
- const message = String(error.message).toLowerCase();
57
- if (
58
- message.includes("fetch") ||
59
- message.includes("network") ||
60
- message.includes("failed to fetch")
61
- ) {
62
- void import("sonner")
63
- .then(({ toast }) => {
64
- const message = config.connectionError?.() ?? {
65
- title: "Unable to connect to API",
66
- description: "The API is currently unavailable. Please try again later.",
67
- };
68
- toast.error(message.title, {
69
- id: "api-connection-error",
70
- description: message.description,
71
- });
72
- })
73
- .catch(() => {});
74
- }
75
- }
76
- }),
77
- ],
78
- fetch(fetchUrl: FetchInput, options?: RequestInit) {
79
- return fetchWithRateLimitRetry(fetchUrl, {
80
- ...options,
81
- credentials: "include",
82
- headers: headers ? mergeHeaders(headers, options?.headers) : options?.headers,
83
- });
84
- },
85
- });
86
- }
87
-
88
- function buildRpcClient<T extends RouterContract>(
89
- config: RpcClientConfig,
90
- headers?: Headers,
91
- ): ContractRouterClient<T> {
92
- if (!config.hostUrl) {
93
- throw new Error("Missing runtime host URL");
94
- }
95
-
96
- return createORPCClient(createRpcLink(config, headers)) as ContractRouterClient<T>;
97
- }
98
-
5
+ /**
6
+ * Bare typed oRPC client over the RPC protocol — a thin convenience over
7
+ * `createORPCClient(new RPCLink({ origin, url }))`.
8
+ *
9
+ * Promise-native, so async/await callers call it directly and Effect callers
10
+ * lift calls with `Effect.tryPromise` (owning timeout/retry/cache in their
11
+ * service layer). Transport customization (headers, timeouts, retries, test
12
+ * doubles) is RPCLink's native surface — construct the link yourself and pass
13
+ * it to `createORPCClient` instead of growing config here.
14
+ */
99
15
  export function createRpcClient<T extends RouterContract = RouterContract>(
100
- config: RpcClientConfig,
101
- headers?: Headers,
16
+ hostUrl: string,
17
+ path: `/${string}` = "/api/rpc",
102
18
  ): ContractRouterClient<T> {
103
- const browserWindow = (globalThis as { window?: unknown }).window;
104
- if (typeof browserWindow !== "undefined" && !headers) {
105
- const key = `${config.hostUrl}${config.path}`;
106
- const memoized = browserClients.get(key);
107
- if (memoized) {
108
- return memoized as ContractRouterClient<T>;
109
- }
110
-
111
- const client = buildRpcClient<T>(config, headers);
112
- browserClients.set(key, client);
113
- return client;
114
- }
115
-
116
- return buildRpcClient<T>(config, headers);
19
+ return createORPCClient(new RPCLink({ origin: hostUrl, url: path })) as ContractRouterClient<T>;
117
20
  }
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ export { getNormalizedRemoteName } from "./runtime/services/normalize";
12
12
  export type {
13
13
  AnyPlugin,
14
14
  EveryPlugin,
15
+ InferRegistryFromEntries,
15
16
  InitializedPlugin,
16
17
  LoadedPlugin,
17
18
  ModuleClientEntry,
@@ -27,6 +28,7 @@ export type {
27
28
  PluginInstance,
28
29
  PluginMetadata,
29
30
  PluginRegistry,
31
+ PluginRegistryEntry,
30
32
  PluginRouterType,
31
33
  PluginRuntimeConfig,
32
34
  PluginSecrets,
@@ -15,6 +15,20 @@ const SRI = z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);
15
15
  /** Slot-relative directory holding one deploy's immutable file copies (#826). */
16
16
  export const VERSION_DIR_PATTERN = /^versions\/[0-9a-f]{16}$/;
17
17
 
18
+ /** The attribution block stored in the manifest's `signature` field. The
19
+ * verifying key rides along so a verifier can distinguish "wrong signature"
20
+ * from "signed by a key the config never registered" — both are
21
+ * `SignatureInvalid`, but the reasons name different compromises. */
22
+ export const ManifestSignatureSchema = z.object({
23
+ algorithm: z.literal("ed25519"),
24
+ /** NEAR-format verifying key (`ed25519:<base58>`) — must equal the
25
+ * config's `publish.verification.publicKey`. */
26
+ publicKey: z.string().min(1),
27
+ /** base64 ed25519 signature over the canonical payload bytes. */
28
+ value: z.string().min(1),
29
+ });
30
+ export type ManifestSignature = z.infer<typeof ManifestSignatureSchema>;
31
+
18
32
  export const WorkspaceVersionManifestSchema = z.object({
19
33
  version: z.string().regex(/^[0-9a-f]{16}$/),
20
34
  builtAt: z.string().optional(),
@@ -69,9 +83,47 @@ export const WorkspaceVersionManifestSchema = z.object({
69
83
  integrity: SRI,
70
84
  })
71
85
  .optional(),
86
+ /**
87
+ * Attribution (ADR 0046 ticket 05): the deploy train's ed25519 signature
88
+ * over `signaturePayloadOf(this document)` — SRI attests WHAT the bytes
89
+ * are, the signature attests WHO published them, with the verifying key
90
+ * anchored in the on-chain config (`publish.verification`). Absent on
91
+ * rollouts with attribution off; readers that predate the field ignore it
92
+ * (zod strips unknown keys). Excluded from the version-id derivation —
93
+ * like `builtAt`, it is not a coordinate of the build.
94
+ */
95
+ signature: ManifestSignatureSchema.optional(),
72
96
  });
73
97
  export type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;
74
98
 
99
+ /** Canonical key-sorted JSON serialization — the same form the version-id
100
+ * derivation hashes (undefined-valued entries dropped). Exported so the
101
+ * signature payload and the version id can never disagree about what
102
+ * "canonical" means. */
103
+ export function stableStringify(value: unknown): string {
104
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
105
+ if (value && typeof value === "object") {
106
+ return `{${Object.entries(value)
107
+ .filter(([, v]) => v !== undefined)
108
+ .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)
109
+ .sort()
110
+ .join(",")}}`;
111
+ }
112
+ return JSON.stringify(value) ?? "null";
113
+ }
114
+
115
+ /**
116
+ * The bytes a manifest signature attests: the document minus its reserved
117
+ * `signature` field, canonically serialized (key-sorted — the version-id
118
+ * rule). The signer signs this before the field exists; the verifier
119
+ * re-derives it from the parsed document, so the stored document's
120
+ * whitespace never matters.
121
+ */
122
+ export function signaturePayloadOf(manifest: WorkspaceVersionManifest): string {
123
+ const { signature: _signature, ...payload } = manifest;
124
+ return stableStringify(payload);
125
+ }
126
+
75
127
  /** A published-artifact coordinate: object path within the bundle namespace + its served SRI. */
76
128
  export type SourceRef = NonNullable<WorkspaceVersionManifest["source"]>;
77
129
 
@@ -119,15 +171,3 @@ export function versionDirPath(files: Record<string, string>): string {
119
171
  const id = createHash("sha256").update(stableStringify(files)).digest("hex").slice(0, 16);
120
172
  return `versions/${id}`;
121
173
  }
122
-
123
- function stableStringify(value: unknown): string {
124
- if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
125
- if (value && typeof value === "object") {
126
- return `{${Object.entries(value)
127
- .filter(([, v]) => v !== undefined)
128
- .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)
129
- .sort()
130
- .join(",")}}`;
131
- }
132
- return JSON.stringify(value) ?? "null";
133
- }