@uns-kit/core 3.0.48 → 3.0.49

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.
Files changed (26) hide show
  1. package/MIGRATIONS.md +17 -0
  2. package/README.md +1 -1
  3. package/dist/runtime/service-access-examples/README.md +3 -2
  4. package/dist/runtime/service-access-examples/background-reader/README.md +5 -1
  5. package/dist/runtime/service-access-examples/background-reader/package.json +12 -3
  6. package/dist/runtime/service-access-examples/background-reader/src/reader.mjs +6 -1
  7. package/dist/runtime/service-access-examples/catalog-summary/README.md +5 -1
  8. package/dist/runtime/service-access-examples/catalog-summary/build.mjs +1 -1
  9. package/dist/runtime/service-access-examples/catalog-summary/package.json +67 -4
  10. package/dist/runtime/service-access-examples/catalog-summary/src/index.mjs +3 -3
  11. package/dist/runtime/service-access-guide.md +38 -7
  12. package/dist/runtime/service-access-package.d.ts +6 -0
  13. package/dist/runtime/service-access-package.d.ts.map +1 -0
  14. package/dist/runtime/service-access-package.js +45 -0
  15. package/dist/runtime/service-access-package.js.map +1 -0
  16. package/dist/runtime/service-access-package.test.d.ts +2 -0
  17. package/dist/runtime/service-access-package.test.d.ts.map +1 -0
  18. package/dist/runtime/service-access-package.test.js +61 -0
  19. package/dist/runtime/service-access-package.test.js.map +1 -0
  20. package/dist/runtime/service-access.d.ts +26 -0
  21. package/dist/runtime/service-access.d.ts.map +1 -1
  22. package/dist/runtime/service-access.js +23 -0
  23. package/dist/runtime/service-access.js.map +1 -1
  24. package/package.json +1 -1
  25. package/dist/runtime/service-access-examples/background-reader/service-access.json +0 -11
  26. package/dist/runtime/service-access-examples/catalog-summary/service-access.json +0 -65
package/MIGRATIONS.md CHANGED
@@ -9,6 +9,23 @@ 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.49 - Inline Service access package declarations
13
+
14
+ Existing file declarations remain compatible; no permission is granted by this
15
+ upgrade. New services may put the declaration object directly at
16
+ `package.json.unsDatahub.serviceAccess`. Use `loadServiceAccessDeclaration` from
17
+ `@uns-kit/core/runtime/service-access-package.js` for runtime reads, or the pure
18
+ resolver/schema from `runtime/service-access.js` for host integrations. Both forms
19
+ normalize to the same semantic digest. Do not declare both `serviceAccess` and
20
+ historical `catalogAccess`.
21
+
22
+ Inline metadata requires a Controller advertising `rttServiceAccessInline`;
23
+ Controller 2.2.96 accepts only the file form. Keep the file form until the target
24
+ Controller supports inline declarations. This patch does not implement the planned
25
+ configuration-save approval UI or automatic installation grants. Administrator
26
+ review, caller authorization, MQTT ACLs and rotating token use remain separate.
27
+ Both complete examples now use inline metadata and require the matching host.
28
+
12
29
  ## 3.0.48 - Checked Service access documentation and examples
13
30
 
14
31
  This patch clarifies existing 3.0.47 contracts; no new permission mode or automatic
package/README.md CHANGED
@@ -95,7 +95,7 @@ const cron = await proc.createCrontabProxy("*/5 * * * *", { event: "tick" });
95
95
 
96
96
  ## Datahub client (last value + history)
97
97
 
98
- `UnsClient` provides a minimal REST client for the UNS OpenHub API, including batch last-value, single-topic catch-all history, and batch range endpoints. Use `ServiceTokenProvider` for Controller-managed services so credential rotation is picked up on every request. Review the service's declared scopes and resource paths before Start; never cache a managed token at application startup.
98
+ `UnsClient` provides a minimal REST client for the UNS OpenHub API, including batch last-value, single-topic catch-all history, and batch range endpoints. Use `ServiceTokenProvider` for Controller-managed services so credential rotation is picked up on every request. Declare requirements in `package.json.unsDatahub.serviceAccess` (SDK 3.0.49+ and an inline-capable Controller), or retain the compatible JSON file reference. Review the service's declared scopes and resource paths before Start; never cache a managed token at application startup.
99
99
 
100
100
  Read the [source Service access walkthrough](https://github.com/uns-openhub/uns-kit/blob/master/docs/catalog-publication-access.md)
101
101
  or bundled `@uns-kit/core/runtime/service-access-guide.md`. Complete background-reader
@@ -4,8 +4,9 @@
4
4
  - `catalog-summary/`: a catalog-only caller requests a bounded derived count; the workload has no direct source permission.
5
5
 
6
6
  Each folder is an independent, credential-free Node.js package. Copy one folder
7
- to a new project, install dependencies and build. Deploy/review/start with a
8
- Controller supporting common Service access (verified with Controller 2.2.95).
7
+ to a new project, install dependencies and build. The declarations are inline in package.json (SDK 3.0.49+). Deploy/review/start
8
+ with a Controller advertising rttServiceAccessInline; Controller 2.2.96 requires
9
+ the compatible file form instead.
9
10
  The [walkthrough](https://github.com/uns-openhub/uns-kit/blob/master/docs/catalog-publication-access.md)
10
11
  explains what the developer, administrator, Controller, SDK and caller each own.
11
12
 
@@ -1,6 +1,6 @@
1
1
  # background-reader
2
2
 
3
- A complete Node.js 22+ example using published SDK APIs from 3.0.47 onward.
3
+ A complete Node.js 22+ example using SDK APIs from 3.0.49 onward.
4
4
  Read the [Service access walkthrough](https://github.com/uns-openhub/uns-kit/blob/master/docs/catalog-publication-access.md) for the declaration, Controller review and acceptance steps.
5
5
 
6
6
  ```sh
@@ -23,3 +23,7 @@ This worker has no API or catalog. It polls five minutes of history, at most
23
23
  your own test environment; zero rows is a valid result. Its token can read that
24
24
  source, while another source must return 403. SIGINT/SIGTERM aborts the current
25
25
  request and stops polling before runtime cleanup.
26
+
27
+ The access declaration is inline in package.json. This representation requires
28
+ Controller capability `rttServiceAccessInline`; it is unsupported by 2.2.96.
29
+ The SDK also accepts a package-relative JSON file reference for older hosts.
@@ -9,7 +9,6 @@
9
9
  "main": "dist/index.js",
10
10
  "files": [
11
11
  "dist",
12
- "service-access.json",
13
12
  "config-example.json"
14
13
  ],
15
14
  "scripts": {
@@ -17,12 +16,22 @@
17
16
  "start": "node dist/index.js"
18
17
  },
19
18
  "dependencies": {
20
- "@uns-kit/core": "^3.0.47"
19
+ "@uns-kit/core": "^3.0.49"
21
20
  },
22
21
  "unsDatahub": {
23
22
  "schemaVersion": 1,
24
23
  "kind": "addon",
25
24
  "controllerCompatibility": ">=2 <3",
26
- "serviceAccess": "service-access.json"
25
+ "serviceAccess": {
26
+ "protocol": 1,
27
+ "permissions": {
28
+ "scopes": [
29
+ "read:uns"
30
+ ],
31
+ "accessRules": [
32
+ "example/background/line-1/equipment/main/temperature"
33
+ ]
34
+ }
35
+ }
27
36
  }
28
37
  }
@@ -1,6 +1,11 @@
1
1
  import { ServiceTokenProvider } from "@uns-kit/core/tools/auth/service-token-provider.js";
2
+ import { loadServiceAccessDeclaration } from "@uns-kit/core/runtime/service-access-package.js";
2
3
 
3
- export const sourceTopic = "example/background/line-1/equipment/main/temperature";
4
+ const declaration = await loadServiceAccessDeclaration(new URL("../package.json", import.meta.url));
5
+ if (declaration?.permissions.scopes.length !== 1 || declaration.permissions.scopes[0] !== "read:uns" ||
6
+ declaration.permissions.accessRules.length !== 1 || /[+#]/.test(declaration.permissions.accessRules[0]))
7
+ throw new Error("This example requires one exact declared background source.");
8
+ export const sourceTopic = declaration.permissions.accessRules[0];
4
9
 
5
10
  /** A background API read has a workload identity and no human caller token. */
6
11
  export async function readHistory(controllerBase, from, to, { tokenProvider = new ServiceTokenProvider(), fetch: request = fetch, signal } = {}) {
@@ -1,6 +1,6 @@
1
1
  # catalog-summary
2
2
 
3
- A complete Node.js 22+ example using published SDK APIs from 3.0.47 onward.
3
+ A complete Node.js 22+ example using SDK APIs from 3.0.49 onward.
4
4
  Read the [Service access walkthrough](https://github.com/uns-openhub/uns-kit/blob/master/docs/catalog-publication-access.md) for the declaration, Controller review and acceptance steps.
5
5
 
6
6
  ```sh
@@ -24,3 +24,7 @@ and approve the publication for the intended audience. Give a test reader access
24
24
  to the operation path, not the private source path. Execute a timezone-qualified
25
25
  window of at most one hour. Expect {count, hasMore}; raw source fields are excluded.
26
26
  A direct source request with the reader or service token must return 403.
27
+
28
+ The access declaration is inline in package.json. This representation requires
29
+ Controller capability `rttServiceAccessInline`; it is unsupported by 2.2.96.
30
+ The SDK also accepts a package-relative JSON file reference for older hosts.
@@ -4,4 +4,4 @@ await rm("dist", { recursive: true, force: true });
4
4
  await mkdir("dist");
5
5
  await copyFile("src/index.mjs", "dist/index.js");
6
6
  await copyFile("src/offer.mjs", "dist/offer.mjs");
7
- // service-access.json stays at the package root, as declared in package.json.
7
+ // The declaration lives in package.json, which npm always retains.
@@ -9,7 +9,6 @@
9
9
  "main": "dist/index.js",
10
10
  "files": [
11
11
  "dist",
12
- "service-access.json",
13
12
  "config-example.json"
14
13
  ],
15
14
  "scripts": {
@@ -17,13 +16,77 @@
17
16
  "start": "node dist/index.js"
18
17
  },
19
18
  "dependencies": {
20
- "@uns-kit/core": "^3.0.47",
21
- "@uns-kit/api": "^3.0.47"
19
+ "@uns-kit/core": "^3.0.49",
20
+ "@uns-kit/api": "^3.0.49"
22
21
  },
23
22
  "unsDatahub": {
24
23
  "schemaVersion": 1,
25
24
  "kind": "addon",
26
25
  "controllerCompatibility": ">=2 <3",
27
- "serviceAccess": "service-access.json"
26
+ "serviceAccess": {
27
+ "protocol": 1,
28
+ "permissions": {
29
+ "scopes": [],
30
+ "accessRules": []
31
+ },
32
+ "publications": {
33
+ "protocol": 1,
34
+ "operations": [
35
+ {
36
+ "id": "sample-count",
37
+ "title": "Temperature sample count",
38
+ "method": "GET",
39
+ "path": "/api/example/public/line-1/equipment/dataset/sample-count/json",
40
+ "mode": "approved-service",
41
+ "requestSchema": {
42
+ "type": "object",
43
+ "additionalProperties": false,
44
+ "required": [
45
+ "from",
46
+ "to"
47
+ ],
48
+ "properties": {
49
+ "from": {
50
+ "type": "string",
51
+ "format": "date-time"
52
+ },
53
+ "to": {
54
+ "type": "string",
55
+ "format": "date-time"
56
+ }
57
+ }
58
+ },
59
+ "responseSchema": {
60
+ "type": "object",
61
+ "additionalProperties": false,
62
+ "required": [
63
+ "count",
64
+ "hasMore"
65
+ ],
66
+ "properties": {
67
+ "count": {
68
+ "type": "integer",
69
+ "minimum": 0
70
+ },
71
+ "hasMore": {
72
+ "type": "boolean"
73
+ }
74
+ }
75
+ },
76
+ "sources": [
77
+ {
78
+ "id": "temperature",
79
+ "kind": "history",
80
+ "topic": "example/private/line-1/equipment/main/temperature",
81
+ "maxWindowMs": 3600000,
82
+ "maxRows": 1000
83
+ }
84
+ ],
85
+ "timeoutMs": 10000,
86
+ "maxResponseBytes": 4096
87
+ }
88
+ ]
89
+ }
90
+ }
28
91
  }
29
92
  }
@@ -2,8 +2,7 @@ import "@uns-kit/api";
2
2
  import { registerApiCatalog } from "@uns-kit/api";
3
3
  import { ConfigFile, UnsProxyProcess } from "@uns-kit/core";
4
4
  import { CatalogPublicationClient } from "@uns-kit/core/runtime/catalog-publication-client.js";
5
- import { parseServiceAccessDeclaration } from "@uns-kit/core/runtime/service-access.js";
6
- import { readFile } from "node:fs/promises";
5
+ import { loadServiceAccessDeclaration } from "@uns-kit/core/runtime/service-access-package.js";
7
6
 
8
7
  import { createSummaryOffer } from "./offer.mjs";
9
8
 
@@ -12,7 +11,8 @@ const required = (key) => {
12
11
  if (!value) throw new Error(`Missing managed runtime value: ${key}`);
13
12
  return value;
14
13
  };
15
- const declaration = parseServiceAccessDeclaration(JSON.parse(await readFile(new URL("../service-access.json", import.meta.url), "utf8")));
14
+ const declaration = await loadServiceAccessDeclaration(new URL("../package.json", import.meta.url));
15
+ if (!declaration?.publications) throw new Error("Package must declare its catalog operations.");
16
16
  const client = new CatalogPublicationClient({
17
17
  controllerBase: required("UNS_CONTROLLER_PUBLIC_BASE"),
18
18
  rttNode: required("RTT_NODE"),
@@ -1,8 +1,12 @@
1
1
  # Service access: from declaration to a running service
2
2
 
3
- For application developers and coding agents using **SDK 3.0.47 or later**.
3
+ For application developers and coding agents using **SDK 3.0.49 or later**.
4
4
  Common Service access also requires a matching Controller; these workflows were
5
- verified with Controller **2.2.95**. SDK installation alone enables no host rights.
5
+ originally verified with Controller **2.2.95** using file declarations. Inline
6
+ package declarations require a Controller advertising `rttServiceAccessInline`.
7
+ Controller 2.2.96 does not support inline declarations. SDK installation alone
8
+ enables no host rights. The configuration-save approval UI is a separate planned
9
+ Controller change; the approval steps below describe the existing released UI.
6
10
 
7
11
  The developer declares a permission ceiling and uses the SDK. An administrator
8
12
  reviews the installed release in **Micro services → instance → Service access**.
@@ -41,16 +45,22 @@ The npm core package also ships this guide at
41
45
  to your own directory before installing/building it; do not edit `node_modules`.
42
46
  Source code, not repeated prose fragments, is the complete runnable example.
43
47
 
44
- Each `package.json` points at the root declaration and retains it in the artifact:
48
+ For new services, put the declaration directly in `package.json`:
45
49
 
46
50
  ```json
47
51
  {
48
- "files": ["dist", "service-access.json", "config-example.json"],
52
+ "files": ["dist", "config-example.json"],
49
53
  "unsDatahub": {
50
54
  "schemaVersion": 1,
51
55
  "kind": "addon",
52
56
  "controllerCompatibility": ">=2 <3",
53
- "serviceAccess": "service-access.json"
57
+ "serviceAccess": {
58
+ "protocol": 1,
59
+ "permissions": {
60
+ "scopes": ["read:uns"],
61
+ "accessRules": ["example/background/line-1/equipment/main/temperature"]
62
+ }
63
+ }
54
64
  }
55
65
  }
56
66
  ```
@@ -60,6 +70,27 @@ real name/version/dependencies/scripts. The SDK is imported as published package
60
70
  no copied validators, source overlays or application-name Controller branches.
61
71
  Do not declare the historical `catalogAccess` marker alongside `serviceAccess`.
62
72
 
73
+ Existing `"serviceAccess": "service-access.json"` references remain supported.
74
+ Retain that file in `files` when using the file form, including for Controllers
75
+ without inline support. Use one representation; the normalized declaration and
76
+ its digest are identical when the content is identical. Neither form contains
77
+ tokens or an administrator's approval.
78
+
79
+ Runtime code can read the same definition without duplicating it:
80
+
81
+ ```js
82
+ import { loadServiceAccessDeclaration } from '@uns-kit/core/runtime/service-access-package.js';
83
+ const declaration = await loadServiceAccessDeclaration(new URL('../package.json', import.meta.url));
84
+ if (!declaration) throw new Error('Declare the service requirements in package.json.');
85
+ ```
86
+
87
+ This example path is for an entry point directly inside `dist/`. The loader
88
+ reads bounded JSON data; it never imports application code. File references are
89
+ relative to the package, with traversal/symlink escapes rejected. Each JSON file,
90
+ including package.json, is limited to 64 KiB. Host integrations can use the pure
91
+ `resolveServiceAccessDeclaration(packageData, readDeclaration)` with their own
92
+ bounded reader and the exported `serviceAccessPackageReferenceSchema`.
93
+
63
94
  In either copied example:
64
95
 
65
96
  ```sh
@@ -132,10 +163,10 @@ optional `publications` declaration defines `sample-count`, GET JSON, from/to wi
132
163
  a timezone, at most one hour/1000 rows from
133
164
  `example/private/line-1/equipment/main/temperature`, and a closed result containing
134
165
  only `{count, hasMore}`. The response cap is 4096 bytes. See its complete
135
- `service-access.json`; the source and response schema are reviewed data.
166
+ `package.json.unsDatahub.serviceAccess`; the source and response schema are reviewed data.
136
167
 
137
168
  `catalog-summary/src/index.mjs` reads and validates that declaration with
138
- `parseServiceAccessDeclaration`, then constructs `CatalogPublicationClient` using
169
+ `loadServiceAccessDeclaration`, then constructs `CatalogPublicationClient` using
139
170
  the real Controller-injected `RTT_NODE`, `RTT_INSTANCE_ID`, `version` and
140
171
  `UNS_CONTROLLER_PUBLIC_BASE`. It configures JWT/JWKS authentication, calls
141
172
  `api.registerCatalogRoute()` for the signed installed-launch route lease, and
@@ -0,0 +1,6 @@
1
+ import { type ServiceAccessDeclaration } from "./service-access.js";
2
+ /** Read the application's package data without importing application code.
3
+ * Pass new URL('../package.json', import.meta.url) from a dist entry point.
4
+ * Referenced declarations stay inside that package and cannot traverse symlinks. */
5
+ export declare function loadServiceAccessDeclaration(packageFile: string | URL): Promise<ServiceAccessDeclaration | null>;
6
+ //# sourceMappingURL=service-access-package.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-access-package.d.ts","sourceRoot":"","sources":["../../src/runtime/service-access-package.ts"],"names":[],"mappings":"AAKA,OAAO,EAAmC,KAAK,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAuBrG;;oFAEoF;AACpF,wBAAsB,4BAA4B,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAWtH"}
@@ -0,0 +1,45 @@
1
+ import { constants } from "node:fs";
2
+ import { lstat, open, realpath } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { resolveServiceAccessDeclaration } from "./service-access.js";
6
+ const MAX_BYTES = 65536;
7
+ async function readJson(file) {
8
+ const handle = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW);
9
+ try {
10
+ const stat = await handle.stat();
11
+ if (!stat.isFile() || stat.size > MAX_BYTES)
12
+ throw Error("service_access_file_invalid");
13
+ const buffer = Buffer.alloc(MAX_BYTES + 1);
14
+ let size = 0;
15
+ while (size < buffer.length) {
16
+ const { bytesRead } = await handle.read(buffer, size, buffer.length - size, size);
17
+ if (!bytesRead)
18
+ break;
19
+ size += bytesRead;
20
+ }
21
+ if (size > MAX_BYTES)
22
+ throw Error("service_access_file_invalid");
23
+ return JSON.parse(buffer.subarray(0, size).toString("utf8"));
24
+ }
25
+ finally {
26
+ await handle.close();
27
+ }
28
+ }
29
+ /** Read the application's package data without importing application code.
30
+ * Pass new URL('../package.json', import.meta.url) from a dist entry point.
31
+ * Referenced declarations stay inside that package and cannot traverse symlinks. */
32
+ export async function loadServiceAccessDeclaration(packageFile) {
33
+ const file = path.resolve(packageFile instanceof URL ? fileURLToPath(packageFile) : packageFile);
34
+ const root = await realpath(path.dirname(file));
35
+ return resolveServiceAccessDeclaration(await readJson(path.join(root, path.basename(file))), async (relative) => {
36
+ let current = root;
37
+ for (const segment of relative.split("/")) {
38
+ current = path.join(current, segment);
39
+ if ((await lstat(current)).isSymbolicLink())
40
+ throw Error("service_access_file_invalid");
41
+ }
42
+ return readJson(current);
43
+ });
44
+ }
45
+ //# sourceMappingURL=service-access-package.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-access-package.js","sourceRoot":"","sources":["../../src/runtime/service-access-package.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,+BAA+B,EAAiC,MAAM,qBAAqB,CAAC;AAErG,MAAM,SAAS,GAAG,KAAK,CAAC;AAExB,KAAK,UAAU,QAAQ,CAAC,IAAY;IAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;IAC3E,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,IAAI,GAAG,SAAS;YAAE,MAAM,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACxF,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;QAC3C,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,OAAO,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;YAC5B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC;YAClF,IAAI,CAAC,SAAS;gBAAE,MAAM;YACtB,IAAI,IAAI,SAAS,CAAC;QACpB,CAAC;QACD,IAAI,IAAI,GAAG,SAAS;YAAE,MAAM,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACjE,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/D,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED;;oFAEoF;AACpF,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,WAAyB;IAC1E,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,YAAY,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACjG,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAChD,OAAO,+BAA+B,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;QAC9G,IAAI,OAAO,GAAG,IAAI,CAAC;QACnB,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1C,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACtC,IAAI,CAAC,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,EAAE;gBAAE,MAAM,KAAK,CAAC,6BAA6B,CAAC,CAAC;QAC1F,CAAC;QACD,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { constants } from \"node:fs\";\nimport { lstat, open, realpath } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { resolveServiceAccessDeclaration, type ServiceAccessDeclaration } from \"./service-access.js\";\n\nconst MAX_BYTES = 65536;\n\nasync function readJson(file: string): Promise<unknown> {\n const handle = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW);\n try {\n const stat = await handle.stat();\n if (!stat.isFile() || stat.size > MAX_BYTES) throw Error(\"service_access_file_invalid\");\n const buffer = Buffer.alloc(MAX_BYTES + 1);\n let size = 0;\n while (size < buffer.length) {\n const { bytesRead } = await handle.read(buffer, size, buffer.length - size, size);\n if (!bytesRead) break;\n size += bytesRead;\n }\n if (size > MAX_BYTES) throw Error(\"service_access_file_invalid\");\n return JSON.parse(buffer.subarray(0, size).toString(\"utf8\"));\n } finally {\n await handle.close();\n }\n}\n\n/** Read the application's package data without importing application code.\n * Pass new URL('../package.json', import.meta.url) from a dist entry point.\n * Referenced declarations stay inside that package and cannot traverse symlinks. */\nexport async function loadServiceAccessDeclaration(packageFile: string | URL): Promise<ServiceAccessDeclaration | null> {\n const file = path.resolve(packageFile instanceof URL ? fileURLToPath(packageFile) : packageFile);\n const root = await realpath(path.dirname(file));\n return resolveServiceAccessDeclaration(await readJson(path.join(root, path.basename(file))), async (relative) => {\n let current = root;\n for (const segment of relative.split(\"/\")) {\n current = path.join(current, segment);\n if ((await lstat(current)).isSymbolicLink()) throw Error(\"service_access_file_invalid\");\n }\n return readJson(current);\n });\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=service-access-package.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-access-package.test.d.ts","sourceRoot":"","sources":["../../src/runtime/service-access-package.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,61 @@
1
+ import { expect, it, vi } from "vitest";
2
+ import { mkdtemp, mkdir, writeFile, symlink, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import { loadServiceAccessDeclaration } from "./service-access-package.js";
7
+ import { resolveServiceAccessDeclaration, serviceAccessDigest } from "./service-access.js";
8
+ const declaration = { protocol: 1, permissions: { scopes: ["read:uns"], accessRules: ["plant/sensor/#"] } };
9
+ const pkg = (serviceAccess) => ({ unsDatahub: { serviceAccess } });
10
+ it("normalizes inline/file representations identically without reading a file for inline data", async () => {
11
+ const read = vi.fn(async () => declaration);
12
+ const inline = await resolveServiceAccessDeclaration(pkg(declaration), read);
13
+ expect(read).not.toHaveBeenCalled();
14
+ const file = await resolveServiceAccessDeclaration(pkg("access.json"), read);
15
+ expect(read).toHaveBeenCalledExactlyOnceWith("access.json");
16
+ expect(inline).toEqual(file);
17
+ expect(serviceAccessDigest(inline)).toBe(serviceAccessDigest(file));
18
+ });
19
+ it.each([null, 42, [], {}, { ...declaration, protocol: 2 }, { ...declaration, approved: true },
20
+ "../access.json", "/access.json", "C:/access.json", "a\\access.json", "a//access.json", "./access.json"])("fails closed on an invalid package reference %j", async (reference) => {
21
+ const read = vi.fn();
22
+ await expect(resolveServiceAccessDeclaration(pkg(reference), read)).rejects.toThrow();
23
+ expect(read).not.toHaveBeenCalled();
24
+ });
25
+ it("keeps absence optional and rejects ambiguous legacy metadata before reading", async () => {
26
+ const read = vi.fn();
27
+ expect(await resolveServiceAccessDeclaration({ name: "ordinary-service" }, read)).toBeNull();
28
+ expect(await resolveServiceAccessDeclaration({ unsDatahub: { catalogAccess: "legacy.json" } }, read)).toBeNull();
29
+ await expect(resolveServiceAccessDeclaration({ unsDatahub: { serviceAccess: declaration, catalogAccess: "old.json" } }, read)).rejects.toThrow("conflict");
30
+ expect(read).not.toHaveBeenCalled();
31
+ });
32
+ it("loads package data and compatible files, refusing symlinks, oversized data and missing declarations", async () => {
33
+ const root = await mkdtemp(path.join(tmpdir(), "service-access-package-"));
34
+ const file = path.join(root, "package.json");
35
+ try {
36
+ const save = (reference) => writeFile(file, JSON.stringify(pkg(reference)));
37
+ await save(declaration);
38
+ expect(await loadServiceAccessDeclaration(pathToFileURL(file))).toEqual(declaration);
39
+ await mkdir(path.join(root, "metadata"));
40
+ await writeFile(path.join(root, "metadata/access.json"), JSON.stringify(declaration));
41
+ await save("metadata/access.json");
42
+ expect(await loadServiceAccessDeclaration(file)).toEqual(declaration);
43
+ await symlink(path.join(root, "metadata"), path.join(root, "linked"));
44
+ await save("linked/access.json");
45
+ await expect(loadServiceAccessDeclaration(file)).rejects.toThrow();
46
+ await save("missing.json");
47
+ await expect(loadServiceAccessDeclaration(file)).rejects.toThrow();
48
+ await writeFile(path.join(root, "huge.json"), " ".repeat(65537));
49
+ await save("huge.json");
50
+ await expect(loadServiceAccessDeclaration(file)).rejects.toThrow("file_invalid");
51
+ await writeFile(file, JSON.stringify({ ...pkg(declaration), description: "x".repeat(65536) }));
52
+ await expect(loadServiceAccessDeclaration(file)).rejects.toThrow("file_invalid");
53
+ await rm(file);
54
+ await symlink(path.join(root, "metadata/access.json"), file);
55
+ await expect(loadServiceAccessDeclaration(file)).rejects.toThrow();
56
+ }
57
+ finally {
58
+ await rm(root, { recursive: true, force: true });
59
+ }
60
+ });
61
+ //# sourceMappingURL=service-access-package.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-access-package.test.js","sourceRoot":"","sources":["../../src/runtime/service-access-package.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, it, vi } from \"vitest\";\nimport { mkdtemp, mkdir, writeFile, symlink, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { loadServiceAccessDeclaration } from \"./service-access-package.js\";\nimport { resolveServiceAccessDeclaration, serviceAccessDigest } from \"./service-access.js\";\n\nconst declaration = { protocol: 1, permissions: { scopes: [\"read:uns\"], accessRules: [\"plant/sensor/#\"] } };\nconst pkg = (serviceAccess: unknown) => ({ unsDatahub: { serviceAccess } });\n\nit(\"normalizes inline/file representations identically without reading a file for inline data\", async () => {\n const read = vi.fn(async () => declaration);\n const inline = await resolveServiceAccessDeclaration(pkg(declaration), read);\n expect(read).not.toHaveBeenCalled();\n const file = await resolveServiceAccessDeclaration(pkg(\"access.json\"), read);\n expect(read).toHaveBeenCalledExactlyOnceWith(\"access.json\");\n expect(inline).toEqual(file);\n expect(serviceAccessDigest(inline)).toBe(serviceAccessDigest(file));\n});\n\nit.each([null, 42, [], {}, { ...declaration, protocol: 2 }, { ...declaration, approved: true },\n \"../access.json\", \"/access.json\", \"C:/access.json\", \"a\\\\access.json\", \"a//access.json\", \"./access.json\"])\n(\"fails closed on an invalid package reference %j\", async (reference) => {\n const read = vi.fn();\n await expect(resolveServiceAccessDeclaration(pkg(reference), read)).rejects.toThrow();\n expect(read).not.toHaveBeenCalled();\n});\n\nit(\"keeps absence optional and rejects ambiguous legacy metadata before reading\", async () => {\n const read = vi.fn();\n expect(await resolveServiceAccessDeclaration({ name: \"ordinary-service\" }, read)).toBeNull();\n expect(await resolveServiceAccessDeclaration({ unsDatahub: { catalogAccess: \"legacy.json\" } }, read)).toBeNull();\n await expect(resolveServiceAccessDeclaration({ unsDatahub: { serviceAccess: declaration, catalogAccess: \"old.json\" } }, read)).rejects.toThrow(\"conflict\");\n expect(read).not.toHaveBeenCalled();\n});\n\nit(\"loads package data and compatible files, refusing symlinks, oversized data and missing declarations\", async () => {\n const root = await mkdtemp(path.join(tmpdir(), \"service-access-package-\"));\n const file = path.join(root, \"package.json\");\n try {\n const save = (reference: unknown) => writeFile(file, JSON.stringify(pkg(reference)));\n await save(declaration);\n expect(await loadServiceAccessDeclaration(pathToFileURL(file))).toEqual(declaration);\n await mkdir(path.join(root, \"metadata\"));\n await writeFile(path.join(root, \"metadata/access.json\"), JSON.stringify(declaration));\n await save(\"metadata/access.json\");\n expect(await loadServiceAccessDeclaration(file)).toEqual(declaration);\n await symlink(path.join(root, \"metadata\"), path.join(root, \"linked\"));\n await save(\"linked/access.json\");\n await expect(loadServiceAccessDeclaration(file)).rejects.toThrow();\n await save(\"missing.json\");\n await expect(loadServiceAccessDeclaration(file)).rejects.toThrow();\n await writeFile(path.join(root, \"huge.json\"), \" \".repeat(65537));\n await save(\"huge.json\");\n await expect(loadServiceAccessDeclaration(file)).rejects.toThrow(\"file_invalid\");\n await writeFile(file, JSON.stringify({ ...pkg(declaration), description: \"x\".repeat(65536) }));\n await expect(loadServiceAccessDeclaration(file)).rejects.toThrow(\"file_invalid\");\n await rm(file);\n await symlink(path.join(root, \"metadata/access.json\"), file);\n await expect(loadServiceAccessDeclaration(file)).rejects.toThrow();\n } finally {\n await rm(root, { recursive: true, force: true });\n }\n});\n"]}
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  import { type CatalogPublicationManifest } from "./catalog-publication.js";
2
3
  export declare const SERVICE_ACCESS_PROTOCOL = 1;
3
4
  export declare const SERVICE_ACCESS_MANIFEST_FIELD = "serviceAccess";
@@ -11,6 +12,31 @@ export type ServiceAccessDeclaration = {
11
12
  publications?: CatalogPublicationManifest;
12
13
  };
13
14
  export declare function parseServiceAccessDeclaration(value: unknown): ServiceAccessDeclaration;
15
+ /** Package metadata accepts one inline declaration or one safe relative JSON
16
+ * reference. Both normalize through the same parser; neither grants authority. */
17
+ export declare const serviceAccessPackageReferenceSchema: z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodObject<{
18
+ protocol: z.ZodLiteral<1>;
19
+ permissions: z.ZodObject<{
20
+ scopes: z.ZodArray<z.ZodEnum<{
21
+ "read:uns": "read:uns";
22
+ "identity:asset:publish": "identity:asset:publish";
23
+ "runtime:captures:read": "runtime:captures:read";
24
+ "runtime:captures:write": "runtime:captures:write";
25
+ "runtime:triggers:read": "runtime:triggers:read";
26
+ }>>;
27
+ accessRules: z.ZodArray<z.ZodString>;
28
+ }, z.core.$strict>;
29
+ publications: z.ZodOptional<z.ZodUnknown>;
30
+ }, z.core.$strict>, z.ZodTransform<ServiceAccessDeclaration, {
31
+ protocol: 1;
32
+ permissions: {
33
+ scopes: ("read:uns" | "identity:asset:publish" | "runtime:captures:read" | "runtime:captures:write" | "runtime:triggers:read")[];
34
+ accessRules: string[];
35
+ };
36
+ publications?: unknown;
37
+ }>>]>;
38
+ export type ServiceAccessPackageReference = string | ServiceAccessDeclaration;
39
+ export declare function resolveServiceAccessDeclaration(packageJson: unknown, readDeclaration: (relativePath: string) => Promise<unknown>): Promise<ServiceAccessDeclaration | null>;
14
40
  export declare const serviceAccessDigest: (value: unknown) => string;
15
41
  /** Publication source rules are deliberately not raw UNS permissions. A service
16
42
  * combining a background reader with a catalog must not gain their cross-product. */
@@ -1 +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"}
1
+ {"version":3,"file":"service-access.d.ts","sourceRoot":"","sources":["../../src/runtime/service-access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,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;AAED;kFACkF;AAClF,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;KAQ9C,CAAC;AAEH,MAAM,MAAM,6BAA6B,GAAG,MAAM,GAAG,wBAAwB,CAAC;AAE9E,wBAAsB,+BAA+B,CACnD,WAAW,EAAE,OAAO,EACpB,eAAe,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAC1D,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAc1C;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"}
@@ -36,6 +36,29 @@ export function parseServiceAccessDeclaration(value) {
36
36
  throw Error("service_access_capability_required");
37
37
  return { protocol: 1, permissions, ...(publications ? { publications } : {}) };
38
38
  }
39
+ /** Package metadata accepts one inline declaration or one safe relative JSON
40
+ * reference. Both normalize through the same parser; neither grants authority. */
41
+ export const serviceAccessPackageReferenceSchema = z.union([
42
+ z.string().min(1).max(512).refine((value) => !value.includes("\\") && !value.startsWith("/") &&
43
+ !/^[A-Za-z]:/.test(value) &&
44
+ value.split("/").every((part) => !!part && part !== "." && part !== ".."), "Expected a package-relative declaration path"),
45
+ envelope.transform(parseServiceAccessDeclaration),
46
+ ]);
47
+ export async function resolveServiceAccessDeclaration(packageJson, readDeclaration) {
48
+ const pkg = z.object({
49
+ unsDatahub: z.object({
50
+ serviceAccess: z.unknown().optional(),
51
+ catalogAccess: z.unknown().optional(),
52
+ }).passthrough().optional(),
53
+ }).passthrough().parse(packageJson);
54
+ const metadata = pkg.unsDatahub;
55
+ if (metadata?.serviceAccess === undefined)
56
+ return null;
57
+ if (metadata.catalogAccess !== undefined)
58
+ throw Error("service_access_declaration_conflict");
59
+ const reference = serviceAccessPackageReferenceSchema.parse(metadata.serviceAccess);
60
+ return parseServiceAccessDeclaration(typeof reference === "string" ? await readDeclaration(reference) : reference);
61
+ }
39
62
  export const serviceAccessDigest = operatorDigest;
40
63
  /** Publication source rules are deliberately not raw UNS permissions. A service
41
64
  * combining a background reader with a catalog must not gain their cross-product. */
@@ -1 +1 @@
1
- {"version":3,"file":"service-access.js","sourceRoot":"","sources":["../../src/runtime/service-access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,oBAAoB,EAAmC,+BAA+B,EAAE,MAAM,0BAA0B,CAAC;AAClI,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjE,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC;AACzC,MAAM,CAAC,MAAM,6BAA6B,GAAG,eAAe,CAAC;AAC7D,gFAAgF;AAChF,6EAA6E;AAC7E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,UAAU;IACV,wBAAwB;IACxB,uBAAuB;IACvB,wBAAwB;IACxB,uBAAuB;CACf,CAAC;AACX,MAAM,IAAI,GAAG,CAAC;KACX,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,GAAG,CAAC;KACR,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/H,MAAM,QAAQ,GAAG,CAAC;KACf,MAAM,CAAC;IACN,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACtB,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE;IAC/H,YAAY,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CACrC,CAAC;KACD,MAAM,EAAE,CAAC;AAMZ,MAAM,UAAU,6BAA6B,CAAC,KAAc;IAC1D,YAAY,CAAC,KAAK,CAAC,CAAC;IACpB,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,WAAW,GAAG,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;IACjJ,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,WAAW,CAAC,WAAW,CAAC,MAAM;QAAE,MAAM,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvH,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,+BAA+B,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC1H,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,YAAY;QAAE,MAAM,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACnG,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACjF,CAAC;AACD,MAAM,CAAC,MAAM,mBAAmB,GAAG,cAAc,CAAC;AAClD;qFACqF;AACrF,MAAM,UAAU,oBAAoB,CAAC,WAAqC;IACxE,OAAO;QACL,MAAM,EAAE,CAAC,GAAG,WAAW,CAAC,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE;QAC/G,WAAW,EAAE,CAAC,GAAG,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC;KACtD,CAAC;AACJ,CAAC;AACD,oFAAoF;AACpF,MAAM,UAAU,iBAAiB,CAAC,SAAiB,EAAE,OAAe;IAClE,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAC1B,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAC9B,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,OAAO,KAAK,CAAC;QACrD,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,OAAO,KAAK,CAAC;IAClD,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;AACxB,CAAC","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"]}
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}\n\n/** Package metadata accepts one inline declaration or one safe relative JSON\n * reference. Both normalize through the same parser; neither grants authority. */\nexport const serviceAccessPackageReferenceSchema = z.union([\n z.string().min(1).max(512).refine(\n (value) => !value.includes(\"\\\\\") && !value.startsWith(\"/\") &&\n !/^[A-Za-z]:/.test(value) &&\n value.split(\"/\").every((part) => !!part && part !== \".\" && part !== \"..\"),\n \"Expected a package-relative declaration path\",\n ),\n envelope.transform(parseServiceAccessDeclaration),\n]);\n\nexport type ServiceAccessPackageReference = string | ServiceAccessDeclaration;\n\nexport async function resolveServiceAccessDeclaration(\n packageJson: unknown,\n readDeclaration: (relativePath: string) => Promise<unknown>,\n): Promise<ServiceAccessDeclaration | null> {\n const pkg = z.object({\n unsDatahub: z.object({\n serviceAccess: z.unknown().optional(),\n catalogAccess: z.unknown().optional(),\n }).passthrough().optional(),\n }).passthrough().parse(packageJson);\n const metadata = pkg.unsDatahub;\n if (metadata?.serviceAccess === undefined) return null;\n if (metadata.catalogAccess !== undefined) throw Error(\"service_access_declaration_conflict\");\n const reference = serviceAccessPackageReferenceSchema.parse(metadata.serviceAccess);\n return parseServiceAccessDeclaration(\n typeof reference === \"string\" ? await readDeclaration(reference) : reference,\n );\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uns-kit/core",
3
- "version": "3.0.48",
3
+ "version": "3.0.49",
4
4
  "description": "Core utilities and runtime building blocks for UNS-based realtime transformers.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,11 +0,0 @@
1
- {
2
- "protocol": 1,
3
- "permissions": {
4
- "scopes": [
5
- "read:uns"
6
- ],
7
- "accessRules": [
8
- "example/background/line-1/equipment/main/temperature"
9
- ]
10
- }
11
- }
@@ -1,65 +0,0 @@
1
- {
2
- "protocol": 1,
3
- "permissions": {
4
- "scopes": [],
5
- "accessRules": []
6
- },
7
- "publications": {
8
- "protocol": 1,
9
- "operations": [
10
- {
11
- "id": "sample-count",
12
- "title": "Temperature sample count",
13
- "method": "GET",
14
- "path": "/api/example/public/line-1/equipment/dataset/sample-count/json",
15
- "mode": "approved-service",
16
- "requestSchema": {
17
- "type": "object",
18
- "additionalProperties": false,
19
- "required": [
20
- "from",
21
- "to"
22
- ],
23
- "properties": {
24
- "from": {
25
- "type": "string",
26
- "format": "date-time"
27
- },
28
- "to": {
29
- "type": "string",
30
- "format": "date-time"
31
- }
32
- }
33
- },
34
- "responseSchema": {
35
- "type": "object",
36
- "additionalProperties": false,
37
- "required": [
38
- "count",
39
- "hasMore"
40
- ],
41
- "properties": {
42
- "count": {
43
- "type": "integer",
44
- "minimum": 0
45
- },
46
- "hasMore": {
47
- "type": "boolean"
48
- }
49
- }
50
- },
51
- "sources": [
52
- {
53
- "id": "temperature",
54
- "kind": "history",
55
- "topic": "example/private/line-1/equipment/main/temperature",
56
- "maxWindowMs": 3600000,
57
- "maxRows": 1000
58
- }
59
- ],
60
- "timeoutMs": 10000,
61
- "maxResponseBytes": 4096
62
- }
63
- ]
64
- }
65
- }