2026-10-25 09:45:26 +08:00
import { analysisSettingLimit } from "../../domain/analysisSettingDefinitions.js" ;
2026-10-22 08:20:30 +08:00
import { WorkerFrameSignal } from "../../process/WorkerFrameSignal.js" ;
2026-10-25 10:29:17 +07:00
import type {
JavaScriptSemanticProjectionCapacity ,
JavaScriptSemanticFileProjection ,
} from "../../domain/javascript/javascriptSemanticFileProjection.js" ;
2026-10-11 07:22:50 +08:00
import { createHash , randomUUID } from "node:crypto" ;
import { readFile , rm , stat , writeFile } from "node:fs/promises" ;
import { join } from "node:path" ;
import { StringDecoder } from "node:string_decoder" ;
import { fileURLToPath } from "node:url" ;
import { getHeapStatistics } from "node:v8" ;
import { z } from "zod" ;
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js" ;
import type { JavaScriptStaticAnalysis } from "../../domain/javascript/javascriptStaticAnalysisTypes.js" ;
import { estimateJavaScriptStaticApplicationProjection } from "../../domain/javascript/javascriptApplicationProjectionResources.js" ;
import type { JavaScriptModuleSemanticIr } from "../../domain/javascript/javascriptModuleSemanticIr.js" ;
import type {
JavaScriptAnalyzedSource ,
JavaScriptSourceAnalysisFailure ,
JavaScriptSourceAnalysisLimits ,
} from "../../domain/javascript/javascriptSourceAnalysis.js" ;
import { AnalysisError } from "../../domain/analysisErrorBase.js" ;
import {
AnalysisCancelledError ,
AnalysisTimeoutError ,
AnalysisOutputError ,
AnalysisResourceConstraintError ,
} from "../../domain/analysisErrorCore.js" ;
import { ProviderAdapterError } from "../../domain/providerAdapterError.js" ;
import { ProviderCleanupError } from "../../domain/providerCleanupError.js" ;
import { ok , err , type Result } from "../../domain/result.js" ;
2026-10-11 07:20:00 -04:00
import { javaScriptRuntime } from "../../domain/runtimeVersion.js" ;
2026-10-22 22:41:25 +05:00
import {
RuntimeControlUnavailableError ,
runtimeControlCapabilityError ,
} from "../../process/RuntimeControlUnavailableError.js" ;
2026-10-11 07:20:00 -04:00
import { javaScriptWorkerMemoryLimitBytes } from "../../process/JavaScriptWorkerMemoryLimit.js" ;
2026-10-20 18:38:00 +08:00
import {
spawnOwnedProviderProcess ,
type ProviderProcessDiagnostic ,
type ProviderProcessSupervisor ,
} from "../../process/ProviderProcess.js" ;
2026-10-11 07:22:50 +08:00
import {
2026-10-19 13:12:25 +08:00
OwnedWorkerRuntime ,
OwnedWorkerRuntimeCleanupError ,
} from "../../process/OwnedWorkerRuntime.js" ;
2026-10-11 07:22:50 +08:00
import { prepareProcessOwnershipInspection } from "../../process/ProcessOwnershipObservation.js" ;
import {
ProviderStartupDeadline ,
waitForAbortableDelay ,
} from "../../process/ProviderDeadline.js" ;
import {
readJavaScriptAnalysisTransfer ,
JAVASCRIPT_ANALYSIS_TRANSFER_RECORD_BYTES ,
2026-10-22 02:44:14 +08:00
javaScriptForwardedTransferRemediation ,
2026-10-11 07:22:50 +08:00
javaScriptTransferRetentionBytes ,
} from "./JavaScriptAnalysisTransfer.js" ;
2026-10-22 02:44:14 +08:00
import {
JAVASCRIPT_PARENT_CHECKPOINT_HEAP_REMEDIATION ,
JAVASCRIPT_PARENT_RESULT_BUDGET_REMEDIATION ,
JAVASCRIPT_WORKER_HEAP_MIB_MAXIMUM ,
JAVASCRIPT_WORKER_HEAP_MIB_MINIMUM ,
javaScriptWorkerHeapExhaustionRecovery ,
javaScriptWorkerHeapRemediation ,
} from "../../domain/javascript/javascriptAnalysisResourceControls.js" ;
2026-10-11 07:22:50 +08:00
import { javaScriptWorkerStaticAnalysisSchema } from "./JavaScriptAnalysisWorkerSchemas.js" ;
import {
JAVASCRIPT_ANALYSIS_WORKER_DEFAULTS as DEFAULTS ,
2026-10-22 02:44:14 +08:00
javaScriptCheckpointCeilingRemediation ,
2026-10-11 07:22:50 +08:00
javaScriptWorkerMessageSchema ,
javaScriptWorkerRequestSchema ,
javaScriptWorkerAdmissionSchema ,
type JavaScriptWorkerMessage ,
} from "./JavaScriptAnalysisWorkerProtocol.js" ;
const OPERATION = "analyze_javascript_application" ;
const PROVIDER = "javascript-static-analysis" ;
const MIB = 1024 * 1024 ;
2026-10-12 02:17:38 +08:00
const STATIC_CHECKPOINT_RETENTION_EXPANSION = 6 ;
2026-10-11 07:20:00 -04:00
/** The worker runs on this executable; Bun accepts but ignores V8 heap flags. */
const WORKER_HEAP_FLAGS_ENFORCED =
javaScriptRuntime ( process . versions ). id === "node" ;
2026-10-11 07:22:50 +08:00
2026-10-20 18:38:00 +08:00
type WorkerPrimaryFailure =
| { readonly kind : "memory" }
| { readonly kind : "output" ; readonly reason : string }
| { readonly kind : "input" ; readonly cause : Error }
| {
readonly kind : "protocol" ;
readonly id : string ;
readonly phase : string ;
readonly response : Extract < JavaScriptWorkerMessage , { kind : "failure" }>;
}
| {
readonly kind : "exit" ;
readonly code : number | null ;
readonly signal : NodeJS.Signals | null ;
readonly heapExhaustion : boolean ;
};
2026-10-11 07:22:50 +08:00
/** One sequential owned worker, retained between source files and closed after analysis. */
export class JavaScriptAnalysisWorker {
readonly # runId = `rea-javascript-analysis- ${ randomUUID () } ` ;
2026-10-19 13:12:25 +08:00
readonly # runtime : OwnedWorkerRuntime ;
2026-10-11 07:22:50 +08:00
# ready = false ;
# heapLimitBytes = 0 ;
# frames : JavaScriptWorkerMessage [] = [];
2026-10-22 08:20:30 +08:00
readonly # frameSignal = new WorkerFrameSignal ();
2026-10-11 07:22:50 +08:00
# pending = "" ;
# decoder = new StringDecoder ( "utf8" );
2026-10-20 18:38:00 +08:00
# primaryFailure : WorkerPrimaryFailure | undefined ;
# activeJobId : string | undefined ;
# activeJobPhase = "startup" ;
2026-10-20 19:05:00 +08:00
# staticResponseId : string | undefined ;
# terminalResponseId : string | undefined ;
2026-10-11 07:22:50 +08:00
# phase = "startup" ;
# lastFailure : AnalysisError | undefined ;
# estimatedRetainedBytes = 0 ;
# applicationProjectionFailure : AnalysisResourceConstraintError | undefined ;
# parentCapacityExhausted = false ;
# sourcesRemaining : number | undefined ;
2026-10-20 18:38:00 +08:00
# deadline : ProviderStartupDeadline | undefined ;
2026-10-21 19:01:20 +08:00
# resultBudgetBytes = 0 ;
2026-10-11 07:20:00 -04:00
/** Null when the runtime enforces the V8 heap flags instead. */
readonly # processMemoryLimitBytes : number | null ;
2026-10-11 07:22:50 +08:00
readonly # onStdout = ( chunk : Buffer | string ) : void => {
2026-10-20 18:38:00 +08:00
if ( this . # deadline ? . interruption !== undefined ) return ;
this . # observeMemoryLimit ();
2026-10-11 07:22:50 +08:00
try {
this . # pending +=
typeof chunk === "string" ? chunk : this. # decoder . write ( chunk );
let newline : number ;
while (( newline = this . # pending . indexOf ( "\n" )) >= 0 ) {
const line = this . # pending . slice ( 0 , newline );
this . # pending = this . # pending . slice ( newline + 1 );
if (
Buffer . byteLength ( line ) > DEFAULTS . protocolBytes ||
this . # frames . length >= 16
2026-10-20 18:38:00 +08:00
) {
this . # latchOutputFailure (
2026-10-11 07:22:50 +08:00
"JavaScript worker exceeded its control-frame budget" ,
);
2026-10-20 18:38:00 +08:00
return ;
}
2026-10-11 07:22:50 +08:00
const raw : unknown = JSON . parse ( line );
2026-10-20 18:38:00 +08:00
const frame = javaScriptWorkerMessageSchema . parse ( raw );
this . # frames . push ( frame );
this . # observeJobFrame ( frame );
2026-10-22 08:20:30 +08:00
this . # frameSignal . wake ( "frame" );
2026-10-11 07:22:50 +08:00
}
if ( Buffer . byteLength ( this . # pending ) > DEFAULTS . protocolBytes )
2026-10-20 18:38:00 +08:00
this . # latchOutputFailure (
2026-10-11 07:22:50 +08:00
"JavaScript worker control frame is unterminated or oversized" ,
);
} catch ( cause : unknown ) {
2026-10-20 18:38:00 +08:00
this . # latchOutputFailure ( message ( cause ));
2026-10-11 07:22:50 +08:00
}
};
constructor (
readonly limits : JavaScriptSourceAnalysisLimits = DEFAULTS ,
readonly launcher : typeof spawnOwnedProviderProcess = spawnOwnedProviderProcess ,
sourceCount? : number ,
) {
2026-10-19 13:12:25 +08:00
this . # runtime = new OwnedWorkerRuntime ({
rootPrefix : "rea-javascript-analysis-" ,
launcher ,
supervisorOptions : {
captureStdout : false ,
maxDiagnosticBytes : 256 * 1024 ,
2026-10-20 18:38:00 +08:00
onDiagnostic : ( event ) => this . # observeProcessDiagnostic ( event ),
2026-10-19 13:12:25 +08:00
},
2026-10-20 18:38:00 +08:00
onInputFailure : ( cause ) => this . # latchInputFailure ( cause ),
2026-10-19 13:12:25 +08:00
});
2026-10-11 07:22:50 +08:00
if (
! Number . isSafeInteger ( limits . heapMb ) ||
2026-10-22 02:44:14 +08:00
limits . heapMb < JAVASCRIPT_WORKER_HEAP_MIB_MINIMUM ||
limits . heapMb > JAVASCRIPT_WORKER_HEAP_MIB_MAXIMUM ||
2026-10-11 07:22:50 +08:00
! Number . isSafeInteger ( limits . timeoutMs ) ||
limits . timeoutMs <= 0 ||
limits . timeoutMs > 2 _147_483_647
)
throw new RangeError (
"JavaScript worker requires valid heap and execution limits" ,
);
if (
sourceCount !== undefined &&
( ! Number . isSafeInteger ( sourceCount ) || sourceCount < 0 )
)
throw new RangeError (
"JavaScript worker source count must be a nonnegative safe integer" ,
);
this . # sourcesRemaining = sourceCount ;
2026-10-11 07:20:00 -04:00
this . # processMemoryLimitBytes = javaScriptWorkerMemoryLimitBytes (
limits . heapMb ,
);
2026-10-21 19:01:20 +08:00
this . # refreshResultBudget ();
}
/**
* Whether this idle process can analyze another application. A failed,
* cancelled, or still-open job must not be reused.
*/
canReuse () : boolean {
return (
this . # ready &&
this . # activeJobId === undefined &&
this . # primaryFailure === undefined &&
this . # lastFailure === undefined &&
this . # frames . length === 0 &&
this . # pending === "" &&
this . # runtime . inputFailure === undefined &&
! this . # runtime . hasPendingCleanup &&
this . # runtime . supervisor !== undefined
);
}
/**
* Reset per-analysis admission for one idle process. The worker heap limit
* stays the one selected at launch; callers must not reuse across limits.
*/
prepareForAnotherAnalysis ( sourceCount : number ) : boolean {
if ( ! this . canReuse ()) return false ;
if ( ! Number . isSafeInteger ( sourceCount ) || sourceCount < 0 )
throw new RangeError (
"JavaScript worker source count must be a nonnegative safe integer" ,
);
this . # sourcesRemaining = sourceCount ;
this . # estimatedRetainedBytes = 0 ;
this . # parentCapacityExhausted = false ;
this . # applicationProjectionFailure = undefined ;
this . # refreshResultBudget ();
return true ;
}
2026-10-22 00:15:25 +08:00
/** Keep the limit and usage in one V8 observation for every admission path. */
# remainingParentHeapBytes () : number {
const { heap_size_limit , used_heap_size } = getHeapStatistics ();
return heap_size_limit - used_heap_size ;
}
2026-10-21 19:01:20 +08:00
/**
* Reserve host-side control, schema/Evidence work, and a maximum record's
* transient copies. Admission also accounts for retained earlier files.
*/
# refreshResultBudget () : void {
2026-10-11 07:22:50 +08:00
this . # resultBudgetBytes = Math . max (
0 ,
Math . floor (
2026-10-22 00:15:25 +08:00
( this . # remainingParentHeapBytes () -
2026-10-11 07:22:50 +08:00
24 * JAVASCRIPT_ANALYSIS_TRANSFER_RECORD_BYTES ) /
2 ,
),
);
}
/** Analyze inert source; hard worker failure does not terminate this process. */
async analyze (
file : JavaScriptArtifactFile ,
2026-10-17 21:57:31 +08:00
capacity : JavaScriptSemanticProjectionCapacity ,
2026-10-11 07:22:50 +08:00
signal? : AbortSignal ,
2026-10-24 02:34:27 +08:00
onPhase ?: ( phase : string ) => Promise < void >,
2026-10-11 07:22:50 +08:00
) : Promise <
Result < JavaScriptAnalyzedSource , JavaScriptSourceAnalysisFailure >
> {
let javascript : JavaScriptStaticAnalysis | null = null ;
let module : JavaScriptModuleSemanticIr | null = null ;
let projection : JavaScriptSemanticFileProjection | null = null ;
const id = randomUUID ();
this . # applicationProjectionFailure = undefined ;
this . # parentCapacityExhausted = false ;
if ( this . # sourcesRemaining !== undefined )
this . # sourcesRemaining = Math . max ( 0 , this . # sourcesRemaining - 1 );
let deadline : ProviderStartupDeadline | undefined ;
let result : Result <
JavaScriptAnalyzedSource ,
JavaScriptSourceAnalysisFailure
> ;
try {
2026-10-20 18:38:00 +08:00
if ( signal ? . aborted === true )
throw new AnalysisCancelledError ( OPERATION , { cause : signal.reason });
2026-10-11 07:22:50 +08:00
if ( ! file . text . included || file . kind !== "javascript" )
throw new TypeError (
"JavaScript analysis worker requires included JavaScript text" ,
);
// Host ownership preparation is outside the parser execution deadline.
await prepareProcessOwnershipInspection ( signal );
2026-10-19 13:12:25 +08:00
await OwnedWorkerRuntime . retryPending ();
2026-10-11 07:22:50 +08:00
deadline = new ProviderStartupDeadline ( this . limits . timeoutMs , signal );
2026-10-20 18:38:00 +08:00
this . # deadline = deadline ;
2026-10-11 07:22:50 +08:00
await this . # start ( deadline );
2026-10-19 13:12:25 +08:00
const rootPath = this . # runtime . rootPath ;
const supervisor = this . # runtime . supervisor ;
if ( rootPath === undefined || supervisor === undefined )
2026-10-11 07:22:50 +08:00
throw new TypeError ( "JavaScript worker resources were not acquired" );
if ( this . # frames . length !== 0 || this . # pending !== "" )
throw new AnalysisOutputError (
OPERATION ,
"JavaScript worker has unsolicited or incomplete control frames" ,
);
supervisor . resetOutput ();
this . # phase = "source-transfer" ;
2026-10-19 13:12:25 +08:00
await writeFile ( join ( rootPath , ` ${ id } .source` ), file . text . value , {
2026-10-11 07:22:50 +08:00
flag : "wx" ,
mode : 0o600 ,
signal : deadline.signal ,
});
const { text : _text , ... metadata } = file ;
let offeredResultBudget = 0 ;
const request = javaScriptWorkerRequestSchema . parse ({
id ,
2026-10-17 21:57:31 +08:00
node_budget : capacity.nodes ,
unknown_budget : capacity.unknowns ,
2026-10-11 07:22:50 +08:00
text_sha256 : createHash ( "sha256" ). update ( file . text . value ). digest ( "hex" ),
file : metadata ,
});
const frame = ` ${ JSON . stringify ( request ) } \ n` ;
if ( Buffer . byteLength ( frame ) > DEFAULTS . protocolBytes )
throw new AnalysisResourceConstraintError (
OPERATION ,
"transport" ,
2026-10-22 02:44:14 +08:00
`Source identity for ${ file . path } exceeds the ${ String ( DEFAULTS . protocolBytes ) } -byte worker request ceiling. This release has no runtime override.` ,
2026-10-11 07:22:50 +08:00
{ worker_request_bytes : DEFAULTS.protocolBytes },
2026-10-22 02:44:14 +08:00
{
remediationAction : `This release has no runtime override for the ${ String ( DEFAULTS . protocolBytes ) } -byte JavaScript worker request ceiling. Raising max_heap_mb does not raise it. Repeating the unchanged request fails the same way.` ,
},
2026-10-11 07:22:50 +08:00
);
const stdin = supervisor . launch . process . stdin ;
if ( stdin === undefined || stdin === null )
throw new TypeError ( "JavaScript worker has no writable request stream" );
2026-10-20 18:38:00 +08:00
this . # activeJobId = id ;
this . # activeJobPhase = this . # phase ;
2026-10-20 19:05:00 +08:00
this . # staticResponseId = undefined ;
this . # terminalResponseId = undefined ;
2026-10-11 07:22:50 +08:00
await new Promise < void >(( resolve , reject ) =>
stdin . write ( frame , ( error ) =>
error === null || error === undefined ? resolve () : reject ( error ),
),
);
for (;;) {
const response = await this . # next ( deadline , file . path );
if ( response . kind === "ready" || response . id !== id )
throw new AnalysisOutputError (
OPERATION ,
"JavaScript worker response names a different source job" ,
);
if ( response . kind === "phase" ) {
this . # phase = response . phase ;
2026-10-24 02:34:27 +08:00
await onPhase ? .( response . phase );
2026-10-11 07:22:50 +08:00
continue ;
}
if ( response . kind === "static" ) {
if ( javascript !== null )
throw new AnalysisOutputError (
OPERATION ,
"JavaScript worker emitted a duplicate static checkpoint" ,
);
javascript = await this . # readCheckpoint ( id , file . path , response );
if ( this . # parentCapacityExhausted )
throw new AnalysisResourceConstraintError (
OPERATION ,
"memory" ,
`The parent's result capacity was exhausted after retaining static facts for ${ file . path } ; remaining sources were not analyzed` ,
{
... this . # limits (),
parent_result_budget_bytes : this. # resultBudgetBytes ,
parent_result_budget_exhausted : true ,
},
2026-10-24 02:34:27 +08:00
{
remediationAction : JAVASCRIPT_PARENT_RESULT_BUDGET_REMEDIATION ,
},
2026-10-11 07:22:50 +08:00
);
offeredResultBudget =
this . # applicationProjectionFailure !== undefined &&
this . # sourcesRemaining !== undefined &&
this . # sourcesRemaining > 0
? 0
: this. # remainingResultBudget ();
const admission = javaScriptWorkerAdmissionSchema . parse ({
id ,
semantic_result_budget_bytes : offeredResultBudget ,
});
await new Promise < void >(( resolve , reject ) =>
stdin . write ( ` ${ JSON . stringify ( admission ) } \ n` , ( error ) =>
error === null || error === undefined ? resolve () : reject ( error ),
),
);
continue ;
}
if ( response . kind === "failure" ) {
2026-10-20 18:38:00 +08:00
if (
this . # primaryFailure ? . kind === "protocol" &&
this . # primaryFailure . id === response . id
)
this . # primaryFailure = undefined ;
2026-10-11 07:22:50 +08:00
if ( response . reason === "resource" ) {
result = err ({
javascript ,
module ,
projection ,
2026-10-20 18:38:00 +08:00
error : this. # protocolFailure ( file . path , response , this . # phase ),
2026-10-11 07:22:50 +08:00
});
break ;
}
2026-10-20 18:38:00 +08:00
throw this . # protocolFailure ( file . path , response , this . # phase );
2026-10-11 07:22:50 +08:00
}
if ( javascript === null )
throw new AnalysisOutputError (
OPERATION ,
"JavaScript worker completed before a static checkpoint" ,
);
if ( response . descriptor === null ) {
result = ok ({ javascript , module : null , projection : null });
break ;
}
const descriptor = response . descriptor ;
// JSON bytes alone omit object/array overhead and the later graph/Evidence
// sealing/serialization copies. Reserve a conservative expansion budget.
const moduleEstimate = javaScriptTransferRetentionBytes (
descriptor . module_bytes ,
descriptor . module_records ,
);
const graphEstimate = javaScriptTransferRetentionBytes (
descriptor . graph_bytes ,
descriptor . graph_records ,
);
const remaining = Math . min (
offeredResultBudget ,
this . # remainingResultBudget (),
);
if ( moduleEstimate > remaining ) {
result = err ({
javascript ,
module : null ,
projection : null ,
error : new AnalysisResourceConstraintError (
OPERATION ,
"memory" ,
`Completed module facts for ${ file . path } exceed the parent's remaining result budget; static facts were retained` ,
{
... this . # limits (),
parent_result_budget_bytes : this. # resultBudgetBytes ,
2026-10-12 02:17:38 +08:00
parent_available_result_budget_bytes : remaining ,
2026-10-11 07:22:50 +08:00
module_transfer_bytes : descriptor.module_bytes ,
2026-10-12 02:17:38 +08:00
module_retention_estimate_bytes : moduleEstimate ,
},
{
2026-10-22 02:44:14 +08:00
remediationAction : `Use the retained static facts or analyze fewer source files. ${ JAVASCRIPT_PARENT_RESULT_BUDGET_REMEDIATION } ` ,
2026-10-11 07:22:50 +08:00
},
),
});
break ;
}
const retainGraph = moduleEstimate + graphEstimate <= remaining ;
this . # phase = "result-admission" ;
const decoded = await readJavaScriptAnalysisTransfer (
2026-10-19 13:12:25 +08:00
join ( rootPath , ` ${ id } .records` ),
2026-10-11 07:22:50 +08:00
descriptor ,
file ,
deadline . signal ,
2026-10-17 21:57:31 +08:00
retainGraph ? capacity : null ,
2026-10-11 07:22:50 +08:00
);
module = decoded . module ;
projection = decoded . projection ;
this . # estimatedRetainedBytes +=
moduleEstimate + ( retainGraph ? graphEstimate : 0 );
result = retainGraph
? ok ({ javascript , module , projection : decoded.projection })
: err ({
javascript ,
module ,
projection ,
error : new AnalysisResourceConstraintError (
OPERATION ,
"memory" ,
`Semantic graph for ${ file . path } exceeds the parent's remaining result budget; static and module facts were retained` ,
{
... this . # limits (),
parent_result_budget_bytes : this. # resultBudgetBytes ,
2026-10-12 02:17:38 +08:00
parent_available_result_budget_bytes : remaining ,
module_retention_estimate_bytes : moduleEstimate ,
graph_retention_estimate_bytes : graphEstimate ,
2026-10-11 07:22:50 +08:00
graph_transfer_bytes : descriptor.graph_bytes ,
graph_records : descriptor.graph_records ,
},
2026-10-12 02:17:38 +08:00
{
2026-10-22 02:44:14 +08:00
remediationAction : `Use the retained static and module facts or analyze fewer source files. ${ JAVASCRIPT_PARENT_RESULT_BUDGET_REMEDIATION } ` ,
2026-10-12 02:17:38 +08:00
},
2026-10-11 07:22:50 +08:00
),
});
break ;
}
2026-10-11 07:20:00 -04:00
// An allocation failure may surface as a protocol failure or be caught
// inside the worker; any reached limit makes this file's result partial.
2026-10-20 18:38:00 +08:00
this . # observeMemoryLimit ();
if ( this . # primaryFailure ? . kind === "memory" )
throw this . # projectPrimaryFailure ( file . path );
2026-10-11 07:22:50 +08:00
} catch ( cause : unknown ) {
2026-10-11 07:36:39 +08:00
const error = this . # failure ( cause , file . path , deadline , signal );
2026-10-20 19:13:06 +08:00
if (
this . # primaryFailure !== undefined &&
deadline ? . interruption === "cancelled"
)
error . retainSecondaryFailure (
new AnalysisCancelledError ( OPERATION , {
cause : deadline.signal.reason ,
capturedOutput : this. # captured (),
}),
);
2026-10-11 07:22:50 +08:00
this . # lastFailure = error ;
// Complete checkpoints remain readable after a child crash or cancellation.
const cleanup = await this . # stop ( error );
if (
cleanup === undefined &&
javascript === null &&
2026-10-19 13:12:25 +08:00
this . # runtime . rootPath !== undefined
2026-10-11 07:22:50 +08:00
) {
try {
javascript = await this . # readCheckpoint ( id , file . path );
} catch ( checkpointCause : unknown ) {
void checkpointCause ;
}
}
result = err ({ error : cleanup ?? error , javascript , module , projection });
} finally {
deadline ? . dispose ();
2026-10-20 18:38:00 +08:00
if ( this . # deadline === deadline ) this . # deadline = undefined ;
if ( this . # activeJobId === id ) this . # activeJobId = undefined ;
2026-10-11 07:22:50 +08:00
}
if ( result . ok ) this . # lastFailure = undefined ;
else if ( ! ( result . error . error instanceof ProviderCleanupError ))
this . # lastFailure = result . error . error ;
// Child stop must be verified before temporary files are released. On
// cleanup uncertainty, close retains this owner for a later retry.
2026-10-19 13:12:25 +08:00
const rootPath = this . # runtime . rootPath ;
if ( rootPath !== undefined && ! this . # runtime . hasPendingCleanup ) {
2026-10-11 07:22:50 +08:00
try {
for ( const suffix of [ "source" , "static.json" , "records" ])
2026-10-19 13:12:25 +08:00
await rm ( join ( rootPath , ` ${ id } . ${ suffix } ` ), { force : true });
2026-10-11 07:22:50 +08:00
} catch ( cause : unknown ) {
2026-10-19 13:12:25 +08:00
this . # runtime . retainForCleanup ();
const error = this . # cleanupError ( message ( cause ));
this . # runtime . bindCleanupFailure ( error );
2026-10-24 02:56:52 +08:00
result = err ({
2026-10-11 07:22:50 +08:00
javascript ,
module ,
projection ,
2026-10-19 13:12:25 +08:00
error ,
2026-10-11 07:22:50 +08:00
});
}
}
2026-10-26 17:26:16 +08:00
if ( result . ok && signal ? . aborted === true ) {
const error = new AnalysisCancelledError ( OPERATION , {
cause : signal.reason ,
capturedOutput : this. # captured (),
});
this . # lastFailure = error ;
result = err ({ error , javascript , module , projection });
}
2026-10-11 07:22:50 +08:00
if ( this . # applicationProjectionFailure !== undefined )
return result . ok
? ok ({
... result . value ,
applicationProjectionFailure : this. # applicationProjectionFailure ,
})
: err ({
... result . error ,
applicationProjectionFailure : this. # applicationProjectionFailure ,
});
return result ;
}
/** Verify process cleanup, then release the private runtime; failed close is retryable. */
async close () : Promise < void > {
2026-10-19 13:12:25 +08:00
try {
await this . # runtime . close ();
this . # ready = false ;
this . # resetState ();
} catch ( cause : unknown ) {
if ( cause instanceof OwnedWorkerRuntimeCleanupError ) {
const error = this . # cleanupError (
cause . message ,
this . # lastFailure ,
cause . cleanupResources ,
cause . processId ,
);
cause . bindCleanupFailure ( error );
throw error ;
2026-10-11 07:22:50 +08:00
}
2026-10-19 13:12:25 +08:00
throw cause ;
2026-10-11 07:22:50 +08:00
}
}
async # start ( deadline : ProviderStartupDeadline ) : Promise < void > {
2026-10-19 13:12:25 +08:00
if ( this . # runtime . supervisor !== undefined && this . # ready ) return ;
2026-10-11 07:22:50 +08:00
this . # phase = "startup" ;
this . # heapLimitBytes = 0 ;
const environment : NodeJS.ProcessEnv = {
... process . env ,
UV_THREADPOOL_SIZE : "1" ,
};
delete environment . NODE_OPTIONS ;
2026-10-19 13:12:25 +08:00
await this . # runtime . start (
2026-10-11 07:22:50 +08:00
{
2026-10-19 13:12:25 +08:00
command : process.execPath ,
arguments : [
`--max-old-space-size= ${ String ( this . limits . heapMb ) } ` ,
"--max-semi-space-size=8" ,
"--v8-pool-size=1" ,
"--expose-gc" ,
"--stack_size=2048" ,
fileURLToPath ( import . meta . resolve ( "#javascript-analysis-worker" )),
],
runId : ` ${ this . # runId } - ${ randomUUID () } ` ,
hostEnvironment : environment ,
stdin : "pipe" ,
...( this . # processMemoryLimitBytes === null
? {}
: { memoryLimitBytes : this. # processMemoryLimitBytes }),
signal : deadline.signal ,
},
async () => {
this . # runtime . onStdout ( this . # onStdout );
const ready = await this . # next ( deadline , "worker startup" );
if ( ready . kind !== "ready" )
throw new AnalysisOutputError (
OPERATION ,
"JavaScript worker emitted facts before its startup handshake" ,
);
if (( ready . heap_limit_bytes === null ) === WORKER_HEAP_FLAGS_ENFORCED )
throw new AnalysisOutputError (
OPERATION ,
"JavaScript worker heap limit does not match the runtime that launched it" ,
);
this . # heapLimitBytes = ready . heap_limit_bytes ?? 0 ;
this . # ready = true ;
2026-10-11 07:22:50 +08:00
},
);
}
async # next (
deadline : ProviderStartupDeadline ,
path : string ,
) : Promise < JavaScriptWorkerMessage > {
for (;;) {
2026-10-20 18:38:00 +08:00
if ( deadline . interruption !== undefined ) {
const failure = this . # interruptionFailure ( path , deadline );
throw failure ;
}
this . # observeMemoryLimit ();
2026-10-19 13:12:25 +08:00
const inputFailure = this . # runtime . inputFailure ;
2026-10-20 18:38:00 +08:00
if ( inputFailure !== undefined ) this . # latchInputFailure ( inputFailure );
if (
this . # primaryFailure !== undefined &&
this . # primaryFailure . kind !== "exit" &&
this . # primaryFailure . kind !== "protocol"
)
throw this . # projectPrimaryFailure ( path );
2026-10-11 07:22:50 +08:00
const frame = this . # frames . shift ();
if ( frame !== undefined ) return frame ;
2026-10-19 13:12:25 +08:00
const supervisor = this . # runtime . supervisor ;
const snapshot = supervisor ? . snapshot ();
2026-10-11 07:22:50 +08:00
if (
2026-10-20 18:38:00 +08:00
supervisor !== undefined &&
2026-10-11 07:22:50 +08:00
snapshot !== undefined &&
( snapshot . exitCode !== undefined || snapshot . signal !== undefined )
) {
2026-10-20 18:38:00 +08:00
// Exit does not complete the protocol: descendants may still hold its pipes
// or write a final response. The operation deadline bounds this wait.
const outcome = await this . # waitForFrameOrOutputClose (
supervisor ,
deadline ,
);
if ( outcome === "interrupted" ) {
const completedFrame = this . # frames . shift ();
if ( completedFrame !== undefined ) return completedFrame ;
throw this . # interruptionFailure ( path , deadline );
}
if ( outcome === "failure" ) {
const bufferedFrame = this . # frames . shift ();
if ( bufferedFrame !== undefined ) return bufferedFrame ;
throw this . # selectedWorkerFailure ( path );
}
const finalFrame = this . # frames . shift ();
if ( finalFrame !== undefined ) return finalFrame ;
if ( outcome === "frame" ) continue ;
throw this . # selectedWorkerFailure ( path );
2026-10-11 07:22:50 +08:00
}
await waitForAbortableDelay ( 10 , deadline . signal );
}
}
async # readCheckpoint (
id : string ,
sourcePath : string ,
expected ?: { readonly bytes : number ; readonly sha256 : string },
) : Promise < JavaScriptStaticAnalysis > {
2026-10-19 13:12:25 +08:00
const rootPath = this . # runtime . rootPath ;
if ( rootPath === undefined )
2026-10-11 07:22:50 +08:00
throw new TypeError ( "JavaScript checkpoint has no owned runtime" );
2026-10-19 13:12:25 +08:00
const path = join ( rootPath , ` ${ id } .static.json` );
2026-10-11 07:22:50 +08:00
const bytes = ( await stat ( path )). size ;
if (
bytes > DEFAULTS . checkpointBytes ||
( expected !== undefined && expected . bytes !== bytes )
)
throw new AnalysisOutputError (
OPERATION ,
"JavaScript static checkpoint byte count exceeds or differs from its declared budget" ,
);
2026-10-22 00:15:25 +08:00
if ( bytes * 16 + 4 * MIB > this . # remainingParentHeapBytes () - 64 * MIB )
2026-10-11 07:22:50 +08:00
throw new AnalysisResourceConstraintError (
OPERATION ,
"memory" ,
"The parent's remaining heap cannot safely decode the completed JavaScript static checkpoint" ,
{ ... this . # limits (), checkpoint_bytes : bytes },
2026-10-22 02:44:14 +08:00
{
remediationAction : JAVASCRIPT_PARENT_CHECKPOINT_HEAP_REMEDIATION ,
},
2026-10-11 07:22:50 +08:00
);
const text = await readFile ( path , "utf8" );
if (
expected !== undefined &&
createHash ( "sha256" ). update ( text ). digest ( "hex" ) !== expected . sha256
)
throw new AnalysisOutputError (
OPERATION ,
"JavaScript static checkpoint digest does not match" ,
);
const value : unknown = JSON . parse ( text );
const facts = javaScriptWorkerStaticAnalysisSchema . parse ( value );
const remaining = this . # remainingTotalResultBudget ();
2026-10-12 02:17:38 +08:00
const retainedBytes = bytes * STATIC_CHECKPOINT_RETENTION_EXPANSION ;
2026-10-11 07:22:50 +08:00
this . # parentCapacityExhausted = retainedBytes > remaining ;
this . # estimatedRetainedBytes += retainedBytes ;
const expandedBytes = estimateJavaScriptStaticApplicationProjection (
facts ,
bytes ,
sourcePath ,
);
2026-10-12 02:17:38 +08:00
const availableResultBudget = this . # remainingResultBudget ();
if ( expandedBytes <= availableResultBudget )
2026-10-11 07:22:50 +08:00
this . # estimatedRetainedBytes += expandedBytes ;
else
this . # applicationProjectionFailure = new AnalysisResourceConstraintError (
OPERATION ,
"memory" ,
`Expanded application relationships for ${ sourcePath } exceed the parent's result budget; complete static facts were retained on the file node` ,
{
parent_result_budget_bytes : this. # resultBudgetBytes ,
2026-10-12 02:17:38 +08:00
parent_available_result_budget_bytes : availableResultBudget ,
2026-10-11 07:22:50 +08:00
application_projection_required_bytes : expandedBytes ,
},
2026-10-12 02:17:38 +08:00
{
2026-10-22 02:44:14 +08:00
remediationAction : `Read the complete static facts on the file node or analyze fewer source files. Expanded application relationships exceed the parent result budget. ${ JAVASCRIPT_PARENT_RESULT_BUDGET_REMEDIATION } ` ,
2026-10-12 02:17:38 +08:00
},
2026-10-11 07:22:50 +08:00
);
return facts ;
}
# limits() {
return {
2026-10-11 07:20:00 -04:00
...( WORKER_HEAP_FLAGS_ENFORCED
? {
worker_old_space_mb : this.limits.heapMb ,
...( this . # heapLimitBytes === 0
? {}
: { worker_heap_limit_bytes : this. # heapLimitBytes }),
}
: // Bun enforces no worker heap limit; report it as unavailable.
{ worker_heap_limit_bytes : null }),
...( this . # processMemoryLimitBytes === null
2026-10-11 07:22:50 +08:00
? {}
2026-10-11 07:20:00 -04:00
: {
worker_process_memory_limit_bytes : this. # processMemoryLimitBytes ,
}),
2026-10-11 07:22:50 +08:00
worker_timeout_ms : this.limits.timeoutMs ,
};
}
2026-10-11 07:20:00 -04:00
# memoryLimitReached () : boolean {
2026-10-19 13:12:25 +08:00
return this . # runtime . supervisor ? . launch . process . memoryLimitReached === true ;
2026-10-11 07:20:00 -04:00
}
# memoryFailure ( path : string ) : AnalysisResourceConstraintError {
return new AnalysisResourceConstraintError (
OPERATION ,
"memory" ,
2026-10-22 02:44:14 +08:00
`Owned JavaScript worker reached its ${ this . # processMemoryLimitBytes === null ? "" : ` ${ String ( this . # processMemoryLimitBytes ) } -byte ` } process memory limit while analyzing ${ path } during ${ this . # phase } . That limit is max_heap_mb ${ String ( this . limits . heapMb ) } MiB plus a 512 MiB Bun runtime reserve` ,
2026-10-11 07:20:00 -04:00
this . # limits (),
2026-10-22 02:44:14 +08:00
{
capturedOutput : this. # captured (),
2026-10-25 09:45:26 +08:00
limit : analysisSettingLimit (
"javascript.max_heap_mb" ,
this . limits . heapMb ,
undefined ,
),
2026-10-22 02:44:14 +08:00
remediationAction : javaScriptWorkerHeapRemediation ( this . limits . heapMb ),
},
2026-10-11 07:20:00 -04:00
);
}
2026-10-11 07:22:50 +08:00
# remainingResultBudget () : number {
2026-10-12 02:17:38 +08:00
// One sequential worker produces one next checkpoint at a time. Reserve
// its maximum protocol size at the same retained expansion used on receipt;
// earlier checkpoints are already charged to estimatedRetainedBytes.
// Reserving every unexamined source's maximum would reject useful results
// merely because many small files remain. Actual retained growth and live
// heap capacity are checked again for every checkpoint and transfer.
const checkpointReserve =
DEFAULTS . checkpointBytes * STATIC_CHECKPOINT_RETENTION_EXPANSION ;
2026-10-11 07:22:50 +08:00
const staticReserve =
this . # sourcesRemaining !== undefined && this . # sourcesRemaining > 0
2026-10-12 02:17:38 +08:00
? checkpointReserve
2026-10-11 07:22:50 +08:00
: 0 ;
return Math . max ( 0 , this . # remainingTotalResultBudget () - staticReserve );
}
# remainingTotalResultBudget () : number {
return Math . max (
0 ,
Math . min (
this . # resultBudgetBytes - this . # estimatedRetainedBytes ,
2026-10-22 00:15:25 +08:00
this . # remainingParentHeapBytes () -
2026-10-11 07:22:50 +08:00
24 * JAVASCRIPT_ANALYSIS_TRANSFER_RECORD_BYTES ,
),
);
}
# captured() {
2026-10-19 13:12:25 +08:00
const snapshot = this . # runtime . supervisor ? . snapshot ();
2026-10-20 18:38:00 +08:00
const supervisor = this . # runtime . supervisor ;
2026-10-11 07:22:50 +08:00
return {
stdout : "" ,
stderr : snapshot?.stderr.text ?? "" ,
2026-10-20 18:38:00 +08:00
truncated :
snapshot?.diagnosticTruncated === true ||
2026-10-20 19:05:00 +08:00
snapshot ? . outputIncomplete === true ||
2026-10-20 18:38:00 +08:00
( supervisor !== undefined && ! supervisor . observedOutputClose ()),
2026-10-11 07:22:50 +08:00
stderr_bytes : snapshot?.stderr.observedBytes ?? 0 ,
exit_code : snapshot?.exitCode ?? null ,
signal : snapshot?.signal ?? null ,
};
}
2026-10-20 18:38:00 +08:00
# observeProcessDiagnostic ( event : ProviderProcessDiagnostic ) : void {
if ( this . # deadline ? . interruption !== undefined ) return ;
this . # observeMemoryLimit ();
2026-10-20 19:05:00 +08:00
if ( event . type === "output-close" ) {
if (
this . # deadline !== undefined &&
( this . # activeJobId === undefined ||
this . # terminalResponseId !== this . # activeJobId )
)
this . # latchOutputFailure (
"JavaScript worker closed output before the operation received its terminal response" ,
);
return ;
}
2026-10-20 18:38:00 +08:00
if (
event . type === "output" &&
event . stream === "stderr" &&
this . # primaryFailure ? . kind === "exit" &&
/heap out of memory|heap limit|allocation failed/iu.test(
this . # runtime . supervisor ? . snapshot (). stderr . text ?? "" ,
)
) {
this . # primaryFailure = { ... this . # primaryFailure , heapExhaustion : true };
2026-10-22 08:20:30 +08:00
this . # frameSignal . wake ( "failure" );
2026-10-20 18:38:00 +08:00
}
if ( event . type === "exit" && ( event . code !== 0 || event . signal !== null )) {
if ( this . # primaryFailure === undefined )
this . # primaryFailure = {
kind : "exit" ,
code : event.code ,
signal : event.signal ,
heapExhaustion :
/heap out of memory|heap limit|allocation failed/iu.test(
event . snapshot . stderr . text ,
),
};
else if (
this . # primaryFailure . kind === "exit" &&
/heap out of memory|heap limit|allocation failed/iu.test(
event . snapshot . stderr . text ,
)
) {
this . # primaryFailure = {
... this . # primaryFailure ,
heapExhaustion : true ,
};
2026-10-22 08:20:30 +08:00
this . # frameSignal . wake ( "failure" );
2026-10-20 18:38:00 +08:00
}
}
}
# observeMemoryLimit () : void {
if (
this . # deadline ? . interruption === undefined &&
this . # memoryLimitReached ()
) {
if ( this . # primaryFailure === undefined )
this . # primaryFailure = { kind : "memory" };
else if ( this . # primaryFailure . kind === "exit" )
this . # primaryFailure = {
... this . # primaryFailure ,
heapExhaustion : true ,
};
if (
this . # primaryFailure ? . kind === "memory" ||
this . # primaryFailure ? . kind === "exit"
)
2026-10-22 08:20:30 +08:00
this . # frameSignal . wake ( "failure" );
2026-10-20 18:38:00 +08:00
}
}
# observeJobFrame ( frame : JavaScriptWorkerMessage ) : void {
if (
this . # deadline ? . interruption !== undefined ||
this . # activeJobId === undefined ||
frame . kind === "ready"
)
return ;
if ( frame . id !== this . # activeJobId ) return ;
if ( frame . kind === "phase" ) {
this . # activeJobPhase = frame . phase ;
return ;
}
2026-10-20 19:05:00 +08:00
if ( frame . kind === "static" ) {
this . # staticResponseId = frame . id ;
return ;
}
if ( frame . kind === "complete" ) {
if ( this . # staticResponseId !== frame . id ) {
if ( this . # primaryFailure === undefined )
this . # primaryFailure = {
kind : "output" ,
reason : "JavaScript worker completed before a static checkpoint" ,
};
2026-10-22 08:20:30 +08:00
this . # frameSignal . wake ( "failure" );
2026-10-20 19:05:00 +08:00
return ;
}
this . # terminalResponseId = frame . id ;
return ;
}
if ( frame . kind === "failure" ) this . # terminalResponseId = frame . id ;
2026-10-20 18:38:00 +08:00
if ( frame . kind === "failure" && this . # primaryFailure === undefined )
this . # primaryFailure = {
kind : "protocol" ,
id : frame.id ,
phase : this. # activeJobPhase ,
response : frame ,
};
}
# latchOutputFailure ( reason : string ) : void {
if ( this . # deadline ? . interruption !== undefined ) return ;
if ( this . # primaryFailure === undefined )
this . # primaryFailure = { kind : "output" , reason };
2026-10-22 08:20:30 +08:00
this . # frameSignal . wake ( "failure" );
2026-10-20 18:38:00 +08:00
}
# latchInputFailure ( cause : Error ) : void {
if ( this . # deadline ? . interruption !== undefined ) return ;
if ( this . # primaryFailure === undefined )
this . # primaryFailure = { kind : "input" , cause };
2026-10-22 08:20:30 +08:00
this . # frameSignal . wake ( "failure" );
2026-10-20 18:38:00 +08:00
}
async # waitForFrameOrOutputClose (
supervisor : ProviderProcessSupervisor ,
deadline : ProviderStartupDeadline ,
) : Promise < "frame" | "failure" | "closed" | "interrupted" > {
if ( this . # frames . length !== 0 ) return "frame" ;
2026-10-22 08:20:30 +08:00
return this . # frameSignal . waitForOutputClose ( supervisor , deadline . signal );
2026-10-20 18:38:00 +08:00
}
# selectedWorkerFailure ( path : string ) : AnalysisError {
if ( this . # primaryFailure !== undefined )
return this . # projectPrimaryFailure ( path );
const snapshot = this . # runtime . supervisor ? . snapshot ();
if ( snapshot ? . exitCode !== undefined || snapshot ? . signal !== undefined )
return this . # workerExitFailure ( path , {
kind : "exit" ,
code : snapshot.exitCode ?? null ,
signal : snapshot.signal ?? null ,
heapExhaustion : false ,
});
return new ProviderAdapterError ( PROVIDER , OPERATION , {
capturedOutput : this. # captured (),
diagnostics : { source_path : path , phase : this. # phase },
});
}
# interruptionFailure (
path : string ,
deadline : ProviderStartupDeadline ,
) : AnalysisError {
if ( this . # primaryFailure !== undefined )
return this . # projectPrimaryFailure ( path );
return deadline . interruption === "timeout"
? new AnalysisTimeoutError ( OPERATION , this . limits . timeoutMs , {
2026-10-25 09:45:26 +08:00
limit : analysisSettingLimit (
"javascript.analysis_timeout_ms" ,
this . limits . timeoutMs ,
undefined ,
),
2026-10-20 18:38:00 +08:00
capturedOutput : this. # captured (),
2026-10-21 00:16:44 +08:00
userMessage : `JavaScript source analysis reached its ${ String ( this . limits . timeoutMs ) } ms per-source server/provider deadline (analysis_timeout_ms), not the MCP client deadline.` ,
remediationAction :
"Reuse accessible partial_observation Evidence to avoid recomputation; reading it is not a prerequisite for a corrected retry. Increase analysis_timeout_ms through MCP, or use rea analyze-javascript-application <input_path> --analysis-timeout-ms <milliseconds> on the CLI. Alternatively, reduce semantic limits or select exact inventoried source_paths (CLI: --source-path on that command). Omit source_paths to analyze all sources; an empty array is invalid. detail: summary reduces response size, not analysis work. Allow execution, cleanup and delivery headroom in the MCP client tool deadline; increasing that client deadline alone does not change this server budget. Resolve any reported incomplete cleanup before retrying." ,
2026-10-20 18:38:00 +08:00
})
: new AnalysisCancelledError ( OPERATION , {
cause : deadline.signal.reason ,
capturedOutput : this. # captured (),
});
}
# workerExitFailure (
path : string ,
failure : Extract < WorkerPrimaryFailure , { kind : "exit" }>,
) : AnalysisError {
2026-10-22 02:44:14 +08:00
if ( failure . heapExhaustion ) {
const recovery = javaScriptWorkerHeapExhaustionRecovery ({
heapFlagsEnforced : WORKER_HEAP_FLAGS_ENFORCED ,
heapMb : this.limits.heapMb ,
path ,
phase : this. # phase ,
});
2026-10-20 18:38:00 +08:00
return new AnalysisResourceConstraintError (
OPERATION ,
"memory" ,
2026-10-22 02:44:14 +08:00
recovery . reason ,
2026-10-20 18:38:00 +08:00
this . # limits (),
2026-10-22 02:44:14 +08:00
{
capturedOutput : this. # captured (),
2026-10-25 09:45:26 +08:00
...( WORKER_HEAP_FLAGS_ENFORCED
? {
limit : analysisSettingLimit (
"javascript.max_heap_mb" ,
this . limits . heapMb ,
undefined ,
),
}
: {}),
2026-10-22 02:44:14 +08:00
remediationAction : recovery.remediationAction ,
},
2026-10-20 18:38:00 +08:00
);
2026-10-22 02:44:14 +08:00
}
2026-10-20 18:38:00 +08:00
return new ProviderAdapterError ( PROVIDER , OPERATION , {
capturedOutput : this. # captured (),
diagnostics : {
source_path : path ,
phase : this. # phase ,
exit_code : failure.code ,
signal : failure.signal ,
},
});
}
# projectPrimaryFailure ( path : string ) : AnalysisError {
const failure = this . # primaryFailure ;
if ( failure === undefined ) return this . # selectedWorkerFailure ( path );
if ( failure . kind === "memory" ) return this . # memoryFailure ( path );
if ( failure . kind === "exit" ) return this . # workerExitFailure ( path , failure );
if ( failure . kind === "input" )
return new AnalysisOutputError (
OPERATION ,
`Owned JavaScript worker request stream failed during ${ path } ` ,
{ cause : failure.cause , capturedOutput : this. # captured () },
);
if ( failure . kind === "protocol" ) {
return this . # protocolFailure ( path , failure . response , failure . phase );
}
return new AnalysisOutputError ( OPERATION , failure . reason , {
capturedOutput : this. # captured (),
});
}
# protocolFailure (
path : string ,
response : Extract < JavaScriptWorkerMessage , { kind : "failure" }>,
phase : string ,
) : AnalysisError {
2026-10-24 02:56:52 +08:00
if ( response . reason === "io" )
return new ProviderAdapterError ( PROVIDER , OPERATION , {
capturedOutput : this. # captured (),
userMessage : `Owned JavaScript worker storage failed for ${ path } during ${ phase } . Retained observations and cleanup status are reported separately.` ,
remediationAction :
"Check the availability and permissions of REA's private temporary storage, correct the reported I/O failure, and retry. Changing the selected source is not required by this storage failure." ,
diagnostics : {
source_path : path ,
phase ,
worker_failure_reason : response.reason ,
message : response.message ,
},
});
2026-10-22 02:44:14 +08:00
if ( response . reason === "resource" ) {
const reported = response . limits ;
const limits = { ... this . # limits (), ... reported };
const transferRemediation =
javaScriptForwardedTransferRemediation ( reported );
const checkpointBytes = reported ? . checkpoint_limit_bytes ;
const checkpointRemediation =
typeof checkpointBytes === "number"
? javaScriptCheckpointCeilingRemediation ( checkpointBytes )
: undefined ;
const parentBudget =
reported ? . semantic_result_budget_bytes !== undefined ||
reported ? . parent_result_budget_bytes !== undefined ;
const remediationAction =
transferRemediation ??
checkpointRemediation ??
( parentBudget
? JAVASCRIPT_PARENT_RESULT_BUDGET_REMEDIATION
: undefined );
2026-10-20 18:38:00 +08:00
return new AnalysisResourceConstraintError (
OPERATION ,
response . resource ?? "memory" ,
response . message ,
2026-10-22 02:44:14 +08:00
limits ,
remediationAction === undefined ? undefined : { remediationAction },
2026-10-20 18:38:00 +08:00
);
2026-10-22 02:44:14 +08:00
}
2026-10-20 18:38:00 +08:00
return new AnalysisOutputError (
OPERATION ,
` ${ path } during ${ phase } : ${ response . message } ` ,
{ capturedOutput : this. # captured () },
);
}
2026-10-11 07:22:50 +08:00
# failure (
cause : unknown ,
path : string ,
deadline : ProviderStartupDeadline | undefined ,
2026-10-11 07:36:39 +08:00
signal : AbortSignal | undefined ,
2026-10-11 07:22:50 +08:00
) : AnalysisError {
2026-10-20 18:38:00 +08:00
if (
deadline ? . interruption === "cancelled" &&
cause === deadline . signal . reason
)
return this . # interruptionFailure ( path , deadline );
if (
deadline === undefined &&
signal ? . aborted === true &&
cause === signal . reason
)
return new AnalysisCancelledError ( OPERATION , {
cause : signal.reason ,
capturedOutput : this. # captured (),
});
2026-10-19 13:12:25 +08:00
if ( cause instanceof OwnedWorkerRuntimeCleanupError ) {
const error = this . # cleanupError (
cause . message ,
this . # lastFailure ,
cause . cleanupResources ,
cause . processId ,
);
cause . bindCleanupFailure ( error );
return error ;
}
2026-10-11 07:36:39 +08:00
// Ownership preparation is abortable before the worker deadline exists.
2026-10-20 18:38:00 +08:00
if ( deadline ? . interruption !== undefined )
return this . # interruptionFailure ( path , deadline );
2026-10-11 07:36:39 +08:00
if (
2026-10-20 18:38:00 +08:00
deadline === undefined &&
signal ? . aborted === true &&
! ( cause instanceof ProviderCleanupError )
2026-10-11 07:36:39 +08:00
)
2026-10-11 07:22:50 +08:00
return new AnalysisCancelledError ( OPERATION , {
2026-10-20 18:38:00 +08:00
cause : signal.reason ,
2026-10-11 07:22:50 +08:00
capturedOutput : this. # captured (),
});
2026-10-19 13:12:25 +08:00
const inputFailure = this . # runtime . inputFailure ;
if ( inputFailure !== undefined )
return new AnalysisOutputError (
OPERATION ,
`Owned JavaScript worker request stream failed during ${ this . # phase } ` ,
{ cause : inputFailure , capturedOutput : this. # captured () },
);
2026-10-11 07:20:00 -04:00
// A reached process limit explains the worker's exit, broken pipe or
// truncated frame better than the symptom it produced.
if (
this . # memoryLimitReached () &&
! ( cause instanceof AnalysisResourceConstraintError )
)
return this . # memoryFailure ( path );
2026-10-11 07:22:50 +08:00
if ( cause instanceof AnalysisError ) return cause ;
if (
cause instanceof z . ZodError ||
cause instanceof TypeError ||
cause instanceof SyntaxError
)
return new AnalysisOutputError (
OPERATION ,
` ${ path } during ${ this . # phase } : ${ message ( cause ) } ` ,
{ cause , capturedOutput : this. # captured () },
);
2026-10-22 22:41:25 +05:00
return cause instanceof RuntimeControlUnavailableError
? runtimeControlCapabilityError ( PROVIDER , OPERATION , cause )
2026-10-11 07:22:50 +08:00
: new ProviderAdapterError ( PROVIDER , OPERATION , {
cause ,
capturedOutput : this. # captured (),
diagnostics : {
source_path : path ,
phase : this. # phase ,
message : message ( cause ),
},
});
}
# cleanupError (
reason : string ,
original : AnalysisError | undefined = this . # lastFailure ,
2026-10-19 13:12:25 +08:00
cleanupResources : readonly string [] = [
this . # runId ,
...( this . # runtime . rootPath === undefined ? [] : [ this . # runtime . rootPath ]),
],
processId : number | null = this . # runtime . supervisor ? . launch . process . pid ??
null ,
2026-10-11 07:22:50 +08:00
) : ProviderCleanupError {
return new ProviderCleanupError (
PROVIDER ,
2026-10-19 13:12:25 +08:00
cleanupResources ,
2026-10-11 07:22:50 +08:00
{
reason ,
2026-10-19 13:12:25 +08:00
worker_pid : processId ,
2026-10-11 07:22:50 +08:00
...( original === undefined
? {}
: {
execution_failure : {
tag : original._tag ,
message : original.message ,
captured_output : {
stdout : original.capturedOutput?.stdout ?? "" ,
stderr : original.capturedOutput?.stderr ?? "" ,
truncated : original.capturedOutput?.truncated ?? false ,
},
},
}),
},
{
operation : OPERATION ,
2026-10-24 04:00:10 +08:00
...( original === undefined ? {} : { cleanupPrimary : original }),
2026-10-10 18:57:38 -07:00
cause : new Error (
reason ,
original === undefined ? undefined : { cause : original },
),
2026-10-11 07:22:50 +08:00
},
);
}
async # stop (
original : AnalysisError | undefined = this . # lastFailure ,
) : Promise < ProviderCleanupError | undefined > {
2026-10-19 13:12:25 +08:00
const stopped = await this . # runtime . stop ();
if ( stopped . status === "incomplete" ) {
this . # resetState ();
const error = this . # cleanupError ( stopped . reason , original );
this . # runtime . bindCleanupFailure ( error );
return error ;
2026-10-11 07:22:50 +08:00
}
2026-10-19 13:12:25 +08:00
this . # ready = false ;
2026-10-13 11:34:54 -03:00
this . # resetState ();
return undefined ;
}
# resetState () : void {
2026-10-11 07:22:50 +08:00
this . # ready = false ;
this . # frames = [];
2026-10-22 08:20:30 +08:00
this . # frameSignal . clear ();
2026-10-11 07:22:50 +08:00
this . # pending = "" ;
this . # decoder = new StringDecoder ( "utf8" );
2026-10-20 18:38:00 +08:00
this . # primaryFailure = undefined ;
2026-10-20 19:05:00 +08:00
this . # activeJobId = undefined ;
this . # staticResponseId = undefined ;
this . # terminalResponseId = undefined ;
2026-10-11 07:22:50 +08:00
}
}
const message = ( cause : unknown ) : string =>
cause instanceof Error ? cause.message : String ( cause );