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 +15 -63
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +12 -11
- package/dist/client.d.cts.map +1 -1
- package/dist/client.d.mts +12 -11
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +16 -64
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/runtime/mf-config.cjs +1 -1
- package/dist/runtime/mf-config.mjs +1 -1
- package/dist/version-manifest.cjs +46 -6
- package/dist/version-manifest.cjs.map +1 -1
- package/dist/version-manifest.d.cts +29 -1
- package/dist/version-manifest.d.cts.map +1 -1
- package/dist/version-manifest.d.mts +29 -1
- package/dist/version-manifest.d.mts.map +1 -1
- package/dist/version-manifest.mjs +44 -7
- package/dist/version-manifest.mjs.map +1 -1
- package/package.json +1 -1
- package/skills/plugin-client/SKILL.md +1 -1
- package/src/client.ts +14 -111
- package/src/index.ts +2 -0
- package/src/version-manifest.ts +52 -12
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
package/dist/client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.cjs","names":["
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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 {
|
|
15
|
+
export { createRpcClient };
|
|
15
16
|
//# sourceMappingURL=client.d.cts.map
|
package/dist/client.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.cts","names":[],"sources":["../src/client.ts"],"mappings":"
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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 {
|
|
15
|
+
export { createRpcClient };
|
|
15
16
|
//# sourceMappingURL=client.d.mts.map
|
package/dist/client.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":"
|
|
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
|
|
1
|
+
import { createORPCClient } from "@orpc/client";
|
|
2
2
|
import { RPCLink } from "@orpc/client/fetch";
|
|
3
3
|
|
|
4
4
|
//#region src/client.ts
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
package/dist/client.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import { createORPCClient
|
|
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 {
|
|
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,
|
|
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 {
|
|
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,
|
|
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.
|
|
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.
|
|
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
|
|
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;
|
|
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;
|
|
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
|
|
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
|
@@ -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" |
|
|
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
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
101
|
-
|
|
16
|
+
hostUrl: string,
|
|
17
|
+
path: `/${string}` = "/api/rpc",
|
|
102
18
|
): ContractRouterClient<T> {
|
|
103
|
-
|
|
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,
|
package/src/version-manifest.ts
CHANGED
|
@@ -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
|
-
}
|