The BrowserX Runtime is a well-architected orchestration layer with strong state-machine design, thorough config validation, and a clean plugin system using topological sort for activation ordering. However, several critical gaps exist: event listeners are wired but never dispatched in core classes, plugin context stubs silently discard contributions, dynamic plugin loading has path-traversal and remote-code-execution risks, and a significant portion of the test suite contains vacuous assertions that provide zero coverage value.
-
BrowserPool.eventListenerspopulated but never dispatched (resources/BrowserPool.ts:71,518-530) —addEventListener/removeEventListenerare implemented but there is noemitEventcall anywhere. Subscribers (e.g., MCP ServiceInitializer) will never receive events for acquire, release, close, or error lifecycle transitions. -
UnifiedMetricsCollector.eventListenerspopulated but never dispatched (metrics/UnifiedMetricsCollector.ts:61,472-484) — Identical issue. Listeners registered viaaddEventListenerare stored but no event is ever emitted. -
Unrestricted dynamic import of arbitrary URLs (
plugins/PluginLoader.ts:48,310) —import(moduleUrl)accepts any path includinghttp:///https://URLs with no allowlist, signature verification, or hash pinning. A compromised config file could execute arbitrary remote code. -
Path traversal in plugin manifest loading (
plugins/PluginLoader.ts:146-148) —manifest.mainfrom untrusted JSON is used to construct a file path without containment checks."main": "../../../../../../etc/passwd"would be imported without restriction. -
registerInitStep/registerShutdownStepsilently discard contributions (plugins/PluginContext.ts:252-262) — Both methods log a debug message but never store or wire the step into any pipeline. Plugin authors calling these get silent no-ops. -
forceStepdoes not cancel the underlying async operation (lifecycle/ShutdownSequence.ts:332-371) — On timeout,forceStepmarks the component STOPPED but the originalstep.execute()promise keeps running, creating races with subsequent shutdown steps. -
Prometheus label values not sanitized (
metrics/UnifiedMetricsCollector.ts:448-452) — Label values containing"or\produce malformed Prometheus output and could inject arbitrary text into the metrics stream. -
Multiple test files contain vacuous assertions —
SignalHandler.test.ts(6 tests assert nothing behavioral),EventCoordinator.test.ts:215(no-op listener test),BrowserXRuntime.test.ts:200(event listeners never fired),HealthChecker.test.ts:302(stale-cache test body is empty), stress testlifecycle-stress.test.ts:493(errorCount >= 0always true). These provide false confidence with zero coverage.
-
validateConfignever called during construction orstart()(BrowserXRuntime.ts:80-126) —ConfigValidatoris well-implemented but never invoked. Invalid configs pass silently to runtime failures. -
getStats()callsbrowserPool.getStats()twice (BrowserXRuntime.ts:683-686) — Redundant duplicate call in the same synchronous method. -
getStats()hardcodes zeros for query/connection/page stats (BrowserXRuntime.ts:688-700) — Returns0for metrics that could be fetched from live_queryEngineand_proxyRuntime. -
DAG built twice per start (
plugins/PluginManager.ts:425-479) —getActivationOrder()anddetectCycles()each build the full DAG independently. Should build once and share. -
ShutdownSequence.getExecutionOrder()relies on registration order (lifecycle/ShutdownSequence.ts:128-131) — Simply reversesthis.stepsslice rather than computing reverse topological sort of the dependency graph. Incorrect if steps registered out of dependency order. -
Signal handler double-shutdown race (
signals/SignalHandler.ts:146-149,BrowserXRuntime.ts:310-316) — Both SIGINT and SIGTERM triggershutdown(). Near-simultaneous signals race onshutdownSequence.execute()which throws if called while running. -
wireDevToolsDomain/unwireDevToolsDomainare no-op stubs (plugins/PluginManager.ts:715-731) — Only emitconsole.debug. Plugins registering DevTools domains have no actual effect. -
_priorityparameter silently ignored on middleware methods (plugins/PluginContext.ts:104,129) — BothaddRequestMiddlewareandaddResponseMiddlewareacceptprioritybut never use it. Plugins get incorrect ordering with no warning. -
emitPluginEventswallows all listener errors (plugins/PluginManager.ts:761) — Barecatch {}discards all exceptions silently. -
Timeout detection via string matching (
lifecycle/ShutdownSequence.ts:293) —err.message.includes("timed out")is fragile; any user error containing that phrase triggers force-shutdown incorrectly. -
No test for
start()failure → ERROR state path — Happy-path lifecycle is tested but no test verifies runtime behavior when a component fails during initialization. -
InitializationSequencecircular dependency test not awaited (tests/unit/InitializationSequence.test.ts:255) —assertRejects()withoutawaitmay pass silently on broken implementations.
-
LifecycleManager.transitiondoesn't record previous state in history (lifecycle/LifecycleManager.ts:98-108) —previousStateis captured but never stored, making root-cause analysis harder. -
LifecycleManager.reset()doesn't clear component registrations (lifecycle/LifecycleManager.ts:252-264) — Re-callingregisterComponentafterreset()throws "already registered". -
Shallow freeze on plugin config (
plugins/PluginContext.ts:88) —Object.freezeonly freezes top-level; nested objects remain mutable. -
Metrics server exposes no authentication (
metrics/UnifiedMetricsCollector.ts:144-179) —config.hostcan be set to0.0.0.0with no auth or IP allowlisting. -
Health check staleness window hardcoded to 10s (
metrics/HealthChecker.ts:167-170) — Ignores configurablehealthCheckInterval, using10000ms always. -
BrowserPool.generateInstanceIdusesDate.now()(resources/BrowserPool.ts:511-513) — Millisecond-resolution timestamps can collide under load.crypto.randomUUID()alone is sufficient. -
exportJSONstuffs histogram data into labels (metrics/UnifiedMetricsCollector.ts:383-389) — Breaks the type contract ofRecord<string, string>. -
mergeConfigshallow-mergesproxy.gateways(config/RuntimeConfig.ts:451-452) — Inconsistent with deep-merge ofquery.cacheandquery.sandbox. -
InitializationSequence.registerStepauto-registers unknown components (lifecycle/InitializationSequence.ts:101-103) — Bypasses deliberate registration inBrowserXRuntime.registerComponents(). -
traceCyclePathalgorithm incorrect for complex graphs (plugins/PluginManager.ts:485-523) — Path construction mixes nodes; fallback papers over it. Should use GraphXCycleErrordata instead. -
Plugin ID collision resolved by load order (
plugins/PluginManager.ts:205-209) — Non-deterministic; should throw or provide explicit resolution. -
findPluginConfigfilename-stem heuristic is fragile (plugins/PluginManager.ts:782-786) — No guarantee plugin IDs match filenames. -
closeInstancenot awaited inrelease()(resources/BrowserPool.ts:255-257) — Errors in async cleanup silently lost. -
LifecycleManager.stateHistoryusesshift()at cap (lifecycle/LifecycleManager.ts:106-108) — O(n) per push. Should use ring buffer. -
No test covers plugin activation timeout — Critical production scenario (hanging plugin stalls startup) with zero coverage.
-
Stress test asserts
maxConcurrent >= 1(tests/stress/lifecycle-stress.test.ts:209) — Always true; should assert> 1to verify parallelism. -
ConfigValidatortest swallows assertion failures (tests/unit/ConfigValidator.test.ts:445) — Usestry/catchinstead ofassertThrows.
-
Event emitter pattern duplicated ~8 times —
addEventListener/removeEventListener/emitEventcopy-pasted across every class (~160 lines). Should extractEventEmitter<T>mixin. -
console.errorused for informational logs (BrowserXRuntime.ts:493,528,561) — "Starting BrowserX Runtime..." written to stderr. Should use logger withlogLevel. -
ComponentIdopen string union under-constrained (types.ts:31) — No enforcement of"plugin:namespace:name"convention. -
previousStatevariable unused (lifecycle/LifecycleManager.ts:98) — Captured but never referenced after assignment. -
maxConcurrencyconfig defined but never used (lifecycle/InitializationSequence.ts:52,64) — Config declaresmaxConcurrency: 3but execution is strictly sequential. -
README documents
runtime.initialize()but code usesruntime.start()(README.md:28) — Stale API in documentation. -
Duplicate mock helpers across test files —
createMockPlugin,createTestPluginManageretc. copy-pasted. Should extract sharedtests/helpers/mocks.ts. -
console.login integration tests (tests/event-loop-verification.test.ts:52,154,197) — Clutters CI output. -
PluginContextImplexported fromplugins/mod.ts— Leaks implementation class; onlyPluginContextinterface should be public. -
getQueryEngine()/getProxyRuntime()returnunknown(plugins/types.ts:197-200) — Every caller must unsafely cast.
-
Declarative state-machine with explicit transition table —
LifecycleManagerdefines all valid transitions inSTATE_TRANSITIONS, making illegal transitions immediately detectable with clear error messages. Far safer than ad-hoc conditionals. -
Timeout-per-step with global budget in shutdown —
ShutdownSequencetracks both per-step and total-timeout deadlines usingMath.min(step.timeout, remaining). Production-grade shutdown orchestration. -
Promise-based waiter queue in BrowserPool — The
waitersarray withresolve/reject/timertuples eliminates polling and correctly hands instances to waiters onrelease(). -
Thorough
ConfigValidator— Checks ranges, cross-field invariants (e.g.,minInstances > maxInstances,drainTimeout > timeout), and separates errors from warnings with actionable messages. -
GraphX DAG for plugin activation — Using topological sort for plugin activation ordering correctly handles dependency chains and cycles, replacing error-prone manual ordering.
-
Disposable pattern throughout plugin system — Every contribution returns a
Disposableregistered with the plugin registry. Cleanup is automatic and predictable without manual tracking. -
Dynamic imports for workspace boundaries —
await import(...)for cross-workspace dependencies (@browserx/proxy-engine,@browserx/browser) keeps module boundaries clean and enables lazy initialization. -
Composable health check handlers —
HealthChecker.createBooleanHandlerandcreateSimpleHandlerallow declarative health registration without coupling toHealthCheckerinternals. -
Excellent dependency-ordering tests —
plugin-manager-graphx.test.tscovers diamond dependencies, 3-node cycles, linear chains, missing deps, and reverse-deactivation invariant. -
Genuine end-to-end plugin integration test —
plugins.test.tsexercises four contribution types simultaneously, verifies wiring, executes functions, deactivates, and confirms complete cleanup.
-
Implement
emitEventin BrowserPool and UnifiedMetricsCollector — These are the most impactful fixes. Wire event dispatch at lifecycle boundaries (acquire, release, close, error, metric change). -
Implement
registerInitStep/registerShutdownStepandwireDevToolsDomainin PluginContext/PluginManager — These are documented APIs that silently no-op. Per project rules, called-but-missing means implement. -
Add path containment check in PluginLoader — Verify
mainPathresolves insidepluginDir. Gate remote URL imports behind explicit config flag. -
Call
validateConfiginBrowserXRuntime.start()— The validator exists and is thorough; it just needs to be wired in. -
Fix vacuous test assertions — Prioritize SignalHandler tests, EventCoordinator listener test, HealthChecker stale-cache test, and stress test assertions. These currently provide false confidence.
-
Extract shared
EventEmitter<T>mixin — Eliminate ~160 lines of duplicated event listener boilerplate across 8+ classes. -
Sanitize Prometheus label values — Escape
"and\informatPrometheusLabelsper the exposition format spec. -
Add
AbortSignalsupport to shutdown steps — AllowforceStepto genuinely cancel timed-out operations rather than leaving orphaned promises.