@uns-kit/core 3.0.40 → 3.0.42
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 +38 -0
- package/README.md +8 -0
- package/dist/runtime/compatibility/energenti-authorization-v1.d.ts +2 -2
- package/dist/runtime/configuration-group-authorization.d.ts +2 -2
- package/dist/runtime/mqtt-handover-state.d.ts +10 -2
- package/dist/runtime/mqtt-handover-state.d.ts.map +1 -1
- package/dist/runtime/mqtt-handover-state.js +8 -1
- package/dist/runtime/mqtt-handover-state.js.map +1 -1
- package/dist/runtime/operator-api.d.ts +74 -0
- package/dist/runtime/operator-api.d.ts.map +1 -0
- package/dist/runtime/operator-api.js +241 -0
- package/dist/runtime/operator-api.js.map +1 -0
- package/dist/runtime/process-group-authorization.d.ts +2 -2
- package/dist/runtime/process-group-control-contract.d.ts +1 -1
- package/dist/uns/uns-interfaces.d.ts +13 -1
- package/dist/uns/uns-interfaces.d.ts.map +1 -1
- package/dist/uns/uns-interfaces.js +12 -0
- package/dist/uns/uns-interfaces.js.map +1 -1
- package/dist/uns/uns-packet.d.ts.map +1 -1
- package/dist/uns/uns-packet.js +4 -9
- package/dist/uns/uns-packet.js.map +1 -1
- package/dist/uns-mqtt/uns-mqtt-proxy.d.ts.map +1 -1
- package/dist/uns-mqtt/uns-mqtt-proxy.js +16 -4
- package/dist/uns-mqtt/uns-mqtt-proxy.js.map +1 -1
- package/package.json +1 -1
package/MIGRATIONS.md
CHANGED
|
@@ -9,6 +9,44 @@ 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.42 - Opt-in registered operator APIs and Service API body metadata
|
|
13
|
+
|
|
14
|
+
Existing applications require no operator-API change when crossing this version.
|
|
15
|
+
To expose a protected read or command to generic Controller orchestration, declare
|
|
16
|
+
`operatorOperations` on the existing GET/POST registration. Use bounded request
|
|
17
|
+
schemas and, for commands, a UUID request ID, write domain and application-owned
|
|
18
|
+
durable idempotency receipt. See the shared
|
|
19
|
+
[operator API contract](https://github.com/uns-openhub/uns-kit/blob/master/docs/operator-api.md)
|
|
20
|
+
and [receipt example](https://github.com/uns-openhub/uns-kit/blob/master/docs/examples/operator-command-receipt.ts).
|
|
21
|
+
|
|
22
|
+
Declarations grant no permissions. Delegated operations require current Controller
|
|
23
|
+
ownership, explicit workload tools and a short-lived purpose-specific credential;
|
|
24
|
+
the SDK checks current parent authorization before invoking the handler. Ordinary
|
|
25
|
+
API bearer credentials remain separate. A missing Controller authority fails a
|
|
26
|
+
delegated call closed without changing standalone drain/signal cleanup. Reconcile
|
|
27
|
+
unknown command outcomes with the same actor/request ID and exact payload instead
|
|
28
|
+
of issuing a new command. Never mark an uncertain effect as not applied.
|
|
29
|
+
|
|
30
|
+
Shared POST/PUT/PATCH/DELETE registration also synchronizes Service API request-body
|
|
31
|
+
metadata with an explicitly supplied OpenAPI body. Existing catalog schemas and
|
|
32
|
+
examples are retained; the full JSON Schema remains in OpenAPI. No application
|
|
33
|
+
handler or permission change is required for that metadata correction.
|
|
34
|
+
|
|
35
|
+
## 3.0.41 - Explicit typed null scalar clears
|
|
36
|
+
|
|
37
|
+
Null publication is opt-in per MQTT publisher (`allowNullData: true`). A null
|
|
38
|
+
`data.value` must declare `data.valueType: "string" | "number"`; it is an explicit
|
|
39
|
+
clear with its own timestamp, not an omitted update, zero, empty string, object or
|
|
40
|
+
delete command. Ordinary scalar inference and non-opted publishers stay unchanged.
|
|
41
|
+
Update every required reader to a compatible SDK before enabling a producer.
|
|
42
|
+
|
|
43
|
+
The nullable opt-in is bound into peer compatibility without changing ordinary
|
|
44
|
+
format 3 hashes. Old/non-opted peers reject before the source relinquishes ownership.
|
|
45
|
+
Snapshots retain the null plus declared scalar type; null cannot be differentiated.
|
|
46
|
+
Keep the normal reviewed operation/recovery rules. Do not relabel a state contract
|
|
47
|
+
to bypass mixed-peer rejection. See the
|
|
48
|
+
[contract and generic example](https://github.com/uns-openhub/uns-kit/blob/master/docs/mqtt-null-data.md).
|
|
49
|
+
|
|
12
50
|
## 3.0.40 - Managed asynchronous MQTT input and bounded RAM output pressure
|
|
13
51
|
|
|
14
52
|
When crossing `<3.0.40` to `>=3.0.40`, legacy input events and queue rejection
|
package/README.md
CHANGED
|
@@ -273,6 +273,14 @@ and apply only the migrations whose version boundaries are crossed. The
|
|
|
273
273
|
`3.0.0` migration documents the MQTT table-column object contract, while the
|
|
274
274
|
`2.0.71` migration documents MQTT publish and shutdown ownership changes.
|
|
275
275
|
|
|
276
|
+
## Explicit scalar clears (3.0.41)
|
|
277
|
+
|
|
278
|
+
An opted-in publisher can send a typed `null` clear with its own timestamp.
|
|
279
|
+
Read the [generic contract and examples](https://github.com/uns-openhub/uns-kit/blob/master/docs/mqtt-null-data.md)
|
|
280
|
+
and the reader-first upgrade requirements in `MIGRATIONS.md`. Ordinary scalar
|
|
281
|
+
publications stay unchanged; no business meaning or universal exactly-once
|
|
282
|
+
guarantee is inferred by the SDK.
|
|
283
|
+
|
|
276
284
|
## State-preserving MQTT handover
|
|
277
285
|
|
|
278
286
|
Correlated-v2 MQTT handover in3.0.38 verifies a uniquely shared input boundary
|
|
@@ -17,8 +17,8 @@ export declare const hvAuthorizationSchema: z.ZodObject<{
|
|
|
17
17
|
[x: string]: string;
|
|
18
18
|
}>;
|
|
19
19
|
role: z.ZodEnum<{
|
|
20
|
-
source: "source";
|
|
21
20
|
target: "target";
|
|
21
|
+
source: "source";
|
|
22
22
|
receiver: "receiver";
|
|
23
23
|
}>;
|
|
24
24
|
configurationSha256: z.ZodString;
|
|
@@ -51,8 +51,8 @@ export declare const processGroupAuthorizationSchema: z.ZodObject<{
|
|
|
51
51
|
instanceId: z.ZodString;
|
|
52
52
|
processName: z.ZodString;
|
|
53
53
|
role: z.ZodEnum<{
|
|
54
|
-
source: "source";
|
|
55
54
|
target: "target";
|
|
55
|
+
source: "source";
|
|
56
56
|
receiver: "receiver";
|
|
57
57
|
}>;
|
|
58
58
|
configurationSha256: z.ZodString;
|
|
@@ -21,8 +21,8 @@ export declare const configurationGroupAuthorizationSchema: z.ZodObject<{
|
|
|
21
21
|
instanceId: z.ZodString;
|
|
22
22
|
processName: z.ZodString;
|
|
23
23
|
role: z.ZodEnum<{
|
|
24
|
-
source: "source";
|
|
25
24
|
target: "target";
|
|
25
|
+
source: "source";
|
|
26
26
|
receiver: "receiver";
|
|
27
27
|
}>;
|
|
28
28
|
configurationSha256: z.ZodString;
|
|
@@ -40,7 +40,7 @@ export declare function verifyConfigurationGroupPolicy(context: ProcessGroupAuth
|
|
|
40
40
|
file: string;
|
|
41
41
|
directory: string;
|
|
42
42
|
journalId: string;
|
|
43
|
-
role: "
|
|
43
|
+
role: "target" | "source" | "receiver";
|
|
44
44
|
groupId: string;
|
|
45
45
|
groupProcessName: string;
|
|
46
46
|
namespace: string;
|
|
@@ -11,7 +11,11 @@ export declare const mqttProxyHandoverStateSchema: z.ZodObject<{
|
|
|
11
11
|
compatibility: z.ZodString;
|
|
12
12
|
values: z.ZodArray<z.ZodObject<{
|
|
13
13
|
topic: z.ZodString;
|
|
14
|
-
value: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodArray<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>]
|
|
14
|
+
value: z.ZodNullable<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodArray<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>]>>;
|
|
15
|
+
valueType: z.ZodOptional<z.ZodEnum<{
|
|
16
|
+
string: "string";
|
|
17
|
+
number: "number";
|
|
18
|
+
}>>;
|
|
15
19
|
uom: z.ZodOptional<z.ZodString>;
|
|
16
20
|
timestamp: z.ZodString;
|
|
17
21
|
}, z.core.$strict>>;
|
|
@@ -33,7 +37,11 @@ export declare const mqttHandoverStateSchema: z.ZodObject<{
|
|
|
33
37
|
compatibility: z.ZodString;
|
|
34
38
|
values: z.ZodArray<z.ZodObject<{
|
|
35
39
|
topic: z.ZodString;
|
|
36
|
-
value: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodArray<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>]
|
|
40
|
+
value: z.ZodNullable<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodArray<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>]>>;
|
|
41
|
+
valueType: z.ZodOptional<z.ZodEnum<{
|
|
42
|
+
string: "string";
|
|
43
|
+
number: "number";
|
|
44
|
+
}>>;
|
|
37
45
|
uom: z.ZodOptional<z.ZodString>;
|
|
38
46
|
timestamp: z.ZodString;
|
|
39
47
|
}, z.core.$strict>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mqtt-handover-state.d.ts","sourceRoot":"","sources":["../../src/runtime/mqtt-handover-state.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAC5C,eAAO,MAAM,6BAA6B,QAAc,CAAC;AAIzD,eAAO,MAAM,uBAAuB;;;;mBAI8E,CAAC;AACnH,eAAO,MAAM,4BAA4B
|
|
1
|
+
{"version":3,"file":"mqtt-handover-state.d.ts","sourceRoot":"","sources":["../../src/runtime/mqtt-handover-state.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAC5C,eAAO,MAAM,6BAA6B,QAAc,CAAC;AAIzD,eAAO,MAAM,uBAAuB;;;;mBAI8E,CAAC;AACnH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;kBAgBvC,CAAC;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;kBAOlC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAClF,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACxE,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAMxE;AACD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAExE;AACD,eAAO,MAAM,4BAA4B;;kBAAoD,CAAC;AAC9F,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAS7E"}
|
|
@@ -19,7 +19,14 @@ export const mqttHandoverOfferSchema = z.array(z.strictObject({
|
|
|
19
19
|
export const mqttProxyHandoverStateSchema = z.strictObject({
|
|
20
20
|
instanceName: z.string().min(1).max(256),
|
|
21
21
|
compatibility: z.string().regex(/^[a-f0-9]{64}$/),
|
|
22
|
-
values: z.array(z.strictObject({
|
|
22
|
+
values: z.array(z.strictObject({
|
|
23
|
+
topic: text, value: value.nullable(),
|
|
24
|
+
valueType: z.enum(["string", "number"]).optional(),
|
|
25
|
+
uom: z.string().max(256).optional(), timestamp: z.string().datetime(),
|
|
26
|
+
}).superRefine((entry, ctx) => {
|
|
27
|
+
if (entry.value === null && !entry.valueType)
|
|
28
|
+
ctx.addIssue({ code: "custom", message: "Null state requires its declared scalar type" });
|
|
29
|
+
})).max(10000),
|
|
23
30
|
sequences: z.array(z.tuple([text, z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER)])).max(10000),
|
|
24
31
|
cursor: inputCursorSchema,
|
|
25
32
|
}).superRefine((state, ctx) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mqtt-handover-state.js","sourceRoot":"","sources":["../../src/runtime/mqtt-handover-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAElF,+EAA+E;AAC/E,oFAAoF;AACpF,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAC5C,MAAM,CAAC,MAAM,6BAA6B,GAAG,IAAI,GAAG,IAAI,CAAC;AACzD,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;AACzE,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACrE,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC3D,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC;IAC5D,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IACxC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC;IACjD,YAAY,EAAE,kBAAkB,CAAC,QAAQ,EAAE;CAC5C,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;AACnH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC,YAAY,CAAC;IACzD,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IACxC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC;IACjD,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"mqtt-handover-state.js","sourceRoot":"","sources":["../../src/runtime/mqtt-handover-state.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport { cloneApplicationHandoverState } from './application-handover-state.js';\nimport { inputCursorSchema, inputPreviewSchema } from './input-queue-boundary.js';\n\n// Same contract for every SDK MQTT consumer. No application/profile selectors.\n// Bound into peer compatibility so older cursor/state readers reject before freeze.\nexport const MQTT_HANDOVER_STATE_FORMAT = 3;\nexport const MQTT_HANDOVER_STATE_MAX_BYTES = 1024 * 1024;\nconst text = z.string().min(1).max(65535).refine(v => !v.includes('\\0'));\nconst scalar = z.union([z.number().finite(), z.string().max(65535)]);\nconst value = z.union([scalar, z.array(scalar).max(4096)]);\nexport const mqttHandoverOfferSchema = z.array(z.strictObject({\n instanceName: z.string().min(1).max(256),\n compatibility: z.string().regex(/^[a-f0-9]{64}$/),\n inputPreview: inputPreviewSchema.optional(),\n})).max(64).refine(offer => new Set(offer.map(p => p.instanceName)).size === offer.length, 'Duplicate MQTT proxy');\nexport const mqttProxyHandoverStateSchema = z.strictObject({\n instanceName: z.string().min(1).max(256),\n compatibility: z.string().regex(/^[a-f0-9]{64}$/),\n values: z.array(z.strictObject({\n topic: text, value: value.nullable(),\n valueType: z.enum([\"string\", \"number\"]).optional(),\n uom: z.string().max(256).optional(), timestamp: z.string().datetime(),\n }).superRefine((entry, ctx) => {\n if (entry.value === null && !entry.valueType)\n ctx.addIssue({ code: \"custom\", message: \"Null state requires its declared scalar type\" });\n })).max(10000),\n sequences: z.array(z.tuple([text, z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER)])).max(10000),\n cursor: inputCursorSchema,\n}).superRefine((state, ctx) => {\n for (const keys of [state.values.map(v => v.topic), state.sequences.map(v => v[0])])\n if (new Set(keys).size !== keys.length) ctx.addIssue({ code: 'custom', message: 'Duplicate MQTT state key' });\n});\nexport const mqttHandoverStateSchema = z.strictObject({\n protocol: z.literal(1),\n application: z.strictObject({ contract: z.string().min(1).max(128), data: z.unknown() }).optional(),\n proxies: z.array(mqttProxyHandoverStateSchema).max(64),\n}).superRefine((state, ctx) => {\n if (new Set(state.proxies.map(p => p.instanceName)).size !== state.proxies.length)\n ctx.addIssue({ code: 'custom', message: 'Duplicate MQTT proxy' });\n});\nexport type MqttProxyHandoverState = z.infer<typeof mqttProxyHandoverStateSchema>;\nexport type MqttHandoverState = z.infer<typeof mqttHandoverStateSchema>;\nexport function parseMqttHandoverState(input: unknown): MqttHandoverState {\n const json = JSON.stringify(input);\n if (!json || Buffer.byteLength(json) > MQTT_HANDOVER_STATE_MAX_BYTES) throw Error('MQTT handover state exceeds its bound');\n const state = mqttHandoverStateSchema.parse(input);\n if (state.application) state.application.data = cloneApplicationHandoverState(state.application.data);\n return state;\n}\nexport function mqttHandoverStateDigest(state: MqttHandoverState): string {\n return createHash('sha256').update(JSON.stringify(parseMqttHandoverState(state))).digest('hex');\n}\nexport const mqttHandoverCapabilitySchema = z.strictObject({ protocolVersion: z.literal(2) });\nexport function readMqttHandoverCapability(packageRoot: string): 2 | undefined {\n const file = path.join(packageRoot, 'package.json');\n let stat: fs.Stats;\n try { stat = fs.lstatSync(file); } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined; throw error; }\n if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1048576) throw Error('Unsafe MQTT handover descriptor');\n const descriptor = JSON.parse(fs.readFileSync(file, 'utf8'))?.unsDatahub?.mqttHandover;\n if (descriptor === undefined) return undefined;\n mqttHandoverCapabilitySchema.parse(descriptor);\n return 2;\n}\n"]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Explicit opt-in. OpenAPI paths alone are never operator authority. */
|
|
3
|
+
export declare const OPERATOR_API_EXTENSION = "x-uns-operator-operations";
|
|
4
|
+
export declare const OPERATOR_DELEGATION_CLAIM = "unsOperatorDelegation";
|
|
5
|
+
export declare const OPERATOR_OUTCOME_HEADER = "x-uns-operator-outcome";
|
|
6
|
+
export declare const OPERATOR_AUTH_SCHEME = "UNS-Operator";
|
|
7
|
+
export declare const OPERATOR_JWT_TYPE = "uns-operator-api+jwt";
|
|
8
|
+
export declare const OPERATOR_JWT_AUDIENCE = "uns-operator-api";
|
|
9
|
+
export declare const OPERATOR_JWT_ALGORITHM = "PS256";
|
|
10
|
+
export declare const OPERATOR_JSON_MAX_BYTES = 65536;
|
|
11
|
+
export declare class OperatorApiError extends Error {
|
|
12
|
+
readonly code: string;
|
|
13
|
+
readonly status: number;
|
|
14
|
+
constructor(code: string, status?: number);
|
|
15
|
+
}
|
|
16
|
+
/** Canonical bounded JSON is shared by reviews, wire claims and receipts.
|
|
17
|
+
* Never hashes JSON with dropped undefined values or non-finite numbers. */
|
|
18
|
+
export declare function operatorJson(value: unknown): string;
|
|
19
|
+
export declare const operatorDigest: (value: unknown) => string;
|
|
20
|
+
/** Protocol 1 deliberately excludes references, executable regex and extensions.
|
|
21
|
+
* UUID format already enforces the standard UUID constraint without a pattern. */
|
|
22
|
+
export declare function operatorRequestSchema(value: unknown): Record<string, unknown>;
|
|
23
|
+
declare const definitionSchema: z.ZodObject<{
|
|
24
|
+
protocol: z.ZodLiteral<1>;
|
|
25
|
+
id: z.ZodString;
|
|
26
|
+
title: z.ZodString;
|
|
27
|
+
kind: z.ZodEnum<{
|
|
28
|
+
read: "read";
|
|
29
|
+
command: "command";
|
|
30
|
+
}>;
|
|
31
|
+
requestSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
32
|
+
writeDomain: z.ZodOptional<z.ZodString>;
|
|
33
|
+
replay: z.ZodOptional<z.ZodLiteral<"request-id-v1">>;
|
|
34
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
35
|
+
maxResponseBytes: z.ZodDefault<z.ZodNumber>;
|
|
36
|
+
}, z.core.$strict>;
|
|
37
|
+
export type OperatorOperationDefinition = z.input<typeof definitionSchema>;
|
|
38
|
+
export type OperatorOperation = z.output<typeof definitionSchema> & {
|
|
39
|
+
method: "GET" | "POST";
|
|
40
|
+
path: string;
|
|
41
|
+
};
|
|
42
|
+
export declare function operatorOperation(definition: unknown, method: unknown, path: unknown): OperatorOperation;
|
|
43
|
+
export declare function operatorOperationFromWire(input: unknown): OperatorOperation;
|
|
44
|
+
export declare function assertOperatorPayload(operation: OperatorOperation, value: unknown): void;
|
|
45
|
+
export declare const operatorDeploymentSchema: z.ZodObject<{
|
|
46
|
+
controllerName: z.ZodString;
|
|
47
|
+
rttNode: z.ZodString;
|
|
48
|
+
version: z.ZodString;
|
|
49
|
+
instanceId: z.ZodString;
|
|
50
|
+
launchId: z.ZodUUID;
|
|
51
|
+
}, z.core.$strict>;
|
|
52
|
+
export type OperatorDeployment = z.infer<typeof operatorDeploymentSchema>;
|
|
53
|
+
export declare const operatorDelegationSchema: z.ZodObject<{
|
|
54
|
+
protocol: z.ZodLiteral<1>;
|
|
55
|
+
operationId: z.ZodString;
|
|
56
|
+
operationDigest: z.ZodString;
|
|
57
|
+
requestDigest: z.ZodString;
|
|
58
|
+
target: z.ZodObject<{
|
|
59
|
+
controllerName: z.ZodString;
|
|
60
|
+
rttNode: z.ZodString;
|
|
61
|
+
version: z.ZodString;
|
|
62
|
+
instanceId: z.ZodString;
|
|
63
|
+
launchId: z.ZodUUID;
|
|
64
|
+
}, z.core.$strict>;
|
|
65
|
+
tool: z.ZodEnum<{
|
|
66
|
+
controller_read_runtime_operator_operation: "controller_read_runtime_operator_operation";
|
|
67
|
+
controller_execute_runtime_operator_command: "controller_execute_runtime_operator_command";
|
|
68
|
+
}>;
|
|
69
|
+
}, z.core.$strict>;
|
|
70
|
+
export type OperatorDelegation = z.infer<typeof operatorDelegationSchema>;
|
|
71
|
+
/** Called only AFTER normal JWT cryptographic, expiry and route-grant checks. */
|
|
72
|
+
export declare function assertOperatorDelegation(claims: Record<string, unknown>, operation: OperatorOperation, payload: unknown, deployment: OperatorDeployment, nowSeconds?: number): OperatorDelegation;
|
|
73
|
+
export {};
|
|
74
|
+
//# sourceMappingURL=operator-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operator-api.d.ts","sourceRoot":"","sources":["../../src/runtime/operator-api.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,yEAAyE;AACzE,eAAO,MAAM,sBAAsB,8BAA8B,CAAC;AAClE,eAAO,MAAM,yBAAyB,0BAA0B,CAAC;AACjE,eAAO,MAAM,uBAAuB,2BAA2B,CAAC;AAChE,eAAO,MAAM,oBAAoB,iBAAiB,CAAC;AACnD,eAAO,MAAM,iBAAiB,yBAAyB,CAAC;AACxD,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AACxD,eAAO,MAAM,sBAAsB,UAAU,CAAC;AAC9C,eAAO,MAAM,uBAAuB,QAAQ,CAAC;AAiB7C,qBAAa,gBAAiB,SAAQ,KAAK;aAEvB,IAAI,EAAE,MAAM;aACZ,MAAM;gBADN,IAAI,EAAE,MAAM,EACZ,MAAM,SAAM;CAI/B;AAED;4EAC4E;AAC5E,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAyBnD;AACD,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,MAAwE,CAAC;AAEzH;kFACkF;AAClF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoE7E;AACD,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;kBAiBlB,CAAC;AACL,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC3E,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,gBAAgB,CAAC,GAAG;IAAE,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAC7G,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,iBAAiB,CAKxG;AACD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAI3E;AACD,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,iBAAiB,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAWxF;AACD,eAAO,MAAM,wBAAwB;;;;;;kBAQ1B,CAAC;AACZ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAC1E,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;kBAS1B,CAAC;AACZ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,iFAAiF;AACjF,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,SAAS,EAAE,iBAAiB,EAC5B,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,kBAAkB,EAC9B,UAAU,SAAgC,GACzC,kBAAkB,CAkCpB"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
/** Explicit opt-in. OpenAPI paths alone are never operator authority. */
|
|
4
|
+
export const OPERATOR_API_EXTENSION = "x-uns-operator-operations";
|
|
5
|
+
export const OPERATOR_DELEGATION_CLAIM = "unsOperatorDelegation";
|
|
6
|
+
export const OPERATOR_OUTCOME_HEADER = "x-uns-operator-outcome";
|
|
7
|
+
export const OPERATOR_AUTH_SCHEME = "UNS-Operator";
|
|
8
|
+
export const OPERATOR_JWT_TYPE = "uns-operator-api+jwt";
|
|
9
|
+
export const OPERATOR_JWT_AUDIENCE = "uns-operator-api";
|
|
10
|
+
export const OPERATOR_JWT_ALGORITHM = "PS256";
|
|
11
|
+
export const OPERATOR_JSON_MAX_BYTES = 65536;
|
|
12
|
+
const identifier = z
|
|
13
|
+
.string()
|
|
14
|
+
.min(1)
|
|
15
|
+
.max(120)
|
|
16
|
+
.regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/);
|
|
17
|
+
const digest = z.string().regex(/^[a-f0-9]{64}$/);
|
|
18
|
+
const resource = z.string().min(1).max(256);
|
|
19
|
+
const localPath = z
|
|
20
|
+
.string()
|
|
21
|
+
.min(1)
|
|
22
|
+
.max(2048)
|
|
23
|
+
.refine((value) => /^\/[a-zA-Z0-9_./-]+$/.test(value) && !value.includes("//") && !value.split("/").some((part) => part === "." || part === ".."), "Expected an exact local API path");
|
|
24
|
+
export class OperatorApiError extends Error {
|
|
25
|
+
code;
|
|
26
|
+
status;
|
|
27
|
+
constructor(code, status = 400) {
|
|
28
|
+
super(code);
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.status = status;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Canonical bounded JSON is shared by reviews, wire claims and receipts.
|
|
34
|
+
* Never hashes JSON with dropped undefined values or non-finite numbers. */
|
|
35
|
+
export function operatorJson(value) {
|
|
36
|
+
let nodes = 0;
|
|
37
|
+
const visit = (item, depth) => {
|
|
38
|
+
if (++nodes > 4096 || depth > 20)
|
|
39
|
+
throw new OperatorApiError("operator_json_budget");
|
|
40
|
+
if (item === null || typeof item === "boolean")
|
|
41
|
+
return item;
|
|
42
|
+
if (typeof item === "number" && Number.isFinite(item))
|
|
43
|
+
return Object.is(item, -0) ? 0 : item;
|
|
44
|
+
if (typeof item === "string" && item.length <= 32768)
|
|
45
|
+
return item;
|
|
46
|
+
if (Array.isArray(item) && item.length <= 1024)
|
|
47
|
+
return item.map((entry) => visit(entry, depth + 1));
|
|
48
|
+
if (item && typeof item === "object" && [Object.prototype, null].includes(Object.getPrototypeOf(item))) {
|
|
49
|
+
const entries = Object.entries(item);
|
|
50
|
+
if (entries.length > 128)
|
|
51
|
+
throw new OperatorApiError("operator_json_budget");
|
|
52
|
+
return Object.fromEntries(entries
|
|
53
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
54
|
+
.map(([key, entry]) => {
|
|
55
|
+
if (key.length > 256 || ["__proto__", "prototype", "constructor"].includes(key))
|
|
56
|
+
throw new OperatorApiError("operator_json_key");
|
|
57
|
+
return [key, visit(entry, depth + 1)];
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
throw new OperatorApiError("operator_json_value");
|
|
61
|
+
};
|
|
62
|
+
const json = JSON.stringify(visit(value, 0));
|
|
63
|
+
if (Buffer.byteLength(json, "utf8") > OPERATOR_JSON_MAX_BYTES)
|
|
64
|
+
throw new OperatorApiError("operator_json_budget");
|
|
65
|
+
return json;
|
|
66
|
+
}
|
|
67
|
+
export const operatorDigest = (value) => createHash("sha256").update(operatorJson(value)).digest("hex");
|
|
68
|
+
/** Protocol 1 deliberately excludes references, executable regex and extensions.
|
|
69
|
+
* UUID format already enforces the standard UUID constraint without a pattern. */
|
|
70
|
+
export function operatorRequestSchema(value) {
|
|
71
|
+
operatorJson(value);
|
|
72
|
+
const vocabulary = new Set([
|
|
73
|
+
"$schema",
|
|
74
|
+
"type",
|
|
75
|
+
"properties",
|
|
76
|
+
"required",
|
|
77
|
+
"additionalProperties",
|
|
78
|
+
"items",
|
|
79
|
+
"minItems",
|
|
80
|
+
"maxItems",
|
|
81
|
+
"minLength",
|
|
82
|
+
"maxLength",
|
|
83
|
+
"minimum",
|
|
84
|
+
"maximum",
|
|
85
|
+
"exclusiveMinimum",
|
|
86
|
+
"exclusiveMaximum",
|
|
87
|
+
"multipleOf",
|
|
88
|
+
"format",
|
|
89
|
+
"const",
|
|
90
|
+
"enum",
|
|
91
|
+
"oneOf",
|
|
92
|
+
"anyOf",
|
|
93
|
+
"allOf",
|
|
94
|
+
"description",
|
|
95
|
+
"title",
|
|
96
|
+
"default",
|
|
97
|
+
"examples",
|
|
98
|
+
"pattern",
|
|
99
|
+
]);
|
|
100
|
+
let count = 0;
|
|
101
|
+
function schema(input, depth = 0) {
|
|
102
|
+
if (++count > 256 || depth > 12 || !input || typeof input !== "object" || Array.isArray(input))
|
|
103
|
+
throw new OperatorApiError("operator_schema_budget");
|
|
104
|
+
const source = input;
|
|
105
|
+
for (const key of Object.keys(source))
|
|
106
|
+
if (!vocabulary.has(key))
|
|
107
|
+
throw new OperatorApiError("operator_schema_keyword");
|
|
108
|
+
const out = { ...source };
|
|
109
|
+
if ("pattern" in out) {
|
|
110
|
+
if (out["format"] !== "uuid")
|
|
111
|
+
throw new OperatorApiError("operator_schema_pattern");
|
|
112
|
+
delete out["pattern"];
|
|
113
|
+
}
|
|
114
|
+
if (out["format"] && !["uuid", "date-time", "date", "time", "email", "uri"].includes(String(out["format"])))
|
|
115
|
+
throw new OperatorApiError("operator_schema_format");
|
|
116
|
+
if (source["properties"]) {
|
|
117
|
+
if (source["additionalProperties"] !== false || typeof source["properties"] !== "object" || Array.isArray(source["properties"]))
|
|
118
|
+
throw new OperatorApiError("operator_schema_object_must_be_closed");
|
|
119
|
+
out["properties"] = Object.fromEntries(Object.entries(source["properties"]).map(([key, item]) => [key, schema(item, depth + 1)]));
|
|
120
|
+
}
|
|
121
|
+
if (source["type"] === "object" && source["additionalProperties"] !== false)
|
|
122
|
+
throw new OperatorApiError("operator_schema_object_must_be_closed");
|
|
123
|
+
if (source["additionalProperties"] !== undefined && source["additionalProperties"] !== false)
|
|
124
|
+
throw new OperatorApiError("operator_schema_object_must_be_closed");
|
|
125
|
+
if (source["items"])
|
|
126
|
+
out["items"] = schema(source["items"], depth + 1);
|
|
127
|
+
for (const key of ["oneOf", "anyOf", "allOf"])
|
|
128
|
+
if (source[key]) {
|
|
129
|
+
if (!Array.isArray(source[key]) || source[key].length > 16)
|
|
130
|
+
throw new OperatorApiError("operator_schema_budget");
|
|
131
|
+
out[key] = source[key].map((item) => schema(item, depth + 1));
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
const normalized = schema(value);
|
|
136
|
+
try {
|
|
137
|
+
z.fromJSONSchema(normalized);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
throw new OperatorApiError("operator_schema_unsupported");
|
|
141
|
+
}
|
|
142
|
+
return normalized;
|
|
143
|
+
}
|
|
144
|
+
const definitionSchema = z
|
|
145
|
+
.object({
|
|
146
|
+
protocol: z.literal(1),
|
|
147
|
+
id: identifier,
|
|
148
|
+
title: z.string().min(1).max(160),
|
|
149
|
+
kind: z.enum(["read", "command"]),
|
|
150
|
+
requestSchema: z.record(z.string(), z.unknown()),
|
|
151
|
+
writeDomain: identifier.optional(),
|
|
152
|
+
replay: z.literal("request-id-v1").optional(),
|
|
153
|
+
timeoutMs: z.number().int().min(500).max(10000).default(5000),
|
|
154
|
+
maxResponseBytes: z.number().int().min(256).max(262144).default(65536),
|
|
155
|
+
})
|
|
156
|
+
.strict()
|
|
157
|
+
.superRefine((d, ctx) => {
|
|
158
|
+
if (d.kind === "command" && (!d.writeDomain || d.replay !== "request-id-v1"))
|
|
159
|
+
ctx.addIssue({ code: "custom", message: "Commands require a write domain and application request-id receipt support" });
|
|
160
|
+
if (d.kind === "read" && (d.writeDomain || d.replay))
|
|
161
|
+
ctx.addIssue({ code: "custom", message: "Reads cannot declare command authority" });
|
|
162
|
+
});
|
|
163
|
+
export function operatorOperation(definition, method, path) {
|
|
164
|
+
const parsed = definitionSchema.parse(definition);
|
|
165
|
+
const checkedMethod = z.enum(["GET", "POST"]).parse(method);
|
|
166
|
+
if (parsed.kind === "command" && checkedMethod !== "POST")
|
|
167
|
+
throw new OperatorApiError("operator_command_requires_post");
|
|
168
|
+
return { ...parsed, requestSchema: operatorRequestSchema(parsed.requestSchema), method: checkedMethod, path: localPath.parse(path) };
|
|
169
|
+
}
|
|
170
|
+
export function operatorOperationFromWire(input) {
|
|
171
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
172
|
+
throw new OperatorApiError("operator_operation_invalid");
|
|
173
|
+
const { method, path, ...definition } = input;
|
|
174
|
+
return operatorOperation(definition, method, path);
|
|
175
|
+
}
|
|
176
|
+
export function assertOperatorPayload(operation, value) {
|
|
177
|
+
operatorJson(value);
|
|
178
|
+
let valid = false;
|
|
179
|
+
try {
|
|
180
|
+
valid = z.fromJSONSchema(operation.requestSchema).safeParse(value).success;
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
/* fail closed */
|
|
184
|
+
}
|
|
185
|
+
if (!valid)
|
|
186
|
+
throw new OperatorApiError("operator_request_invalid");
|
|
187
|
+
if (operation.kind === "command" && !z.uuid().safeParse(value?.["requestId"]).success)
|
|
188
|
+
throw new OperatorApiError("operator_request_id_required");
|
|
189
|
+
}
|
|
190
|
+
export const operatorDeploymentSchema = z
|
|
191
|
+
.object({
|
|
192
|
+
controllerName: resource,
|
|
193
|
+
rttNode: resource,
|
|
194
|
+
version: resource,
|
|
195
|
+
instanceId: resource,
|
|
196
|
+
launchId: z.uuid(),
|
|
197
|
+
})
|
|
198
|
+
.strict();
|
|
199
|
+
export const operatorDelegationSchema = z
|
|
200
|
+
.object({
|
|
201
|
+
protocol: z.literal(1),
|
|
202
|
+
operationId: identifier,
|
|
203
|
+
operationDigest: digest,
|
|
204
|
+
requestDigest: digest,
|
|
205
|
+
target: operatorDeploymentSchema,
|
|
206
|
+
tool: z.enum(["controller_read_runtime_operator_operation", "controller_execute_runtime_operator_command"]),
|
|
207
|
+
})
|
|
208
|
+
.strict();
|
|
209
|
+
/** Called only AFTER normal JWT cryptographic, expiry and route-grant checks. */
|
|
210
|
+
export function assertOperatorDelegation(claims, operation, payload, deployment, nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
211
|
+
const d = operatorDelegationSchema.parse(claims[OPERATOR_DELEGATION_CLAIM]);
|
|
212
|
+
if (claims["serviceToken"] !== true ||
|
|
213
|
+
claims["identityType"] !== "machine" ||
|
|
214
|
+
claims["workloadIdentityType"] !== "CONTROLLER_MCP" ||
|
|
215
|
+
claims["sub"] !== claims["machineIdentityId"] ||
|
|
216
|
+
!z.uuid().safeParse(claims["sub"]).success ||
|
|
217
|
+
!Number.isSafeInteger(claims["tokenVersion"]) ||
|
|
218
|
+
Number(claims["tokenVersion"]) < 1 ||
|
|
219
|
+
claims["role"] !== "operator" ||
|
|
220
|
+
!Number.isSafeInteger(claims["iat"]) ||
|
|
221
|
+
!Number.isSafeInteger(claims["exp"]) ||
|
|
222
|
+
Number(claims["exp"]) <= nowSeconds ||
|
|
223
|
+
Number(claims["iat"]) > nowSeconds + 2 ||
|
|
224
|
+
Number(claims["exp"]) - Number(claims["iat"]) > 30 ||
|
|
225
|
+
Number(claims["exp"]) <= Number(claims["iat"]) ||
|
|
226
|
+
!Array.isArray(claims["accessRules"]) ||
|
|
227
|
+
claims["accessRules"].length !== 1 ||
|
|
228
|
+
claims["accessRules"][0] !== operation.path) {
|
|
229
|
+
throw new OperatorApiError("operator_delegation_invalid", 403);
|
|
230
|
+
}
|
|
231
|
+
if (d.operationId !== operation.id ||
|
|
232
|
+
d.operationDigest !== operatorDigest(operation) ||
|
|
233
|
+
d.requestDigest !== operatorDigest(payload) ||
|
|
234
|
+
operatorDigest(d.target) !== operatorDigest(operatorDeploymentSchema.parse(deployment)) ||
|
|
235
|
+
d.tool !== (operation.kind === "command" ? "controller_execute_runtime_operator_command" : "controller_read_runtime_operator_operation")) {
|
|
236
|
+
throw new OperatorApiError("operator_delegation_binding_changed", 403);
|
|
237
|
+
}
|
|
238
|
+
assertOperatorPayload(operation, payload);
|
|
239
|
+
return d;
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=operator-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operator-api.js","sourceRoot":"","sources":["../../src/runtime/operator-api.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n/** Explicit opt-in. OpenAPI paths alone are never operator authority. */\nexport const OPERATOR_API_EXTENSION = \"x-uns-operator-operations\";\nexport const OPERATOR_DELEGATION_CLAIM = \"unsOperatorDelegation\";\nexport const OPERATOR_OUTCOME_HEADER = \"x-uns-operator-outcome\";\nexport const OPERATOR_AUTH_SCHEME = \"UNS-Operator\";\nexport const OPERATOR_JWT_TYPE = \"uns-operator-api+jwt\";\nexport const OPERATOR_JWT_AUDIENCE = \"uns-operator-api\";\nexport const OPERATOR_JWT_ALGORITHM = \"PS256\";\nexport const OPERATOR_JSON_MAX_BYTES = 65536;\nconst identifier = z\n .string()\n .min(1)\n .max(120)\n .regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/);\nconst digest = z.string().regex(/^[a-f0-9]{64}$/);\nconst resource = z.string().min(1).max(256);\nconst localPath = z\n .string()\n .min(1)\n .max(2048)\n .refine(\n (value) => /^\\/[a-zA-Z0-9_./-]+$/.test(value) && !value.includes(\"//\") && !value.split(\"/\").some((part) => part === \".\" || part === \"..\"),\n \"Expected an exact local API path\",\n );\n\nexport class OperatorApiError extends Error {\n constructor(\n public readonly code: string,\n public readonly status = 400,\n ) {\n super(code);\n }\n}\n\n/** Canonical bounded JSON is shared by reviews, wire claims and receipts.\n * Never hashes JSON with dropped undefined values or non-finite numbers. */\nexport function operatorJson(value: unknown): string {\n let nodes = 0;\n const visit = (item: unknown, depth: number): unknown => {\n if (++nodes > 4096 || depth > 20) throw new OperatorApiError(\"operator_json_budget\");\n if (item === null || typeof item === \"boolean\") return item;\n if (typeof item === \"number\" && Number.isFinite(item)) return Object.is(item, -0) ? 0 : item;\n if (typeof item === \"string\" && item.length <= 32768) return item;\n if (Array.isArray(item) && item.length <= 1024) return item.map((entry) => visit(entry, depth + 1));\n if (item && typeof item === \"object\" && [Object.prototype, null].includes(Object.getPrototypeOf(item))) {\n const entries = Object.entries(item as Record<string, unknown>);\n if (entries.length > 128) throw new OperatorApiError(\"operator_json_budget\");\n return Object.fromEntries(\n entries\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([key, entry]) => {\n if (key.length > 256 || [\"__proto__\", \"prototype\", \"constructor\"].includes(key)) throw new OperatorApiError(\"operator_json_key\");\n return [key, visit(entry, depth + 1)];\n }),\n );\n }\n throw new OperatorApiError(\"operator_json_value\");\n };\n const json = JSON.stringify(visit(value, 0));\n if (Buffer.byteLength(json, \"utf8\") > OPERATOR_JSON_MAX_BYTES) throw new OperatorApiError(\"operator_json_budget\");\n return json;\n}\nexport const operatorDigest = (value: unknown): string => createHash(\"sha256\").update(operatorJson(value)).digest(\"hex\");\n\n/** Protocol 1 deliberately excludes references, executable regex and extensions.\n * UUID format already enforces the standard UUID constraint without a pattern. */\nexport function operatorRequestSchema(value: unknown): Record<string, unknown> {\n operatorJson(value);\n const vocabulary = new Set([\n \"$schema\",\n \"type\",\n \"properties\",\n \"required\",\n \"additionalProperties\",\n \"items\",\n \"minItems\",\n \"maxItems\",\n \"minLength\",\n \"maxLength\",\n \"minimum\",\n \"maximum\",\n \"exclusiveMinimum\",\n \"exclusiveMaximum\",\n \"multipleOf\",\n \"format\",\n \"const\",\n \"enum\",\n \"oneOf\",\n \"anyOf\",\n \"allOf\",\n \"description\",\n \"title\",\n \"default\",\n \"examples\",\n \"pattern\",\n ]);\n let count = 0;\n function schema(input: unknown, depth = 0): Record<string, unknown> {\n if (++count > 256 || depth > 12 || !input || typeof input !== \"object\" || Array.isArray(input))\n throw new OperatorApiError(\"operator_schema_budget\");\n const source = input as Record<string, unknown>;\n for (const key of Object.keys(source)) if (!vocabulary.has(key)) throw new OperatorApiError(\"operator_schema_keyword\");\n const out = { ...source };\n if (\"pattern\" in out) {\n if (out[\"format\"] !== \"uuid\") throw new OperatorApiError(\"operator_schema_pattern\");\n delete out[\"pattern\"];\n }\n if (out[\"format\"] && ![\"uuid\", \"date-time\", \"date\", \"time\", \"email\", \"uri\"].includes(String(out[\"format\"])))\n throw new OperatorApiError(\"operator_schema_format\");\n if (source[\"properties\"]) {\n if (source[\"additionalProperties\"] !== false || typeof source[\"properties\"] !== \"object\" || Array.isArray(source[\"properties\"]))\n throw new OperatorApiError(\"operator_schema_object_must_be_closed\");\n out[\"properties\"] = Object.fromEntries(\n Object.entries(source[\"properties\"] as Record<string, unknown>).map(([key, item]) => [key, schema(item, depth + 1)]),\n );\n }\n if (source[\"type\"] === \"object\" && source[\"additionalProperties\"] !== false) throw new OperatorApiError(\"operator_schema_object_must_be_closed\");\n if (source[\"additionalProperties\"] !== undefined && source[\"additionalProperties\"] !== false)\n throw new OperatorApiError(\"operator_schema_object_must_be_closed\");\n if (source[\"items\"]) out[\"items\"] = schema(source[\"items\"], depth + 1);\n for (const key of [\"oneOf\", \"anyOf\", \"allOf\"])\n if (source[key]) {\n if (!Array.isArray(source[key]) || source[key].length > 16) throw new OperatorApiError(\"operator_schema_budget\");\n out[key] = source[key].map((item) => schema(item, depth + 1));\n }\n return out;\n }\n const normalized = schema(value);\n try {\n z.fromJSONSchema(normalized as never);\n } catch {\n throw new OperatorApiError(\"operator_schema_unsupported\");\n }\n return normalized;\n}\nconst definitionSchema = z\n .object({\n protocol: z.literal(1),\n id: identifier,\n title: z.string().min(1).max(160),\n kind: z.enum([\"read\", \"command\"]),\n requestSchema: z.record(z.string(), z.unknown()),\n writeDomain: identifier.optional(),\n replay: z.literal(\"request-id-v1\").optional(),\n timeoutMs: z.number().int().min(500).max(10000).default(5000),\n maxResponseBytes: z.number().int().min(256).max(262144).default(65536),\n })\n .strict()\n .superRefine((d, ctx) => {\n if (d.kind === \"command\" && (!d.writeDomain || d.replay !== \"request-id-v1\"))\n ctx.addIssue({ code: \"custom\", message: \"Commands require a write domain and application request-id receipt support\" });\n if (d.kind === \"read\" && (d.writeDomain || d.replay)) ctx.addIssue({ code: \"custom\", message: \"Reads cannot declare command authority\" });\n });\nexport type OperatorOperationDefinition = z.input<typeof definitionSchema>;\nexport type OperatorOperation = z.output<typeof definitionSchema> & { method: \"GET\" | \"POST\"; path: string };\nexport function operatorOperation(definition: unknown, method: unknown, path: unknown): OperatorOperation {\n const parsed = definitionSchema.parse(definition);\n const checkedMethod = z.enum([\"GET\", \"POST\"]).parse(method);\n if (parsed.kind === \"command\" && checkedMethod !== \"POST\") throw new OperatorApiError(\"operator_command_requires_post\");\n return { ...parsed, requestSchema: operatorRequestSchema(parsed.requestSchema), method: checkedMethod, path: localPath.parse(path) };\n}\nexport function operatorOperationFromWire(input: unknown): OperatorOperation {\n if (!input || typeof input !== \"object\" || Array.isArray(input)) throw new OperatorApiError(\"operator_operation_invalid\");\n const { method, path, ...definition } = input as Record<string, unknown>;\n return operatorOperation(definition, method, path);\n}\nexport function assertOperatorPayload(operation: OperatorOperation, value: unknown): void {\n operatorJson(value);\n let valid = false;\n try {\n valid = z.fromJSONSchema(operation.requestSchema as never).safeParse(value).success;\n } catch {\n /* fail closed */\n }\n if (!valid) throw new OperatorApiError(\"operator_request_invalid\");\n if (operation.kind === \"command\" && !z.uuid().safeParse((value as Record<string, unknown>)?.[\"requestId\"]).success)\n throw new OperatorApiError(\"operator_request_id_required\");\n}\nexport const operatorDeploymentSchema = z\n .object({\n controllerName: resource,\n rttNode: resource,\n version: resource,\n instanceId: resource,\n launchId: z.uuid(),\n })\n .strict();\nexport type OperatorDeployment = z.infer<typeof operatorDeploymentSchema>;\nexport const operatorDelegationSchema = z\n .object({\n protocol: z.literal(1),\n operationId: identifier,\n operationDigest: digest,\n requestDigest: digest,\n target: operatorDeploymentSchema,\n tool: z.enum([\"controller_read_runtime_operator_operation\", \"controller_execute_runtime_operator_command\"]),\n })\n .strict();\nexport type OperatorDelegation = z.infer<typeof operatorDelegationSchema>;\n\n/** Called only AFTER normal JWT cryptographic, expiry and route-grant checks. */\nexport function assertOperatorDelegation(\n claims: Record<string, unknown>,\n operation: OperatorOperation,\n payload: unknown,\n deployment: OperatorDeployment,\n nowSeconds = Math.floor(Date.now() / 1000),\n): OperatorDelegation {\n const d = operatorDelegationSchema.parse(claims[OPERATOR_DELEGATION_CLAIM]);\n if (\n claims[\"serviceToken\"] !== true ||\n claims[\"identityType\"] !== \"machine\" ||\n claims[\"workloadIdentityType\"] !== \"CONTROLLER_MCP\" ||\n claims[\"sub\"] !== claims[\"machineIdentityId\"] ||\n !z.uuid().safeParse(claims[\"sub\"]).success ||\n !Number.isSafeInteger(claims[\"tokenVersion\"]) ||\n Number(claims[\"tokenVersion\"]) < 1 ||\n claims[\"role\"] !== \"operator\" ||\n !Number.isSafeInteger(claims[\"iat\"]) ||\n !Number.isSafeInteger(claims[\"exp\"]) ||\n Number(claims[\"exp\"]) <= nowSeconds ||\n Number(claims[\"iat\"]) > nowSeconds + 2 ||\n Number(claims[\"exp\"]) - Number(claims[\"iat\"]) > 30 ||\n Number(claims[\"exp\"]) <= Number(claims[\"iat\"]) ||\n !Array.isArray(claims[\"accessRules\"]) ||\n claims[\"accessRules\"].length !== 1 ||\n claims[\"accessRules\"][0] !== operation.path\n ) {\n throw new OperatorApiError(\"operator_delegation_invalid\", 403);\n }\n if (\n d.operationId !== operation.id ||\n d.operationDigest !== operatorDigest(operation) ||\n d.requestDigest !== operatorDigest(payload) ||\n operatorDigest(d.target) !== operatorDigest(operatorDeploymentSchema.parse(deployment)) ||\n d.tool !== (operation.kind === \"command\" ? \"controller_execute_runtime_operator_command\" : \"controller_read_runtime_operator_operation\")\n ) {\n throw new OperatorApiError(\"operator_delegation_binding_changed\", 403);\n }\n assertOperatorPayload(operation, payload);\n return d;\n}\n"]}
|
|
@@ -22,8 +22,8 @@ export declare const processGroupAuthorizationSchema: z.ZodObject<{
|
|
|
22
22
|
instanceId: z.ZodString;
|
|
23
23
|
processName: z.ZodString;
|
|
24
24
|
role: z.ZodEnum<{
|
|
25
|
-
source: "source";
|
|
26
25
|
target: "target";
|
|
26
|
+
source: "source";
|
|
27
27
|
receiver: "receiver";
|
|
28
28
|
}>;
|
|
29
29
|
configurationSha256: z.ZodString;
|
|
@@ -75,8 +75,8 @@ export declare const hvAuthorizationSchema: z.ZodObject<{
|
|
|
75
75
|
[x: string]: string;
|
|
76
76
|
}>;
|
|
77
77
|
role: z.ZodEnum<{
|
|
78
|
-
source: "source";
|
|
79
78
|
target: "target";
|
|
79
|
+
source: "source";
|
|
80
80
|
receiver: "receiver";
|
|
81
81
|
}>;
|
|
82
82
|
configurationSha256: z.ZodString;
|
|
@@ -46,8 +46,8 @@ export declare const groupMemberCommandSchema: z.ZodObject<{
|
|
|
46
46
|
}, z.core.$strict>;
|
|
47
47
|
operationId: z.ZodString;
|
|
48
48
|
role: z.ZodEnum<{
|
|
49
|
-
source: "source";
|
|
50
49
|
target: "target";
|
|
50
|
+
source: "source";
|
|
51
51
|
}>;
|
|
52
52
|
expectedConfigurationChecksum: z.ZodOptional<z.ZodString>;
|
|
53
53
|
requestId: z.ZodOptional<z.ZodString>;
|