@hypequery/deployment 0.0.0-canary-20260719200737 → 0.1.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.
@@ -1,274 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { chmod, lstat, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
3
- import { tmpdir } from 'node:os';
4
- import path from 'node:path';
5
- import { Worker } from 'node:worker_threads';
6
- const DEFAULT_STARTUP_TIMEOUT_MS = 30_000;
7
- const MAX_STARTUP_TIMEOUT_MS = 5 * 60_000;
8
- const WORKER_SOURCE = [
9
- "const { parentPort, workerData } = require('node:worker_threads');",
10
- "const { pathToFileURL } = require('node:url');",
11
- 'const handlers = new Map();',
12
- 'function errorValue(error) {',
13
- " return { name: error instanceof Error ? error.name : 'Error',",
14
- " message: error instanceof Error ? error.message : String(error) };",
15
- '}',
16
- 'function resolveEntrypoint(module, entrypoint) {',
17
- ' let value = module;',
18
- " for (const segment of entrypoint.split('.')) value = value?.[segment];",
19
- " if (typeof value !== 'function') throw new Error(`Runtime entrypoint is unavailable: ${entrypoint}`);",
20
- ' return value;',
21
- '}',
22
- '(async () => {',
23
- ' for (const artifact of workerData.artifacts) {',
24
- ' const module = await import(pathToFileURL(artifact.path).href);',
25
- ' for (const entrypoint of artifact.entrypoints) {',
26
- ' handlers.set(entrypoint, resolveEntrypoint(module, entrypoint));',
27
- ' }',
28
- ' }',
29
- " parentPort.postMessage({ kind: 'ready' });",
30
- " parentPort.on('message', async message => {",
31
- " if (message.kind === 'health') {",
32
- " parentPort.postMessage({ kind: 'result', id: message.id, value: null });",
33
- ' return;',
34
- ' }',
35
- " if (message.kind !== 'invoke') return;",
36
- ' try {',
37
- ' const handler = handlers.get(message.entrypoint);',
38
- " if (!handler) throw new Error(`Runtime entrypoint is unavailable: ${message.entrypoint}`);",
39
- ' const value = await handler(message.argument);',
40
- " parentPort.postMessage({ kind: 'result', id: message.id, value });",
41
- ' } catch (error) {',
42
- " parentPort.postMessage({ kind: 'failure', id: message.id, error: errorValue(error) });",
43
- ' }',
44
- ' });',
45
- '})().catch(error => {',
46
- " parentPort.postMessage({ kind: 'fatal', error: errorValue(error) });",
47
- '});',
48
- ].join('\n');
49
- export class NodeDeploymentRuntimeError extends Error {
50
- code;
51
- constructor(code, message, options = {}) {
52
- super(message, options.cause === undefined ? undefined : { cause: options.cause });
53
- this.name = 'NodeDeploymentRuntimeError';
54
- this.code = code;
55
- }
56
- }
57
- function nodeRuntimeError(code, message, cause) {
58
- return new NodeDeploymentRuntimeError(code, message, { cause });
59
- }
60
- function startupTimeout(input) {
61
- const value = input ?? DEFAULT_STARTUP_TIMEOUT_MS;
62
- if (!Number.isSafeInteger(value) || value < 1 || value > MAX_STARTUP_TIMEOUT_MS) {
63
- throw nodeRuntimeError('HQ_NODE_RUNTIME_CONFIGURATION', `startupTimeoutMs must be between 1 and ${MAX_STARTUP_TIMEOUT_MS}.`);
64
- }
65
- return value;
66
- }
67
- function sha256(bytes) {
68
- return createHash('sha256').update(bytes).digest('hex');
69
- }
70
- async function ensureTemporaryDirectory(input) {
71
- const directory = path.resolve(input ?? tmpdir());
72
- await mkdir(directory, { recursive: true, mode: 0o700 });
73
- const stat = await lstat(directory);
74
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
75
- throw nodeRuntimeError('HQ_NODE_RUNTIME_CONFIGURATION', 'The Node runtime temporary directory must be a regular directory.');
76
- }
77
- return directory;
78
- }
79
- function remoteError(input) {
80
- const message = typeof input?.message === 'string'
81
- ? input.message.slice(0, 4096)
82
- : 'The runtime worker failed without an error message.';
83
- const error = new Error(message);
84
- if (typeof input?.name === 'string')
85
- error.name = input.name.slice(0, 128);
86
- return error;
87
- }
88
- function workerInstance(worker, directory, ready) {
89
- const pending = new Map();
90
- let sequence = 0;
91
- let closed = false;
92
- let closePromise;
93
- let terminalError;
94
- function failPending(error) {
95
- for (const call of pending.values()) {
96
- call.cleanup();
97
- call.reject(error);
98
- }
99
- pending.clear();
100
- }
101
- worker.on('message', (message) => {
102
- if ((message.kind !== 'result' && message.kind !== 'failure')
103
- || !Number.isSafeInteger(message.id))
104
- return;
105
- const call = pending.get(message.id);
106
- if (!call)
107
- return;
108
- pending.delete(message.id);
109
- call.cleanup();
110
- if (message.kind === 'result')
111
- call.resolve(message.value);
112
- else
113
- call.reject(nodeRuntimeError('HQ_NODE_RUNTIME_INVOCATION_FAILED', 'The Node deployment runtime invocation failed.', remoteError(message.error)));
114
- });
115
- worker.on('error', error => {
116
- terminalError = nodeRuntimeError('HQ_NODE_RUNTIME_WORKER_EXITED', 'The Node deployment runtime worker failed.', error);
117
- failPending(terminalError);
118
- });
119
- worker.on('exit', code => {
120
- if (!closed) {
121
- terminalError = nodeRuntimeError('HQ_NODE_RUNTIME_WORKER_EXITED', `The Node deployment runtime worker exited unexpectedly with code ${code}.`);
122
- failPending(terminalError);
123
- }
124
- });
125
- function call(kind, payload, signal) {
126
- if (closed || terminalError) {
127
- return Promise.reject(terminalError ?? nodeRuntimeError('HQ_NODE_RUNTIME_CLOSED', 'The Node deployment runtime worker is closed.'));
128
- }
129
- if (signal?.aborted) {
130
- return Promise.reject(nodeRuntimeError('HQ_NODE_RUNTIME_ABORTED', 'The Node deployment runtime invocation was aborted.', signal.reason));
131
- }
132
- const id = sequence;
133
- sequence += 1;
134
- return new Promise((resolve, reject) => {
135
- const observeAbort = kind === 'health';
136
- const abort = () => {
137
- pending.delete(id);
138
- reject(nodeRuntimeError('HQ_NODE_RUNTIME_ABORTED', 'The Node deployment runtime health check was aborted.', signal?.reason));
139
- };
140
- const cleanup = () => signal?.removeEventListener('abort', abort);
141
- pending.set(id, { resolve, reject, cleanup });
142
- if (observeAbort)
143
- signal?.addEventListener('abort', abort, { once: true });
144
- try {
145
- worker.postMessage({ kind, id, ...payload });
146
- }
147
- catch (error) {
148
- pending.delete(id);
149
- cleanup();
150
- reject(nodeRuntimeError('HQ_NODE_RUNTIME_INVOCATION_FAILED', 'The Node deployment runtime invocation could not be sent.', error));
151
- }
152
- });
153
- }
154
- return {
155
- ready,
156
- async healthCheck({ signal }) {
157
- await call('health', {}, signal);
158
- },
159
- invoke(input) {
160
- return call('invoke', {
161
- entrypoint: input.binding.entrypoint,
162
- argument: input.argument,
163
- }, input.signal);
164
- },
165
- async close() {
166
- if (closePromise)
167
- return closePromise;
168
- closed = true;
169
- const error = nodeRuntimeError('HQ_NODE_RUNTIME_CLOSED', 'The Node deployment runtime worker was closed.');
170
- failPending(error);
171
- closePromise = (async () => {
172
- try {
173
- await worker.terminate();
174
- }
175
- finally {
176
- await rm(directory, { force: true, recursive: true });
177
- }
178
- })();
179
- return closePromise;
180
- },
181
- };
182
- }
183
- async function startWorker(snapshot, directory, timeoutMs, signal) {
184
- if (signal?.aborted) {
185
- throw nodeRuntimeError('HQ_NODE_RUNTIME_ABORTED', 'Node runtime startup was aborted.', signal.reason);
186
- }
187
- const artifacts = [];
188
- for (const artifact of snapshot.artifacts) {
189
- if (artifact.runtime !== 'node') {
190
- throw nodeRuntimeError('HQ_NODE_RUNTIME_UNSUPPORTED_ARTIFACT', `The Node runtime factory cannot load a ${artifact.runtime} artifact.`);
191
- }
192
- const bytes = artifact.read();
193
- if (bytes.byteLength !== artifact.byteLength || sha256(bytes) !== artifact.artifactSha256) {
194
- throw nodeRuntimeError('HQ_NODE_RUNTIME_INVALID_ARTIFACT', 'Materialized Node runtime bytes do not match their identity.');
195
- }
196
- const artifactPath = path.join(directory, `${artifact.artifactSha256}.mjs`);
197
- await writeFile(artifactPath, bytes, { flag: 'wx', mode: 0o600 });
198
- artifacts.push(Object.freeze({ path: artifactPath, entrypoints: artifact.entrypoints }));
199
- }
200
- if (artifacts.length === 0) {
201
- throw nodeRuntimeError('HQ_NODE_RUNTIME_UNSUPPORTED_ARTIFACT', 'The deployment snapshot contains no Node runtime artifacts.');
202
- }
203
- const worker = new Worker(WORKER_SOURCE, {
204
- eval: true,
205
- workerData: { artifacts },
206
- });
207
- let resolveReady;
208
- let rejectReady;
209
- const ready = new Promise((resolve, reject) => {
210
- resolveReady = resolve;
211
- rejectReady = reject;
212
- });
213
- const timer = setTimeout(() => {
214
- rejectReady(nodeRuntimeError('HQ_NODE_RUNTIME_START_FAILED', 'The Node deployment runtime did not start before its deadline.'));
215
- }, timeoutMs);
216
- timer.unref();
217
- const abort = () => rejectReady(nodeRuntimeError('HQ_NODE_RUNTIME_ABORTED', 'Node runtime startup was aborted.', signal?.reason));
218
- signal?.addEventListener('abort', abort, { once: true });
219
- const startupMessage = (message) => {
220
- if (message.kind === 'ready')
221
- resolveReady();
222
- if (message.kind === 'fatal')
223
- rejectReady(nodeRuntimeError('HQ_NODE_RUNTIME_START_FAILED', 'The Node deployment runtime could not import its artifacts.', remoteError(message.error)));
224
- };
225
- const startupError = (error) => rejectReady(nodeRuntimeError('HQ_NODE_RUNTIME_START_FAILED', 'The Node deployment runtime worker failed during startup.', error));
226
- const startupExit = (code) => rejectReady(nodeRuntimeError('HQ_NODE_RUNTIME_START_FAILED', `The Node deployment runtime worker exited during startup with code ${code}.`));
227
- worker.on('message', startupMessage);
228
- worker.once('error', startupError);
229
- worker.once('exit', startupExit);
230
- const instance = workerInstance(worker, directory, ready);
231
- try {
232
- await ready;
233
- return instance;
234
- }
235
- catch (error) {
236
- try {
237
- await instance.close();
238
- }
239
- catch {
240
- // Preserve the startup failure; cleanup can be retried by the factory.
241
- }
242
- throw error;
243
- }
244
- finally {
245
- clearTimeout(timer);
246
- signal?.removeEventListener('abort', abort);
247
- worker.off('message', startupMessage);
248
- worker.off('error', startupError);
249
- worker.off('exit', startupExit);
250
- }
251
- }
252
- export function createNodeWorkerDeploymentRuntimeFactory(options = {}) {
253
- const timeoutMs = startupTimeout(options.startupTimeoutMs);
254
- return Object.freeze({
255
- async start(snapshot, input) {
256
- const root = await ensureTemporaryDirectory(options.temporaryDirectory);
257
- const directory = await mkdtemp(path.join(root, 'hypequery-node-runtime-'));
258
- try {
259
- await chmod(directory, 0o700);
260
- return await startWorker(snapshot, directory, timeoutMs, input.signal);
261
- }
262
- catch (error) {
263
- try {
264
- await rm(directory, { force: true, recursive: true });
265
- }
266
- catch (cleanupError) {
267
- options.onCleanupError?.(cleanupError, directory);
268
- // Preserve the error that prevented a runtime from starting.
269
- }
270
- throw error;
271
- }
272
- },
273
- });
274
- }
@@ -1,58 +0,0 @@
1
- import type { ProtocolDeploymentContract, ProtocolDeploymentReleaseEnvelope, ProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
2
- import type { DeploymentActivationRecord, DeploymentActivationRegistry } from './activation.js';
3
- import { type VerifiedDeploymentBundle } from './bundle.js';
4
- export type DeploymentRuntimeMaterializationErrorCode = 'HQ_RUNTIME_MATERIALIZATION_CONFIGURATION' | 'HQ_RUNTIME_MATERIALIZATION_ACTIVATION_UNAVAILABLE' | 'HQ_RUNTIME_MATERIALIZATION_RELEASE_NOT_FOUND' | 'HQ_RUNTIME_MATERIALIZATION_RELEASE_INVALID' | 'HQ_RUNTIME_MATERIALIZATION_ARTIFACT_INVALID' | 'HQ_RUNTIME_MATERIALIZATION_UNSTABLE_ACTIVATION';
5
- export declare class DeploymentRuntimeMaterializationError extends Error {
6
- readonly code: DeploymentRuntimeMaterializationErrorCode;
7
- constructor(code: DeploymentRuntimeMaterializationErrorCode, message: string, options?: {
8
- readonly cause?: unknown;
9
- });
10
- }
11
- export interface DeploymentRuntimeRelease {
12
- readonly release: ProtocolDeploymentReleaseEnvelope;
13
- readonly releaseIdentity: string;
14
- readonly bundle: VerifiedDeploymentBundle;
15
- }
16
- export interface DeploymentRuntimeReleaseReader {
17
- /** Return only a fully revalidated accepted release and its closed bundle. */
18
- read(releaseIdentity: string): Promise<DeploymentRuntimeRelease | undefined>;
19
- }
20
- export interface DeploymentRuntimeArtifactSnapshot {
21
- readonly runtime: 'node' | 'python';
22
- readonly artifactSha256: string;
23
- readonly byteLength: number;
24
- readonly entrypoints: readonly string[];
25
- /** Returns a fresh copy so callers cannot mutate the materialized bytes. */
26
- read(): Uint8Array;
27
- }
28
- export interface DeploymentRuntimeQueryBinding {
29
- readonly query: string;
30
- readonly runtime: 'node' | 'python';
31
- readonly artifactSha256: string;
32
- readonly entrypoint: string;
33
- }
34
- export interface DeploymentRuntimeSnapshot {
35
- readonly target: ProtocolDeploymentReleaseTarget;
36
- readonly activation: DeploymentActivationRecord;
37
- readonly release: ProtocolDeploymentReleaseEnvelope;
38
- readonly releaseIdentity: string;
39
- readonly bundleIdentity: string;
40
- readonly deployment: ProtocolDeploymentContract;
41
- readonly artifacts: readonly DeploymentRuntimeArtifactSnapshot[];
42
- readonly queries: readonly DeploymentRuntimeQueryBinding[];
43
- }
44
- export interface DeploymentRuntimeMaterializer {
45
- /**
46
- * Materialize the current activation and confirm it remained current while
47
- * its bytes were copied. Returns undefined when the target has no activation.
48
- */
49
- current(target: ProtocolDeploymentReleaseTarget): Promise<DeploymentRuntimeSnapshot | undefined>;
50
- }
51
- export interface DeploymentRuntimeMaterializerOptions {
52
- readonly activations: DeploymentActivationRegistry;
53
- readonly releases: DeploymentRuntimeReleaseReader;
54
- /** Activation-stability attempts from 1 through 16. */
55
- readonly maxStabilityAttempts?: number;
56
- }
57
- export declare function createDeploymentRuntimeMaterializer(options: DeploymentRuntimeMaterializerOptions): DeploymentRuntimeMaterializer;
58
- //# sourceMappingURL=runtime-materialization.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runtime-materialization.d.ts","sourceRoot":"","sources":["../src/runtime-materialization.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,0BAA0B,EAC1B,iCAAiC,EACjC,+BAA+B,EAChC,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EACV,0BAA0B,EAC1B,4BAA4B,EAC7B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,aAAa,CAAC;AAKrB,MAAM,MAAM,yCAAyC,GACjD,0CAA0C,GAC1C,mDAAmD,GACnD,8CAA8C,GAC9C,4CAA4C,GAC5C,6CAA6C,GAC7C,gDAAgD,CAAC;AAErD,qBAAa,qCAAsC,SAAQ,KAAK;IAC9D,QAAQ,CAAC,IAAI,EAAE,yCAAyC,CAAC;gBAGvD,IAAI,EAAE,yCAAyC,EAC/C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAM7C;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,iCAAiC,CAAC;IACpD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAC;CAC3C;AAED,MAAM,WAAW,8BAA8B;IAC7C,8EAA8E;IAC9E,IAAI,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC,CAAC;CAC9E;AAED,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,4EAA4E;IAC5E,IAAI,IAAI,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,UAAU,EAAE,0BAA0B,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,iCAAiC,CAAC;IACpD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,0BAA0B,CAAC;IAChD,QAAQ,CAAC,SAAS,EAAE,SAAS,iCAAiC,EAAE,CAAC;IACjE,QAAQ,CAAC,OAAO,EAAE,SAAS,6BAA6B,EAAE,CAAC;CAC5D;AAED,MAAM,WAAW,6BAA6B;IAC5C;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,+BAA+B,GAAG,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAAC;CAClG;AAED,MAAM,WAAW,oCAAoC;IACnD,QAAQ,CAAC,WAAW,EAAE,4BAA4B,CAAC;IACnD,QAAQ,CAAC,QAAQ,EAAE,8BAA8B,CAAC;IAClD,uDAAuD;IACvD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACxC;AA4LD,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,oCAAoC,GAC5C,6BAA6B,CA+C/B"}
@@ -1,187 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { constants } from 'node:fs';
3
- import { lstat, open } from 'node:fs/promises';
4
- import path from 'node:path';
5
- import { prepareProtocolDeploymentReleaseEnvelope } from '@hypequery/protocol';
6
- import { validateDeploymentActivationRecord } from './activation.js';
7
- import { verifyDeploymentBundle, } from './bundle.js';
8
- const DEFAULT_STABILITY_ATTEMPTS = 4;
9
- const MAX_STABILITY_ATTEMPTS = 16;
10
- export class DeploymentRuntimeMaterializationError extends Error {
11
- code;
12
- constructor(code, message, options = {}) {
13
- super(message, options.cause === undefined ? undefined : { cause: options.cause });
14
- this.name = 'DeploymentRuntimeMaterializationError';
15
- this.code = code;
16
- }
17
- }
18
- function materializationError(code, message, cause) {
19
- return new DeploymentRuntimeMaterializationError(code, message, { cause });
20
- }
21
- function sameTarget(left, right) {
22
- return left.project === right.project && left.environment === right.environment;
23
- }
24
- function sha256(bytes) {
25
- return createHash('sha256').update(bytes).digest('hex');
26
- }
27
- async function readArtifact(bundleDirectory, artifact) {
28
- const artifactPath = path.join(bundleDirectory, ...artifact.path.split('/'));
29
- let handle;
30
- try {
31
- const initial = await lstat(artifactPath);
32
- if (initial.isSymbolicLink() || !initial.isFile() || initial.size !== artifact.byteLength) {
33
- throw new Error('Runtime artifact is not the declared bounded regular file.');
34
- }
35
- handle = await open(artifactPath, constants.O_RDONLY | constants.O_NOFOLLOW);
36
- const stat = await handle.stat();
37
- if (!stat.isFile() || stat.size !== artifact.byteLength) {
38
- throw new Error('Runtime artifact changed before materialization.');
39
- }
40
- const bytes = await handle.readFile();
41
- if (bytes.byteLength !== artifact.byteLength || sha256(bytes) !== artifact.sha256) {
42
- throw new Error('Runtime artifact bytes do not match the closed bundle manifest.');
43
- }
44
- await handle.close();
45
- handle = undefined;
46
- return Uint8Array.from(bytes);
47
- }
48
- catch (error) {
49
- try {
50
- await handle?.close();
51
- }
52
- catch {
53
- // Preserve the validation failure that made this artifact unusable.
54
- }
55
- throw materializationError('HQ_RUNTIME_MATERIALIZATION_ARTIFACT_INVALID', 'A deployment runtime artifact could not be materialized.', error);
56
- }
57
- }
58
- function artifactSnapshot(input, bytes) {
59
- const materialized = Uint8Array.from(bytes);
60
- return Object.freeze({
61
- runtime: input.runtime,
62
- artifactSha256: input.artifactSha256,
63
- byteLength: materialized.byteLength,
64
- entrypoints: Object.freeze([...input.entrypoints]),
65
- read: () => Uint8Array.from(materialized),
66
- });
67
- }
68
- async function materializeActivation(activation, releases) {
69
- let stored;
70
- try {
71
- stored = await releases.read(activation.releaseIdentity);
72
- }
73
- catch (error) {
74
- throw materializationError('HQ_RUNTIME_MATERIALIZATION_RELEASE_INVALID', 'The active deployment release could not be revalidated.', error);
75
- }
76
- if (!stored) {
77
- throw materializationError('HQ_RUNTIME_MATERIALIZATION_RELEASE_NOT_FOUND', 'The active deployment release was not found.');
78
- }
79
- let release;
80
- let releaseIdentity;
81
- try {
82
- const prepared = prepareProtocolDeploymentReleaseEnvelope(stored.release);
83
- release = prepared.release;
84
- releaseIdentity = prepared.identity;
85
- }
86
- catch (error) {
87
- throw materializationError('HQ_RUNTIME_MATERIALIZATION_RELEASE_INVALID', 'The active deployment release envelope is invalid.', error);
88
- }
89
- if (stored.releaseIdentity !== activation.releaseIdentity
90
- || releaseIdentity !== activation.releaseIdentity
91
- || !sameTarget(release.target, activation.target)
92
- || release.bundleIdentity !== stored.bundle.identity) {
93
- throw materializationError('HQ_RUNTIME_MATERIALIZATION_RELEASE_INVALID', 'The active deployment release is inconsistent with its activation or bundle.');
94
- }
95
- let bundle;
96
- try {
97
- bundle = await verifyDeploymentBundle(stored.bundle.directory);
98
- }
99
- catch (error) {
100
- throw materializationError('HQ_RUNTIME_MATERIALIZATION_RELEASE_INVALID', 'The active deployment bundle could not be revalidated.', error);
101
- }
102
- if (bundle.identity !== stored.bundle.identity
103
- || bundle.identity !== release.bundleIdentity) {
104
- throw materializationError('HQ_RUNTIME_MATERIALIZATION_RELEASE_INVALID', 'The active deployment bundle identity is inconsistent.');
105
- }
106
- const entrypoints = new Map();
107
- const queries = [];
108
- for (const query of bundle.contract.queries) {
109
- const implementation = query.implementation;
110
- if (implementation.kind !== 'runtime-reference')
111
- continue;
112
- const names = entrypoints.get(implementation.artifactSha256) ?? [];
113
- names.push(implementation.entrypoint);
114
- entrypoints.set(implementation.artifactSha256, names);
115
- queries.push(Object.freeze({
116
- query: query.name,
117
- runtime: implementation.runtime,
118
- artifactSha256: implementation.artifactSha256,
119
- entrypoint: implementation.entrypoint,
120
- }));
121
- }
122
- const artifacts = [];
123
- for (const artifact of bundle.manifest.artifacts) {
124
- const bytes = await readArtifact(bundle.directory, artifact);
125
- artifacts.push(artifactSnapshot({
126
- runtime: artifact.runtime,
127
- artifactSha256: artifact.sha256,
128
- entrypoints: [...new Set(entrypoints.get(artifact.sha256) ?? [])].sort(),
129
- }, bytes));
130
- }
131
- artifacts.sort((left, right) => left.artifactSha256.localeCompare(right.artifactSha256));
132
- queries.sort((left, right) => left.query.localeCompare(right.query));
133
- return Object.freeze({
134
- target: activation.target,
135
- activation,
136
- release,
137
- releaseIdentity,
138
- bundleIdentity: bundle.identity,
139
- deployment: bundle.contract,
140
- artifacts: Object.freeze(artifacts),
141
- queries: Object.freeze(queries),
142
- });
143
- }
144
- function stabilityAttempts(input) {
145
- const value = input ?? DEFAULT_STABILITY_ATTEMPTS;
146
- if (!Number.isSafeInteger(value) || value < 1 || value > MAX_STABILITY_ATTEMPTS) {
147
- throw materializationError('HQ_RUNTIME_MATERIALIZATION_CONFIGURATION', `maxStabilityAttempts must be between 1 and ${MAX_STABILITY_ATTEMPTS}.`);
148
- }
149
- return value;
150
- }
151
- export function createDeploymentRuntimeMaterializer(options) {
152
- const maximumAttempts = stabilityAttempts(options.maxStabilityAttempts);
153
- if (!constants.O_NOFOLLOW) {
154
- throw materializationError('HQ_RUNTIME_MATERIALIZATION_CONFIGURATION', 'Runtime materialization requires filesystem no-follow support.');
155
- }
156
- async function currentActivation(target) {
157
- try {
158
- const activation = await options.activations.current(target);
159
- if (!activation)
160
- return undefined;
161
- const validated = validateDeploymentActivationRecord(activation);
162
- if (!sameTarget(validated.target, target)) {
163
- throw new Error('Deployment activation target does not match the requested target.');
164
- }
165
- return validated;
166
- }
167
- catch (error) {
168
- throw materializationError('HQ_RUNTIME_MATERIALIZATION_ACTIVATION_UNAVAILABLE', 'Deployment activation state could not be read.', error);
169
- }
170
- }
171
- return Object.freeze({
172
- async current(target) {
173
- for (let attempt = 0; attempt < maximumAttempts; attempt += 1) {
174
- const activation = await currentActivation(target);
175
- if (!activation)
176
- return undefined;
177
- const snapshot = await materializeActivation(activation, options.releases);
178
- const confirmed = await currentActivation(target);
179
- if (!confirmed)
180
- return undefined;
181
- if (confirmed.revision === activation.revision)
182
- return snapshot;
183
- }
184
- throw materializationError('HQ_RUNTIME_MATERIALIZATION_UNSTABLE_ACTIVATION', 'Deployment activation changed repeatedly during runtime materialization.');
185
- },
186
- });
187
- }
@@ -1,70 +0,0 @@
1
- import type { ProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
2
- import type { DeploymentRuntimeMaterializer, DeploymentRuntimeQueryBinding, DeploymentRuntimeSnapshot } from './runtime-materialization.js';
3
- export type DeploymentRuntimeSupervisorErrorCode = 'HQ_RUNTIME_SUPERVISOR_CONFIGURATION' | 'HQ_RUNTIME_SUPERVISOR_CLOSED' | 'HQ_RUNTIME_NOT_READY' | 'HQ_RUNTIME_QUERY_NOT_FOUND' | 'HQ_RUNTIME_QUERY_NOT_EXECUTABLE' | 'HQ_RUNTIME_START_FAILED' | 'HQ_RUNTIME_HEALTH_FAILED' | 'HQ_RUNTIME_INVOCATION_FAILED' | 'HQ_RUNTIME_RECONCILE_UNSTABLE' | 'HQ_RUNTIME_ABORTED';
4
- export declare class DeploymentRuntimeSupervisorError extends Error {
5
- readonly code: DeploymentRuntimeSupervisorErrorCode;
6
- constructor(code: DeploymentRuntimeSupervisorErrorCode, message: string, options?: {
7
- readonly cause?: unknown;
8
- });
9
- }
10
- export interface DeploymentRuntimeInstanceInvocation {
11
- readonly query: string;
12
- readonly binding: DeploymentRuntimeQueryBinding;
13
- readonly argument: unknown;
14
- readonly signal?: AbortSignal;
15
- }
16
- export interface DeploymentRuntimeInstance {
17
- healthCheck(input: {
18
- readonly signal?: AbortSignal;
19
- }): Promise<void>;
20
- invoke(input: DeploymentRuntimeInstanceInvocation): Promise<unknown>;
21
- close(): Promise<void>;
22
- }
23
- export interface DeploymentRuntimeFactory {
24
- start(snapshot: DeploymentRuntimeSnapshot, input: {
25
- readonly signal?: AbortSignal;
26
- }): Promise<DeploymentRuntimeInstance>;
27
- }
28
- export interface DeploymentRuntimeInvocation {
29
- readonly target: ProtocolDeploymentReleaseTarget;
30
- readonly query: string;
31
- readonly argument: unknown;
32
- readonly signal?: AbortSignal;
33
- }
34
- export interface DeploymentRuntimeStatus {
35
- readonly target: ProtocolDeploymentReleaseTarget;
36
- readonly activationRevision: string;
37
- readonly releaseIdentity: string;
38
- readonly bundleIdentity: string;
39
- }
40
- export type DeploymentRuntimeReconcileResult = {
41
- readonly status: 'activated';
42
- readonly runtime: DeploymentRuntimeStatus;
43
- } | {
44
- readonly status: 'already-current';
45
- readonly runtime: DeploymentRuntimeStatus;
46
- } | {
47
- readonly status: 'deactivated';
48
- readonly previous: DeploymentRuntimeStatus;
49
- } | {
50
- readonly status: 'no-active-release';
51
- };
52
- export interface DeploymentRuntimeSupervisor {
53
- reconcile(target: ProtocolDeploymentReleaseTarget, options?: {
54
- readonly signal?: AbortSignal;
55
- }): Promise<DeploymentRuntimeReconcileResult>;
56
- invoke(input: DeploymentRuntimeInvocation): Promise<unknown>;
57
- status(target: ProtocolDeploymentReleaseTarget): DeploymentRuntimeStatus | undefined;
58
- close(): Promise<void>;
59
- }
60
- export interface DeploymentRuntimeSupervisorOptions {
61
- readonly materializer: DeploymentRuntimeMaterializer;
62
- readonly factory: DeploymentRuntimeFactory;
63
- /** In-flight drain deadline from 0 through 300,000 milliseconds. */
64
- readonly drainTimeoutMs?: number;
65
- /** Activation-stability attempts from 1 through 16. */
66
- readonly maxReconcileAttempts?: number;
67
- readonly onBackgroundError?: (error: unknown) => void;
68
- }
69
- export declare function createDeploymentRuntimeSupervisor(options: DeploymentRuntimeSupervisorOptions): DeploymentRuntimeSupervisor;
70
- //# sourceMappingURL=runtime-supervisor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runtime-supervisor.d.ts","sourceRoot":"","sources":["../src/runtime-supervisor.ts"],"names":[],"mappings":"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"}