@uns-kit/core 3.0.46 → 3.0.47

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/MIGRATIONS.md CHANGED
@@ -9,6 +9,25 @@ Agents must inspect the application's existing ownership and shutdown flow
9
9
  before editing it. The examples below describe the intended behavior, not a
10
10
  mechanical search-and-replace operation.
11
11
 
12
+ ## 3.0.47 - Common Service access and publications
13
+
14
+ When crossing `<3.0.47` to `>=3.0.47`, adoption is explicit; existing endpoint authorization and caller forwarding do
15
+ not change automatically. Ship `service-access.json` referenced by
16
+ `package.json` `unsDatahub.serviceAccess`, retaining the data file in the build.
17
+ Use `parseServiceAccessDeclaration` and review the exact installed release in
18
+ Controller Service access. Background services need no Catalog/API. Optional
19
+ `publications` reuse the catalog manifest, `CatalogPublicationClient`, and API
20
+ `catalogPublicationHandler`; call `api.registerCatalogRoute()` for signed launch
21
+ route publication. Source approval remains separate from raw workload rights.
22
+ The unpublished earlier `catalogAccess` marker is a compatibility reader only;
23
+ never declare both. Read the [bundled Service access guide](dist/runtime/service-access-guide.md).
24
+
25
+ Do not remove existing credentials until the new path passes deployed
26
+ acceptance. The matching Controller capability must be installed and reviewed
27
+ before enabling the declaration. SDK publication and consumer frozen-lock
28
+ updates remain separate steps. This bounded adapter covers UNS history; it
29
+ does not create SQL, broker or arbitrary external-resource permissions.
30
+
12
31
  ## 3.0.46 - Verified retained-store copy and recovery
13
32
 
14
33
  When crossing `<3.0.46` to `>=3.0.46`, existing rename and unbound consumers
@@ -0,0 +1,21 @@
1
+ import { type AccessTokenProvider } from "../tools/auth/service-token-provider.js";
2
+ import { type CatalogPublicationManifest } from "./catalog-publication.js";
3
+ /** No caller-token fallback and no arbitrary source URL or topic. */
4
+ export declare class CatalogPublicationClient {
5
+ private readonly options;
6
+ constructor(options: {
7
+ controllerBase: string;
8
+ rttNode: string;
9
+ version: string;
10
+ instanceId: string;
11
+ manifest: CatalogPublicationManifest;
12
+ tokenProvider?: AccessTokenProvider;
13
+ fetch?: typeof fetch;
14
+ });
15
+ private call;
16
+ execute<T>(operationId: string, input: Record<string, unknown>, callerToken: string, transform: (context: {
17
+ signal: AbortSignal;
18
+ history: (sourceId: string) => Promise<unknown>;
19
+ }) => Promise<T>): Promise<T>;
20
+ }
21
+ //# sourceMappingURL=catalog-publication-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-publication-client.d.ts","sourceRoot":"","sources":["../../src/runtime/catalog-publication-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAAwB,MAAM,yCAAyC,CAAC;AACzG,OAAO,EAIL,KAAK,0BAA0B,EAIhC,MAAM,0BAA0B,CAAC;AAElC,qEAAqE;AACrE,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE;QACxB,cAAc,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,0BAA0B,CAAC;QACrC,aAAa,CAAC,EAAE,mBAAmB,CAAC;QACpC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;KACtB;YAEW,IAAI;IAmDZ,OAAO,CAAC,CAAC,EACb,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,CAAC,OAAO,EAAE;QAAE,MAAM,EAAE,WAAW,CAAC;QAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAC3G,OAAO,CAAC,CAAC,CAAC;CA6Bd"}
@@ -0,0 +1,92 @@
1
+ import { ServiceTokenProvider } from "../tools/auth/service-token-provider.js";
2
+ import { CatalogAccessError, assertCatalogRequest, assertCatalogResponse, catalogAccessDigest, } from "./catalog-publication.js";
3
+ /** No caller-token fallback and no arbitrary source URL or topic. */
4
+ export class CatalogPublicationClient {
5
+ options;
6
+ constructor(options) {
7
+ this.options = options;
8
+ }
9
+ async call(action, operation, input, callerToken, sourceId, signal) {
10
+ assertCatalogRequest(operation, input);
11
+ const token = await (this.options.tokenProvider ?? new ServiceTokenProvider()).getAccessToken();
12
+ if (!token)
13
+ throw Error("Catalog service credential unavailable.");
14
+ const body = {
15
+ rttNode: this.options.rttNode,
16
+ version: this.options.version,
17
+ instanceId: this.options.instanceId,
18
+ operationId: operation.id,
19
+ manifestDigest: catalogAccessDigest(this.options.manifest),
20
+ input,
21
+ callerToken,
22
+ };
23
+ const response = await (this.options.fetch ?? fetch)(new URL(`/api/catalog-publications/${action}`, this.options.controllerBase), {
24
+ method: "POST",
25
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
26
+ body: JSON.stringify({ ...body, ...(sourceId ? { sourceId } : {}) }),
27
+ signal: signal ?? AbortSignal.timeout(operation.timeoutMs),
28
+ redirect: "error",
29
+ });
30
+ if (!response.ok)
31
+ throw new CatalogAccessError("catalog_access_denied", [400, 401, 403, 409, 429].includes(response.status) ? response.status : 503);
32
+ if (!response.body)
33
+ throw new CatalogAccessError("catalog_source_unavailable", 503);
34
+ const reader = response.body.getReader();
35
+ let size = 0, text = "";
36
+ const decoder = new TextDecoder();
37
+ try {
38
+ while (true) {
39
+ const r = await reader.read();
40
+ if (r.done)
41
+ break;
42
+ size += r.value.byteLength;
43
+ if (size > 1048576) {
44
+ await reader.cancel();
45
+ throw new CatalogAccessError("catalog_source_budget", 503);
46
+ }
47
+ text += decoder.decode(r.value, { stream: true });
48
+ }
49
+ text += decoder.decode();
50
+ return JSON.parse(text);
51
+ }
52
+ finally {
53
+ reader.releaseLock();
54
+ }
55
+ }
56
+ async execute(operationId, input, callerToken, transform) {
57
+ const operation = this.options.manifest.operations.find((op) => op.id === operationId);
58
+ if (!operation)
59
+ throw Error("Catalog operation not declared.");
60
+ const signal = AbortSignal.timeout(operation.timeoutMs);
61
+ await this.call("authorize", operation, input, callerToken, undefined, signal);
62
+ const work = transform({
63
+ signal,
64
+ history: async (sourceId) => {
65
+ if (!operation.sources.some((source) => source.id === sourceId))
66
+ throw Error("Catalog source not declared.");
67
+ return this.call("history", operation, input, callerToken, sourceId, signal);
68
+ },
69
+ });
70
+ let stop;
71
+ const deadline = new Promise((_resolve, reject) => {
72
+ stop = () => reject(new CatalogAccessError("catalog_execution_timeout", 504));
73
+ if (signal.aborted)
74
+ stop();
75
+ else
76
+ signal.addEventListener("abort", stop, { once: true });
77
+ });
78
+ let result;
79
+ try {
80
+ result = await Promise.race([work, deadline]);
81
+ }
82
+ finally {
83
+ if (stop)
84
+ signal.removeEventListener("abort", stop);
85
+ }
86
+ // Recheck after asynchronous processing, including a cached application result.
87
+ await this.call("authorize", operation, input, callerToken, undefined, signal);
88
+ assertCatalogResponse(operation, result);
89
+ return result;
90
+ }
91
+ }
92
+ //# sourceMappingURL=catalog-publication-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-publication-client.js","sourceRoot":"","sources":["../../src/runtime/catalog-publication-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { type AccessTokenProvider, ServiceTokenProvider } from \"../tools/auth/service-token-provider.js\";\nimport {\n CatalogAccessError,\n type CatalogInvocation,\n type CatalogOperation,\n type CatalogPublicationManifest,\n assertCatalogRequest,\n assertCatalogResponse,\n catalogAccessDigest,\n} from \"./catalog-publication.js\";\n\n/** No caller-token fallback and no arbitrary source URL or topic. */\nexport class CatalogPublicationClient {\n constructor(\n private readonly options: {\n controllerBase: string;\n rttNode: string;\n version: string;\n instanceId: string;\n manifest: CatalogPublicationManifest;\n tokenProvider?: AccessTokenProvider;\n fetch?: typeof fetch;\n },\n ) {}\n private async call(\n action: \"authorize\" | \"history\",\n operation: CatalogOperation,\n input: Record<string, unknown>,\n callerToken: string,\n sourceId?: string,\n signal?: AbortSignal,\n ): Promise<unknown> {\n assertCatalogRequest(operation, input);\n const token = await (this.options.tokenProvider ?? new ServiceTokenProvider()).getAccessToken();\n if (!token) throw Error(\"Catalog service credential unavailable.\");\n const body: CatalogInvocation = {\n rttNode: this.options.rttNode,\n version: this.options.version,\n instanceId: this.options.instanceId,\n operationId: operation.id,\n manifestDigest: catalogAccessDigest(this.options.manifest),\n input,\n callerToken,\n };\n const response = await (this.options.fetch ?? fetch)(new URL(`/api/catalog-publications/${action}`, this.options.controllerBase), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${token}` },\n body: JSON.stringify({ ...body, ...(sourceId ? { sourceId } : {}) }),\n signal: signal ?? AbortSignal.timeout(operation.timeoutMs),\n redirect: \"error\",\n });\n if (!response.ok)\n throw new CatalogAccessError(\"catalog_access_denied\", [400, 401, 403, 409, 429].includes(response.status) ? response.status : 503);\n if (!response.body) throw new CatalogAccessError(\"catalog_source_unavailable\", 503);\n const reader = response.body.getReader();\n let size = 0,\n text = \"\";\n const decoder = new TextDecoder();\n try {\n while (true) {\n const r = await reader.read();\n if (r.done) break;\n size += r.value.byteLength;\n if (size > 1048576) {\n await reader.cancel();\n throw new CatalogAccessError(\"catalog_source_budget\", 503);\n }\n text += decoder.decode(r.value, { stream: true });\n }\n text += decoder.decode();\n return JSON.parse(text);\n } finally {\n reader.releaseLock();\n }\n }\n async execute<T>(\n operationId: string,\n input: Record<string, unknown>,\n callerToken: string,\n transform: (context: { signal: AbortSignal; history: (sourceId: string) => Promise<unknown> }) => Promise<T>,\n ): Promise<T> {\n const operation = this.options.manifest.operations.find((op) => op.id === operationId);\n if (!operation) throw Error(\"Catalog operation not declared.\");\n const signal = AbortSignal.timeout(operation.timeoutMs);\n await this.call(\"authorize\", operation, input, callerToken, undefined, signal);\n const work = transform({\n signal,\n history: async (sourceId) => {\n if (!operation.sources.some((source) => source.id === sourceId)) throw Error(\"Catalog source not declared.\");\n return this.call(\"history\", operation, input, callerToken, sourceId, signal);\n },\n });\n let stop: (() => void) | undefined;\n const deadline = new Promise<never>((_resolve, reject) => {\n stop = () => reject(new CatalogAccessError(\"catalog_execution_timeout\", 504));\n if (signal.aborted) stop();\n else signal.addEventListener(\"abort\", stop, { once: true });\n });\n let result: T;\n try {\n result = await Promise.race([work, deadline]);\n } finally {\n if (stop) signal.removeEventListener(\"abort\", stop);\n }\n // Recheck after asynchronous processing, including a cached application result.\n await this.call(\"authorize\", operation, input, callerToken, undefined, signal);\n assertCatalogResponse(operation, result);\n return result;\n }\n}\n"]}
@@ -0,0 +1,97 @@
1
+ import { z } from "zod";
2
+ export declare const CATALOG_PUBLICATION_PROTOCOL = 1;
3
+ export declare const CATALOG_SOURCE_SCOPE = "catalog:sources:read";
4
+ export declare const CATALOG_MANIFEST_FIELD = "catalogAccess";
5
+ export declare class CatalogAccessError extends Error {
6
+ readonly code: string;
7
+ readonly status: number;
8
+ constructor(code: string, status?: number);
9
+ }
10
+ declare const historySource: z.ZodObject<{
11
+ id: z.ZodString;
12
+ kind: z.ZodLiteral<"history">;
13
+ topic: z.ZodString;
14
+ maxWindowMs: z.ZodDefault<z.ZodNumber>;
15
+ maxRows: z.ZodDefault<z.ZodNumber>;
16
+ fromParameter: z.ZodDefault<z.ZodString>;
17
+ toParameter: z.ZodDefault<z.ZodString>;
18
+ }, z.core.$strict>;
19
+ declare const operation: z.ZodObject<{
20
+ id: z.ZodString;
21
+ title: z.ZodString;
22
+ method: z.ZodLiteral<"GET">;
23
+ path: z.ZodString;
24
+ mode: z.ZodEnum<{
25
+ caller: "caller";
26
+ "approved-service": "approved-service";
27
+ }>;
28
+ requestSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
29
+ responseSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
30
+ sources: z.ZodArray<z.ZodObject<{
31
+ id: z.ZodString;
32
+ kind: z.ZodLiteral<"history">;
33
+ topic: z.ZodString;
34
+ maxWindowMs: z.ZodDefault<z.ZodNumber>;
35
+ maxRows: z.ZodDefault<z.ZodNumber>;
36
+ fromParameter: z.ZodDefault<z.ZodString>;
37
+ toParameter: z.ZodDefault<z.ZodString>;
38
+ }, z.core.$strict>>;
39
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
40
+ maxResponseBytes: z.ZodDefault<z.ZodNumber>;
41
+ }, z.core.$strict>;
42
+ declare const manifest: z.ZodObject<{
43
+ protocol: z.ZodLiteral<1>;
44
+ operations: z.ZodArray<z.ZodObject<{
45
+ id: z.ZodString;
46
+ title: z.ZodString;
47
+ method: z.ZodLiteral<"GET">;
48
+ path: z.ZodString;
49
+ mode: z.ZodEnum<{
50
+ caller: "caller";
51
+ "approved-service": "approved-service";
52
+ }>;
53
+ requestSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
54
+ responseSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
55
+ sources: z.ZodArray<z.ZodObject<{
56
+ id: z.ZodString;
57
+ kind: z.ZodLiteral<"history">;
58
+ topic: z.ZodString;
59
+ maxWindowMs: z.ZodDefault<z.ZodNumber>;
60
+ maxRows: z.ZodDefault<z.ZodNumber>;
61
+ fromParameter: z.ZodDefault<z.ZodString>;
62
+ toParameter: z.ZodDefault<z.ZodString>;
63
+ }, z.core.$strict>>;
64
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
65
+ maxResponseBytes: z.ZodDefault<z.ZodNumber>;
66
+ }, z.core.$strict>>;
67
+ }, z.core.$strict>;
68
+ export type CatalogOperationInput = z.input<typeof operation>;
69
+ export type CatalogOperation = z.output<typeof operation>;
70
+ export type CatalogHistorySource = z.output<typeof historySource>;
71
+ export type CatalogPublicationManifest = z.output<typeof manifest>;
72
+ export declare const catalogAccessDigest: (value: unknown) => string;
73
+ /** Catalog routes use the same UNS resource path as SDK endpoint guards. */
74
+ export declare const catalogOperationResource: (path: string) => string;
75
+ /** Inspection consumes bounded JSON data, never application JavaScript. */
76
+ export declare function parseCatalogPublicationManifest(input: unknown): CatalogPublicationManifest;
77
+ export declare function assertCatalogRequest(op: CatalogOperation, input: unknown): void;
78
+ export declare function assertCatalogResponse(op: CatalogOperation, value: unknown): void;
79
+ export declare function catalogHistoryQuery(source: CatalogHistorySource, input: unknown): {
80
+ topics: string[];
81
+ from: string;
82
+ to: string;
83
+ limit: number;
84
+ dedupe: false;
85
+ };
86
+ export declare const catalogInvocationSchema: z.ZodObject<{
87
+ rttNode: z.ZodString;
88
+ version: z.ZodString;
89
+ instanceId: z.ZodString;
90
+ operationId: z.ZodString;
91
+ manifestDigest: z.ZodString;
92
+ input: z.ZodRecord<z.ZodString, z.ZodUnknown>;
93
+ callerToken: z.ZodString;
94
+ }, z.core.$strict>;
95
+ export type CatalogInvocation = z.infer<typeof catalogInvocationSchema>;
96
+ export {};
97
+ //# sourceMappingURL=catalog-publication.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-publication.d.ts","sourceRoot":"","sources":["../../src/runtime/catalog-publication.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAC3D,eAAO,MAAM,sBAAsB,kBAAkB,CAAC;AAkBtD,qBAAa,kBAAmB,SAAQ,KAAK;aAEzB,IAAI,EAAE,MAAM;aACZ,MAAM;gBADN,IAAI,EAAE,MAAM,EACZ,MAAM,SAAM;CAI/B;AACD,QAAA,MAAM,aAAa;;;;;;;;kBAeR,CAAC;AACZ,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;kBAaJ,CAAC;AACZ,QAAA,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;kBAA+F,CAAC;AAC9G,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9D,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAC1D,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAClE,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAC;AACnE,eAAO,MAAM,mBAAmB,4BAAiB,CAAC;AAClD,4EAA4E;AAC5E,eAAO,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,MAAsC,CAAC;AAE/F,2EAA2E;AAC3E,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,0BAA0B,CAmC1F;AAiDD,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,gBAAgB,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAI/E;AACD,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,gBAAgB,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAEhF;AACD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,oBAAoB,EAC5B,KAAK,EAAE,OAAO,GACb;IACD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,KAAK,CAAC;CACf,CAWA;AAED,eAAO,MAAM,uBAAuB;;;;;;;;kBAUzB,CAAC;AACZ,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
@@ -0,0 +1,185 @@
1
+ import { z } from "zod";
2
+ import { operatorDigest, operatorJson, operatorRequestSchema } from "./operator-api.js";
3
+ export const CATALOG_PUBLICATION_PROTOCOL = 1;
4
+ export const CATALOG_SOURCE_SCOPE = "catalog:sources:read";
5
+ export const CATALOG_MANIFEST_FIELD = "catalogAccess";
6
+ const id = z
7
+ .string()
8
+ .min(1)
9
+ .max(120)
10
+ .regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/);
11
+ const topic = z
12
+ .string()
13
+ .min(1)
14
+ .max(512)
15
+ .regex(/^[A-Za-z0-9-]+(?:\/[A-Za-z0-9-]+)*$/)
16
+ .refine((value) => !value.split("/").some((part) => part === "." || part === ".."));
17
+ const localPath = z
18
+ .string()
19
+ .min(1)
20
+ .max(1024)
21
+ .regex(/^\/api\/[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_.-]+)*$/)
22
+ .refine((value) => !value.split("/").some((part) => part === "." || part === ".."));
23
+ export class CatalogAccessError extends Error {
24
+ code;
25
+ status;
26
+ constructor(code, status = 400) {
27
+ super(code);
28
+ this.code = code;
29
+ this.status = status;
30
+ }
31
+ }
32
+ const historySource = z
33
+ .object({
34
+ id,
35
+ kind: z.literal("history"),
36
+ topic,
37
+ maxWindowMs: z
38
+ .number()
39
+ .int()
40
+ .min(1000)
41
+ .max(31 * 86400000)
42
+ .default(7 * 86400000),
43
+ maxRows: z.number().int().min(1).max(27000).default(1000),
44
+ fromParameter: id.default("from"),
45
+ toParameter: id.default("to"),
46
+ })
47
+ .strict();
48
+ const operation = z
49
+ .object({
50
+ id,
51
+ title: z.string().min(1).max(160),
52
+ method: z.literal("GET"),
53
+ path: localPath,
54
+ mode: z.enum(["caller", "approved-service"]),
55
+ requestSchema: z.record(z.string(), z.unknown()),
56
+ responseSchema: z.record(z.string(), z.unknown()),
57
+ sources: z.array(historySource).min(1).max(16),
58
+ timeoutMs: z.number().int().min(500).max(30000).default(10000),
59
+ maxResponseBytes: z.number().int().min(256).max(1048576).default(262144),
60
+ })
61
+ .strict();
62
+ const manifest = z.object({ protocol: z.literal(1), operations: z.array(operation).min(1).max(32) }).strict();
63
+ export const catalogAccessDigest = operatorDigest;
64
+ /** Catalog routes use the same UNS resource path as SDK endpoint guards. */
65
+ export const catalogOperationResource = (path) => path.replace(/^\/api\//, "");
66
+ /** Inspection consumes bounded JSON data, never application JavaScript. */
67
+ export function parseCatalogPublicationManifest(input) {
68
+ try {
69
+ operatorJson(input);
70
+ const parsed = manifest.parse(input), ids = new Set(), paths = new Set();
71
+ for (const op of parsed.operations) {
72
+ if (ids.has(op.id) || paths.has(op.path))
73
+ throw Error("duplicate operation");
74
+ ids.add(op.id);
75
+ paths.add(op.path);
76
+ op.requestSchema = catalogSchema(op.requestSchema);
77
+ op.responseSchema = catalogSchema(op.responseSchema);
78
+ if (op.requestSchema["type"] !== "object")
79
+ throw Error("request must be an object");
80
+ if (Object.values(op.requestSchema["properties"] ?? {}).some((p) => p["type"] !== "string"))
81
+ throw Error("GET parameters must be strings");
82
+ const sources = new Set();
83
+ for (const source of op.sources) {
84
+ if (sources.has(source.id) || source.fromParameter === source.toParameter)
85
+ throw Error("source binding");
86
+ sources.add(source.id);
87
+ const properties = op.requestSchema["properties"];
88
+ for (const key of [source.fromParameter, source.toParameter]) {
89
+ const parameter = properties?.[key];
90
+ if (parameter?.["type"] !== "string" ||
91
+ parameter["format"] !== "date-time" ||
92
+ !op.requestSchema["required"]?.includes(key))
93
+ throw Error("time parameter binding");
94
+ }
95
+ }
96
+ }
97
+ return parsed;
98
+ }
99
+ catch {
100
+ throw new CatalogAccessError("catalog_manifest_invalid");
101
+ }
102
+ }
103
+ function catalogSchema(value) {
104
+ const schema = operatorRequestSchema(value);
105
+ const visit = (node) => {
106
+ if (!["object", "array", "string", "integer", "number", "boolean", "null"].includes(String(node["type"])) ||
107
+ ["oneOf", "anyOf", "allOf", "default"].some((key) => key in node))
108
+ throw Error("unsupported catalog schema");
109
+ if (node["properties"])
110
+ for (const child of Object.values(node["properties"]))
111
+ visit(child);
112
+ if (node["items"])
113
+ visit(node["items"]);
114
+ };
115
+ visit(schema);
116
+ return schema;
117
+ }
118
+ /** Zod's JSON Schema importer limits date-time to UTC. JSON Schema permits
119
+ * explicit offsets. Check these formats separately without altering input. */
120
+ function offsetSchema(schema, value) {
121
+ const copy = { ...schema };
122
+ if (copy["format"] === "date-time") {
123
+ if (value !== undefined && !z.iso.datetime({ offset: true }).safeParse(value).success)
124
+ throw Error("date-time");
125
+ delete copy["format"];
126
+ }
127
+ if (copy["properties"])
128
+ copy["properties"] = Object.fromEntries(Object.entries(copy["properties"]).map(([key, child]) => [
129
+ key,
130
+ offsetSchema(child, value && typeof value === "object" ? value[key] : undefined),
131
+ ]));
132
+ if (copy["items"]) {
133
+ const child = copy["items"];
134
+ if (Array.isArray(value))
135
+ for (const item of value)
136
+ offsetSchema(child, item);
137
+ copy["items"] = offsetSchema(child, undefined);
138
+ }
139
+ return copy;
140
+ }
141
+ function assertSchema(schema, value, code, maxBytes) {
142
+ try {
143
+ const json = JSON.stringify(value);
144
+ if (json === undefined || Buffer.byteLength(json, "utf8") > maxBytes)
145
+ throw Error("size");
146
+ // fromJSONSchema strips unknown keys by default, so validate the unchanged
147
+ // value against a strict schema rather than returning parsed/transformed data.
148
+ if (!z.fromJSONSchema(offsetSchema(schema, value)).safeParse(value).success)
149
+ throw Error("schema");
150
+ }
151
+ catch {
152
+ throw new CatalogAccessError(code, code === "catalog_response_invalid" ? 502 : 400);
153
+ }
154
+ }
155
+ export function assertCatalogRequest(op, input) {
156
+ operatorJson(input);
157
+ assertSchema(op.requestSchema, input, "catalog_request_invalid", 65536);
158
+ for (const source of op.sources)
159
+ catalogHistoryQuery(source, input);
160
+ }
161
+ export function assertCatalogResponse(op, value) {
162
+ assertSchema(op.responseSchema, value, "catalog_response_invalid", op.maxResponseBytes);
163
+ }
164
+ export function catalogHistoryQuery(source, input) {
165
+ const values = input;
166
+ const from = values?.[source.fromParameter], to = values?.[source.toParameter];
167
+ if (!z.iso.datetime({ offset: true }).safeParse(from).success || !z.iso.datetime({ offset: true }).safeParse(to).success)
168
+ throw new CatalogAccessError("catalog_window_invalid");
169
+ const start = Date.parse(from), end = Date.parse(to);
170
+ if (!Number.isFinite(start) || !Number.isFinite(end) || start > end || end - start > source.maxWindowMs)
171
+ throw new CatalogAccessError("catalog_window_invalid");
172
+ return { topics: [source.topic], from: new Date(start).toISOString(), to: new Date(end).toISOString(), limit: source.maxRows, dedupe: false };
173
+ }
174
+ export const catalogInvocationSchema = z
175
+ .object({
176
+ rttNode: id,
177
+ version: id,
178
+ instanceId: id,
179
+ operationId: id,
180
+ manifestDigest: z.string().regex(/^[a-f0-9]{64}$/),
181
+ input: z.record(z.string(), z.unknown()),
182
+ callerToken: z.string().min(1).max(16384),
183
+ })
184
+ .strict();
185
+ //# sourceMappingURL=catalog-publication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-publication.js","sourceRoot":"","sources":["../../src/runtime/catalog-publication.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\n\nimport { operatorDigest, operatorJson, operatorRequestSchema } from \"./operator-api.js\";\n\nexport const CATALOG_PUBLICATION_PROTOCOL = 1;\nexport const CATALOG_SOURCE_SCOPE = \"catalog:sources:read\";\nexport const CATALOG_MANIFEST_FIELD = \"catalogAccess\";\nconst id = z\n .string()\n .min(1)\n .max(120)\n .regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/);\nconst topic = z\n .string()\n .min(1)\n .max(512)\n .regex(/^[A-Za-z0-9-]+(?:\\/[A-Za-z0-9-]+)*$/)\n .refine((value) => !value.split(\"/\").some((part) => part === \".\" || part === \"..\"));\nconst localPath = z\n .string()\n .min(1)\n .max(1024)\n .regex(/^\\/api\\/[A-Za-z0-9_-]+(?:\\/[A-Za-z0-9_.-]+)*$/)\n .refine((value) => !value.split(\"/\").some((part) => part === \".\" || part === \"..\"));\nexport class CatalogAccessError extends Error {\n constructor(\n public readonly code: string,\n public readonly status = 400,\n ) {\n super(code);\n }\n}\nconst historySource = z\n .object({\n id,\n kind: z.literal(\"history\"),\n topic,\n maxWindowMs: z\n .number()\n .int()\n .min(1000)\n .max(31 * 86400000)\n .default(7 * 86400000),\n maxRows: z.number().int().min(1).max(27000).default(1000),\n fromParameter: id.default(\"from\"),\n toParameter: id.default(\"to\"),\n })\n .strict();\nconst operation = z\n .object({\n id,\n title: z.string().min(1).max(160),\n method: z.literal(\"GET\"),\n path: localPath,\n mode: z.enum([\"caller\", \"approved-service\"]),\n requestSchema: z.record(z.string(), z.unknown()),\n responseSchema: z.record(z.string(), z.unknown()),\n sources: z.array(historySource).min(1).max(16),\n timeoutMs: z.number().int().min(500).max(30000).default(10000),\n maxResponseBytes: z.number().int().min(256).max(1048576).default(262144),\n })\n .strict();\nconst manifest = z.object({ protocol: z.literal(1), operations: z.array(operation).min(1).max(32) }).strict();\nexport type CatalogOperationInput = z.input<typeof operation>;\nexport type CatalogOperation = z.output<typeof operation>;\nexport type CatalogHistorySource = z.output<typeof historySource>;\nexport type CatalogPublicationManifest = z.output<typeof manifest>;\nexport const catalogAccessDigest = operatorDigest;\n/** Catalog routes use the same UNS resource path as SDK endpoint guards. */\nexport const catalogOperationResource = (path: string): string => path.replace(/^\\/api\\//, \"\");\n\n/** Inspection consumes bounded JSON data, never application JavaScript. */\nexport function parseCatalogPublicationManifest(input: unknown): CatalogPublicationManifest {\n try {\n operatorJson(input);\n const parsed = manifest.parse(input),\n ids = new Set<string>(),\n paths = new Set<string>();\n for (const op of parsed.operations) {\n if (ids.has(op.id) || paths.has(op.path)) throw Error(\"duplicate operation\");\n ids.add(op.id);\n paths.add(op.path);\n op.requestSchema = catalogSchema(op.requestSchema);\n op.responseSchema = catalogSchema(op.responseSchema);\n if (op.requestSchema[\"type\"] !== \"object\") throw Error(\"request must be an object\");\n if (Object.values((op.requestSchema[\"properties\"] as Record<string, Record<string, unknown>>) ?? {}).some((p) => p[\"type\"] !== \"string\"))\n throw Error(\"GET parameters must be strings\");\n const sources = new Set<string>();\n for (const source of op.sources) {\n if (sources.has(source.id) || source.fromParameter === source.toParameter) throw Error(\"source binding\");\n sources.add(source.id);\n const properties = op.requestSchema[\"properties\"] as Record<string, unknown> | undefined;\n for (const key of [source.fromParameter, source.toParameter]) {\n const parameter = properties?.[key] as Record<string, unknown> | undefined;\n if (\n parameter?.[\"type\"] !== \"string\" ||\n parameter[\"format\"] !== \"date-time\" ||\n !(op.requestSchema[\"required\"] as string[] | undefined)?.includes(key)\n )\n throw Error(\"time parameter binding\");\n }\n }\n }\n return parsed;\n } catch {\n throw new CatalogAccessError(\"catalog_manifest_invalid\");\n }\n}\n\nfunction catalogSchema(value: unknown): Record<string, unknown> {\n const schema = operatorRequestSchema(value);\n const visit = (node: Record<string, unknown>): void => {\n if (\n ![\"object\", \"array\", \"string\", \"integer\", \"number\", \"boolean\", \"null\"].includes(String(node[\"type\"])) ||\n [\"oneOf\", \"anyOf\", \"allOf\", \"default\"].some((key) => key in node)\n )\n throw Error(\"unsupported catalog schema\");\n if (node[\"properties\"]) for (const child of Object.values(node[\"properties\"] as Record<string, Record<string, unknown>>)) visit(child);\n if (node[\"items\"]) visit(node[\"items\"] as Record<string, unknown>);\n };\n visit(schema);\n return schema;\n}\n/** Zod's JSON Schema importer limits date-time to UTC. JSON Schema permits\n * explicit offsets. Check these formats separately without altering input. */\nfunction offsetSchema(schema: Record<string, unknown>, value: unknown): Record<string, unknown> {\n const copy = { ...schema };\n if (copy[\"format\"] === \"date-time\") {\n if (value !== undefined && !z.iso.datetime({ offset: true }).safeParse(value).success) throw Error(\"date-time\");\n delete copy[\"format\"];\n }\n if (copy[\"properties\"])\n copy[\"properties\"] = Object.fromEntries(\n Object.entries(copy[\"properties\"] as Record<string, Record<string, unknown>>).map(([key, child]) => [\n key,\n offsetSchema(child, value && typeof value === \"object\" ? (value as Record<string, unknown>)[key] : undefined),\n ]),\n );\n if (copy[\"items\"]) {\n const child = copy[\"items\"] as Record<string, unknown>;\n if (Array.isArray(value)) for (const item of value) offsetSchema(child, item);\n copy[\"items\"] = offsetSchema(child, undefined);\n }\n return copy;\n}\nfunction assertSchema(schema: Record<string, unknown>, value: unknown, code: string, maxBytes: number): void {\n try {\n const json = JSON.stringify(value);\n if (json === undefined || Buffer.byteLength(json, \"utf8\") > maxBytes) throw Error(\"size\");\n // fromJSONSchema strips unknown keys by default, so validate the unchanged\n // value against a strict schema rather than returning parsed/transformed data.\n if (!z.fromJSONSchema(offsetSchema(schema, value) as never).safeParse(value).success) throw Error(\"schema\");\n } catch {\n throw new CatalogAccessError(code, code === \"catalog_response_invalid\" ? 502 : 400);\n }\n}\nexport function assertCatalogRequest(op: CatalogOperation, input: unknown): void {\n operatorJson(input);\n assertSchema(op.requestSchema, input, \"catalog_request_invalid\", 65536);\n for (const source of op.sources) catalogHistoryQuery(source, input);\n}\nexport function assertCatalogResponse(op: CatalogOperation, value: unknown): void {\n assertSchema(op.responseSchema, value, \"catalog_response_invalid\", op.maxResponseBytes);\n}\nexport function catalogHistoryQuery(\n source: CatalogHistorySource,\n input: unknown,\n): {\n topics: string[];\n from: string;\n to: string;\n limit: number;\n dedupe: false;\n} {\n const values = input as Record<string, unknown> | null;\n const from = values?.[source.fromParameter],\n to = values?.[source.toParameter];\n if (!z.iso.datetime({ offset: true }).safeParse(from).success || !z.iso.datetime({ offset: true }).safeParse(to).success)\n throw new CatalogAccessError(\"catalog_window_invalid\");\n const start = Date.parse(from as string),\n end = Date.parse(to as string);\n if (!Number.isFinite(start) || !Number.isFinite(end) || start > end || end - start > source.maxWindowMs)\n throw new CatalogAccessError(\"catalog_window_invalid\");\n return { topics: [source.topic], from: new Date(start).toISOString(), to: new Date(end).toISOString(), limit: source.maxRows, dedupe: false };\n}\n\nexport const catalogInvocationSchema = z\n .object({\n rttNode: id,\n version: id,\n instanceId: id,\n operationId: id,\n manifestDigest: z.string().regex(/^[a-f0-9]{64}$/),\n input: z.record(z.string(), z.unknown()),\n callerToken: z.string().min(1).max(16384),\n })\n .strict();\nexport type CatalogInvocation = z.infer<typeof catalogInvocationSchema>;\n"]}
@@ -0,0 +1,130 @@
1
+ # Service access and publications (3.0.47)
2
+
3
+ This opt-in protocol separates permission to call a catalog operation from
4
+ permission to read its source. It is not a sandbox for arbitrary application
5
+ code or a substitute for restricted database and MQTT identities.
6
+
7
+ Package metadata requests authority; it never grants it. Keep existing JWT/JWKS
8
+ endpoint guards. Do not forward caller tokens to privileged sources or retry a
9
+ denied caller read with stronger credentials.
10
+
11
+ ## Declaration
12
+
13
+ Ship a bounded JSON file with the installed package, and retain it in the build:
14
+
15
+ ```json
16
+ {"unsDatahub":{"serviceAccess":"service-access.json"}}
17
+ ```
18
+
19
+ Use `parseServiceAccessDeclaration` from `@uns-kit/core/runtime/service-access.js`:
20
+
21
+ ```json
22
+ {
23
+ "protocol": 1,
24
+ "permissions": {
25
+ "scopes": ["read:uns"],
26
+ "accessRules": ["example/background/#"]
27
+ }
28
+ }
29
+ ```
30
+
31
+ This works without a catalog, HTTP endpoint, or human requester. The declaration
32
+ requests a ceiling; an administrator must apply the effective policy under
33
+ **Microservices → instance → Service access** before the service can start with
34
+ a managed identity. Review the exact installed version and declaration digest.
35
+ The administrator can select fewer capabilities and narrower MQTT path filters;
36
+ unknown or administrative scopes are rejected. The identity, revocation and
37
+ same-request reissue mechanisms are the existing managed workload mechanisms.
38
+ UNS resource rules bound direct UNS reads; other recognized runtime scopes retain
39
+ their existing authorization semantics. This protocol does not create new
40
+ SQL, broker, filesystem or arbitrary external-service permissions.
41
+
42
+ A combined service adds optional `publications`, containing the manifest parsed
43
+ by `parseCatalogPublicationManifest`, to the same envelope. A publication-only
44
+ service uses empty `permissions.scopes` and `permissions.accessRules`. Its
45
+ managed token then has `catalog:sources:read` and **no raw UNS paths**. Adding
46
+ publication sources never adds them to the service's direct `read:uns` rules.
47
+ Publication approval is a separate operation/audience grant and never silently
48
+ grants other package-requested capabilities. Review both in Service access.
49
+ The Catalog contains execution/status and an administrator link to that editor.
50
+ There is one writable publication editor.
51
+
52
+ The earlier unpublished `unsDatahub.catalogAccess` marker remains an isolated
53
+ compatibility adapter for the first local fixtures. New consumers use
54
+ `serviceAccess`; declaring both markers is rejected. No released applications
55
+ are automatically migrated.
56
+
57
+ The optional publication manifest supports GET JSON,
58
+ closed JSON schemas (primitive, object and array types; no references, defaults,
59
+ or composition), string query parameters, and exact history source topics.
60
+ Source path segments follow the existing managed-identity resource vocabulary:
61
+ ASCII letters, digits and hyphens. Each source binds required `from`/`to` date-time parameters, a maximum window
62
+ (at most 31 days) and row count (at most 27000). Dates require a UTC offset.
63
+ Wildcard topics, arbitrary URLs/SQL/source selectors and undeclared input fields
64
+ are rejected. The broker caps source responses at 1 MiB; published responses
65
+ have a separately declared cap of at most 1 MiB.
66
+
67
+ Each operation declares `caller` or `approved-service`. Both require an explicit
68
+ administrator publication review and current caller permission on the SDK UNS
69
+ resource path (the operation path without `/api/`). In caller mode, source rules
70
+ and `read:uns` must also belong to the caller. Service mode never grants these
71
+ raw permissions to the caller.
72
+
73
+ ## Runtime
74
+
75
+ Use `CatalogPublicationClient` with the installed, normalized manifest and the
76
+ Controller-provided runtime identity (`RTT_NODE`, `version`, `RTT_INSTANCE_ID`,
77
+ `UNS_CONTROLLER_PUBLIC_BASE`). The default `ServiceTokenProvider` reads the
78
+ Controller's rotating token file. There is no caller-token fallback.
79
+
80
+ Use `catalogPublicationHandler(client, operationId, transform)` in an existing
81
+ `registerApiCatalog` handler. Call `api.registerCatalogRoute()` to opt into the
82
+ existing signed launch route lease. The transform receives `history(sourceId)`
83
+ and the already validated input. It owns the derived result, not authorization.
84
+ The HTTP helper sets `Cache-Control: no-store` and rejects undeclared result
85
+ fields. Each execution rechecks current authority after asynchronous work,
86
+ including cached application results. Custom downloads and other handlers must
87
+ use the same mediated path; this candidate adds no persistent result cache.
88
+
89
+ A publication-only workload token has `catalog:sources:read` and no direct
90
+ `read:uns` authority. A combined service may separately receive `read:uns` on
91
+ its reviewed background paths, without receiving raw access to publication
92
+ sources. The Controller broker checks current user policy, active machine
93
+ identity/token generation, reviewed identity binding, launch, loaded portable
94
+ configuration, publication revision, audience and source bounds. Its internal
95
+ history token is exact-topic, lasts 15 seconds, and never leaves the broker.
96
+ Neither token bytes nor raw records belong in logs, review receipts or audit.
97
+
98
+ Two local acceptance consumers (energy intervals and quality sample counts) use
99
+ the same helper and Controller path. No application-name branch is needed.
100
+
101
+ ## Adoption boundary
102
+
103
+ The SDK contract is introduced in 3.0.47. A matching Controller implementation
104
+ must also be installed; an SDK upgrade alone does not enable the host capability.
105
+ Existing applications do not change mode
106
+ implicitly. Install the shared SDK release first, update consumer lockfiles,
107
+ then review the matching installed declaration through the Controller.
108
+ Controller publication is bound to the installed declaration and portable
109
+ configuration; edits require review. A changed loaded configuration requires a
110
+ new launch, not token refresh. With the matching Controller candidate, the
111
+ signed digest of the same confirmed live launch survives ordinary credential
112
+ expiry and Controller restart. Current identity, policy, generation and approval
113
+ are still checked; expired bearer tokens remain invalid. Missing, corrupt,
114
+ foreign or changed-launch evidence requires explicit reviewed recovery.
115
+
116
+
117
+ ## Local common-contract acceptance
118
+
119
+ An unrelated background history processor, with no API or Catalog offer, was
120
+ reviewed, narrowed live, revoked, reissued with the same identity and cold-moved
121
+ to another Controller. It retained the same policy and generation across the
122
+ move. A combined service read its separate background source directly, while a
123
+ catalog-only user consumed a derived result from a different approved source.
124
+ Both the user and workload were denied direct access to that publication source.
125
+ A publication-only empty raw policy stayed empty in storage and actual tokens.
126
+
127
+ These are local candidate checks, including clean packed-SDK installs. They are
128
+ not production, arbitrary-code isolation or external-resource adapter acceptance.
129
+ SDK publication and downstream frozen dependency updates
130
+ remain separate steps.
@@ -0,0 +1,23 @@
1
+ import { type CatalogPublicationManifest } from "./catalog-publication.js";
2
+ export declare const SERVICE_ACCESS_PROTOCOL = 1;
3
+ export declare const SERVICE_ACCESS_MANIFEST_FIELD = "serviceAccess";
4
+ export declare const SERVICE_RESOURCE_SCOPES: readonly ["read:uns", "identity:asset:publish", "runtime:captures:read", "runtime:captures:write", "runtime:triggers:read"];
5
+ export type ServiceAccessDeclaration = {
6
+ protocol: 1;
7
+ permissions: {
8
+ scopes: string[];
9
+ accessRules: string[];
10
+ };
11
+ publications?: CatalogPublicationManifest;
12
+ };
13
+ export declare function parseServiceAccessDeclaration(value: unknown): ServiceAccessDeclaration;
14
+ export declare const serviceAccessDigest: (value: unknown) => string;
15
+ /** Publication source rules are deliberately not raw UNS permissions. A service
16
+ * combining a background reader with a catalog must not gain their cross-product. */
17
+ export declare function serviceAccessCeiling(declaration: ServiceAccessDeclaration): {
18
+ scopes: string[];
19
+ accessRules: string[];
20
+ };
21
+ /** Conservative MQTT filter inclusion, not matching a filter as if it were data. */
22
+ export declare function serviceRuleWithin(requested: string, allowed: string): boolean;
23
+ //# sourceMappingURL=service-access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-access.d.ts","sourceRoot":"","sources":["../../src/runtime/service-access.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,0BAA0B,EAAmC,MAAM,0BAA0B,CAAC;AAGlI,eAAO,MAAM,uBAAuB,IAAI,CAAC;AACzC,eAAO,MAAM,6BAA6B,kBAAkB,CAAC;AAI7D,eAAO,MAAM,uBAAuB,6HAM1B,CAAC;AAaX,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,CAAC,CAAC;IACZ,WAAW,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACzD,YAAY,CAAC,EAAE,0BAA0B,CAAC;CAC3C,CAAC;AACF,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,wBAAwB,CAQtF;AACD,eAAO,MAAM,mBAAmB,4BAAiB,CAAC;AAClD;qFACqF;AACrF,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,wBAAwB;;;EAKzE;AACD,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAU7E"}
@@ -0,0 +1,62 @@
1
+ import { z } from "zod";
2
+ import { CATALOG_SOURCE_SCOPE, parseCatalogPublicationManifest } from "./catalog-publication.js";
3
+ import { operatorDigest, operatorJson } from "./operator-api.js";
4
+ export const SERVICE_ACCESS_PROTOCOL = 1;
5
+ export const SERVICE_ACCESS_MANIFEST_FIELD = "serviceAccess";
6
+ // Only existing platform data/runtime capabilities. Administrative authority is
7
+ // never a package-requestable scope. UNS rules bound direct UNS reads; other
8
+ // runtime capabilities retain their existing platform authorization semantics.
9
+ export const SERVICE_RESOURCE_SCOPES = [
10
+ "read:uns",
11
+ "identity:asset:publish",
12
+ "runtime:captures:read",
13
+ "runtime:captures:write",
14
+ "runtime:triggers:read",
15
+ ];
16
+ const rule = z
17
+ .string()
18
+ .min(1)
19
+ .max(512)
20
+ .refine((s) => s.split("/").every((p, i, a) => p === "+" || (p === "#" && i === a.length - 1) || /^[A-Za-z0-9-]+$/.test(p)));
21
+ const envelope = z
22
+ .object({
23
+ protocol: z.literal(1),
24
+ permissions: z.object({ scopes: z.array(z.enum(SERVICE_RESOURCE_SCOPES)).max(5), accessRules: z.array(rule).max(64) }).strict(),
25
+ publications: z.unknown().optional(),
26
+ })
27
+ .strict();
28
+ export function parseServiceAccessDeclaration(value) {
29
+ operatorJson(value);
30
+ const parsed = envelope.parse(value);
31
+ const permissions = { scopes: [...new Set(parsed.permissions.scopes)].sort(), accessRules: [...new Set(parsed.permissions.accessRules)].sort() };
32
+ if (!!permissions.scopes.length !== !!permissions.accessRules.length)
33
+ throw Error("service_access_resources_required");
34
+ const publications = parsed.publications === undefined ? undefined : parseCatalogPublicationManifest(parsed.publications);
35
+ if (!permissions.scopes.length && !publications)
36
+ throw Error("service_access_capability_required");
37
+ return { protocol: 1, permissions, ...(publications ? { publications } : {}) };
38
+ }
39
+ export const serviceAccessDigest = operatorDigest;
40
+ /** Publication source rules are deliberately not raw UNS permissions. A service
41
+ * combining a background reader with a catalog must not gain their cross-product. */
42
+ export function serviceAccessCeiling(declaration) {
43
+ return {
44
+ scopes: [...declaration.permissions.scopes, ...(declaration.publications ? [CATALOG_SOURCE_SCOPE] : [])].sort(),
45
+ accessRules: [...declaration.permissions.accessRules],
46
+ };
47
+ }
48
+ /** Conservative MQTT filter inclusion, not matching a filter as if it were data. */
49
+ export function serviceRuleWithin(requested, allowed) {
50
+ const a = allowed.split("/"), r = requested.split("/");
51
+ let i = 0;
52
+ for (; i < a.length; i++) {
53
+ if (a[i] === "#")
54
+ return true;
55
+ if (r[i] === undefined || r[i] === "#")
56
+ return false;
57
+ if (a[i] !== r[i] && a[i] !== "+")
58
+ return false;
59
+ }
60
+ return i === r.length;
61
+ }
62
+ //# sourceMappingURL=service-access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-access.js","sourceRoot":"","sources":["../../src/runtime/service-access.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\n\nimport { CATALOG_SOURCE_SCOPE, type CatalogPublicationManifest, parseCatalogPublicationManifest } from \"./catalog-publication.js\";\nimport { operatorDigest, operatorJson } from \"./operator-api.js\";\n\nexport const SERVICE_ACCESS_PROTOCOL = 1;\nexport const SERVICE_ACCESS_MANIFEST_FIELD = \"serviceAccess\";\n// Only existing platform data/runtime capabilities. Administrative authority is\n// never a package-requestable scope. UNS rules bound direct UNS reads; other\n// runtime capabilities retain their existing platform authorization semantics.\nexport const SERVICE_RESOURCE_SCOPES = [\n \"read:uns\",\n \"identity:asset:publish\",\n \"runtime:captures:read\",\n \"runtime:captures:write\",\n \"runtime:triggers:read\",\n] as const;\nconst rule = z\n .string()\n .min(1)\n .max(512)\n .refine((s) => s.split(\"/\").every((p, i, a) => p === \"+\" || (p === \"#\" && i === a.length - 1) || /^[A-Za-z0-9-]+$/.test(p)));\nconst envelope = z\n .object({\n protocol: z.literal(1),\n permissions: z.object({ scopes: z.array(z.enum(SERVICE_RESOURCE_SCOPES)).max(5), accessRules: z.array(rule).max(64) }).strict(),\n publications: z.unknown().optional(),\n })\n .strict();\nexport type ServiceAccessDeclaration = {\n protocol: 1;\n permissions: { scopes: string[]; accessRules: string[] };\n publications?: CatalogPublicationManifest;\n};\nexport function parseServiceAccessDeclaration(value: unknown): ServiceAccessDeclaration {\n operatorJson(value);\n const parsed = envelope.parse(value);\n const permissions = { scopes: [...new Set(parsed.permissions.scopes)].sort(), accessRules: [...new Set(parsed.permissions.accessRules)].sort() };\n if (!!permissions.scopes.length !== !!permissions.accessRules.length) throw Error(\"service_access_resources_required\");\n const publications = parsed.publications === undefined ? undefined : parseCatalogPublicationManifest(parsed.publications);\n if (!permissions.scopes.length && !publications) throw Error(\"service_access_capability_required\");\n return { protocol: 1, permissions, ...(publications ? { publications } : {}) };\n}\nexport const serviceAccessDigest = operatorDigest;\n/** Publication source rules are deliberately not raw UNS permissions. A service\n * combining a background reader with a catalog must not gain their cross-product. */\nexport function serviceAccessCeiling(declaration: ServiceAccessDeclaration) {\n return {\n scopes: [...declaration.permissions.scopes, ...(declaration.publications ? [CATALOG_SOURCE_SCOPE] : [])].sort(),\n accessRules: [...declaration.permissions.accessRules],\n };\n}\n/** Conservative MQTT filter inclusion, not matching a filter as if it were data. */\nexport function serviceRuleWithin(requested: string, allowed: string): boolean {\n const a = allowed.split(\"/\"),\n r = requested.split(\"/\");\n let i = 0;\n for (; i < a.length; i++) {\n if (a[i] === \"#\") return true;\n if (r[i] === undefined || r[i] === \"#\") return false;\n if (a[i] !== r[i] && a[i] !== \"+\") return false;\n }\n return i === r.length;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=service-access.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-access.test.d.ts","sourceRoot":"","sources":["../../src/runtime/service-access.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,57 @@
1
+ import { expect, it } from "vitest";
2
+ import { parseServiceAccessDeclaration, serviceAccessCeiling, serviceAccessDigest } from "./service-access.js";
3
+ import { serviceRuleWithin } from "./service-access.js";
4
+ const background = { protocol: 1, permissions: { scopes: ["read:uns"], accessRules: ["factory/sensors/#"] } };
5
+ it("supports an unrelated background service without catalog or HTTP", () => {
6
+ const d = parseServiceAccessDeclaration(background);
7
+ expect(d.publications).toBeUndefined();
8
+ expect(serviceAccessCeiling(d)).toEqual(background.permissions);
9
+ });
10
+ it.each(["admin:all", "catalog:sources:read", "write:anything"])("rejects package-requested privileged or unknown scope %s", (scope) => {
11
+ expect(() => parseServiceAccessDeclaration({ ...background, permissions: { ...background.permissions, scopes: [scope] } })).toThrow();
12
+ });
13
+ it.each([
14
+ { protocol: 2 },
15
+ { unknown: true },
16
+ { permissions: { scopes: ["read:uns"], accessRules: [] } },
17
+ { permissions: { scopes: [], accessRules: [] } },
18
+ { permissions: { scopes: ["read:uns"], accessRules: ["factory/#/escape"] } },
19
+ ])("fails closed on malformed declaration %j", (patch) => expect(() => parseServiceAccessDeclaration({ ...background, ...patch })).toThrow());
20
+ it("normalizes duplicate requirements for stable review digests", () => expect(serviceAccessDigest(parseServiceAccessDeclaration({
21
+ ...background,
22
+ permissions: { scopes: ["read:uns", "read:uns"], accessRules: ["factory/sensors/#", "factory/sensors/#"] },
23
+ }))).toBe(serviceAccessDigest(parseServiceAccessDeclaration(background))));
24
+ it.each([
25
+ ["plant/a", "plant/#", true],
26
+ ["plant/+", "plant/#", true],
27
+ ["plant/#", "plant/+", false],
28
+ ["#", "plant/#", false],
29
+ ["other/a", "plant/#", false],
30
+ ])("validates resource narrowing %s within %s", (r, a, expected) => expect(serviceRuleWithin(r, a)).toBe(expected));
31
+ it("does not combine publication sources with raw background rules", () => {
32
+ const d = parseServiceAccessDeclaration({
33
+ ...background,
34
+ publications: {
35
+ protocol: 1,
36
+ operations: [
37
+ {
38
+ id: "summary",
39
+ title: "Summary",
40
+ method: "GET",
41
+ path: "/api/derived/summary",
42
+ mode: "approved-service",
43
+ requestSchema: {
44
+ type: "object",
45
+ additionalProperties: false,
46
+ required: ["from", "to"],
47
+ properties: { from: { type: "string", format: "date-time" }, to: { type: "string", format: "date-time" } },
48
+ },
49
+ responseSchema: { type: "integer" },
50
+ sources: [{ id: "history", kind: "history", topic: "private/published" }],
51
+ },
52
+ ],
53
+ },
54
+ });
55
+ expect(serviceAccessCeiling(d)).toEqual({ scopes: ["catalog:sources:read", "read:uns"], accessRules: ["factory/sensors/#"] });
56
+ });
57
+ //# sourceMappingURL=service-access.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-access.test.js","sourceRoot":"","sources":["../../src/runtime/service-access.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, it } from \"vitest\";\n\nimport { parseServiceAccessDeclaration, serviceAccessCeiling, serviceAccessDigest } from \"./service-access.js\";\nimport { serviceRuleWithin } from \"./service-access.js\";\n\nconst background = { protocol: 1, permissions: { scopes: [\"read:uns\"], accessRules: [\"factory/sensors/#\"] } };\nit(\"supports an unrelated background service without catalog or HTTP\", () => {\n const d = parseServiceAccessDeclaration(background);\n expect(d.publications).toBeUndefined();\n expect(serviceAccessCeiling(d)).toEqual(background.permissions);\n});\nit.each([\"admin:all\", \"catalog:sources:read\", \"write:anything\"])(\"rejects package-requested privileged or unknown scope %s\", (scope) => {\n expect(() => parseServiceAccessDeclaration({ ...background, permissions: { ...background.permissions, scopes: [scope] } })).toThrow();\n});\nit.each([\n { protocol: 2 },\n { unknown: true },\n { permissions: { scopes: [\"read:uns\"], accessRules: [] } },\n { permissions: { scopes: [], accessRules: [] } },\n { permissions: { scopes: [\"read:uns\"], accessRules: [\"factory/#/escape\"] } },\n])(\"fails closed on malformed declaration %j\", (patch) => expect(() => parseServiceAccessDeclaration({ ...background, ...patch })).toThrow());\nit(\"normalizes duplicate requirements for stable review digests\", () =>\n expect(\n serviceAccessDigest(\n parseServiceAccessDeclaration({\n ...background,\n permissions: { scopes: [\"read:uns\", \"read:uns\"], accessRules: [\"factory/sensors/#\", \"factory/sensors/#\"] },\n }),\n ),\n ).toBe(serviceAccessDigest(parseServiceAccessDeclaration(background))));\n\nit.each([\n [\"plant/a\", \"plant/#\", true],\n [\"plant/+\", \"plant/#\", true],\n [\"plant/#\", \"plant/+\", false],\n [\"#\", \"plant/#\", false],\n [\"other/a\", \"plant/#\", false],\n])(\"validates resource narrowing %s within %s\", (r, a, expected) => expect(serviceRuleWithin(r as string, a as string)).toBe(expected));\nit(\"does not combine publication sources with raw background rules\", () => {\n const d = parseServiceAccessDeclaration({\n ...background,\n publications: {\n protocol: 1,\n operations: [\n {\n id: \"summary\",\n title: \"Summary\",\n method: \"GET\",\n path: \"/api/derived/summary\",\n mode: \"approved-service\",\n requestSchema: {\n type: \"object\",\n additionalProperties: false,\n required: [\"from\", \"to\"],\n properties: { from: { type: \"string\", format: \"date-time\" }, to: { type: \"string\", format: \"date-time\" } },\n },\n responseSchema: { type: \"integer\" },\n sources: [{ id: \"history\", kind: \"history\", topic: \"private/published\" }],\n },\n ],\n },\n });\n expect(serviceAccessCeiling(d)).toEqual({ scopes: [\"catalog:sources:read\", \"read:uns\"], accessRules: [\"factory/sensors/#\"] });\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uns-kit/core",
3
- "version": "3.0.46",
3
+ "version": "3.0.47",
4
4
  "description": "Core utilities and runtime building blocks for UNS-based realtime transformers.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,6 +23,19 @@
23
23
  "MIGRATIONS.md"
24
24
  ],
25
25
  "exports": {
26
+ "./runtime/service-access.js": {
27
+ "types": "./dist/runtime/service-access.d.ts",
28
+ "import": "./dist/runtime/service-access.js"
29
+ },
30
+ "./runtime/catalog-publication.js": {
31
+ "types": "./dist/runtime/catalog-publication.d.ts",
32
+ "import": "./dist/runtime/catalog-publication.js"
33
+ },
34
+ "./runtime/catalog-publication-client.js": {
35
+ "types": "./dist/runtime/catalog-publication-client.d.ts",
36
+ "import": "./dist/runtime/catalog-publication-client.js"
37
+ },
38
+ "./runtime/service-access-guide.md": "./dist/runtime/service-access-guide.md",
26
39
  "./runtime/retained-store.js": {
27
40
  "types": "./dist/runtime/retained-store.d.ts",
28
41
  "import": "./dist/runtime/retained-store.js"