@serve.zone/interfaces 28.4.0 → 28.5.0

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/changelog.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-09-09 - 28.5.0
4
+
5
+ ### Features
6
+
7
+ - add runtime config v1 contract (data/runtimeconfig)
8
+ - Introduce runtime config v1 interfaces for execution intent, target ports, readiness, log policy, and immutable image metadata.
9
+ - Add exact-schema snapshot validation, canonical digest computation, digest validation, and assignment binding helpers.
10
+ - Export the runtime config data module and add tests for digest vectors, schema bounds, assignment binding, and detached async snapshots.
11
+
3
12
  ## 2026-09-09 - 28.4.0
4
13
 
5
14
  ### Features
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '28.4.0',
6
+ version: '28.5.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLHVGQUF1RjtDQUNyRyxDQUFBIn0=
@@ -24,6 +24,7 @@ export * from './nodecredential.js';
24
24
  export * from './nodeenrollment.js';
25
25
  export * from './registry.js';
26
26
  export * from './runtimeassignment.js';
27
+ export * from './runtimeconfig.js';
27
28
  export * from './secret.js';
28
29
  export * from './baremetal.js';
29
30
  export * from './baseos.js';
@@ -24,6 +24,7 @@ export * from './nodecredential.js';
24
24
  export * from './nodeenrollment.js';
25
25
  export * from './registry.js';
26
26
  export * from './runtimeassignment.js';
27
+ export * from './runtimeconfig.js';
27
28
  export * from './secret.js';
28
29
  export * from './baremetal.js';
29
30
  export * from './baseos.js';
@@ -38,4 +39,4 @@ export * from './traffic.js';
38
39
  export * from './user.js';
39
40
  export * from './version.js';
40
41
  export * from './webpush.js';
41
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxlQUFlLENBQUM7QUFDOUIsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMseUJBQXlCLENBQUM7QUFDeEMsY0FBYywwQkFBMEIsQ0FBQztBQUN6QyxjQUFjLDBCQUEwQixDQUFDO0FBQ3pDLGNBQWMsZ0JBQWdCLENBQUM7QUFDL0IsY0FBYyxVQUFVLENBQUM7QUFDekIsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxZQUFZLENBQUM7QUFDM0IsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLGdCQUFnQixDQUFDO0FBQy9CLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxzQkFBc0IsQ0FBQztBQUNyQyxjQUFjLDZCQUE2QixDQUFDO0FBQzVDLGNBQWMsV0FBVyxDQUFDO0FBQzFCLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLHdCQUF3QixDQUFDO0FBQ3ZDLGNBQWMsYUFBYSxDQUFDO0FBQzVCLGNBQWMsZ0JBQWdCLENBQUM7QUFDL0IsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxrQkFBa0IsQ0FBQztBQUNqQyxjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLG1CQUFtQixDQUFDO0FBQ2xDLGNBQWMsYUFBYSxDQUFDO0FBQzVCLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxXQUFXLENBQUM7QUFDMUIsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxjQUFjLENBQUMifQ==
42
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxlQUFlLENBQUM7QUFDOUIsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMseUJBQXlCLENBQUM7QUFDeEMsY0FBYywwQkFBMEIsQ0FBQztBQUN6QyxjQUFjLDBCQUEwQixDQUFDO0FBQ3pDLGNBQWMsZ0JBQWdCLENBQUM7QUFDL0IsY0FBYyxVQUFVLENBQUM7QUFDekIsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxZQUFZLENBQUM7QUFDM0IsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLGdCQUFnQixDQUFDO0FBQy9CLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxzQkFBc0IsQ0FBQztBQUNyQyxjQUFjLDZCQUE2QixDQUFDO0FBQzVDLGNBQWMsV0FBVyxDQUFDO0FBQzFCLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLHdCQUF3QixDQUFDO0FBQ3ZDLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGtCQUFrQixDQUFDO0FBQ2pDLGNBQWMsZUFBZSxDQUFDO0FBQzlCLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsbUJBQW1CLENBQUM7QUFDbEMsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLFdBQVcsQ0FBQztBQUMxQixjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLGNBQWMsQ0FBQyJ9
@@ -0,0 +1,122 @@
1
+ import type { IImmutableContainerInvocationPlatformEvidenceV1, IImmutableContainerInvocationV1, TSha256Digest } from './immutableimage.js';
2
+ import type { IRuntimeAssignmentV1, IRuntimeAuthorityReferenceV1 } from './runtimeassignment.js';
3
+ /** A container target only; declaration does not publish a host or gateway port. */
4
+ export interface IRuntimeConfigTargetPortV1 {
5
+ name: string;
6
+ port: number;
7
+ /** Inclusive; equal to port for a single port. */
8
+ portEnd: number;
9
+ protocol: 'tcp' | 'udp';
10
+ }
11
+ export interface IRuntimeConfigReadinessTimingV1 {
12
+ /** Measured from the evidenced container start; no probes before this delay. */
13
+ initialDelayMs: number;
14
+ /** Failures during this startup grace cannot mark the workload unhealthy. */
15
+ startupGraceMs: number;
16
+ /** Probe start-to-start interval; probes must never overlap. */
17
+ intervalMs: number;
18
+ /** Per-probe deadline, including connection, TLS and HTTP response headers. */
19
+ timeoutMs: number;
20
+ /** Consecutive successful probes needed before ready becomes true. */
21
+ successThreshold: number;
22
+ /** Consecutive failures after startup grace needed to declare unhealthy. */
23
+ failureThreshold: number;
24
+ }
25
+ export type TRuntimeConfigReadinessV1 = IRuntimeConfigReadinessTimingV1 & ({
26
+ kind: 'process';
27
+ } | {
28
+ kind: 'tcp';
29
+ targetPort: string;
30
+ port: number;
31
+ } | {
32
+ kind: 'http';
33
+ targetPort: string;
34
+ port: number;
35
+ scheme: 'http' | 'https';
36
+ /**
37
+ * Host header name and HTTPS SNI/certificate hostname. Connect only to the
38
+ * evidenced container endpoint, never resolve this name as a probe target.
39
+ * HTTPS verifies system trust and this hostname; no insecure TLS mode.
40
+ */
41
+ host: string;
42
+ /** Canonical origin-form request target, including any nonsecret query. */
43
+ path: string;
44
+ /** Sorted unique final HTTP response status codes. */
45
+ expectedStatusCodes: number[];
46
+ /** A redirect response is evaluated as received; it cannot redirect probes. */
47
+ followRedirects: false;
48
+ });
49
+ export interface IRuntimeConfigLogPolicyV1 {
50
+ /** Maximum UTF-8 bytes in one captured stdout/stderr line. */
51
+ maximumLineBytes: number;
52
+ /** Maximum bytes waiting for delivery; overflow requires explicit evidence. */
53
+ maximumBufferedBytes: number;
54
+ /** Retention of archived logs; durable payloads belong to SmartBucket. */
55
+ retentionMs: number;
56
+ }
57
+ /**
58
+ * Controller-owned resolved nonsecret execution intent. OCI command, effective
59
+ * environment, working directory and identity must be resolved before sealing;
60
+ * consumers must not append inherited image defaults or choose runtime defaults.
61
+ * Secrets, network identity and storage claims remain separately assignment-bound
62
+ * owner references. This document contains no registry credentials or host paths.
63
+ */
64
+ export interface IRuntimeConfigV1 extends IRuntimeAuthorityReferenceV1 {
65
+ schemaVersion: 1;
66
+ organizationId: string;
67
+ serviceId: string;
68
+ image: {
69
+ registryHost: string;
70
+ repository: string;
71
+ /** Root descriptor digest, which may be an OCI platform index. */
72
+ imageDigest: TSha256Digest;
73
+ invocation: IImmutableContainerInvocationV1;
74
+ };
75
+ /** Complete effective public environment; secret values must never be inlined. */
76
+ environment: Record<string, string>;
77
+ workingDirectory: string;
78
+ cpuMillis: number;
79
+ memoryBytes: number;
80
+ readonlyRootfs: boolean;
81
+ runAsUser: number;
82
+ runAsGroup: number;
83
+ /** Sorted unique names, with no overlapping ranges for the same transport. */
84
+ targetPorts: IRuntimeConfigTargetPortV1[];
85
+ readiness: TRuntimeConfigReadinessV1;
86
+ /**
87
+ * SmartData owns log metadata and SmartBucket owns archived payloads. This
88
+ * policy does not authorize persistent local files. Any CRI log capture uses
89
+ * disposable process-local transport artifacts and must evidence bounded
90
+ * buffering, backpressure and lost data; it must not silently truncate logs.
91
+ */
92
+ logs: IRuntimeConfigLogPolicyV1;
93
+ }
94
+ export interface IRuntimeConfigAssignmentBindingV1 {
95
+ config: IRuntimeConfigV1;
96
+ assignment: IRuntimeAssignmentV1;
97
+ platformEvidence: IImmutableContainerInvocationPlatformEvidenceV1;
98
+ }
99
+ export declare const runtimeConfigV1Contract: Readonly<{
100
+ readonly digestDomain: "serve.zone/runtime-config-v1";
101
+ readonly maximumCanonicalBytes: 262144;
102
+ readonly maximumEnvironmentEntries: 128;
103
+ readonly maximumTargetPorts: 64;
104
+ readonly maximumReadinessDurationMs: 3600000;
105
+ readonly maximumLogRetentionMs: 31536000000;
106
+ }>;
107
+ /**
108
+ * Detached exact-schema snapshot for synchronous persistence assertions. Digest
109
+ * integrity, scope authorization, admission and runtime readiness are not implied.
110
+ */
111
+ export declare const snapshotRuntimeConfigV1: (valueArg: unknown) => IRuntimeConfigV1;
112
+ /** Domain-separated canonical JSON excludes only the config's own digest. */
113
+ export declare const computeRuntimeConfigV1Digest: (configArg: IRuntimeConfigV1) => Promise<TSha256Digest>;
114
+ export declare const validateRuntimeConfigV1: (valueArg: unknown) => Promise<string[]>;
115
+ /**
116
+ * Bind valid content to one exact assignment and select its platform evidence.
117
+ * No assignment digest is embedded in the config, avoiding circular hashes.
118
+ * This proves content consistency only: the caller must authenticate the
119
+ * controller, durably admit the attempt, resolve other owner references, and
120
+ * verify runtime and readiness evidence before effects or route publication.
121
+ */
122
+ export declare const bindRuntimeConfigV1ToAssignment: (configArg: unknown, assignmentArg: unknown) => Promise<IRuntimeConfigAssignmentBindingV1>;
@@ -0,0 +1,278 @@
1
+ import { buildDigestPinnedImageReference, isSha256Digest, validateImmutableContainerInvocation, verifyImmutableContainerInvocationDigest, } from './immutableimage.js';
2
+ import { snapshotRuntimeAssignmentV1, validateRuntimeAssignmentV1 } from './runtimeassignment.js';
3
+ import { canonicalizeStrictJson, createCanonicalJsonSha256Hex } from '../private/canonicaljson.js';
4
+ export const runtimeConfigV1Contract = Object.freeze({
5
+ digestDomain: 'serve.zone/runtime-config-v1',
6
+ maximumCanonicalBytes: 262_144,
7
+ maximumEnvironmentEntries: 128,
8
+ maximumTargetPorts: 64,
9
+ maximumReadinessDurationMs: 3_600_000,
10
+ maximumLogRetentionMs: 31_536_000_000,
11
+ });
12
+ const fail = (messageArg) => { throw new Error(messageArg); };
13
+ const encoder = new TextEncoder();
14
+ const isInteger = (valueArg, minimumArg, maximumArg) => typeof valueArg === 'number' && Number.isSafeInteger(valueArg) && !Object.is(valueArg, -0)
15
+ && valueArg >= minimumArg && valueArg <= maximumArg;
16
+ const isIdentifier = (valueArg) => typeof valueArg === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/.test(valueArg);
17
+ const isDigest = (valueArg) => typeof valueArg === 'string' && isSha256Digest(valueArg);
18
+ const isPortName = (valueArg) => typeof valueArg === 'string' && /^[a-z0-9][a-z0-9_.-]{0,62}$/.test(valueArg);
19
+ const isExactRecord = (valueArg, keysArg) => {
20
+ if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg))
21
+ return false;
22
+ const keys = Object.keys(valueArg);
23
+ return keys.length === keysArg.length && keys.every((keyArg) => keysArg.includes(keyArg));
24
+ };
25
+ /**
26
+ * Bound traversal before canonicalization and copy descriptor values so input
27
+ * getters/toJSON never run. The canonical helper supplies scalar Unicode and
28
+ * safe-integer validation. Detached output is obtained before any async work.
29
+ */
30
+ const snapshotBoundedJson = (valueArg) => {
31
+ let nodes = 0;
32
+ let stringBytes = 0;
33
+ const ancestors = new Set();
34
+ const accountString = (value) => {
35
+ if (value.length > runtimeConfigV1Contract.maximumCanonicalBytes)
36
+ fail('runtime config string is oversized');
37
+ stringBytes += encoder.encode(value).byteLength;
38
+ if (stringBytes > runtimeConfigV1Contract.maximumCanonicalBytes)
39
+ fail('runtime config is oversized');
40
+ };
41
+ const copy = (value, depth) => {
42
+ if (++nodes > 8192 || depth > 12)
43
+ fail('runtime config exceeds structural bounds');
44
+ if (typeof value === 'string') {
45
+ accountString(value);
46
+ return value;
47
+ }
48
+ if (value === null || typeof value === 'boolean' || typeof value === 'number')
49
+ return value;
50
+ if (!value || typeof value !== 'object')
51
+ fail('runtime config contains a non-JSON value');
52
+ if (ancestors.has(value))
53
+ fail('runtime config contains a cycle');
54
+ ancestors.add(value);
55
+ try {
56
+ const array = Array.isArray(value);
57
+ const prototype = Object.getPrototypeOf(value);
58
+ if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) {
59
+ fail('runtime config requires plain records and arrays');
60
+ }
61
+ const keys = Reflect.ownKeys(value);
62
+ if (keys.length > 257)
63
+ fail('runtime config object exceeds key bounds');
64
+ if (array) {
65
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, 'length');
66
+ const length = lengthDescriptor?.value;
67
+ if (!isInteger(length, 0, 256) || keys.length !== length + 1) {
68
+ fail('runtime config requires bounded dense arrays');
69
+ }
70
+ const result = [];
71
+ for (let index = 0; index < length; index++) {
72
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
73
+ if (!descriptor || !descriptor.enumerable || !Object.hasOwn(descriptor, 'value')) {
74
+ fail('runtime config array contains a missing or non-data entry');
75
+ }
76
+ result.push(copy(descriptor.value, depth + 1));
77
+ }
78
+ return result;
79
+ }
80
+ const result = Object.create(null);
81
+ for (const key of keys) {
82
+ if (typeof key !== 'string')
83
+ fail('runtime config contains a symbol key');
84
+ accountString(key);
85
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
86
+ if (!descriptor || !descriptor.enumerable || !Object.hasOwn(descriptor, 'value')) {
87
+ fail('runtime config requires enumerable data properties');
88
+ }
89
+ result[key] = copy(descriptor.value, depth + 1);
90
+ }
91
+ return result;
92
+ }
93
+ finally {
94
+ ancestors.delete(value);
95
+ }
96
+ };
97
+ const canonical = canonicalizeStrictJson(copy(valueArg, 0), fail);
98
+ if (encoder.encode(canonical).byteLength > runtimeConfigV1Contract.maximumCanonicalBytes) {
99
+ fail('runtime config canonical JSON is oversized');
100
+ }
101
+ return JSON.parse(canonical);
102
+ };
103
+ const timingKeys = ['initialDelayMs', 'startupGraceMs', 'intervalMs', 'timeoutMs',
104
+ 'successThreshold', 'failureThreshold'];
105
+ const assertReadiness = (valueArg, portsArg) => {
106
+ if (!valueArg || typeof valueArg !== 'object')
107
+ fail('runtime config readiness is required');
108
+ const value = valueArg;
109
+ const keys = value.kind === 'process' ? ['kind', ...timingKeys]
110
+ : value.kind === 'tcp' ? ['kind', ...timingKeys, 'targetPort', 'port']
111
+ : value.kind === 'http' ? ['kind', ...timingKeys, 'targetPort', 'port',
112
+ 'scheme', 'host', 'path', 'expectedStatusCodes', 'followRedirects'] : [];
113
+ if (keys.length === 0 || !isExactRecord(value, keys)
114
+ || !isInteger(value.initialDelayMs, 0, runtimeConfigV1Contract.maximumReadinessDurationMs)
115
+ || !isInteger(value.startupGraceMs, 0, runtimeConfigV1Contract.maximumReadinessDurationMs)
116
+ || !isInteger(value.intervalMs, 100, runtimeConfigV1Contract.maximumReadinessDurationMs)
117
+ || !isInteger(value.timeoutMs, 1, 60_000)
118
+ || !isInteger(value.successThreshold, 1, 100)
119
+ || !isInteger(value.failureThreshold, 1, 100)
120
+ || value.timeoutMs > value.intervalMs || value.initialDelayMs > value.startupGraceMs) {
121
+ fail('runtime config readiness must use explicit bounded timing and exact schema');
122
+ }
123
+ if (value.kind === 'process')
124
+ return;
125
+ const target = portsArg.find((portArg) => portArg.name === value.targetPort);
126
+ if (!isPortName(value.targetPort) || !target || target.protocol !== 'tcp'
127
+ || !isInteger(value.port, target.port, target.portEnd)) {
128
+ fail('runtime config readiness must select a declared TCP target and port');
129
+ }
130
+ if (value.kind === 'tcp')
131
+ return;
132
+ if ((value.scheme !== 'http' && value.scheme !== 'https') || value.followRedirects !== false
133
+ || typeof value.host !== 'string' || value.host.length > 253
134
+ || !/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/.test(value.host)
135
+ || typeof value.path !== 'string' || value.path.length > 4096
136
+ || !value.path.startsWith('/') || value.path.startsWith('//')
137
+ || /[^\x21-\x7e]|[\\#]/.test(value.path)
138
+ || /%(?![0-9A-F]{2})/.test(value.path)
139
+ || !Array.isArray(value.expectedStatusCodes) || value.expectedStatusCodes.length === 0
140
+ || value.expectedStatusCodes.length > 32) {
141
+ fail('runtime config HTTP readiness must use an exact bounded request policy');
142
+ }
143
+ const parsed = new URL(value.path, 'http://runtime.invalid');
144
+ if (`${parsed.pathname}${parsed.search}` !== value.path)
145
+ fail('runtime config HTTP path must be canonical');
146
+ let previousCode = 0;
147
+ for (const code of value.expectedStatusCodes) {
148
+ if (!isInteger(code, 200, 599) || code <= previousCode) {
149
+ fail('runtime config HTTP status expectations must be sorted unique final codes');
150
+ }
151
+ previousCode = code;
152
+ }
153
+ };
154
+ const assertConfig = (valueArg) => {
155
+ if (!isExactRecord(valueArg, ['schemaVersion', 'id', 'generation', 'organizationId', 'serviceId',
156
+ 'image', 'environment', 'workingDirectory', 'cpuMillis', 'memoryBytes', 'readonlyRootfs',
157
+ 'runAsUser', 'runAsGroup', 'targetPorts', 'readiness', 'logs', 'digest'])
158
+ || valueArg.schemaVersion !== 1 || !isIdentifier(valueArg.id)
159
+ || !isInteger(valueArg.generation, 1, Number.MAX_SAFE_INTEGER)
160
+ || !isIdentifier(valueArg.organizationId) || !isIdentifier(valueArg.serviceId)
161
+ || !isDigest(valueArg.digest))
162
+ fail('runtime config must use its exact v1 schema');
163
+ const image = valueArg.image;
164
+ if (!isExactRecord(image, ['registryHost', 'repository', 'imageDigest', 'invocation'])
165
+ || typeof image.registryHost !== 'string' || image.registryHost.length > 253
166
+ || typeof image.repository !== 'string' || image.repository.length > 255
167
+ || !isDigest(image.imageDigest)
168
+ || validateImmutableContainerInvocation(image.invocation).length !== 0) {
169
+ fail('runtime config image must contain exact immutable invocation evidence');
170
+ }
171
+ buildDigestPinnedImageReference(image.registryHost, image.repository, image.imageDigest);
172
+ const environment = valueArg.environment;
173
+ if (!environment || typeof environment !== 'object' || Array.isArray(environment)
174
+ || Object.keys(environment).length > runtimeConfigV1Contract.maximumEnvironmentEntries) {
175
+ fail('runtime config requires an explicit bounded nonsecret environment');
176
+ }
177
+ for (const [key, value] of Object.entries(environment)) {
178
+ if (!/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(key) || typeof value !== 'string'
179
+ || value.includes('\u0000') || encoder.encode(value).byteLength > 8192) {
180
+ fail('runtime config environment must contain bounded string entries');
181
+ }
182
+ }
183
+ const directory = valueArg.workingDirectory;
184
+ if (typeof directory !== 'string' || !directory.startsWith('/')
185
+ || encoder.encode(directory).byteLength > 4096 || /[\u0000-\u001f\u007f]/.test(directory)
186
+ || (directory !== '/' && directory.split('/').slice(1).some((partArg) => !partArg || partArg === '.' || partArg === '..'))
187
+ || !isInteger(valueArg.cpuMillis, 1, 1_000_000)
188
+ || !isInteger(valueArg.memoryBytes, 16 * 1024 * 1024, 2 ** 50)
189
+ || typeof valueArg.readonlyRootfs !== 'boolean'
190
+ || !isInteger(valueArg.runAsUser, 0, 2 ** 31 - 1)
191
+ || !isInteger(valueArg.runAsGroup, 0, 2 ** 31 - 1)) {
192
+ fail('runtime config requires canonical directory, explicit resources and numeric identity');
193
+ }
194
+ const ports = valueArg.targetPorts;
195
+ if (!Array.isArray(ports) || ports.length > runtimeConfigV1Contract.maximumTargetPorts) {
196
+ fail('runtime config target ports must be a bounded array');
197
+ }
198
+ let previousName = '';
199
+ const validatedPorts = [];
200
+ for (const port of ports) {
201
+ if (!isExactRecord(port, ['name', 'port', 'portEnd', 'protocol'])
202
+ || !isPortName(port.name) || port.name <= previousName
203
+ || !isInteger(port.port, 1, 65535) || !isInteger(port.portEnd, port.port, 65535)
204
+ || (port.protocol !== 'tcp' && port.protocol !== 'udp')
205
+ || validatedPorts.some((previousArg) => previousArg.protocol === port.protocol
206
+ && port.port <= previousArg.portEnd && port.portEnd >= previousArg.port)) {
207
+ fail('runtime config target ports require sorted unique names and nonoverlapping transport ranges');
208
+ }
209
+ previousName = port.name;
210
+ validatedPorts.push(port);
211
+ }
212
+ assertReadiness(valueArg.readiness, validatedPorts);
213
+ const logs = valueArg.logs;
214
+ if (!isExactRecord(logs, ['maximumLineBytes', 'maximumBufferedBytes', 'retentionMs'])
215
+ || !isInteger(logs.maximumLineBytes, 1, 1_048_576)
216
+ || !isInteger(logs.maximumBufferedBytes, 1, 67_108_864)
217
+ || logs.maximumBufferedBytes < logs.maximumLineBytes
218
+ || !isInteger(logs.retentionMs, 1, runtimeConfigV1Contract.maximumLogRetentionMs)) {
219
+ fail('runtime config logs require explicit bounded line, buffer and retention limits');
220
+ }
221
+ return valueArg;
222
+ };
223
+ /**
224
+ * Detached exact-schema snapshot for synchronous persistence assertions. Digest
225
+ * integrity, scope authorization, admission and runtime readiness are not implied.
226
+ */
227
+ export const snapshotRuntimeConfigV1 = (valueArg) => assertConfig(snapshotBoundedJson(valueArg));
228
+ /** Domain-separated canonical JSON excludes only the config's own digest. */
229
+ export const computeRuntimeConfigV1Digest = async (configArg) => {
230
+ const { digest: _digest, ...payload } = snapshotRuntimeConfigV1(configArg);
231
+ return `sha256:${await createCanonicalJsonSha256Hex(canonicalizeStrictJson({
232
+ domain: runtimeConfigV1Contract.digestDomain, payload,
233
+ }, fail), fail)}`;
234
+ };
235
+ const validateSnapshot = async (configArg) => {
236
+ if (!await verifyImmutableContainerInvocationDigest(configArg.image.invocation)) {
237
+ return ['runtime config immutable invocation digest does not match'];
238
+ }
239
+ return configArg.digest === await computeRuntimeConfigV1Digest(configArg)
240
+ ? [] : ['runtime config digest does not match'];
241
+ };
242
+ export const validateRuntimeConfigV1 = async (valueArg) => {
243
+ try {
244
+ return await validateSnapshot(snapshotRuntimeConfigV1(valueArg));
245
+ }
246
+ catch {
247
+ return ['runtime config must use its exact canonical v1 schema'];
248
+ }
249
+ };
250
+ /**
251
+ * Bind valid content to one exact assignment and select its platform evidence.
252
+ * No assignment digest is embedded in the config, avoiding circular hashes.
253
+ * This proves content consistency only: the caller must authenticate the
254
+ * controller, durably admit the attempt, resolve other owner references, and
255
+ * verify runtime and readiness evidence before effects or route publication.
256
+ */
257
+ export const bindRuntimeConfigV1ToAssignment = async (configArg, assignmentArg) => {
258
+ const config = snapshotRuntimeConfigV1(configArg);
259
+ const assignment = snapshotRuntimeAssignmentV1(snapshotBoundedJson(assignmentArg));
260
+ if ((await validateSnapshot(config)).length !== 0
261
+ || (await validateRuntimeAssignmentV1(assignment)).length !== 0) {
262
+ fail('runtime config binding requires valid config, invocation and assignment digests');
263
+ }
264
+ const reference = assignment.workload.config;
265
+ if (reference.id !== config.id || reference.generation !== config.generation
266
+ || reference.digest !== config.digest || assignment.organizationId !== config.organizationId
267
+ || assignment.serviceId !== config.serviceId
268
+ || assignment.workload.registryHost !== config.image.registryHost
269
+ || assignment.workload.repository !== config.image.repository
270
+ || assignment.workload.imageDigest !== config.image.imageDigest) {
271
+ fail('runtime config does not match the exact assignment reference, ownership and image');
272
+ }
273
+ const platformEvidence = config.image.invocation.evidence.find((entryArg) => entryArg.platform === assignment.workload.platform);
274
+ if (!platformEvidence)
275
+ fail('runtime config lacks exact assignment platform evidence');
276
+ return { config, assignment, platformEvidence };
277
+ };
278
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVudGltZWNvbmZpZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvcnVudGltZWNvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFLQSxPQUFPLEVBQ0wsK0JBQStCLEVBQy9CLGNBQWMsRUFDZCxvQ0FBb0MsRUFDcEMsd0NBQXdDLEdBQ3pDLE1BQU0scUJBQXFCLENBQUM7QUFFN0IsT0FBTyxFQUFFLDJCQUEyQixFQUFFLDJCQUEyQixFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDbEcsT0FBTyxFQUFFLHNCQUFzQixFQUFFLDRCQUE0QixFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFzR25HLE1BQU0sQ0FBQyxNQUFNLHVCQUF1QixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDbkQsWUFBWSxFQUFFLDhCQUE4QjtJQUM1QyxxQkFBcUIsRUFBRSxPQUFPO0lBQzlCLHlCQUF5QixFQUFFLEdBQUc7SUFDOUIsa0JBQWtCLEVBQUUsRUFBRTtJQUN0QiwwQkFBMEIsRUFBRSxTQUFTO0lBQ3JDLHFCQUFxQixFQUFFLGNBQWM7Q0FDN0IsQ0FBQyxDQUFDO0FBRVosTUFBTSxJQUFJLEdBQWtDLENBQUMsVUFBVSxFQUFFLEVBQUUsR0FBRyxNQUFNLElBQUksS0FBSyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzdGLE1BQU0sT0FBTyxHQUFHLElBQUksV0FBVyxFQUFFLENBQUM7QUFDbEMsTUFBTSxTQUFTLEdBQUcsQ0FBQyxRQUFpQixFQUFFLFVBQWtCLEVBQUUsVUFBa0IsRUFBc0IsRUFBRSxDQUNsRyxPQUFPLFFBQVEsS0FBSyxRQUFRLElBQUksTUFBTSxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFDO09BQ3ZGLFFBQVEsSUFBSSxVQUFVLElBQUksUUFBUSxJQUFJLFVBQVUsQ0FBQztBQUN0RCxNQUFN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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "28.4.0",
3
+ "version": "28.5.0",
4
4
  "private": false,
5
5
  "description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
6
6
  "exports": {
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '28.4.0',
6
+ version: '28.5.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }
package/ts/data/index.ts CHANGED
@@ -24,6 +24,7 @@ export * from './nodecredential.js';
24
24
  export * from './nodeenrollment.js';
25
25
  export * from './registry.js';
26
26
  export * from './runtimeassignment.js';
27
+ export * from './runtimeconfig.js';
27
28
  export * from './secret.js';
28
29
  export * from './baremetal.js';
29
30
  export * from './baseos.js';
@@ -0,0 +1,385 @@
1
+ import type {
2
+ IImmutableContainerInvocationPlatformEvidenceV1,
3
+ IImmutableContainerInvocationV1,
4
+ TSha256Digest,
5
+ } from './immutableimage.js';
6
+ import {
7
+ buildDigestPinnedImageReference,
8
+ isSha256Digest,
9
+ validateImmutableContainerInvocation,
10
+ verifyImmutableContainerInvocationDigest,
11
+ } from './immutableimage.js';
12
+ import type { IRuntimeAssignmentV1, IRuntimeAuthorityReferenceV1 } from './runtimeassignment.js';
13
+ import { snapshotRuntimeAssignmentV1, validateRuntimeAssignmentV1 } from './runtimeassignment.js';
14
+ import { canonicalizeStrictJson, createCanonicalJsonSha256Hex } from '../private/canonicaljson.js';
15
+
16
+ /** A container target only; declaration does not publish a host or gateway port. */
17
+ export interface IRuntimeConfigTargetPortV1 {
18
+ name: string;
19
+ port: number;
20
+ /** Inclusive; equal to port for a single port. */
21
+ portEnd: number;
22
+ protocol: 'tcp' | 'udp';
23
+ }
24
+
25
+ export interface IRuntimeConfigReadinessTimingV1 {
26
+ /** Measured from the evidenced container start; no probes before this delay. */
27
+ initialDelayMs: number;
28
+ /** Failures during this startup grace cannot mark the workload unhealthy. */
29
+ startupGraceMs: number;
30
+ /** Probe start-to-start interval; probes must never overlap. */
31
+ intervalMs: number;
32
+ /** Per-probe deadline, including connection, TLS and HTTP response headers. */
33
+ timeoutMs: number;
34
+ /** Consecutive successful probes needed before ready becomes true. */
35
+ successThreshold: number;
36
+ /** Consecutive failures after startup grace needed to declare unhealthy. */
37
+ failureThreshold: number;
38
+ }
39
+
40
+ export type TRuntimeConfigReadinessV1 = IRuntimeConfigReadinessTimingV1 & (
41
+ | { kind: 'process' }
42
+ | { kind: 'tcp'; targetPort: string; port: number }
43
+ | {
44
+ kind: 'http';
45
+ targetPort: string;
46
+ port: number;
47
+ scheme: 'http' | 'https';
48
+ /**
49
+ * Host header name and HTTPS SNI/certificate hostname. Connect only to the
50
+ * evidenced container endpoint, never resolve this name as a probe target.
51
+ * HTTPS verifies system trust and this hostname; no insecure TLS mode.
52
+ */
53
+ host: string;
54
+ /** Canonical origin-form request target, including any nonsecret query. */
55
+ path: string;
56
+ /** Sorted unique final HTTP response status codes. */
57
+ expectedStatusCodes: number[];
58
+ /** A redirect response is evaluated as received; it cannot redirect probes. */
59
+ followRedirects: false;
60
+ }
61
+ );
62
+
63
+ export interface IRuntimeConfigLogPolicyV1 {
64
+ /** Maximum UTF-8 bytes in one captured stdout/stderr line. */
65
+ maximumLineBytes: number;
66
+ /** Maximum bytes waiting for delivery; overflow requires explicit evidence. */
67
+ maximumBufferedBytes: number;
68
+ /** Retention of archived logs; durable payloads belong to SmartBucket. */
69
+ retentionMs: number;
70
+ }
71
+
72
+ /**
73
+ * Controller-owned resolved nonsecret execution intent. OCI command, effective
74
+ * environment, working directory and identity must be resolved before sealing;
75
+ * consumers must not append inherited image defaults or choose runtime defaults.
76
+ * Secrets, network identity and storage claims remain separately assignment-bound
77
+ * owner references. This document contains no registry credentials or host paths.
78
+ */
79
+ export interface IRuntimeConfigV1 extends IRuntimeAuthorityReferenceV1 {
80
+ schemaVersion: 1;
81
+ organizationId: string;
82
+ serviceId: string;
83
+ image: {
84
+ registryHost: string;
85
+ repository: string;
86
+ /** Root descriptor digest, which may be an OCI platform index. */
87
+ imageDigest: TSha256Digest;
88
+ invocation: IImmutableContainerInvocationV1;
89
+ };
90
+ /** Complete effective public environment; secret values must never be inlined. */
91
+ environment: Record<string, string>;
92
+ workingDirectory: string;
93
+ cpuMillis: number;
94
+ memoryBytes: number;
95
+ readonlyRootfs: boolean;
96
+ runAsUser: number;
97
+ runAsGroup: number;
98
+ /** Sorted unique names, with no overlapping ranges for the same transport. */
99
+ targetPorts: IRuntimeConfigTargetPortV1[];
100
+ readiness: TRuntimeConfigReadinessV1;
101
+ /**
102
+ * SmartData owns log metadata and SmartBucket owns archived payloads. This
103
+ * policy does not authorize persistent local files. Any CRI log capture uses
104
+ * disposable process-local transport artifacts and must evidence bounded
105
+ * buffering, backpressure and lost data; it must not silently truncate logs.
106
+ */
107
+ logs: IRuntimeConfigLogPolicyV1;
108
+ }
109
+
110
+ export interface IRuntimeConfigAssignmentBindingV1 {
111
+ config: IRuntimeConfigV1;
112
+ assignment: IRuntimeAssignmentV1;
113
+ platformEvidence: IImmutableContainerInvocationPlatformEvidenceV1;
114
+ }
115
+
116
+ export const runtimeConfigV1Contract = Object.freeze({
117
+ digestDomain: 'serve.zone/runtime-config-v1',
118
+ maximumCanonicalBytes: 262_144,
119
+ maximumEnvironmentEntries: 128,
120
+ maximumTargetPorts: 64,
121
+ maximumReadinessDurationMs: 3_600_000,
122
+ maximumLogRetentionMs: 31_536_000_000,
123
+ } as const);
124
+
125
+ const fail: (messageArg: string) => never = (messageArg) => { throw new Error(messageArg); };
126
+ const encoder = new TextEncoder();
127
+ const isInteger = (valueArg: unknown, minimumArg: number, maximumArg: number): valueArg is number =>
128
+ typeof valueArg === 'number' && Number.isSafeInteger(valueArg) && !Object.is(valueArg, -0)
129
+ && valueArg >= minimumArg && valueArg <= maximumArg;
130
+ const isIdentifier = (valueArg: unknown): valueArg is string =>
131
+ typeof valueArg === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/.test(valueArg);
132
+ const isDigest = (valueArg: unknown): valueArg is TSha256Digest =>
133
+ typeof valueArg === 'string' && isSha256Digest(valueArg);
134
+ const isPortName = (valueArg: unknown): valueArg is string =>
135
+ typeof valueArg === 'string' && /^[a-z0-9][a-z0-9_.-]{0,62}$/.test(valueArg);
136
+ const isExactRecord = (
137
+ valueArg: unknown, keysArg: readonly string[],
138
+ ): valueArg is Record<string, unknown> => {
139
+ if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) return false;
140
+ const keys = Object.keys(valueArg);
141
+ return keys.length === keysArg.length && keys.every((keyArg) => keysArg.includes(keyArg));
142
+ };
143
+
144
+ /**
145
+ * Bound traversal before canonicalization and copy descriptor values so input
146
+ * getters/toJSON never run. The canonical helper supplies scalar Unicode and
147
+ * safe-integer validation. Detached output is obtained before any async work.
148
+ */
149
+ const snapshotBoundedJson = (valueArg: unknown): unknown => {
150
+ let nodes = 0;
151
+ let stringBytes = 0;
152
+ const ancestors = new Set<object>();
153
+ const accountString = (value: string): void => {
154
+ if (value.length > runtimeConfigV1Contract.maximumCanonicalBytes) fail('runtime config string is oversized');
155
+ stringBytes += encoder.encode(value).byteLength;
156
+ if (stringBytes > runtimeConfigV1Contract.maximumCanonicalBytes) fail('runtime config is oversized');
157
+ };
158
+ const copy = (value: unknown, depth: number): unknown => {
159
+ if (++nodes > 8192 || depth > 12) fail('runtime config exceeds structural bounds');
160
+ if (typeof value === 'string') { accountString(value); return value; }
161
+ if (value === null || typeof value === 'boolean' || typeof value === 'number') return value;
162
+ if (!value || typeof value !== 'object') fail('runtime config contains a non-JSON value');
163
+ if (ancestors.has(value)) fail('runtime config contains a cycle');
164
+ ancestors.add(value);
165
+ try {
166
+ const array = Array.isArray(value);
167
+ const prototype = Object.getPrototypeOf(value);
168
+ if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) {
169
+ fail('runtime config requires plain records and arrays');
170
+ }
171
+ const keys = Reflect.ownKeys(value);
172
+ if (keys.length > 257) fail('runtime config object exceeds key bounds');
173
+ if (array) {
174
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, 'length');
175
+ const length = lengthDescriptor?.value;
176
+ if (!isInteger(length, 0, 256) || keys.length !== length + 1) {
177
+ fail('runtime config requires bounded dense arrays');
178
+ }
179
+ const result: unknown[] = [];
180
+ for (let index = 0; index < length; index++) {
181
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
182
+ if (!descriptor || !descriptor.enumerable || !Object.hasOwn(descriptor, 'value')) {
183
+ fail('runtime config array contains a missing or non-data entry');
184
+ }
185
+ result.push(copy(descriptor.value, depth + 1));
186
+ }
187
+ return result;
188
+ }
189
+ const result: Record<string, unknown> = Object.create(null);
190
+ for (const key of keys) {
191
+ if (typeof key !== 'string') fail('runtime config contains a symbol key');
192
+ accountString(key);
193
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
194
+ if (!descriptor || !descriptor.enumerable || !Object.hasOwn(descriptor, 'value')) {
195
+ fail('runtime config requires enumerable data properties');
196
+ }
197
+ result[key] = copy(descriptor.value, depth + 1);
198
+ }
199
+ return result;
200
+ } finally { ancestors.delete(value); }
201
+ };
202
+ const canonical = canonicalizeStrictJson(copy(valueArg, 0), fail);
203
+ if (encoder.encode(canonical).byteLength > runtimeConfigV1Contract.maximumCanonicalBytes) {
204
+ fail('runtime config canonical JSON is oversized');
205
+ }
206
+ return JSON.parse(canonical);
207
+ };
208
+
209
+ const timingKeys = ['initialDelayMs', 'startupGraceMs', 'intervalMs', 'timeoutMs',
210
+ 'successThreshold', 'failureThreshold'] as const;
211
+ const assertReadiness = (valueArg: unknown, portsArg: IRuntimeConfigTargetPortV1[]): void => {
212
+ if (!valueArg || typeof valueArg !== 'object') fail('runtime config readiness is required');
213
+ const value = valueArg as Record<string, unknown>;
214
+ const keys = value.kind === 'process' ? ['kind', ...timingKeys]
215
+ : value.kind === 'tcp' ? ['kind', ...timingKeys, 'targetPort', 'port']
216
+ : value.kind === 'http' ? ['kind', ...timingKeys, 'targetPort', 'port',
217
+ 'scheme', 'host', 'path', 'expectedStatusCodes', 'followRedirects'] : [];
218
+ if (keys.length === 0 || !isExactRecord(value, keys)
219
+ || !isInteger(value.initialDelayMs, 0, runtimeConfigV1Contract.maximumReadinessDurationMs)
220
+ || !isInteger(value.startupGraceMs, 0, runtimeConfigV1Contract.maximumReadinessDurationMs)
221
+ || !isInteger(value.intervalMs, 100, runtimeConfigV1Contract.maximumReadinessDurationMs)
222
+ || !isInteger(value.timeoutMs, 1, 60_000)
223
+ || !isInteger(value.successThreshold, 1, 100)
224
+ || !isInteger(value.failureThreshold, 1, 100)
225
+ || value.timeoutMs > value.intervalMs || value.initialDelayMs > value.startupGraceMs) {
226
+ fail('runtime config readiness must use explicit bounded timing and exact schema');
227
+ }
228
+ if (value.kind === 'process') return;
229
+ const target = portsArg.find((portArg) => portArg.name === value.targetPort);
230
+ if (!isPortName(value.targetPort) || !target || target.protocol !== 'tcp'
231
+ || !isInteger(value.port, target.port, target.portEnd)) {
232
+ fail('runtime config readiness must select a declared TCP target and port');
233
+ }
234
+ if (value.kind === 'tcp') return;
235
+ if ((value.scheme !== 'http' && value.scheme !== 'https') || value.followRedirects !== false
236
+ || typeof value.host !== 'string' || value.host.length > 253
237
+ || !/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/.test(value.host)
238
+ || typeof value.path !== 'string' || value.path.length > 4096
239
+ || !value.path.startsWith('/') || value.path.startsWith('//')
240
+ || /[^\x21-\x7e]|[\\#]/.test(value.path)
241
+ || /%(?![0-9A-F]{2})/.test(value.path)
242
+ || !Array.isArray(value.expectedStatusCodes) || value.expectedStatusCodes.length === 0
243
+ || value.expectedStatusCodes.length > 32) {
244
+ fail('runtime config HTTP readiness must use an exact bounded request policy');
245
+ }
246
+ const parsed = new URL(value.path, 'http://runtime.invalid');
247
+ if (`${parsed.pathname}${parsed.search}` !== value.path) fail('runtime config HTTP path must be canonical');
248
+ let previousCode = 0;
249
+ for (const code of value.expectedStatusCodes) {
250
+ if (!isInteger(code, 200, 599) || code <= previousCode) {
251
+ fail('runtime config HTTP status expectations must be sorted unique final codes');
252
+ }
253
+ previousCode = code;
254
+ }
255
+ };
256
+
257
+ const assertConfig = (valueArg: unknown): IRuntimeConfigV1 => {
258
+ if (!isExactRecord(valueArg, ['schemaVersion', 'id', 'generation', 'organizationId', 'serviceId',
259
+ 'image', 'environment', 'workingDirectory', 'cpuMillis', 'memoryBytes', 'readonlyRootfs',
260
+ 'runAsUser', 'runAsGroup', 'targetPorts', 'readiness', 'logs', 'digest'])
261
+ || valueArg.schemaVersion !== 1 || !isIdentifier(valueArg.id)
262
+ || !isInteger(valueArg.generation, 1, Number.MAX_SAFE_INTEGER)
263
+ || !isIdentifier(valueArg.organizationId) || !isIdentifier(valueArg.serviceId)
264
+ || !isDigest(valueArg.digest)) fail('runtime config must use its exact v1 schema');
265
+ const image = valueArg.image;
266
+ if (!isExactRecord(image, ['registryHost', 'repository', 'imageDigest', 'invocation'])
267
+ || typeof image.registryHost !== 'string' || image.registryHost.length > 253
268
+ || typeof image.repository !== 'string' || image.repository.length > 255
269
+ || !isDigest(image.imageDigest)
270
+ || validateImmutableContainerInvocation(image.invocation).length !== 0) {
271
+ fail('runtime config image must contain exact immutable invocation evidence');
272
+ }
273
+ buildDigestPinnedImageReference(image.registryHost as string, image.repository as string,
274
+ image.imageDigest as TSha256Digest);
275
+ const environment = valueArg.environment;
276
+ if (!environment || typeof environment !== 'object' || Array.isArray(environment)
277
+ || Object.keys(environment).length > runtimeConfigV1Contract.maximumEnvironmentEntries) {
278
+ fail('runtime config requires an explicit bounded nonsecret environment');
279
+ }
280
+ for (const [key, value] of Object.entries(environment)) {
281
+ if (!/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(key) || typeof value !== 'string'
282
+ || value.includes('\u0000') || encoder.encode(value).byteLength > 8192) {
283
+ fail('runtime config environment must contain bounded string entries');
284
+ }
285
+ }
286
+ const directory = valueArg.workingDirectory;
287
+ if (typeof directory !== 'string' || !directory.startsWith('/')
288
+ || encoder.encode(directory).byteLength > 4096 || /[\u0000-\u001f\u007f]/.test(directory)
289
+ || (directory !== '/' && directory.split('/').slice(1).some((partArg) => !partArg || partArg === '.' || partArg === '..'))
290
+ || !isInteger(valueArg.cpuMillis, 1, 1_000_000)
291
+ || !isInteger(valueArg.memoryBytes, 16 * 1024 * 1024, 2 ** 50)
292
+ || typeof valueArg.readonlyRootfs !== 'boolean'
293
+ || !isInteger(valueArg.runAsUser, 0, 2 ** 31 - 1)
294
+ || !isInteger(valueArg.runAsGroup, 0, 2 ** 31 - 1)) {
295
+ fail('runtime config requires canonical directory, explicit resources and numeric identity');
296
+ }
297
+ const ports = valueArg.targetPorts;
298
+ if (!Array.isArray(ports) || ports.length > runtimeConfigV1Contract.maximumTargetPorts) {
299
+ fail('runtime config target ports must be a bounded array');
300
+ }
301
+ let previousName = '';
302
+ const validatedPorts: IRuntimeConfigTargetPortV1[] = [];
303
+ for (const port of ports) {
304
+ if (!isExactRecord(port, ['name', 'port', 'portEnd', 'protocol'])
305
+ || !isPortName(port.name) || port.name <= previousName
306
+ || !isInteger(port.port, 1, 65535) || !isInteger(port.portEnd, port.port, 65535)
307
+ || (port.protocol !== 'tcp' && port.protocol !== 'udp')
308
+ || validatedPorts.some((previousArg) => previousArg.protocol === port.protocol
309
+ && (port.port as number) <= previousArg.portEnd && (port.portEnd as number) >= previousArg.port)) {
310
+ fail('runtime config target ports require sorted unique names and nonoverlapping transport ranges');
311
+ }
312
+ previousName = port.name;
313
+ validatedPorts.push(port as unknown as IRuntimeConfigTargetPortV1);
314
+ }
315
+ assertReadiness(valueArg.readiness, validatedPorts);
316
+ const logs = valueArg.logs;
317
+ if (!isExactRecord(logs, ['maximumLineBytes', 'maximumBufferedBytes', 'retentionMs'])
318
+ || !isInteger(logs.maximumLineBytes, 1, 1_048_576)
319
+ || !isInteger(logs.maximumBufferedBytes, 1, 67_108_864)
320
+ || logs.maximumBufferedBytes < logs.maximumLineBytes
321
+ || !isInteger(logs.retentionMs, 1, runtimeConfigV1Contract.maximumLogRetentionMs)) {
322
+ fail('runtime config logs require explicit bounded line, buffer and retention limits');
323
+ }
324
+ return valueArg as unknown as IRuntimeConfigV1;
325
+ };
326
+
327
+ /**
328
+ * Detached exact-schema snapshot for synchronous persistence assertions. Digest
329
+ * integrity, scope authorization, admission and runtime readiness are not implied.
330
+ */
331
+ export const snapshotRuntimeConfigV1 = (valueArg: unknown): IRuntimeConfigV1 =>
332
+ assertConfig(snapshotBoundedJson(valueArg));
333
+
334
+ /** Domain-separated canonical JSON excludes only the config's own digest. */
335
+ export const computeRuntimeConfigV1Digest = async (configArg: IRuntimeConfigV1): Promise<TSha256Digest> => {
336
+ const { digest: _digest, ...payload } = snapshotRuntimeConfigV1(configArg);
337
+ return `sha256:${await createCanonicalJsonSha256Hex(canonicalizeStrictJson({
338
+ domain: runtimeConfigV1Contract.digestDomain, payload,
339
+ }, fail), fail)}`;
340
+ };
341
+
342
+ const validateSnapshot = async (configArg: IRuntimeConfigV1): Promise<string[]> => {
343
+ if (!await verifyImmutableContainerInvocationDigest(configArg.image.invocation)) {
344
+ return ['runtime config immutable invocation digest does not match'];
345
+ }
346
+ return configArg.digest === await computeRuntimeConfigV1Digest(configArg)
347
+ ? [] : ['runtime config digest does not match'];
348
+ };
349
+
350
+ export const validateRuntimeConfigV1 = async (valueArg: unknown): Promise<string[]> => {
351
+ try { return await validateSnapshot(snapshotRuntimeConfigV1(valueArg)); }
352
+ catch { return ['runtime config must use its exact canonical v1 schema']; }
353
+ };
354
+
355
+ /**
356
+ * Bind valid content to one exact assignment and select its platform evidence.
357
+ * No assignment digest is embedded in the config, avoiding circular hashes.
358
+ * This proves content consistency only: the caller must authenticate the
359
+ * controller, durably admit the attempt, resolve other owner references, and
360
+ * verify runtime and readiness evidence before effects or route publication.
361
+ */
362
+ export const bindRuntimeConfigV1ToAssignment = async (
363
+ configArg: unknown, assignmentArg: unknown,
364
+ ): Promise<IRuntimeConfigAssignmentBindingV1> => {
365
+ const config = snapshotRuntimeConfigV1(configArg);
366
+ const assignment = snapshotRuntimeAssignmentV1(snapshotBoundedJson(assignmentArg));
367
+ if ((await validateSnapshot(config)).length !== 0
368
+ || (await validateRuntimeAssignmentV1(assignment)).length !== 0) {
369
+ fail('runtime config binding requires valid config, invocation and assignment digests');
370
+ }
371
+ const reference = assignment.workload.config;
372
+ if (reference.id !== config.id || reference.generation !== config.generation
373
+ || reference.digest !== config.digest || assignment.organizationId !== config.organizationId
374
+ || assignment.serviceId !== config.serviceId
375
+ || assignment.workload.registryHost !== config.image.registryHost
376
+ || assignment.workload.repository !== config.image.repository
377
+ || assignment.workload.imageDigest !== config.image.imageDigest) {
378
+ fail('runtime config does not match the exact assignment reference, ownership and image');
379
+ }
380
+ const platformEvidence = config.image.invocation.evidence.find(
381
+ (entryArg) => entryArg.platform === assignment.workload.platform,
382
+ );
383
+ if (!platformEvidence) fail('runtime config lacks exact assignment platform evidence');
384
+ return { config, assignment, platformEvidence };
385
+ };