@uns-kit/core 3.0.33 → 3.0.35
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 +50 -0
- package/dist/config/app-config.d.ts +1 -1
- package/dist/config/app-config.d.ts.map +1 -1
- package/dist/config/app-config.js.map +1 -1
- package/dist/runtime/mqtt-handover-state.d.ts +48 -0
- package/dist/runtime/mqtt-handover-state.d.ts.map +1 -0
- package/dist/runtime/mqtt-handover-state.js +61 -0
- package/dist/runtime/mqtt-handover-state.js.map +1 -0
- package/dist/runtime/process-group-authorization.d.ts +72 -0
- package/dist/runtime/process-group-authorization.d.ts.map +1 -1
- package/dist/runtime/process-group-authorization.js +31 -0
- package/dist/runtime/process-group-authorization.js.map +1 -1
- package/dist/runtime/process-group-contract.d.ts +1 -0
- package/dist/runtime/process-group-contract.d.ts.map +1 -1
- package/dist/runtime/process-group-contract.js +2 -1
- package/dist/runtime/process-group-contract.js.map +1 -1
- package/dist/runtime/process-group-direct-contract.d.ts +181 -0
- package/dist/runtime/process-group-direct-contract.d.ts.map +1 -0
- package/dist/runtime/process-group-direct-contract.js +151 -0
- package/dist/runtime/process-group-direct-contract.js.map +1 -0
- package/dist/runtime/process-group-direct-review.d.ts +203 -0
- package/dist/runtime/process-group-direct-review.d.ts.map +1 -0
- package/dist/runtime/process-group-direct-review.js +263 -0
- package/dist/runtime/process-group-direct-review.js.map +1 -0
- package/dist/runtime/process-group-direct-runtime.d.ts +18 -0
- package/dist/runtime/process-group-direct-runtime.d.ts.map +1 -0
- package/dist/runtime/process-group-direct-runtime.js +154 -0
- package/dist/runtime/process-group-direct-runtime.js.map +1 -0
- package/dist/runtime/process-group-snapshot.mjs +44 -0
- package/dist/uns/handover-manager.d.ts +10 -1
- package/dist/uns/handover-manager.d.ts.map +1 -1
- package/dist/uns/handover-manager.js +113 -5
- package/dist/uns/handover-manager.js.map +1 -1
- package/dist/uns/uns-interfaces.d.ts +4 -0
- package/dist/uns/uns-interfaces.d.ts.map +1 -1
- package/dist/uns/uns-interfaces.js.map +1 -1
- package/dist/uns/uns-proxy-process.d.ts +1 -0
- package/dist/uns/uns-proxy-process.d.ts.map +1 -1
- package/dist/uns/uns-proxy-process.js +6 -2
- package/dist/uns/uns-proxy-process.js.map +1 -1
- package/dist/uns-config/config-schema.d.ts +4 -1
- package/dist/uns-config/config-schema.d.ts.map +1 -1
- package/dist/uns-config/uns-core-schema.d.ts +4 -1
- package/dist/uns-config/uns-core-schema.d.ts.map +1 -1
- package/dist/uns-config/uns-core-schema.js +1 -1
- package/dist/uns-config/uns-core-schema.js.map +1 -1
- package/dist/uns-mqtt/mqtt-proxy.d.ts +1 -0
- package/dist/uns-mqtt/mqtt-proxy.d.ts.map +1 -1
- package/dist/uns-mqtt/mqtt-proxy.js +5 -0
- package/dist/uns-mqtt/mqtt-proxy.js.map +1 -1
- package/dist/uns-mqtt/mqtt-worker.d.ts.map +1 -1
- package/dist/uns-mqtt/mqtt-worker.js +13 -1
- package/dist/uns-mqtt/mqtt-worker.js.map +1 -1
- package/dist/uns-mqtt/throttled-queue.d.ts +8 -1
- package/dist/uns-mqtt/throttled-queue.d.ts.map +1 -1
- package/dist/uns-mqtt/throttled-queue.js +32 -4
- package/dist/uns-mqtt/throttled-queue.js.map +1 -1
- package/dist/uns-mqtt/uns-mqtt-proxy.d.ts +26 -2
- package/dist/uns-mqtt/uns-mqtt-proxy.d.ts.map +1 -1
- package/dist/uns-mqtt/uns-mqtt-proxy.js +82 -6
- package/dist/uns-mqtt/uns-mqtt-proxy.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-group-direct-review.js","sourceRoot":"","sources":["../../src/runtime/process-group-direct-review.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { configurationGroupSnapshot, configurationResourceHash } from \"./configuration-group-contract.js\";\nimport { authorizeProcessGroupRuntime, processGroupManifestSchema } from \"./process-group-authorization.js\";\nimport {\n processGroupJournalContract,\n processGroupJournalSchema,\n processGroupSourceIdentity,\n readConfigurationSelector,\n resolvedProcessGroupChannels,\n} from \"./process-group-contract.js\";\nimport {\n type ProcessGroupDirectDescriptor,\n type ProcessGroupDirectInput,\n processGroupDirectDescriptorSchema,\n processGroupDirectInputSchema,\n processGroupRetirementPolicySchema,\n retiredJournalReferenceSchema,\n} from \"./process-group-direct-contract.js\";\nimport { describeInstalledProcessGroupGeneration, inspectGenerationJournal } from \"./process-group-generation.js\";\nimport type { GenerationJournalReference } from \"./process-group-generation.js\";\nimport {\n groupArtifactHash,\n groupChannelsHash,\n configurationAuthorizationHash as hash,\n readProcessGroupResource as read,\n sha256,\n} from \"./process-group-resources.js\";\nimport { normalizeRuntimeProcessGroup, selectedProcessGroupDescriptor } from \"./process-group-runtime-selection.js\";\nimport type { UpgradeConfiguration } from \"./process-group-upgrade.js\";\n\ntype Config = UpgradeConfiguration;\nexport type DirectTransitionConfigurations = { source: { publisher: Config; receiver: Config }; target: Config };\nconst inside = (root: string, file: string) => {\n const resolved = path.resolve(root, file);\n if (!resolved.startsWith(path.resolve(root) + path.sep)) throw Error(\"Direct resource escapes installed package\");\n return read(resolved);\n};\nexport function processGroupDirectDescriptor(packageRoot: string): ProcessGroupDirectDescriptor | undefined {\n const pkg = JSON.parse(read(path.join(packageRoot, \"package.json\")).toString());\n return pkg.unsDatahub?.processGroupDirectRuntime === undefined\n ? undefined\n : processGroupDirectDescriptorSchema.parse(pkg.unsDatahub.processGroupDirectRuntime);\n}\nexport function describeDirectProcessGroup(c: Config) {\n const descriptor = processGroupDirectDescriptor(c.packageRoot);\n if (\n !descriptor ||\n (descriptor.match && readConfigurationSelector(c.raw, descriptor.match.path) !== descriptor.match.value) ||\n readConfigurationSelector(c.raw, descriptor.runtimeRolePath) !== descriptor.directRole ||\n readConfigurationSelector(c.raw, descriptor.journalPath) !== undefined\n )\n throw Error(\"Exact declared direct mode without an input journal required\");\n const publisher = readConfigurationSelector(c.raw, descriptor.publisherPath);\n if (!publisher || typeof publisher !== \"object\" || Array.isArray(publisher)) throw Error(\"Explicit direct publisher required\");\n const selectedResources = descriptor.resources.filter(\n (r) => readConfigurationSelector(c.raw, r.kind === \"file\" ? r.manifestPath : r.groupPath) !== undefined,\n );\n if (selectedResources.length !== 1) throw Error(\"Select exactly one declared direct resource\");\n const resource = selectedResources[0]!;\n if (resource.kind === \"configuration\") {\n const snapshot = configurationGroupSnapshot(readConfigurationSelector(c.raw, resource.groupPath));\n if (\n configurationResourceHash(readConfigurationSelector(c.resolved, resource.groupPath)) !==\n configurationResourceHash(readConfigurationSelector(c.raw, resource.groupPath))\n )\n throw Error(\"Resolved direct business resource differs from its reviewed configuration\");\n return {\n descriptor,\n resource,\n groupId: snapshot.group.id,\n processName: snapshot.group.processName,\n resourceSha256: snapshot.sha256,\n profiles: snapshot.profiles.map((p) => ({ processName: p.processName, mappingSha256: p.resourceSha256, dataGroup: p.dataGroup })),\n snapshot,\n };\n }\n const manifestPath = readConfigurationSelector(c.raw, resource.manifestPath);\n const selected = resource.manifests.find((m) => m.file === manifestPath);\n if (!selected) throw Error(\"Unregistered direct manifest\");\n if (readConfigurationSelector(c.resolved, resource.manifestPath) !== selected.file) throw Error(\"Resolved direct manifest selection changed\");\n const bytes = inside(c.packageRoot, selected.file),\n manifest = processGroupManifestSchema.parse(JSON.parse(bytes.toString()));\n if (manifest.id !== selected.id) throw Error(\"Direct manifest identity differs\");\n for (const p of manifest.profiles)\n if (sha256(inside(c.packageRoot, path.join(path.dirname(selected.file), p.mappingFile))) !== p.mappingSha256)\n throw Error(\"Direct profile resource changed\");\n return {\n descriptor,\n resource,\n groupId: manifest.id,\n processName: manifest.processName,\n resourceSha256: sha256(bytes),\n profiles: manifest.profiles.map(({ processName, mappingSha256, dataGroup }) => ({ processName, mappingSha256, dataGroup })),\n manifest,\n manifestPath: selected.file,\n manifestFile: path.resolve(c.packageRoot, selected.file),\n };\n}\nfunction remove(raw: Record<string, unknown>, keys: readonly string[]): void {\n let current = raw;\n const parents: Array<[Record<string, unknown>, string]> = [];\n for (const key of keys.slice(0, -1)) {\n const next = current[key];\n if (!next || typeof next !== \"object\" || Array.isArray(next)) return;\n parents.push([current, key]);\n current = next as Record<string, unknown>;\n }\n delete current[keys.at(-1)!];\n for (const [parent, key] of parents.reverse()) {\n const value = parent[key];\n if (value && typeof value === \"object\" && !Object.keys(value).length) delete parent[key];\n else break;\n }\n}\nfunction projection(raw: Record<string, unknown>, d: ProcessGroupDirectDescriptor, excluded: readonly string[]) {\n const copy = structuredClone(raw);\n remove(copy, d.journalPath);\n remove(copy, d.runtimeRolePath);\n for (const r of d.resources) if (r.kind === \"file\") remove(copy, r.manifestPath);\n const uns = copy[\"uns\"];\n if (uns && typeof uns === \"object\" && !Array.isArray(uns)) for (const key of excluded) delete (uns as Record<string, unknown>)[key];\n // Removing a lifecycle leaf must not turn an otherwise empty publisher into a difference.\n const publisher = readConfigurationSelector(copy, d.publisherPath);\n if (publisher && typeof publisher === \"object\" && !Object.keys(publisher).length) remove(copy, d.publisherPath);\n return hash(copy);\n}\n/** Data-only installed-resource review. Does not stop, initialize, rewrite or grant launch authority. */\nexport function reviewInstalledProcessGroupDirectTransition(\n operationId: string,\n rawInput: unknown,\n configs: DirectTransitionConfigurations,\n stateRoot: string,\n excludedUnsFields: readonly string[] = [],\n) {\n const input = processGroupDirectInputSchema.parse(rawInput),\n owner = input.target.controller,\n first = configs.source.publisher,\n target = configs.target;\n const pkg = (c: Config) => JSON.parse(read(path.join(c.packageRoot, \"package.json\")).toString());\n for (const [p, c] of [\n [input.source.publisher, first],\n [input.source.receiver, configs.source.receiver],\n [input.target, target],\n ] as const) {\n if (\n path.resolve(c.packageRoot, \"instances\", p.instanceId) !== c.instanceDirectory ||\n fs.realpathSync(c.instanceDirectory) !== c.instanceDirectory ||\n pkg(c).name !== p.rttNode ||\n pkg(c).version !== p.version\n )\n throw Error(\"Exact installed package and instance placement required\");\n }\n const context = (c: Config) => ({\n packageRoot: c.packageRoot,\n instanceDirectory: c.instanceDirectory,\n rawConfiguration: c.raw,\n channels: resolvedProcessGroupChannels(c.resolved),\n controllerName: owner,\n stateRoot,\n });\n const old = authorizeProcessGroupRuntime(context(first)),\n receiver = authorizeProcessGroupRuntime(context(configs.source.receiver));\n if (\n old.role !== \"target\" ||\n receiver.role !== \"receiver\" ||\n old.file !== receiver.file ||\n old.sha256 !== receiver.sha256 ||\n old.directory !== receiver.directory ||\n !old.journalId ||\n old.journalId !== receiver.journalId\n )\n throw Error(\"Exact authorized publisher/receiver owners required\");\n const sourcePolicy = read(old.file, true),\n sourceDescriptor = selectedProcessGroupDescriptor(first.packageRoot, first.raw);\n if (!sourceDescriptor) throw Error(\"Missing source shared contract\");\n const normalized = normalizeRuntimeProcessGroup(first.raw, sourceDescriptor),\n direct = describeDirectProcessGroup(target),\n d = direct.descriptor;\n const namespace = sourceDescriptor.namespace;\n if (\n d.namespace !== namespace ||\n hash(d.publisherPath) !== hash(sourceDescriptor.publisherPath) ||\n hash(d.journalPath) !== hash(sourceDescriptor.journalPath) ||\n hash(d.runtimeRolePath) !== hash(sourceDescriptor.runtimeRolePath) ||\n direct.groupId !== old.groupId ||\n direct.processName !== old.groupProcessName ||\n (target.raw[\"uns\"] as Record<string, unknown>)?.[\"processName\"] !== old.groupProcessName ||\n projection(first.raw, d, excludedUnsFields) !== projection(target.raw, d, excludedUnsFields)\n )\n throw Error(\"Direct business configuration, selector or group identity changed\");\n const channels = groupChannelsHash(context(first).channels);\n if (channels !== groupChannelsHash(context(configs.source.receiver).channels) || channels !== groupChannelsHash(context(target).channels))\n throw Error(\"Direct broker/channel identity changed\");\n for (const c of [first, configs.source.receiver, target])\n if ((resolvedProcessGroupChannels(c.resolved)[2] as { publishQueue?: { enabled?: boolean } }).publishQueue?.enabled === true)\n throw Error(\"Initial direct retirement requires disabled disk MQTT output\");\n let reference, sourceResourceSha256: string;\n if (sourceDescriptor.protocolVersion === 2) {\n if (direct.resource.kind !== \"configuration\" || !direct.snapshot) throw Error(\"Resource kind must remain configuration based\");\n const generation = describeInstalledProcessGroupGeneration(first);\n reference = generation.reference;\n sourceResourceSha256 = configurationGroupSnapshot(readConfigurationSelector(first.raw, sourceDescriptor.groupPath)).sha256;\n } else {\n if (direct.resource.kind !== \"file\" || !direct.manifest) throw Error(\"Resource kind must remain file based\");\n const journal = processGroupJournalSchema.parse(normalized.journal);\n const declared = sourceDescriptor.manifests.find((m) => m.file === journal.groupManifest);\n if (!declared) throw Error(\"Unregistered original manifest\");\n const bytes = inside(first.packageRoot, declared.file),\n manifest = processGroupManifestSchema.parse(JSON.parse(bytes.toString()));\n sourceResourceSha256 = sha256(bytes);\n const contract = processGroupJournalContract(processGroupSourceIdentity(context(first).channels[1] as never), journal, manifest.profiles);\n reference = {\n directory: old.directory,\n journalId: old.journalId,\n journalContractSha256: sha256(contract),\n generationId: manifest.id,\n contractVersion: 1 as const,\n maxRecords: journal.maxRecords,\n maxBytes: journal.maxBytes,\n minFreeBytes: journal.minFreeBytes,\n profiles: manifest.profiles.map(({ processName, mappingSha256, dataGroup }) => ({ processName, mappingSha256, dataGroup })),\n };\n }\n const journal = retiredJournalReferenceSchema.parse(reference) as GenerationJournalReference;\n if (sourceResourceSha256 !== direct.resourceSha256 || hash(journal.profiles) !== hash(direct.profiles))\n throw Error(\"Direct profile resources, paths or units changed\");\n const usage = inspectGenerationJournal(journal); // Pending work is allowed at preview, never at policy switch.\n if (!Buffer.from(sourcePolicy.toString(), \"utf8\").equals(sourcePolicy)) throw Error(\"Original policy encoding cannot be retained exactly\");\n const member = (placement: typeof input.target, c: Config) => ({\n placement,\n processName: String((c.raw[\"uns\"] as Record<string, unknown>)[\"processName\"]),\n configurationSha256: hash(c.raw),\n artifactSha256: groupArtifactHash(c.packageRoot),\n });\n const policy = processGroupRetirementPolicySchema.parse({\n schemaVersion: 5,\n kind: \"uns-process-group-direct-retirement\",\n protocolVersion: 1,\n operationId,\n packageName: pkg(first).name,\n namespace,\n groupId: old.groupId,\n controllerName: owner,\n stateRoot,\n recovery: \"open\",\n journal,\n sourcePolicyJson: sourcePolicy.toString(),\n sourcePolicySha256: sha256(sourcePolicy),\n source: {\n publisher: {\n ...member(input.source.publisher, first),\n policyConfigurationSha256: sourceDescriptor.protocolVersion === 2 ? configurationResourceHash(first.raw) : hash(first.raw),\n },\n receiver: {\n ...member(input.source.receiver, configs.source.receiver),\n policyConfigurationSha256:\n sourceDescriptor.protocolVersion === 2 ? configurationResourceHash(configs.source.receiver.raw) : hash(configs.source.receiver.raw),\n },\n },\n target: {\n ...member(input.target, target),\n packageRoot: target.packageRoot,\n descriptorSha256: hash(d),\n resourceSha256: direct.resourceSha256,\n channelsSha256: channels,\n },\n });\n if (sha256(read(old.file, true)) !== policy.sourcePolicySha256) throw Error(\"Original policy changed during direct review\");\n const targetPolicy = Buffer.from(JSON.stringify(policy));\n if (targetPolicy.length > 65536) throw Error(\"Retirement policy byte budget exceeded\");\n return {\n review: {\n protocolVersion: 1 as const,\n kind: \"paired-to-direct\" as const,\n input,\n operationId,\n policyFile: old.file,\n sourcePolicySha256: sha256(sourcePolicy),\n targetPolicySha256: sha256(targetPolicy),\n journal,\n source: policy.source,\n target: policy.target,\n namespace,\n groupId: old.groupId,\n delivery: { inputPausePossible: true, exactlyOnceOutput: false, brokerReplayNotTransferred: true },\n usage,\n },\n sourcePolicy,\n targetPolicy,\n };\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { UpgradeConfiguration } from "./process-group-upgrade.js";
|
|
2
|
+
export type DirectRuntimeContext = UpgradeConfiguration & {
|
|
3
|
+
controllerName: string;
|
|
4
|
+
operationId?: string;
|
|
5
|
+
launchId?: string;
|
|
6
|
+
};
|
|
7
|
+
/** Startup guard, shared by host and application. An installed declaration never grants authority. */
|
|
8
|
+
export declare function assertProcessGroupDirectLaunch(context: DirectRuntimeContext): void;
|
|
9
|
+
/** PID/launch-bound observation, never authority. Start after output confirmation and input handler setup. */
|
|
10
|
+
export declare function trackProcessGroupDirectReadiness(context: DirectRuntimeContext, ready: () => boolean): () => void;
|
|
11
|
+
export declare function inspectProcessGroupDirectReadiness(directory: string, expected: {
|
|
12
|
+
pid: number;
|
|
13
|
+
launchId: string;
|
|
14
|
+
configurationSha256: string;
|
|
15
|
+
}, now?: number): boolean;
|
|
16
|
+
/** Preserve the managed registry in a direct process; never inherit a journal-mode launch permit. */
|
|
17
|
+
export declare function processGroupDirectLaunchEnvironment(operationId: string | undefined): Record<string, string>;
|
|
18
|
+
//# sourceMappingURL=process-group-direct-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-group-direct-runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/process-group-direct-runtime.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAEvE,MAAM,MAAM,oBAAoB,GAAG,oBAAoB,GAAG;IACxD,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AACF,sGAAsG;AACtG,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CA4ElF;AAED,8GAA8G;AAC9G,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,oBAAoB,EAAE,KAAK,EAAE,MAAM,OAAO,GAAG,MAAM,IAAI,CAgChH;AACD,wBAAgB,kCAAkC,CAChD,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAA;CAAE,EACxE,GAAG,SAAa,GACf,OAAO,CAuBT;AAED,qGAAqG;AACrG,wBAAgB,mCAAmC,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQ3G"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { configuredGroupProcessNames, hasProcessGroupAuthorization, parseProcessGroupRetirementPolicy } from "./process-group-authorization.js";
|
|
6
|
+
import { resolvedProcessGroupChannels } from "./process-group-contract.js";
|
|
7
|
+
import { describeDirectProcessGroup, processGroupDirectDescriptor } from "./process-group-direct-review.js";
|
|
8
|
+
import { inspectGenerationJournal } from "./process-group-generation.js";
|
|
9
|
+
import { groupArtifactHash, groupChannelsHash, configurationAuthorizationHash as hash, assertPrivateProcessGroupDirectory as privateDirectory, readProcessGroupResource as read, } from "./process-group-resources.js";
|
|
10
|
+
/** Startup guard, shared by host and application. An installed declaration never grants authority. */
|
|
11
|
+
export function assertProcessGroupDirectLaunch(context) {
|
|
12
|
+
if (!hasProcessGroupAuthorization())
|
|
13
|
+
return; // Standalone deployments without a managed journal remain supported.
|
|
14
|
+
const names = configuredGroupProcessNames(), name = String(context.raw["uns"]?.["processName"]);
|
|
15
|
+
if (!names.has(name))
|
|
16
|
+
return;
|
|
17
|
+
const d = processGroupDirectDescriptor(context.packageRoot);
|
|
18
|
+
if (!d)
|
|
19
|
+
throw Error("Retained group identity requires a reviewed direct runtime");
|
|
20
|
+
const root = process.env["UNS_PROCESS_GROUP_STATE_ROOT"];
|
|
21
|
+
const directory = process.env["UNS_PROCESS_GROUP_AUTHORIZATION_DIRECTORY"];
|
|
22
|
+
const single = process.env["UNS_PROCESS_GROUP_AUTHORIZATION_FILE"];
|
|
23
|
+
const registry = path.join(root, d.namespace, "authorizations");
|
|
24
|
+
privateDirectory(root);
|
|
25
|
+
privateDirectory(registry);
|
|
26
|
+
if (directory && directory !== root && directory !== registry)
|
|
27
|
+
throw Error("Foreign direct policy registry");
|
|
28
|
+
const files = new Set();
|
|
29
|
+
if (single)
|
|
30
|
+
files.add(single);
|
|
31
|
+
if (directory) {
|
|
32
|
+
const entries = fs.readdirSync(registry, { withFileTypes: true });
|
|
33
|
+
if (entries.length > 64)
|
|
34
|
+
throw Error("Direct policy budget exceeded");
|
|
35
|
+
for (const e of entries)
|
|
36
|
+
if (e.name.endsWith(".json"))
|
|
37
|
+
files.add(path.join(registry, e.name));
|
|
38
|
+
}
|
|
39
|
+
const policies = [...files]
|
|
40
|
+
.map((file) => ({ file, raw: JSON.parse(read(file, true).toString()) }))
|
|
41
|
+
.filter((p) => p.raw.schemaVersion === 5 && p.raw.target?.processName === name);
|
|
42
|
+
if (policies.length !== 1)
|
|
43
|
+
throw Error("Group input owners remain active or retired; reviewed direct authority required");
|
|
44
|
+
const { file, raw } = policies[0], p = parseProcessGroupRetirementPolicy(raw), actual = describeDirectProcessGroup(context), channels = resolvedProcessGroupChannels(context.resolved), pkg = JSON.parse(read(path.join(context.packageRoot, "package.json")).toString());
|
|
45
|
+
if (p.stateRoot !== root ||
|
|
46
|
+
p.namespace !== d.namespace ||
|
|
47
|
+
file !== path.join(registry, p.journal.generationId + ".json") ||
|
|
48
|
+
context.controllerName !== p.controllerName ||
|
|
49
|
+
pkg.name !== p.packageName ||
|
|
50
|
+
actual.groupId !== p.groupId ||
|
|
51
|
+
actual.processName !== name ||
|
|
52
|
+
actual.resourceSha256 !== p.target.resourceSha256 ||
|
|
53
|
+
hash(actual.profiles) !== hash(p.journal.profiles))
|
|
54
|
+
throw Error("Retained direct group resource or identity changed");
|
|
55
|
+
if (path.resolve(context.packageRoot) !== context.packageRoot ||
|
|
56
|
+
fs.realpathSync(context.packageRoot) !== context.packageRoot ||
|
|
57
|
+
path.dirname(context.instanceDirectory) !== path.join(context.packageRoot, "instances") ||
|
|
58
|
+
fs.realpathSync(context.instanceDirectory) !== context.instanceDirectory ||
|
|
59
|
+
!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(path.basename(context.instanceDirectory)))
|
|
60
|
+
throw Error("Canonical installed direct instance required");
|
|
61
|
+
const usage = inspectGenerationJournal(p.journal);
|
|
62
|
+
if (usage.records || usage.bytes)
|
|
63
|
+
throw Error("Retained input journal is not empty; direct launch blocked");
|
|
64
|
+
if (p.recovery === "closed")
|
|
65
|
+
return; // Original/profile and journal-mode launches remain fenced by schema5.
|
|
66
|
+
if (context.packageRoot !== p.target.packageRoot ||
|
|
67
|
+
pkg.version !== p.target.placement.version ||
|
|
68
|
+
context.instanceDirectory !== path.join(context.packageRoot, "instances", p.target.placement.instanceId) ||
|
|
69
|
+
fs.realpathSync(context.instanceDirectory) !== context.instanceDirectory ||
|
|
70
|
+
hash(context.raw) !== p.target.configurationSha256 ||
|
|
71
|
+
groupArtifactHash(context.packageRoot) !== p.target.artifactSha256 ||
|
|
72
|
+
hash(d) !== p.target.descriptorSha256 ||
|
|
73
|
+
groupChannelsHash(channels) !== p.target.channelsSha256 ||
|
|
74
|
+
channels[2].publishQueue?.enabled === true)
|
|
75
|
+
throw Error("Exact reviewed direct placement, configuration and artifact required");
|
|
76
|
+
const r = JSON.parse(read(path.join(context.instanceDirectory, "process-group-reservation.json"), true).toString());
|
|
77
|
+
if (!z.string().uuid().safeParse(context.launchId).success ||
|
|
78
|
+
context.operationId !== p.operationId ||
|
|
79
|
+
r.operationId !== p.operationId ||
|
|
80
|
+
r.role !== "target" ||
|
|
81
|
+
r.configurationChecksum !== p.target.configurationSha256 ||
|
|
82
|
+
r.lastLaunch?.launchId !== context.launchId ||
|
|
83
|
+
!z.string().uuid().safeParse(r.lastLaunch?.requestId).success)
|
|
84
|
+
throw Error("Direct target requires its exact reviewed launch reservation");
|
|
85
|
+
}
|
|
86
|
+
/** PID/launch-bound observation, never authority. Start after output confirmation and input handler setup. */
|
|
87
|
+
export function trackProcessGroupDirectReadiness(context, ready) {
|
|
88
|
+
assertProcessGroupDirectLaunch(context);
|
|
89
|
+
if (!context.launchId)
|
|
90
|
+
return () => { };
|
|
91
|
+
const file = path.join(context.instanceDirectory, "process-group-direct-readiness.json");
|
|
92
|
+
let active = true;
|
|
93
|
+
const publish = () => {
|
|
94
|
+
const bytes = JSON.stringify({
|
|
95
|
+
protocolVersion: 1,
|
|
96
|
+
pid: process.pid,
|
|
97
|
+
launchId: context.launchId,
|
|
98
|
+
configurationSha256: hash(context.raw),
|
|
99
|
+
ready: active && ready(),
|
|
100
|
+
at: Date.now(),
|
|
101
|
+
});
|
|
102
|
+
const temp = file + "." + randomUUID() + ".tmp", fd = fs.openSync(temp, "wx", 0o600);
|
|
103
|
+
try {
|
|
104
|
+
fs.writeFileSync(fd, bytes);
|
|
105
|
+
fs.fsyncSync(fd);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
fs.closeSync(fd);
|
|
109
|
+
}
|
|
110
|
+
fs.renameSync(temp, file);
|
|
111
|
+
};
|
|
112
|
+
publish();
|
|
113
|
+
const timer = setInterval(publish, 1000);
|
|
114
|
+
timer.unref();
|
|
115
|
+
return () => {
|
|
116
|
+
active = false;
|
|
117
|
+
clearInterval(timer);
|
|
118
|
+
publish();
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export function inspectProcessGroupDirectReadiness(directory, expected, now = Date.now()) {
|
|
122
|
+
try {
|
|
123
|
+
const r = z
|
|
124
|
+
.strictObject({
|
|
125
|
+
protocolVersion: z.literal(1),
|
|
126
|
+
pid: z.number().int().positive(),
|
|
127
|
+
launchId: z.string().uuid(),
|
|
128
|
+
configurationSha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
129
|
+
ready: z.boolean(),
|
|
130
|
+
at: z.number().int().positive(),
|
|
131
|
+
})
|
|
132
|
+
.parse(JSON.parse(read(path.join(directory, "process-group-direct-readiness.json"), true).toString()));
|
|
133
|
+
return (r.ready &&
|
|
134
|
+
r.pid === expected.pid &&
|
|
135
|
+
r.launchId === expected.launchId &&
|
|
136
|
+
r.configurationSha256 === expected.configurationSha256 &&
|
|
137
|
+
r.at <= now + 1000 &&
|
|
138
|
+
now - r.at <= 5000);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** Preserve the managed registry in a direct process; never inherit a journal-mode launch permit. */
|
|
145
|
+
export function processGroupDirectLaunchEnvironment(operationId) {
|
|
146
|
+
return {
|
|
147
|
+
UNS_PROCESS_GROUP_LOCAL_PILOT: "0",
|
|
148
|
+
UNS_PROCESS_GROUP_AUTHORIZATION_FILE: process.env["UNS_PROCESS_GROUP_AUTHORIZATION_FILE"] ?? "",
|
|
149
|
+
UNS_PROCESS_GROUP_AUTHORIZATION_DIRECTORY: process.env["UNS_PROCESS_GROUP_AUTHORIZATION_DIRECTORY"] ?? "",
|
|
150
|
+
UNS_PROCESS_GROUP_STATE_ROOT: process.env["UNS_PROCESS_GROUP_STATE_ROOT"] ?? "",
|
|
151
|
+
UNS_PROCESS_GROUP_OPERATION_ID: operationId ?? "",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=process-group-direct-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-group-direct-runtime.js","sourceRoot":"","sources":["../../src/runtime/process-group-direct-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\n\nimport { configuredGroupProcessNames, hasProcessGroupAuthorization, parseProcessGroupRetirementPolicy } from \"./process-group-authorization.js\";\nimport { resolvedProcessGroupChannels } from \"./process-group-contract.js\";\nimport { describeDirectProcessGroup, processGroupDirectDescriptor } from \"./process-group-direct-review.js\";\nimport { type GenerationJournalReference, inspectGenerationJournal } from \"./process-group-generation.js\";\nimport {\n groupArtifactHash,\n groupChannelsHash,\n configurationAuthorizationHash as hash,\n assertPrivateProcessGroupDirectory as privateDirectory,\n readProcessGroupResource as read,\n} from \"./process-group-resources.js\";\nimport type { UpgradeConfiguration } from \"./process-group-upgrade.js\";\n\nexport type DirectRuntimeContext = UpgradeConfiguration & {\n controllerName: string;\n operationId?: string;\n launchId?: string;\n};\n/** Startup guard, shared by host and application. An installed declaration never grants authority. */\nexport function assertProcessGroupDirectLaunch(context: DirectRuntimeContext): void {\n if (!hasProcessGroupAuthorization()) return; // Standalone deployments without a managed journal remain supported.\n const names = configuredGroupProcessNames(),\n name = String((context.raw[\"uns\"] as Record<string, unknown>)?.[\"processName\"]);\n if (!names.has(name)) return;\n const d = processGroupDirectDescriptor(context.packageRoot);\n if (!d) throw Error(\"Retained group identity requires a reviewed direct runtime\");\n const root = process.env[\"UNS_PROCESS_GROUP_STATE_ROOT\"]!;\n const directory = process.env[\"UNS_PROCESS_GROUP_AUTHORIZATION_DIRECTORY\"];\n const single = process.env[\"UNS_PROCESS_GROUP_AUTHORIZATION_FILE\"];\n const registry = path.join(root, d.namespace, \"authorizations\");\n privateDirectory(root);\n privateDirectory(registry);\n if (directory && directory !== root && directory !== registry) throw Error(\"Foreign direct policy registry\");\n const files = new Set<string>();\n if (single) files.add(single);\n if (directory) {\n const entries = fs.readdirSync(registry, { withFileTypes: true });\n if (entries.length > 64) throw Error(\"Direct policy budget exceeded\");\n for (const e of entries) if (e.name.endsWith(\".json\")) files.add(path.join(registry, e.name));\n }\n const policies = [...files]\n .map((file) => ({ file, raw: JSON.parse(read(file, true).toString()) }))\n .filter((p) => p.raw.schemaVersion === 5 && p.raw.target?.processName === name);\n if (policies.length !== 1) throw Error(\"Group input owners remain active or retired; reviewed direct authority required\");\n const { file, raw } = policies[0]!,\n p = parseProcessGroupRetirementPolicy(raw),\n actual = describeDirectProcessGroup(context),\n channels = resolvedProcessGroupChannels(context.resolved),\n pkg = JSON.parse(read(path.join(context.packageRoot, \"package.json\")).toString());\n if (\n p.stateRoot !== root ||\n p.namespace !== d.namespace ||\n file !== path.join(registry, p.journal.generationId + \".json\") ||\n context.controllerName !== p.controllerName ||\n pkg.name !== p.packageName ||\n actual.groupId !== p.groupId ||\n actual.processName !== name ||\n actual.resourceSha256 !== p.target.resourceSha256 ||\n hash(actual.profiles) !== hash(p.journal.profiles)\n )\n throw Error(\"Retained direct group resource or identity changed\");\n if (\n path.resolve(context.packageRoot) !== context.packageRoot ||\n fs.realpathSync(context.packageRoot) !== context.packageRoot ||\n path.dirname(context.instanceDirectory) !== path.join(context.packageRoot, \"instances\") ||\n fs.realpathSync(context.instanceDirectory) !== context.instanceDirectory ||\n !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(path.basename(context.instanceDirectory))\n )\n throw Error(\"Canonical installed direct instance required\");\n const usage = inspectGenerationJournal(p.journal as GenerationJournalReference);\n if (usage.records || usage.bytes) throw Error(\"Retained input journal is not empty; direct launch blocked\");\n if (p.recovery === \"closed\") return; // Original/profile and journal-mode launches remain fenced by schema5.\n if (\n context.packageRoot !== p.target.packageRoot ||\n pkg.version !== p.target.placement.version ||\n context.instanceDirectory !== path.join(context.packageRoot, \"instances\", p.target.placement.instanceId) ||\n fs.realpathSync(context.instanceDirectory) !== context.instanceDirectory ||\n hash(context.raw) !== p.target.configurationSha256 ||\n groupArtifactHash(context.packageRoot) !== p.target.artifactSha256 ||\n hash(d) !== p.target.descriptorSha256 ||\n groupChannelsHash(channels) !== p.target.channelsSha256 ||\n (channels[2] as { publishQueue?: { enabled?: boolean } }).publishQueue?.enabled === true\n )\n throw Error(\"Exact reviewed direct placement, configuration and artifact required\");\n const r = JSON.parse(read(path.join(context.instanceDirectory, \"process-group-reservation.json\"), true).toString());\n if (\n !z.string().uuid().safeParse(context.launchId).success ||\n context.operationId !== p.operationId ||\n r.operationId !== p.operationId ||\n r.role !== \"target\" ||\n r.configurationChecksum !== p.target.configurationSha256 ||\n r.lastLaunch?.launchId !== context.launchId ||\n !z.string().uuid().safeParse(r.lastLaunch?.requestId).success\n )\n throw Error(\"Direct target requires its exact reviewed launch reservation\");\n}\n\n/** PID/launch-bound observation, never authority. Start after output confirmation and input handler setup. */\nexport function trackProcessGroupDirectReadiness(context: DirectRuntimeContext, ready: () => boolean): () => void {\n assertProcessGroupDirectLaunch(context);\n if (!context.launchId) return () => {};\n const file = path.join(context.instanceDirectory, \"process-group-direct-readiness.json\");\n let active = true;\n const publish = () => {\n const bytes = JSON.stringify({\n protocolVersion: 1,\n pid: process.pid,\n launchId: context.launchId,\n configurationSha256: hash(context.raw),\n ready: active && ready(),\n at: Date.now(),\n });\n const temp = file + \".\" + randomUUID() + \".tmp\",\n fd = fs.openSync(temp, \"wx\", 0o600);\n try {\n fs.writeFileSync(fd, bytes);\n fs.fsyncSync(fd);\n } finally {\n fs.closeSync(fd);\n }\n fs.renameSync(temp, file);\n };\n publish();\n const timer = setInterval(publish, 1000);\n timer.unref();\n return () => {\n active = false;\n clearInterval(timer);\n publish();\n };\n}\nexport function inspectProcessGroupDirectReadiness(\n directory: string,\n expected: { pid: number; launchId: string; configurationSha256: string },\n now = Date.now(),\n): boolean {\n try {\n const r = z\n .strictObject({\n protocolVersion: z.literal(1),\n pid: z.number().int().positive(),\n launchId: z.string().uuid(),\n configurationSha256: z.string().regex(/^[a-f0-9]{64}$/),\n ready: z.boolean(),\n at: z.number().int().positive(),\n })\n .parse(JSON.parse(read(path.join(directory, \"process-group-direct-readiness.json\"), true).toString()));\n return (\n r.ready &&\n r.pid === expected.pid &&\n r.launchId === expected.launchId &&\n r.configurationSha256 === expected.configurationSha256 &&\n r.at <= now + 1000 &&\n now - r.at <= 5000\n );\n } catch {\n return false;\n }\n}\n\n/** Preserve the managed registry in a direct process; never inherit a journal-mode launch permit. */\nexport function processGroupDirectLaunchEnvironment(operationId: string | undefined): Record<string, string> {\n return {\n UNS_PROCESS_GROUP_LOCAL_PILOT: \"0\",\n UNS_PROCESS_GROUP_AUTHORIZATION_FILE: process.env[\"UNS_PROCESS_GROUP_AUTHORIZATION_FILE\"] ?? \"\",\n UNS_PROCESS_GROUP_AUTHORIZATION_DIRECTORY: process.env[\"UNS_PROCESS_GROUP_AUTHORIZATION_DIRECTORY\"] ?? \"\",\n UNS_PROCESS_GROUP_STATE_ROOT: process.env[\"UNS_PROCESS_GROUP_STATE_ROOT\"] ?? \"\",\n UNS_PROCESS_GROUP_OPERATION_ID: operationId ?? \"\",\n };\n}\n"]}
|
|
@@ -79,6 +79,7 @@ const namespaces = rootEntries
|
|
|
79
79
|
.sort();
|
|
80
80
|
if (namespaces.length > 64) fail();
|
|
81
81
|
const capturedGroups = new Set();
|
|
82
|
+
const retirementChecks = new Map();
|
|
82
83
|
for (const namespace of namespaces) {
|
|
83
84
|
if (!/^[a-z][a-z0-9-]{0,99}$/.test(namespace)) fail();
|
|
84
85
|
const namespaceRoot = path.join(root, namespace),
|
|
@@ -240,9 +241,52 @@ for (const namespace of namespaces) {
|
|
|
240
241
|
if (snapshotBytes > limits.maximumSnapshotBytes) fail();
|
|
241
242
|
info[name === "admission.sqlite" ? "admissionSha256" : "ownershipSha256"] = fileHash(p);
|
|
242
243
|
}
|
|
244
|
+
// Retired ownership is part of recovery evidence, never an automatic launch permit.
|
|
245
|
+
// Version2 makes an older transport fail closed instead of silently omitting it.
|
|
246
|
+
const policyDirectory=path.join(namespaceRoot,"authorizations"),policyFile=path.join(policyDirectory,group+".json");
|
|
247
|
+
let policyExists=false;
|
|
248
|
+
try{fs.lstatSync(policyFile);policyExists=true;}catch(e){if(e.code!=="ENOENT")throw e;}
|
|
249
|
+
if(policyExists){
|
|
250
|
+
check(policyDirectory,true);check(policyFile);
|
|
251
|
+
if(fs.statSync(policyFile).size>65536)fail();
|
|
252
|
+
const policy=fs.readFileSync(policyFile),p=JSON.parse(policy.toString());
|
|
253
|
+
if(p.schemaVersion===5){
|
|
254
|
+
if(info.usage.records!==0||info.usage.bytes!==0||p.kind!=="uns-process-group-direct-retirement"||p.protocolVersion!==1||p.stateRoot!==root||p.namespace!==namespace||
|
|
255
|
+
p.journal?.directory!==source||p.journal?.generationId!==group||p.journal?.journalId!==info.journalId||
|
|
256
|
+
p.journal?.journalContractSha256!==info.contractSha256||typeof p.sourcePolicyJson!=="string"||Buffer.byteLength(p.sourcePolicyJson)>32768||hash(p.sourcePolicyJson)!==p.sourcePolicySha256||
|
|
257
|
+
!["open","closed"].includes(p.recovery))fail();
|
|
258
|
+
fs.writeFileSync(path.join(target,"retirement-policy.json"),policy,{mode:0o600,flag:"wx"});
|
|
259
|
+
info.retirementPolicySha256=hash(policy);info.retirementPolicyBytes=policy.length;
|
|
260
|
+
snapshotBytes+=policy.length;if(snapshotBytes>limits.maximumSnapshotBytes)fail();
|
|
261
|
+
// Freeze this observation; a concurrent policy switch requires a new consistent capture.
|
|
262
|
+
if(!fs.readFileSync(policyFile).equals(policy))fail();
|
|
263
|
+
manifest.schemaVersion=2;
|
|
264
|
+
retirementChecks.set(policyFile,hash(policy));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
243
267
|
manifest.groups.push(info);
|
|
244
268
|
}
|
|
245
269
|
}
|
|
270
|
+
// Direct configurations no longer name the old journal. Its retained policy must
|
|
271
|
+
// still resolve to captured state, even after the stopped source configs are removed.
|
|
272
|
+
let registryFiles=0;
|
|
273
|
+
for(const namespace of namespaces){
|
|
274
|
+
const registry=path.join(root,namespace,"authorizations");
|
|
275
|
+
let entries;
|
|
276
|
+
try{check(registry,true);entries=fs.readdirSync(registry,{withFileTypes:true});}catch(e){if(e.code==="ENOENT")continue;throw e;}
|
|
277
|
+
if(entries.length>64||entries.some(e=>e.isSymbolicLink()))fail();
|
|
278
|
+
for(const entry of entries){
|
|
279
|
+
if(!entry.name.endsWith(".json"))continue;
|
|
280
|
+
if(++registryFiles>64||!entry.isFile()||!/^([a-z][a-z0-9-]{0,99})\.json$/.test(entry.name))fail();
|
|
281
|
+
const file=path.join(registry,entry.name);check(file);if(fs.statSync(file).size>65536)fail();
|
|
282
|
+
const bytes=fs.readFileSync(file),policy=JSON.parse(bytes.toString());
|
|
283
|
+
if(policy.schemaVersion!==5)continue;
|
|
284
|
+
const captured=manifest.groups.find(g=>g.namespace===namespace&&g.group===policy.journal?.generationId);
|
|
285
|
+
if(!captured||captured.retirementPolicySha256!==hash(bytes)||captured.journalId!==policy.journal?.journalId||
|
|
286
|
+
policy.namespace!==namespace||policy.stateRoot!==root||entry.name!==policy.journal.generationId+".json")fail();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
for(const [file,digest] of retirementChecks){check(file);if(hash(fs.readFileSync(file))!==digest)fail();}
|
|
246
290
|
const remaining = fs.statfsSync(output, { bigint: true });
|
|
247
291
|
if (remaining.bavail * remaining.bsize < BigInt(snapshotBytes + 64 * 1024 * 1024)) fail();
|
|
248
292
|
const manifestBytes = JSON.stringify(manifest) + "\n";
|
|
@@ -10,6 +10,7 @@ import { UnsEvents } from "./uns-interfaces.js";
|
|
|
10
10
|
* and processing handover responses.
|
|
11
11
|
*/
|
|
12
12
|
export declare class HandoverManager {
|
|
13
|
+
private readonly statefulHandover;
|
|
13
14
|
event: HandoverManagerEventEmitter<HandoverManagerEvents>;
|
|
14
15
|
private processName;
|
|
15
16
|
private processId;
|
|
@@ -26,10 +27,16 @@ export declare class HandoverManager {
|
|
|
26
27
|
private sourcePreparation;
|
|
27
28
|
private sourceExit;
|
|
28
29
|
private readonly shutdownHooks;
|
|
30
|
+
private expectedSourceId;
|
|
31
|
+
private preparedState;
|
|
32
|
+
private stateDigest;
|
|
33
|
+
private acceptedStateDigest;
|
|
34
|
+
private targetPreparation;
|
|
35
|
+
private finRetry;
|
|
29
36
|
handoverRequestEnabled: boolean;
|
|
30
37
|
handoverEnabled: boolean;
|
|
31
38
|
forceStartEnabled: boolean;
|
|
32
|
-
constructor(processName: string, processId: string, mqttProxy: MqttProxy, unsMqttProxies: UnsMqttProxy[], handoverRequestEnabled: boolean, handoverEnabled: boolean, forceStartEnabled: boolean, handoverId?: string, shutdownHooks?: HandoverShutdownHooks);
|
|
39
|
+
constructor(processName: string, processId: string, mqttProxy: MqttProxy, unsMqttProxies: UnsMqttProxy[], handoverRequestEnabled: boolean, handoverEnabled: boolean, forceStartEnabled: boolean, handoverId?: string, shutdownHooks?: HandoverShutdownHooks, statefulHandover?: boolean);
|
|
33
40
|
private getUserProperties;
|
|
34
41
|
private handoverPayload;
|
|
35
42
|
private getHandoverId;
|
|
@@ -47,6 +54,8 @@ export declare class HandoverManager {
|
|
|
47
54
|
*/
|
|
48
55
|
private exitSource;
|
|
49
56
|
private prepareSourceHandover;
|
|
57
|
+
private sendPreparedState;
|
|
58
|
+
private acceptPreparedState;
|
|
50
59
|
private finishSourceHandover;
|
|
51
60
|
private handleHandover;
|
|
52
61
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handover-manager.d.ts","sourceRoot":"","sources":["../../src/uns/handover-manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"handover-manager.d.ts","sourceRoot":"","sources":["../../src/uns/handover-manager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiD,KAAK,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACnH,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,SAAS,MAAM,2BAA2B,CAAC;AAElD,OAAO,YAAY,MAAM,+BAA+B,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAElF,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAQhD;;;;GAIG;AACH,qBAAa,eAAe;IAqCxB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IApC5B,KAAK,EAAE,2BAA2B,CAAC,qBAAqB,CAAC,CAA4D;IAC5H,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,iBAAiB,CAA4B;IACrD,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoC;IAClE,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,aAAa,CAAgC;IACrD,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,iBAAiB,CAA4B;IACrD,OAAO,CAAC,QAAQ,CAA6B;IACtC,sBAAsB,EAAE,OAAO,CAAS;IACxC,eAAe,EAAE,OAAO,CAAQ;IAChC,iBAAiB,EAAE,OAAO,CAAS;gBAGxC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,YAAY,EAAE,EAC9B,sBAAsB,EAAE,OAAO,EAC/B,eAAe,EAAE,OAAO,EACxB,iBAAiB,EAAE,OAAO,EAC1B,UAAU,CAAC,EAAE,MAAM,EACnB,aAAa,CAAC,EAAE,qBAAqB,EACpB,gBAAgB,UAAQ;IAkC3C,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,oBAAoB;IAU5B;;;OAGG;IACU,iBAAiB,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAqFxE;;OAEG;IACH,OAAO,CAAC,UAAU;YAIJ,qBAAqB;YA8ErB,iBAAiB;YAOjB,mBAAmB;YAkBnB,oBAAoB;YA2BpB,cAAc;CAwH7B"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mqttHandoverOfferSchema, parseMqttHandoverState, mqttHandoverStateDigest } from "../runtime/mqtt-handover-state.js";
|
|
1
2
|
import logger from "../logger.js";
|
|
2
3
|
import { runHandoverShutdown, validateHandoverShutdown } from "./handover-shutdown.js";
|
|
3
4
|
import { MqttTopicBuilder } from "../uns-mqtt/mqtt-topic-builder.js";
|
|
@@ -15,6 +16,7 @@ function normalizeHandoverId(value) {
|
|
|
15
16
|
* and processing handover responses.
|
|
16
17
|
*/
|
|
17
18
|
export class HandoverManager {
|
|
19
|
+
statefulHandover;
|
|
18
20
|
event = new HandoverManagerEventEmitter();
|
|
19
21
|
processName;
|
|
20
22
|
processId;
|
|
@@ -31,10 +33,17 @@ export class HandoverManager {
|
|
|
31
33
|
sourcePreparation;
|
|
32
34
|
sourceExit;
|
|
33
35
|
shutdownHooks;
|
|
36
|
+
expectedSourceId;
|
|
37
|
+
preparedState;
|
|
38
|
+
stateDigest;
|
|
39
|
+
acceptedStateDigest;
|
|
40
|
+
targetPreparation;
|
|
41
|
+
finRetry;
|
|
34
42
|
handoverRequestEnabled = false;
|
|
35
43
|
handoverEnabled = true;
|
|
36
44
|
forceStartEnabled = false;
|
|
37
|
-
constructor(processName, processId, mqttProxy, unsMqttProxies, handoverRequestEnabled, handoverEnabled, forceStartEnabled, handoverId, shutdownHooks) {
|
|
45
|
+
constructor(processName, processId, mqttProxy, unsMqttProxies, handoverRequestEnabled, handoverEnabled, forceStartEnabled, handoverId, shutdownHooks, statefulHandover = false) {
|
|
46
|
+
this.statefulHandover = statefulHandover;
|
|
38
47
|
validateHandoverShutdown(shutdownHooks);
|
|
39
48
|
this.shutdownHooks = shutdownHooks ? { ...shutdownHooks } : undefined;
|
|
40
49
|
this.processName = processName;
|
|
@@ -51,6 +60,8 @@ export class HandoverManager {
|
|
|
51
60
|
this.topicBuilder = new MqttTopicBuilder(`uns-infra/${MqttTopicBuilder.sanitizeTopicPart(packageName)}/${MqttTopicBuilder.sanitizeTopicPart(version)}/${MqttTopicBuilder.sanitizeTopicPart(this.processName)}/`);
|
|
52
61
|
// Set status as active after a timeout if no other active process are detected.
|
|
53
62
|
this.activeTimeout = setTimeout(() => {
|
|
63
|
+
if (this.statefulHandover && this.handoverId)
|
|
64
|
+
return; // A reviewed successor never self-promotes.
|
|
54
65
|
logger.info(`${this.processName} - No active message received within timeout. Assuming no other process is running.`);
|
|
55
66
|
this.active = true;
|
|
56
67
|
this.event.emit("handoverManager", { active: this.active });
|
|
@@ -75,6 +86,7 @@ export class HandoverManager {
|
|
|
75
86
|
const normalized = normalizeHandoverId(handoverId ?? this.handoverId);
|
|
76
87
|
return JSON.stringify({
|
|
77
88
|
type,
|
|
89
|
+
...(this.statefulHandover ? { protocol: 2 } : {}),
|
|
78
90
|
...fields,
|
|
79
91
|
...(normalized ? { handoverId: normalized } : {}),
|
|
80
92
|
});
|
|
@@ -161,6 +173,7 @@ export class HandoverManager {
|
|
|
161
173
|
this.activeTimeout = undefined;
|
|
162
174
|
this.event.emit("handoverManager", { active: this.active });
|
|
163
175
|
this.requestingHandover = true;
|
|
176
|
+
this.expectedSourceId = sourceProcessId;
|
|
164
177
|
const eventHandoverTopic = new MqttTopicBuilder(MqttTopicBuilder.extractBaseTopic(event.topic)).getHandoverTopic();
|
|
165
178
|
await this.mqttProxy.publish(eventHandoverTopic, this.handoverPayload("handover_intent"), {
|
|
166
179
|
retain: false,
|
|
@@ -172,7 +185,7 @@ export class HandoverManager {
|
|
|
172
185
|
setTimeout(async () => {
|
|
173
186
|
logger.info(`${this.processName} - Requesting handover ${eventHandoverTopic}.`);
|
|
174
187
|
this.handoverInProgress = true;
|
|
175
|
-
await this.mqttProxy.publish(eventHandoverTopic, this.handoverPayload("handover_request"), {
|
|
188
|
+
await this.mqttProxy.publish(eventHandoverTopic, this.handoverPayload("handover_request", undefined, this.statefulHandover ? { offer: this.unsMqttProxies.map(proxy => proxy.getHandoverCompatibility()) } : {}), {
|
|
176
189
|
retain: false,
|
|
177
190
|
properties: {
|
|
178
191
|
responseTopic: this.topicBuilder.getHandoverTopic(),
|
|
@@ -231,9 +244,29 @@ export class HandoverManager {
|
|
|
231
244
|
const mqttWorkerData = [];
|
|
232
245
|
for (let i = 0; i < this.unsMqttProxies.length; i++) {
|
|
233
246
|
const unsProxy = this.unsMqttProxies[i];
|
|
234
|
-
const workerData = await unsProxy.setSubscriberPassiveAndDrainQueue();
|
|
247
|
+
const workerData = await unsProxy.setSubscriberPassiveAndDrainQueue(this.statefulHandover);
|
|
235
248
|
mqttWorkerData.push(workerData);
|
|
236
249
|
}
|
|
250
|
+
if (this.statefulHandover) {
|
|
251
|
+
// Finish accepted application callbacks before taking the calculation-state boundary.
|
|
252
|
+
await this.shutdownHooks?.drain();
|
|
253
|
+
for (const proxy of this.unsMqttProxies)
|
|
254
|
+
await proxy.flush();
|
|
255
|
+
this.preparedState = parseMqttHandoverState({ protocol: 1, proxies: this.unsMqttProxies.map((proxy, i) => proxy.exportHandoverState({ batchSize: mqttWorkerData[i].batchSize, referenceHash: mqttWorkerData[i].referenceHash })) });
|
|
256
|
+
this.stateDigest = mqttHandoverStateDigest(this.preparedState);
|
|
257
|
+
await this.sendPreparedState(event, handoverId);
|
|
258
|
+
let retries = 0;
|
|
259
|
+
this.finRetry = setInterval(() => {
|
|
260
|
+
if (++retries > 30 || this.sourceExit) {
|
|
261
|
+
clearInterval(this.finRetry);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
void this.sendPreparedState(event, handoverId).catch(() => logger.warn("MQTT handover receipt retry unavailable"));
|
|
265
|
+
}, 1000);
|
|
266
|
+
this.finRetry.unref();
|
|
267
|
+
// Keep the old owner passive until the successor acknowledges this exact state.
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
237
270
|
logger.info(`${this.processName} - Handover request accepted. Sending handover_subscriber messages.`);
|
|
238
271
|
// Publish handover_subscriber messages for each instance that has processed some data.
|
|
239
272
|
for (let i = 0; i < mqttWorkerData.length; i++) {
|
|
@@ -265,10 +298,48 @@ export class HandoverManager {
|
|
|
265
298
|
logger.info(`${this.processName} - Handover fin message sent.`);
|
|
266
299
|
await Promise.all(this.unsMqttProxies.map((unsProxy) => unsProxy.stop()));
|
|
267
300
|
}
|
|
301
|
+
async sendPreparedState(event, handoverId) {
|
|
302
|
+
await this.mqttProxy.publish(event.packet.properties?.responseTopic ?? "", this.handoverPayload("handover_fin", handoverId, { state: this.preparedState, stateDigest: this.stateDigest }), {
|
|
303
|
+
retain: false, qos: 1, properties: { responseTopic: this.topicBuilder.getHandoverTopic(), userProperties: this.getUserProperties(handoverId) },
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
async acceptPreparedState(response) {
|
|
307
|
+
const state = parseMqttHandoverState(response.state);
|
|
308
|
+
const digest = mqttHandoverStateDigest(state);
|
|
309
|
+
if (digest !== response.stateDigest)
|
|
310
|
+
throw Error("MQTT handover state digest mismatch");
|
|
311
|
+
const byName = new Map(this.unsMqttProxies.map(proxy => [proxy.instanceName, proxy]));
|
|
312
|
+
if (byName.size !== this.unsMqttProxies.length || state.proxies.length !== byName.size || state.proxies.some(p => !byName.has(p.instanceName)))
|
|
313
|
+
throw Error("MQTT handover proxy set differs");
|
|
314
|
+
for (const snapshot of state.proxies)
|
|
315
|
+
byName.get(snapshot.instanceName).validateHandoverState(snapshot);
|
|
316
|
+
// Validate every cursor before importing any state or activating any subscriber.
|
|
317
|
+
for (const snapshot of state.proxies)
|
|
318
|
+
await byName.get(snapshot.instanceName).prepareHandoverResume(snapshot.cursor);
|
|
319
|
+
for (const snapshot of state.proxies)
|
|
320
|
+
byName.get(snapshot.instanceName).importHandoverState(snapshot);
|
|
321
|
+
for (const proxy of this.unsMqttProxies)
|
|
322
|
+
proxy.setPublisherActive();
|
|
323
|
+
for (const proxy of this.unsMqttProxies)
|
|
324
|
+
await proxy.activateHandoverResume();
|
|
325
|
+
this.acceptedStateDigest = digest;
|
|
326
|
+
this.active = true;
|
|
327
|
+
this.event.emit("handoverManager", { active: true });
|
|
328
|
+
}
|
|
268
329
|
async finishSourceHandover() {
|
|
269
330
|
const result = await runHandoverShutdown(async () => {
|
|
270
331
|
await this.sourcePreparation;
|
|
271
332
|
await this.shutdownHooks?.drain();
|
|
333
|
+
if (this.statefulHandover) {
|
|
334
|
+
await Promise.all(this.unsMqttProxies.map(proxy => proxy.stop()));
|
|
335
|
+
// The host persists this receipt synchronously before the real process
|
|
336
|
+
// exit. No receipt is emitted for a partial/failed application drain.
|
|
337
|
+
process.emit("uns-kit:mqtt-handover-drained", {
|
|
338
|
+
protocol: 2, handoverId: this.acceptedHandoverId,
|
|
339
|
+
stateDigest: this.stateDigest, pendingRecords: 0,
|
|
340
|
+
outputs: this.unsMqttProxies.length,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
272
343
|
}, this.shutdownHooks?.timeoutMs ?? 30_000);
|
|
273
344
|
this.handoverInProgress = false;
|
|
274
345
|
this.requestingHandover = false;
|
|
@@ -284,14 +355,32 @@ export class HandoverManager {
|
|
|
284
355
|
}
|
|
285
356
|
async handleHandover(event) {
|
|
286
357
|
try {
|
|
358
|
+
if (event.message.length > 1100000)
|
|
359
|
+
return;
|
|
287
360
|
const response = JSON.parse(event.message.toString());
|
|
361
|
+
// No downgrade: stateful peers must agree before either relinquishes ownership.
|
|
362
|
+
if ((response.protocol === 2) !== this.statefulHandover)
|
|
363
|
+
return;
|
|
364
|
+
if (this.statefulHandover && (event.packet?.retain || !this.getHandoverId(response, event)))
|
|
365
|
+
return;
|
|
288
366
|
// Responder process
|
|
289
367
|
// Check if the message is a handover request and publish MULTIPLE handover_subscriber messages
|
|
290
368
|
if (response.type === "handover_request") {
|
|
291
369
|
const peerId = this.getSourceProcessId(event);
|
|
292
370
|
const replyTopic = event.packet?.properties?.responseTopic;
|
|
371
|
+
if (this.statefulHandover && this.sourcePeerId === peerId && this.acceptedHandoverId === this.getHandoverId(response, event) && this.preparedState) {
|
|
372
|
+
await this.sendPreparedState(event, this.acceptedHandoverId);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
293
375
|
if (this.sourcePeerId || !this.active || !peerId || typeof replyTopic !== "string" || !replyTopic)
|
|
294
376
|
return;
|
|
377
|
+
if (this.statefulHandover) {
|
|
378
|
+
// Reject incompatible upgrades before freezing the current owner.
|
|
379
|
+
const offer = mqttHandoverOfferSchema.parse(response.offer);
|
|
380
|
+
const own = this.unsMqttProxies.map(proxy => proxy.getHandoverCompatibility());
|
|
381
|
+
if (offer.length !== own.length || own.some(p => !offer.some(o => o.instanceName === p.instanceName && o.compatibility === p.compatibility)))
|
|
382
|
+
throw Error("MQTT source/destination or semantic contract differs; source remains active");
|
|
383
|
+
}
|
|
295
384
|
this.sourcePeerId = peerId;
|
|
296
385
|
this.acceptedHandoverId = this.getHandoverId(response, event);
|
|
297
386
|
this.handoverInProgress = true;
|
|
@@ -303,8 +392,8 @@ export class HandoverManager {
|
|
|
303
392
|
}
|
|
304
393
|
catch {
|
|
305
394
|
// Subscriber transfer or onRelease failed; never report successful source completion.
|
|
306
|
-
logger.error(`${this.processName} - Handover source preparation failed
|
|
307
|
-
if (!this.sourceExit)
|
|
395
|
+
logger.error(`${this.processName} - Handover source preparation failed; ownership requires review.`);
|
|
396
|
+
if (!this.sourceExit && !this.statefulHandover)
|
|
308
397
|
this.exitSource(1);
|
|
309
398
|
}
|
|
310
399
|
}
|
|
@@ -312,6 +401,8 @@ export class HandoverManager {
|
|
|
312
401
|
// Check if the message is one of the handover_subscriber message in response to handover_request
|
|
313
402
|
// and publish a handover_ack message
|
|
314
403
|
if (response.type === "handover_subscriber") {
|
|
404
|
+
if (this.statefulHandover)
|
|
405
|
+
return;
|
|
315
406
|
if (this.sourcePeerId)
|
|
316
407
|
return;
|
|
317
408
|
if (!this.acceptsHandoverResponse(this.getHandoverId(response, event)))
|
|
@@ -326,6 +417,20 @@ export class HandoverManager {
|
|
|
326
417
|
// Requestor process
|
|
327
418
|
// Check if the message is a handover_fin at the end of handover_subscriber messages
|
|
328
419
|
if (response.type === "handover_fin") {
|
|
420
|
+
if (this.statefulHandover) {
|
|
421
|
+
const id = this.getHandoverId(response, event);
|
|
422
|
+
if (this.sourcePeerId || !this.requestingHandover || id !== this.handoverId || this.getSourceProcessId(event) !== this.expectedSourceId)
|
|
423
|
+
return;
|
|
424
|
+
if (this.acceptedStateDigest && response.stateDigest !== this.acceptedStateDigest)
|
|
425
|
+
return;
|
|
426
|
+
this.targetPreparation ??= this.acceptPreparedState(response);
|
|
427
|
+
await this.targetPreparation;
|
|
428
|
+
// Duplicate fin only replays the receipt; it never reapplies a baseline.
|
|
429
|
+
if (response.stateDigest !== this.acceptedStateDigest)
|
|
430
|
+
return;
|
|
431
|
+
await this.mqttProxy.publish(event.packet.properties?.responseTopic ?? "", this.handoverPayload("handover_ack", id, { stateDigest: this.acceptedStateDigest }), { retain: false, qos: 1, properties: { userProperties: this.getUserProperties(id) } });
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
329
434
|
if (this.sourcePeerId)
|
|
330
435
|
return;
|
|
331
436
|
const handoverId = this.getHandoverId(response, event);
|
|
@@ -363,6 +468,9 @@ export class HandoverManager {
|
|
|
363
468
|
logger.warn(`${this.processName} - Ignoring acknowledgement for another migration.`);
|
|
364
469
|
return;
|
|
365
470
|
}
|
|
471
|
+
if (this.statefulHandover && (handoverId !== this.acceptedHandoverId || response.stateDigest !== this.stateDigest || !this.stateDigest))
|
|
472
|
+
return;
|
|
473
|
+
clearInterval(this.finRetry);
|
|
366
474
|
logger.info(`${this.processName} - Received handover ack from ${event.packet?.properties?.userProperties?.processName}.`);
|
|
367
475
|
this.sourceExit ??= this.finishSourceHandover();
|
|
368
476
|
await this.sourceExit;
|