@hydraharness/harness-code-runtime-worker-thread 0.1.1-rc.6

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.
@@ -0,0 +1,23 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@hydraharness/harness-code-runtime-worker-thread`.
4
+ * @module @hydraharness/harness-code-runtime-worker-thread/invariant
5
+ */
6
+ const PACKAGE_NAME = "@hydraharness/harness-code-runtime-worker-thread";
7
+ /** Cordis companion plugin name. */
8
+ const name = "code-runtime-worker-thread-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: this process-boundary implementation exposes no same-process event relation;
13
+ * worker protocol and built-worker tests cover it.
14
+ */
15
+ const install = () => {};
16
+ /**
17
+ * Register this package's invariant companion.
18
+ * @param ctx - Cordis context carrying the invariant service.
19
+ * @returns the installed registration's disposer after setup succeeds.
20
+ */
21
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
22
+ //#endregion
23
+ export { apply, inject, name };
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Worker-side execution logic, written as plain functions over an injected port so the unit
3
+ * suite can run every line IN-PROCESS against a fake port (a real worker thread is a separate
4
+ * V8 isolate the coverage provider cannot observe).
5
+ * @module @hydraharness/harness-code-runtime-worker-thread/src/bootstrap
6
+ */
7
+ import type { DoneMessage, ReplyMessage, WorkerBootData, WorkerToHost } from './protocol.ts';
8
+ /** The port API the bootstrap needs — satisfied by `parentPort` and by the tests' fake. */
9
+ export interface BootstrapPort {
10
+ postMessage(message: WorkerToHost): void;
11
+ on(event: 'message', listener: (message: ReplyMessage) => void): void;
12
+ }
13
+ /**
14
+ * A writable stream's `write` slot, as the bootstrap patches it (see
15
+ * {@link captureStreamWrites}). Method-typed so the real
16
+ * `process.stdout`/`process.stderr` (narrower chunk parameters) remain
17
+ * assignable.
18
+ */
19
+ export interface PatchableStream {
20
+ write(chunk: unknown, ...rest: unknown[]): boolean;
21
+ }
22
+ /**
23
+ * Ordered text capture under the shared outer JSON-byte budget, delivered to
24
+ * a sink as each item lands (the real sink streams text over the port eagerly,
25
+ * so captured output survives a mid-run termination). It includes the log
26
+ * array syntax and string escaping in its accounting. Once exhausted it emits
27
+ * the fitting prefix and reports the limit once; the host turns that condition
28
+ * into an explicit `output-limit` run failure.
29
+ */
30
+ export declare class LogBuffer {
31
+ private bytes;
32
+ private entries;
33
+ private truncated;
34
+ private readonly sink;
35
+ private readonly onLimit;
36
+ private readonly maxBytes;
37
+ constructor(maxBytes: number, sink: (text: string) => void, onLimit?: () => void);
38
+ /**
39
+ * Emit text to the sink, charging it against the budget (drops + marks once exhausted).
40
+ * @param text - the captured text to deliver.
41
+ */
42
+ push(text: string): void;
43
+ /** Remaining exact JSON-byte budget for the completion value or failure message. */
44
+ remainingOutputBytes(): number;
45
+ }
46
+ /** The five console methods the shim captures, in the seam's level vocabulary. */
47
+ declare const CONSOLE_LEVELS: readonly ["log", "info", "warn", "error", "debug"];
48
+ /**
49
+ * A `console` replacement whose five leveled methods render their arguments
50
+ * `util.inspect`-style (matching real console formatting closely enough for
51
+ * a model to recognize its own output) into the buffer. Only these five
52
+ * exist — the program gets a deliberately small console, not Node's full
53
+ * console API.
54
+ * @param logs - the buffer every rendered line is pushed into.
55
+ * @returns the five-method console object handed to the program.
56
+ */
57
+ export declare function makeConsoleShim(logs: LogBuffer): Record<(typeof CONSOLE_LEVELS)[number], (...args: unknown[]) => void>;
58
+ /**
59
+ * Redirect a stream's `write` into the log buffer (the program-visible
60
+ * `process.stdout`/`process.stderr` in the real worker), so raw writes land in emission order
61
+ * alongside console output instead of racing down a pipe. It preserves Node's optional callback
62
+ * contract: the callback runs asynchronously after admission, even when the log budget drops
63
+ * the write.
64
+ *
65
+ * @param logs - the buffer captured writes are pushed into.
66
+ * @param stream - the stream whose `write` slot is patched.
67
+ * @returns the restore function (the in-process tests un-patch; the real
68
+ * worker never needs to).
69
+ */
70
+ export declare function captureStreamWrites(logs: LogBuffer, stream: PatchableStream): () => void;
71
+ /**
72
+ * Prepare the program's completion value for the done message. Only lossless
73
+ * JSON crosses, and a value that does not fit the remaining combined outer
74
+ * budget reports `output-limit`; the host revalidates hostile traffic and
75
+ * remains authoritative for native pipe writes the worker cannot observe.
76
+ *
77
+ * @param value - the program's completion value.
78
+ * @param remainingOutputBytes - exact bytes left after captured logs.
79
+ * @param maxOutputBytes - the configured cap named in an overflow diagnostic.
80
+ * @returns the done-message fragment: `{}` for `undefined`, else a flat wire `{ value }`.
81
+ */
82
+ export declare function prepareCompletion(value: unknown, remainingOutputBytes: number, maxOutputBytes?: number): Omit<DoneMessage, 'type'>;
83
+ /**
84
+ * Prepare a thrown program value without sending an unbounded stack or
85
+ * string across the worker port.
86
+ * @param error - the value thrown by the program.
87
+ * @param remainingOutputBytes - exact bytes left after captured logs.
88
+ * @param maxOutputBytes - the configured cap named in an overflow diagnostic.
89
+ * @returns a bounded exception or fixed output-limit fragment.
90
+ */
91
+ export declare function prepareException(error: unknown, remainingOutputBytes: number, maxOutputBytes?: number): Omit<DoneMessage, 'type'>;
92
+ /** One awaited binding call's settlement handles, keyed by call id in the pending map. */
93
+ export interface PendingCall {
94
+ resolve(value: unknown): void;
95
+ reject(error: Error): void;
96
+ }
97
+ /** Constructor type for one program-visible binding rejection class. */
98
+ export type BindingErrorConstructor = new (memberName: string, message: string) => Error;
99
+ /**
100
+ * Build each declared error class once so calls and `instanceof` share constructor identity.
101
+ * @param data - binding namespace declarations from the boot payload.
102
+ * @returns constructors keyed by their owning namespace global.
103
+ */
104
+ export declare function makeBindingErrorClasses(data: Pick<WorkerBootData, 'namespaces'>): Map<string, BindingErrorConstructor>;
105
+ /**
106
+ * Route host replies into the pending-call map: each reply settles its call
107
+ * at most once, and a reply for an unknown id (stray, or a duplicate answer
108
+ * to an id already settled) is ignored. Shared wiring between
109
+ * {@link runWorkerMain} and the tests that exercise {@link makeNamespaces}
110
+ * standalone.
111
+ * @param port - the port whose `message` events carry the replies.
112
+ * @param pending - the id-keyed map of unsettled binding calls.
113
+ */
114
+ export declare function wireReplies(port: BootstrapPort, pending: Map<number, PendingCall>): void;
115
+ /**
116
+ * Build the binding namespace objects the program sees: one null-prototype global per
117
+ * namespace, each declared name an own enumerable async function that bridges over the port
118
+ * (`__proto__`/`constructor`/`toString` are ordinary keys, never prototype collisions).
119
+ * Lossy arguments reject before posting; clone failures and host failure
120
+ * replies reject only the corresponding call.
121
+ *
122
+ * @param data - the boot payload's namespace declarations (globals + names).
123
+ * @param port - the port binding calls are posted to.
124
+ * @param pending - the id-keyed map each posted call parks its handles in.
125
+ * @param nextId - the shared mutable id counter (worker-issued correlation ids).
126
+ * @param errorClasses - per-namespace constructors shared with program globals.
127
+ * @returns one namespace object per declaration, in declaration order.
128
+ */
129
+ export declare function makeNamespaces(data: Pick<WorkerBootData, 'namespaces'>, port: BootstrapPort, pending: Map<number, PendingCall>, nextId: {
130
+ value: number;
131
+ }, errorClasses?: Map<string, BindingErrorConstructor>): Record<string, unknown>[];
132
+ /**
133
+ * Run one strict async-function body, allowing top-level `await` and `return`, and post exactly
134
+ * one terminal {@link DoneMessage}; a thrown program error becomes its `error` field.
135
+ * @param port - host message port or test double.
136
+ * @param data - the boot payload the host sent.
137
+ * @param streams - stdout/stderr objects captured as program logs.
138
+ * @returns after posting the done message.
139
+ */
140
+ export declare function runWorkerMain(port: BootstrapPort, data: WorkerBootData, streams: {
141
+ stdout: PatchableStream;
142
+ stderr: PatchableStream;
143
+ }): Promise<void>;
144
+ export {};
145
+ //# sourceMappingURL=bootstrap.d.ts.map
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Worker-thread code runtime: a fresh worker runs each host-type-stripped TypeScript program
3
+ * and bridges bindings over its message port. This is containment, not a security boundary:
4
+ * model code has bash-equivalent trust despite an empty environment, a heap cap, measured
5
+ * event-loop busy-time and wall-time budgets, and termination that also stops synchronous loops.
6
+ * @module @hydraharness/harness-code-runtime-worker-thread
7
+ */
8
+ import { Context } from '@hydraharness/cordis';
9
+ import z from '@hydraharness/schemastery';
10
+ import { CodeRuntime } from '@hydraharness/harness-code-runtime';
11
+ import type { CodeRunRequest, CodeRunResult } from '@hydraharness/harness-code-runtime';
12
+ /** Plugin config: every execution cap, changeable from `cordis.yml` (no hardcoded tunables). */
13
+ export interface Config {
14
+ /**
15
+ * Busy-time budget in milliseconds: the run fails with kind `'timeout'`
16
+ * once the worker's MEASURED event-loop active time
17
+ * (`worker.performance.eventLoopUtilization()`) exceeds this. Metering
18
+ * measured busy time — not wall time, not host-side pending-call
19
+ * bookkeeping — is what makes the budget both fair (a program awaiting a
20
+ * slow tool accrues nothing) and ungameable (a hot loop accrues whether
21
+ * or not a decoy dispatch is in flight).
22
+ */
23
+ computeMs?: number;
24
+ /**
25
+ * Wall-clock ceiling in milliseconds; never pauses for anything. The
26
+ * backstop for what busy-time cannot see (a program awaiting a promise
27
+ * nobody will resolve). At most `2_147_483_647` (Node's maximum
28
+ * `setTimeout` delay, about 24.9 days): a longer value is rejected at load
29
+ * because `setTimeout` would clamp it to 1 ms.
30
+ */
31
+ maxWallMs?: number;
32
+ /**
33
+ * Hard cap for serialized log-array, completion-value, and failure-message payloads;
34
+ * fixed result-envelope syntax is excluded.
35
+ */
36
+ maxOutputBytes?: number;
37
+ /** The worker's max old-generation heap in MiB (`resourceLimits`); overflow kills the worker, surfacing as kind `'worker-exit'`. */
38
+ maxOldGenerationSizeMb?: number;
39
+ }
40
+ /**
41
+ * The shipped {@link CodeRuntime} backend (`ctx.codeRuntime`). Registers as
42
+ * the `codeRuntime` service; every cap comes from validated config. See the
43
+ * module doc for the containment model and the Service Definition's class JSDoc for
44
+ * the contract this implements (error-as-field, hostile-peer port,
45
+ * no cross-run state, dispose to quiescence).
46
+ */
47
+ export declare class WorkerThreadCodeRuntime extends CodeRuntime {
48
+ static Config: z<Config>;
49
+ readonly language = "typescript";
50
+ readonly isolation = "worker-thread";
51
+ private readonly config;
52
+ private readonly live;
53
+ private disposed;
54
+ constructor(ctx: Context, config: Config);
55
+ /**
56
+ * Dispose to quiescence: mark the service unusable, fail every in-flight
57
+ * run as aborted, and AWAIT each worker's exit so no worker outlives the
58
+ * fiber.
59
+ */
60
+ private teardown;
61
+ /**
62
+ * Execute one program in a fresh worker. Program outcomes — including a
63
+ * type-strip syntax error, which never spawns a worker — resolve with
64
+ * `result.error`; the method rejects only for Service Definition contract misuse (a disposed
65
+ * runtime, an invalid binding namespace).
66
+ * @param request - the program, its bindings, and the abort signal.
67
+ * @returns the run's outcome per the seam contract.
68
+ */
69
+ run(request: CodeRunRequest): Promise<CodeRunResult>;
70
+ /** Apply the outer-output ledger to failures that occur before a worker owns one. */
71
+ private failureBeforeWorker;
72
+ /** Reject malformed binding globals or typed-error declarations as Service Definition contract misuse. */
73
+ private validateBindings;
74
+ /** Spawn the worker for one validated, type-stripped run and drive it to settlement. */
75
+ private execute;
76
+ }
77
+ export default WorkerThreadCodeRuntime;
78
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@hydraharness/harness-code-runtime-worker-thread`.
3
+ * @module @hydraharness/harness-code-runtime-worker-thread/invariant
4
+ */
5
+ import type { Context } from '@hydraharness/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "code-runtime-worker-thread-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1,26 @@
1
+ /** JSON string-prefix accounting for the outer-output ledger. @module @hydraharness/harness-code-runtime-worker-thread/output-json */
2
+ import type { CodeJsonValue } from '@hydraharness/harness-code-runtime';
3
+ /**
4
+ * Measure one JSON string without materializing its complete escaped form.
5
+ * @param text - the candidate string.
6
+ * @param maxBytes - largest serialized size the caller can admit.
7
+ * @returns Exact serialized bytes, or `undefined` as soon as the cap is crossed.
8
+ */
9
+ export declare function jsonStringBytesUpTo(text: string, maxBytes: number): number | undefined;
10
+ /**
11
+ * Measure one lossless JSON value without allocating its serialized form.
12
+ * @param value - already validated lossless JSON.
13
+ * @param maxBytes - largest serialized size the caller can admit.
14
+ * @returns Exact serialized bytes, or `undefined` as soon as the cap is crossed.
15
+ */
16
+ export declare function jsonValueBytesUpTo(value: CodeJsonValue, maxBytes: number): number | undefined;
17
+ /**
18
+ * Return the longest code-point-aligned prefix whose JSON string encoding,
19
+ * including its surrounding quotes, fits `maxBytes`.
20
+ *
21
+ * @param text - the candidate string.
22
+ * @param maxBytes - serialized JSON-string bytes available.
23
+ * @returns the fitting prefix, or an empty string when even useful content cannot fit.
24
+ */
25
+ export declare function truncateJsonStringBytes(text: string, maxBytes: number): string;
26
+ //# sourceMappingURL=output-json.d.ts.map
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Versionless, structured-clone wire protocol between co-shipped host and worker code. The host
3
+ * treats inbound traffic as hostile because model code can forge `parentPort` messages; the
4
+ * worker trusts host replies.
5
+ * @module @hydraharness/harness-code-runtime-worker-thread/src/protocol
6
+ */
7
+ import type { WorkerJsonWire } from './worker-json.ts';
8
+ /** What the host hands the worker at spawn, via `workerData`. */
9
+ export interface WorkerBootData {
10
+ /** The type-stripped (plain JS) program body. */
11
+ code: string;
12
+ /** Binding namespaces to materialize; functions themselves stay host-side. */
13
+ namespaces: {
14
+ global: string;
15
+ names: string[];
16
+ errorClass?: {
17
+ name: string;
18
+ memberNameProperty: string;
19
+ };
20
+ }[];
21
+ /** Hard cap for the combined serialized outer logs plus completion value or failure diagnostic. */
22
+ maxOutputBytes: number;
23
+ }
24
+ /** Worker → host: one bridged binding call. */
25
+ interface CallMessage {
26
+ type: 'call';
27
+ /** Worker-issued correlation id; the host answers each id at most once and ignores duplicates. */
28
+ id: number;
29
+ /** The namespace global the call targets. */
30
+ global: string;
31
+ /** The function name within the namespace. */
32
+ name: string;
33
+ /** The single argument as a flat lossless-JSON wire value. */
34
+ args: WorkerJsonWire;
35
+ }
36
+ /** Worker → host: captured text, streamed eagerly so output survives a mid-run termination (timeout, abort, OOM). */
37
+ interface LogMessage {
38
+ type: 'log';
39
+ text: string;
40
+ }
41
+ /** Worker → host: worker-side capture or completion measurement exceeded the outer cap. */
42
+ interface OutputLimitMessage {
43
+ type: 'output-limit';
44
+ }
45
+ /**
46
+ * Worker → host: the program settled. `error` carries a program exception,
47
+ * invalid completion, or output overflow (budgets, aborts, and substrate death
48
+ * are observed host-side). `value` is present only on a clean completion that
49
+ * produced one, as a flat wire value already lossless and admitted against
50
+ * the remaining combined output cap. Logs are NOT carried here — they streamed
51
+ * eagerly as {@link LogMessage}s.
52
+ */
53
+ export interface DoneMessage {
54
+ type: 'done';
55
+ value?: WorkerJsonWire;
56
+ error?: {
57
+ kind: 'exception' | 'invalid-output' | 'output-limit';
58
+ message: string;
59
+ };
60
+ }
61
+ /** Every message the worker sends. */
62
+ export type WorkerToHost = CallMessage | LogMessage | OutputLimitMessage | DoneMessage;
63
+ /** Host → worker: the answer to one {@link CallMessage}. */
64
+ export type ReplyMessage = {
65
+ type: 'reply';
66
+ id: number;
67
+ ok: true;
68
+ value: WorkerJsonWire;
69
+ } | {
70
+ type: 'reply';
71
+ id: number;
72
+ ok: false;
73
+ message: string;
74
+ };
75
+ export {};
76
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Lossless-JSON snapshots for the dependency-free source worker closure.
3
+ * @module @hydraharness/harness-code-runtime-worker-thread/worker-json
4
+ */
5
+ import type { CodeJsonValue } from '@hydraharness/harness-code-runtime';
6
+ /**
7
+ * Validate and detach one worker-boundary value without loading another
8
+ * workspace package at runtime. This mirrors the session-owned canonical
9
+ * JSON boundary while remaining safe to import from the unbuilt worker.
10
+ * Its iterative traversal adds no JavaScript call-stack depth limit.
11
+ *
12
+ * @param value - the candidate completion value.
13
+ * @returns a detached lossless-JSON snapshot, or `undefined` when invalid.
14
+ */
15
+ export declare function snapshotCodeJsonValue(value: unknown): CodeJsonValue | undefined;
16
+ interface ArrayWireToken {
17
+ kind: 'array';
18
+ length: number;
19
+ }
20
+ interface ObjectWireToken {
21
+ kind: 'object';
22
+ keys: string[];
23
+ }
24
+ type WorkerJsonToken = null | boolean | number | string | ArrayWireToken | ObjectWireToken;
25
+ /**
26
+ * A pre-order, bounded-depth transport for one lossless JSON value. Container
27
+ * markers and scalar leaves share one flat token array, so `worker_threads`
28
+ * never has to structured-clone the value's application nesting.
29
+ */
30
+ export type WorkerJsonWire = WorkerJsonToken[];
31
+ /**
32
+ * Flatten one validated JSON value for the worker-thread message port.
33
+ * @param value - the lossless JSON value to transport.
34
+ * @returns a pre-order token stream whose own nesting is bounded.
35
+ */
36
+ export declare function encodeWorkerJson(value: CodeJsonValue): WorkerJsonWire;
37
+ /**
38
+ * Rebuild one lossless JSON value from the flat worker-thread wire format.
39
+ * Malformed or incomplete traffic returns `undefined`; traversal is iterative
40
+ * and therefore independent of the transported value's application depth.
41
+ * @param input - untrusted message-port payload.
42
+ * @returns the detached JSON value, or `undefined` when the wire is invalid.
43
+ */
44
+ export declare function decodeWorkerJson(input: unknown): CodeJsonValue | undefined;
45
+ export {};
46
+ //# sourceMappingURL=worker-json.d.ts.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Spawn-only worker entrypoint over {@link runWorkerMain}. Executable logic stays in
3
+ * `bootstrap.ts` for in-process coverage; real-worker tests cover this glue.
4
+ * @module @hydraharness/harness-code-runtime-worker-thread/src/worker
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=worker.d.ts.map