feat(threads): run an isolated application in a dedicated worker thread - #2524
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements isolated applications, allowing configured applications to run in dedicated worker threads that are reachable only via Unix Domain Socket (UDS) mirrors. The changes span worker management, socket routing, and deployment validation. The review feedback identifies a style guide violation regarding the node: prefix for the path import, and a bug in server/threads/socketRouter.ts where heapShareCount is calculated inconsistently inside a loop during worker reconciliation.
5564c23 to
39d28d6
Compare
cb1kenobi
left a comment
There was a problem hiding this comment.
Isolated apps that keep tables on a shared store never run schema TTL or expiresAt eviction, because ownership is only granted for branch paths and the runtime-TTL exception skips the load path. A dedicated worker that fails to start frees its slot without waiting for shutdown, so a follow-up deploy or drop can start a replacement or delete branch files while the old worker still holds the store. Set the existing dedicated-worker TTL exception on the fromSchema and expiresAt paths, and await slot.shutdown() in the start-failure catch.
—
Reviewed 39d28d6
…ker thread (#642) Tier 2 of application isolation, first piece. An application whose root-config entry carries `isolated: true` is loaded by exactly one worker thread that loads no other application, so its process globals, `process.env` and restarts are its own. - Placement is decided per application before any of its modules are imported: a dedicated worker loads only its own application; pool workers and the main thread load only the non-isolated ones. With no worker threads at all the application fails closed instead of sharing the only thread. - `startHTTPThreads` starts one `http` worker per isolated application, numbered past the pool and sized against the total worker count; `threads.maxIsolated` (default 8) caps admission. After every root-component reload the main thread reconciles dedicated workers with the config and the component directories: starts one for a newly isolated application, stops one whose application is gone. `system_information` threads carry `application` for the dedicated ones. - A dedicated worker binds none of the shared ports (SO_REUSEPORT would hand it every application's connections); it binds only its UDS mirrors, named `app-<percent-encoded name>-<port>`, whose metadata publishes `application` and `applicationHosts` separately from certificate coverage for the proxy to route by. - `restartWorkers` takes an application scope: undefined restarts the shared pool, a name restarts that application's worker, '*' restarts all. Deploying or dropping an isolated application restarts only its worker; a shared application's deploy or drop leaves the dedicated workers running. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Co-Authored-By: GPT-5 Codex <noreply@openai.com>
39d28d6 to
f712781
Compare
|
Reviewed; no blockers found. |
Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Co-Authored-By: GPT-5 Codex <noreply@openai.com>
cb1kenobi
left a comment
There was a problem hiding this comment.
The two latest commits stop an @expiresAt-only declaration from clearing loaded TTL and re-arm cleanup when that preserved config still has a scan or eviction. Earlier blockers on dedicated-worker TTL ownership, start-failure shutdown, and isolated file-drop restart scope are already addressed at this head. No new confirmed defect remains on the changed lines.
—
Reviewed da5de67
watchDedicatedStart() removed the application's slot from the registry before awaiting slot.shutdown(), so for the whole drain window the application looked unclaimed: a concurrent reconcile would start a replacement over the old worker's still-bound UDS mirror and still-open branch stores, and a concurrent drop's `await slot.shutdown()` would not cover the draining worker before removeBranchesForApplication() deleted its storage. Await the shutdown first, then withdraw the lease under the same identity guard. A drop that withdrew the lease in the meantime is left alone, and the existing replacement check still guards the socket cleanup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
updatedAttributes() now refreshes expiresAtProperty on every live table redeclaration, so a redeclaration that drops the @expiresAt directive clears it while recordExpirationInterval stays armed. The interval then dereferenced expiresAtProperty.name and logged a TypeError once a minute for the life of the worker. Skip the sweep instead; it re-activates if the attribute comes back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review-adjudication pass at
|
cb1kenobi
left a comment
There was a problem hiding this comment.
With dynamic threads enabled, startHTTPThreads takes a branch that never assigns poolSize, so the isolated-worker reconcile is skipped entirely. An isolated: true application then gets no dedicated worker and is also skipped by every pool thread, so it loads nowhere and nothing is logged or reported as a failed component. Assign poolSize in that branch so the reconcile runs, or report the refusal there so the application fails loudly instead of disappearing. Everything else in the diff checked out, including the eight earlier review threads, which are addressed at this head.
—
Reviewed 8e74b1b
Ruling on the
|
cb1kenobi
left a comment
There was a problem hiding this comment.
No new confirmed blockers on the changed lines at this head. Earlier TTL ownership, start-failure lease, file-drop scope, and expiresAt-wipe threads are already addressed. The dynamic-threads poolSize comment is already on that path, so it is not repeated.
—
Reviewed 8e74b1b
cb1kenobi
left a comment
There was a problem hiding this comment.
Traced the isolation lifecycle paths at this head: restart-scope encoding, the failed-start lease ordering, TTL ownership and the expiresAt interval, drop scoping, and dedicated-worker ingress all hold up. The prior blocking threads are genuinely fixed rather than papered over, and the one comment already on this head is refuted by the actual placement of the poolSize assignment. No new confirmed blocking defect on changed lines.
—
Reviewed 8e74b1b
The two branches that closed out review had no unit coverage. A dedicated worker's uWS entry filter (`!cfg.socketPath`) is only reachable under HARPER_UWS_UDS, so the regression it fixed -- a dedicated worker skipping its own mirror and answering nothing -- is invisible to the default suites; the helper it replaced was deleted along with its test. Extract the rule as shouldStartUwsListenerHere() next to the other placement predicates, which all take an explicit owner so they can be asserted without a worker thread. The `|| evictionMs` disjunct in setTTLExpiration() is reachable only from a table whose eviction came from persisted metadata: every in-process declaration that sets evictionMs also sets expirationScanScheduled. The new case builds that hydrated shape directly and asserts the preserved eviction arms exactly one cleanup scan, and none when nothing was loaded to clean up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
restartWorkers() has always cleared the node's restart-required flag, which was right while every restart replaced every worker. Scoped restarts changed that: a restart aimed at one isolated application's dedicated worker leaves every pool worker on the old code, so clearing the flag there reports restartRequired: false while a component deployed with restart: false is still loaded nowhere. Clear it only when the pool is in scope (the pool sentinel or all workers). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gate's comment claimed the flag only ever stands for pool-loaded code. It does not: a deploy of an already-isolated application without a restart sets the bit too, and a later app-scoped restart of that application now leaves it set. Record that as the deliberate direction of the one-bit flag's error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Re-adjudication of all 17 review threads at
|
| Thread | Finding | Prior ruling | Verification at 74fb011cc |
|---|---|---|---|
| gemini | path import lacks the node: prefix |
fixed | holds — socketRouter.ts:22-23, databases.ts:6 |
| gemini | heapShareCount computed inside the start loop |
fixed | holds — one denominator at socketRouter.ts:239, applied to pool and incumbent isolated slots (:240-241) before any start |
| codex | isolation flip must reach restart-required detection | fixed | holds — operations.js:899-905 → requestRestartAfterDeploy, which returns early only on wasIsolated === isIsolated; both flips unit-covered |
| codex | worker-local isolatedSlots is not an authoritative admission source |
fixed | holds — operations.js:616 queries the main thread's registry under a 5 s bound and unions it with present-and-unrefused names; unavailable topology is a 503, not a pass |
| codex | presence filtered after admission spent the budget | fixed | holds — admittedIsolatedApplications() resolves presentIsolatedApplicationNames() first (socketRouter.ts:143) and filters the running set through it |
| codex + cb1kenobi | failed start frees the slot without awaiting shutdown | fixed, reopened once, re-fixed | holds — socketRouter.ts:200 awaits slot.shutdown() before the identity-guarded delete at :202 and the socket cleanup after it |
| codex | repeated startHTTPThreads() double-starts dedicated workers |
fixed | holds — seeds from isolatedSlots.keys() (:100), monotonic index via Math.max (:99), has() skip (:110) |
| cb1kenobi | schema TTL never runs on an isolated app's shared store | no-change, then fixed | the no-change ruling did not hold (already overturned in-PR); the fix does — Table.ts:7496 and :7638 both carry the ttlConfiguredByApplication && isDedicatedWorker() leg |
| cb1kenobi | isolated file drop restarts the pool | fixed | holds — operations.js:1381-1391 derives scope from live topology under the preparation lock; the !file guards now cover only config/symlink/package.json/branch removal |
| cb1kenobi + claude | @expiresAt-only declaration wipes the loaded TTL |
fixed + hardened | holds — preserveLoadedConfiguration (Table.ts:1579) skips the replace, and scheduleCleanup() is reached only when something was loaded to clean up, so no default daily timer is manufactured |
| claude | || evictionMs disjunct unexercised |
covered | holds, and the reasoning checks out — both evictionMs (:1591) and expirationScanScheduled (:1598) are assigned inside !preserveLoadedConfiguration, so only a table whose eviction came from persisted metadata reaches the disjunct; that is exactly the shape the new case builds |
| claude | no coverage for the dedicated-worker uWS entry rule | fixed | holds — shouldStartUwsListenerHere() pinned across all four quadrants |
| claude | coverage parity for runRecordExpirationEviction |
coverage ruling | holds — accepted as a non-blocking coverage judgment; the unit pins exactly one 60 s interval on initial and live declarations |
The one counter-read, re-checked independently
@cb1kenobi's dynamicThreads finding — isolated applications load nowhere on that path — was declined with a counter-read rather than a fix. That refutation is correct, and here is the check rather than the assertion:
poolSize = threadCount;is atserver/threads/socketRouter.ts:98, a sibling statement after theif (dynamicThreads) { … } else { … }construct closes at:97. It runs on both branches, so the reconcile is not skipped andpoolSlots.push(slot)is not dead.- The "start one worker in the if-branch, then fall through to the loop that starts
threadCountmore" shape is onorigin/maintoo (startHTTPThreadsthere, same structure), so this PR neither introduces nor worsens it. - No caller passes the flag:
bin/run.ts:225andbin/lite.ts:2both callstartHTTPThreadswith a single argument, matching theNo caller currently passes dynamicThreadscomment already on the branch.
Verification
npm run build— passed. Prettier andgit diff --checkclean on both changed files.unitTests/server/**/*test*.js— 932 passing, 0 failing.unitTests/components/**/*test*.js— 1641 passing, 2 pending, 1 failing:gitCredentials.test.js"grants the credential environment to the spawn that clones, and to no other", a known local-environment failure unrelated to this diff.unitTests/server/threads/isolatedApplications.test.js,expiresAtAttribute.test.js,requestRestart.test.js,threadInfoTimeout.test.js— 40 passing before the change, 41 after.- Independent pre-push review, two delta rounds on the new commits (Claude graded + Gemini). Round 21 raised one finding against the gate's comment, not its behaviour; the comment was rewritten and round 22 on
74fb011ccreturned no findings.
— Claude Opus 5
cb1kenobi
left a comment
There was a problem hiding this comment.
A pool-only restart still clears the process-wide restart-required flag, so a pending isolated deploy can report restartRequired false while its dedicated worker keeps old code. The new gate treats the pool sentinel as if it replaced every worker, but that scope skips dedicated workers that already have a slot. Clear the flag only when the restart scope is all workers (*), and update the unit case that currently expects a pool-only restart to clear the bit.
—
Reviewed 74fb011
…ry worker A pool-scoped restart replaces every pool worker and reconciles dedicated slots, but leaves an already-running dedicated worker on its old modules — so clearing the node's restart-required bit there has the same silent failure as clearing it from an application-scoped restart: get_status reports restartRequired: false while a deployed component is loaded nowhere. Clear it for all workers always, and for the pool only while no dedicated worker is running — which is every node that uses no isolated application, leaving the historic behavior unchanged there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
cb1kenobi
left a comment
There was a problem hiding this comment.
Traced the isolation lifecycle at this head: restart-scope encoding and arity fallback, the failed-start lease ordering, dedicated-worker ingress, TTL ownership and eviction arming, and deploy/drop restart scoping all hold up. Prior blocking threads are genuinely fixed rather than worked around, and the new code fails closed on unreachable, over-budget, malformed, and topology-unavailable cases. No new confirmed blocking defect on changed lines; the one open reviewer thread on manageThreads.js:679 is not repeated here.
—
Reviewed 74fb011
The restart-scope case pushes a plain object into manageThreads' shared `workers` array. Give it `recentELU` so the entry is complete on its own rather than relying on the monitor tick to backfill it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Independent adjudication pass at
|
| Finding | Ruling | Evidence at b1d5cbc5c |
|---|---|---|
dynamicThreads skips the isolated reconcile (socketRouter.ts:80) |
no change — premise does not hold | poolSize = threadCount is socketRouter.ts:98, a sibling of the if/else that closes at :97, so the dynamicThreads branch (:73–:80) falls through to it and to the reconcile at :100. origin/main carries the identical fall-through into its own for (let i = 0; i < threadCount; i++) loop, so the shape is pre-existing, not introduced here. |
A pool-only restart clears restart-required while a dedicated worker keeps old code (manageThreads.js:679) |
confirmed, fixed at 6868c4eba with the covers-every-worker rule rather than the suggested '*'-only rule |
coversEveryWorker at manageThreads.js:675 is application === '*' || (application === undefined && !workers.some((w) => w.application)). The '*'-only alternative is genuinely worse: components/operations.js:858 computes restartScope = wasIsolated && nowIsolated ? application.name : undefined, so an everyday shared deploy_component with restart: true passes the pool scope and would never clear the bit on any node. |
drop_component with restart: false reaches restartWorkers / clears a lock (raised four rounds by the graded leg) |
refuted | components/operations.js has exactly one req.restart !== true guard (:1418) whose return at :1421 is unconditional, and exactly one manageThreads.restartWorkers(...) in dropComponent at :1441, after it. branched only appends BRANCH_STORAGE_RETAINED to the message. |
| Failed dedicated start frees its slot before the worker exits | fixed | socketRouter.ts:200 awaits slot.shutdown() before the identity-guarded delete at :201–:202, and :205 re-checks isolatedSlots.has(application) after the dynamic import() so a replacement's live mirror is never unlinked. |
Topology stand-in may flake on a monitor tick (b1d5cbc5c) |
self-corrected in-round; property kept for self-containment | The stand-in is wasShutdown: true, so the restart loop's :737 filter skips it; it stands in only for what the :675 gate reads. |
One property of the restart-flag gate worth stating plainly
On a node with a running dedicated worker, deploy_component/drop_component with restart: true never clears restartRequired — those paths pass the pool scope, and the gate requires no dedicated worker. Only an all-workers restart clears it there: restart_service with no scope (decodeRestartScope returns '*' for an absent scope, manageThreads.js:1000), the watcher-driven restart at componentLoader.ts:1202, and manageThreads.js:1952.
That is the intended direction of the trade in item 9 — a stale restartRequired: true that the next restart_service clears, rather than a false reported while a deployed component is loaded nowhere. Recording it because the current item-9 wording names only the narrower "a dedicated worker the reconcile is about to stop is still counted" case; the operator-visible consequence is the broader one.
Not a finding about this code
The non-required review / review check on this head failed after 25 minutes with Prompt is too long — the review agent's own transcript capacity on a 43-file diff at --effort max, with no comment posted. Every required check is green.
No code change was warranted, so nothing was pushed and no thread state was altered.
🤖 Adjudicated with Claude Opus 5
cb1kenobi
left a comment
There was a problem hiding this comment.
Prior blocking threads on TTL ownership, failed-start lease ordering, file-drop restart scope, expiresAt config wipe, and the restart-required flag are addressed at this commit. I re-traced admission, placement, UDS binding, slot shutdown, drop/deploy restart scope, and store-maintenance ownership against the current diff and existing discussion. No new confirmed blocking defect remains on the changed lines.
—
Reviewed b1d5cbc
…gistration obligation Rebased onto main, which now creates dedicated application worker threads (#2524). "Every worker" in the registration obligation would otherwise read as the HTTP workers only, and a dedicated application worker that serves a lock() without a transport is exactly the case the obligation exists for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0182Qf1qmKDwJG52Uuu4FKFx
…gistration obligation Rebased onto main, which now creates dedicated application worker threads (#2524). "Every worker" in the registration obligation would otherwise read as the HTTP workers only, and a dedicated application worker that serves a lock() without a transport is exactly the case the obligation exists for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0182Qf1qmKDwJG52Uuu4FKFx
An application declared with
isolated: truenow runs in one dedicated HTTP worker that loads no other application, exposes an application-addressed secure UDS mirror, and can be restarted without rotating unrelated workers. Admission, placement, restart, expiration, and cleanup paths fail closed so an unsupported, over-budget, malformed, or exhausted application is reported instead of silently appearing on the shared listener.For the human reviewer
UDS-only ingress. Dedicated workers expose only application-named secure UDS mirrors. The metadata includes the application identity and optional hosts; a host is intentionally not required because application-ID routing and direct socket consumers are supported. Joining public listeners would change the proxy and security boundary.
Asynchronous readiness. Pool readiness still marks the node started while dedicated applications boot, so one application cannot delay the node. A deploy can therefore finish before the dedicated UDS appears; the integration test waits on the socket itself. Making a deploy success mean “dedicated route is serving” requires a product decision about whether one app may stall a pool restart.
Shared-store TTL ownership. Schema and runtime TTL declared by an isolated application run where that application code runs, even for a shared store already maintained by a pool owner. This guarantees eventual cleanup when only the isolated app declares the schema, but can duplicate version-guarded scans. Central ownership needs IPC for application-defined functions/configuration. The same locality means a shared
sourcedFromcache has no dedicated low-disk reclamation owner.Node-local admission. The origin rejects unreachable or over-budget isolation, while replicated peers persist desired config and may refuse locally. Concurrent deployments of different applications can both observe the final free slot; reconciliation admits one and reports the other failed. Atomic admission requires a main-thread reservation spanning configuration, installation, replication, and rollback.
Configuration semantics. Omitting
isolatedpreserves an existing true value, and isolation intent is persisted before package preparation so a failed install leaves desired topology for recovery. Reads, admission, persistence, and activation for one application are now serialized by the component preparation lock. Changing failed-deploy rollback or sticky isolation is a separate API decision.Restart and drop policy. Restart scope uses absent/
*for all workers, empty string for the pool, and an application name for one dedicated worker;scopeFallbackaccepts only the pool sentinel. A drop queries live topology under the component lock and returns 503 before mutation if topology is unavailable. The core managed-worker path is covered; whether an external replication executor can invoke this without a topology channel needs validation in the enterprise wrapper.Package boundary. Ordinary package entries are applications and obey isolation placement. Trusted built-ins still load everywhere, including a forced package replacement using a protected name. If forced protected-name deployment must support isolation, the loader needs a distinct built-in-versus-application contract.
Lifecycle fencing. The slot registry is the authoritative lease: removing a slot prevents every old generation from auto-restarting, shutdown is idempotent, and dedicated UDS replacements do not pre-start over a live fixed socket path. A start that fails or never reports ready now awaits
slot.shutdown()before withdrawing the lease, so the application is never registered-as-absent while its worker is still draining; the earlier ordering let a reconcile start a replacement over the live mirror and let a drop'sremoveBranchesForApplication()run past a worker still holding the stores. Unlinking application sockets on every worker exit was rejected because an outgoing rolling-restart worker can share the path with its replacement and would delete the replacement's live endpoint. Cleanup therefore runs on deliberate retirement or final restart exhaustion, guarded by slot identity.Restart-required is one bit, so only a restart that covers every worker clears it.
restartWorkers()cleared the node's restart-required flag on every call, which was correct while every restart replaced every worker. Restart scopes broke that in two ways: an application-scoped restart leaves the whole pool on its old code, and a pool-scoped restart leaves an already-running dedicated worker on its old modules. Either one clearing the bit reportsrestartRequired: falsewhile a deployed component is loaded nowhere. It is now cleared for all workers always, and for the pool only while no dedicated worker is running — which is every node that uses no isolated application, leaving the historic behavior unchanged there. The rejected alternative, clearing only on the all-workers sentinel, would stop an ordinary shareddeploy_componentwithrestart: truefrom ever clearing the bit (that path passes the pool scope), leaving the flag permanently on for every node. The one remaining conservative case: a dedicated worker the reconcile is about to stop is still counted, because the check reads topology before the reconcile runs. A precise answer needs a per-application signal rather than one process-wide bit.Where to look hardest. The lease ordering above has no direct unit coverage:
watchDedicatedStartandisolatedSlotsare module-private, and asserting the ordering would mean exporting both purely for a test. The integration suite exercises the happy path and the drop paths, not a hung start racing a reconcile. The two coverage threads that were open on the last head are now closed by unit tests: the dedicated worker's uWS entry rule is named asshouldStartUwsListenerHere()and pinned across all four quadrants, and the preserved-eviction gate has a differential case. One honest limit on the latter: it reachesscheduleCleanup()through theownsStoreMaintenance()leg, not thettlConfiguredByApplication && isDedicatedWorker()leg a real dedicated worker takes, so it pins the scheduling gate rather than the ownership path. The per-thread rulings for every review finding on this PR are now published as a single comment rather than as review threads: the author's pending review (pullrequestreview-5184029425, 19 comments) anchors on outdated commits, so submitting it would open 19 new threads on lines that have since moved.Implementation
isolatedas a boolean andthreads.maxIsolatedas a bounded count. Malformed values fail placement before any application import.0/1. Scheduler, data-loader, cache-source, audit, reclamation, branch, and expiration ownership follow the defining application or store.@expiresAtstate, and the isolated schema integration fixture proves physical eviction from the shared store.Verification
npm run build— passed on the final head.Prettier,
oxlint, andgit diff --checkover every changed source/test file — passed. Repository-widenpm run lintstill reports 15 warnings in files unchanged fromorigin/main.The final focused Mocha set covering activation, serialized preparation, TTL, placement, topology timeouts, UDS binding, safe-mode preloads, and deploy operations — 115 passing, 1 platform-dependent pending.
npm run test:integration -- integrationTests/components/isolated-application.test.ts— 7 passing: placement, capacity refusal, public-port exclusion, UDS routing, shared drop, file-only isolated drop, and full isolated drop. The same 7 pass withHARPER_UWS_UDS=1, including the application-named uWS mirror.The schema-TTL integration assertion failed before the TTL ownership fix (
rawTtlRecordsremained 1) and passed after it. The stale-config capacity assertion likewise failed on the rebased pre-fix head because the second real isolated worker was absent.npm run test:unit:mainreached 5,515 passing and 197 pending; 32 failures were baseline/environment failures from ancestor-checkout dependency resolution plus existing credential/path-length cases. The Windows/API baseline issues and the six current API assertions reproduce onmain.Four full independent reviews and exact remediation deltas ran across Claude, Gemini, Cursor Grok/Composer, and Harper domain adjudication. A required round-9 framing recheck compared global serialization, universal generations, config checks, and slot-owned leases; Gemini certified the slot-lease approach. On the latest whole-branch pass Gemini completed, Claude hit its weekly usage limit, and the Harper domain pass timed out; the one reported major was falsified by the unconditional
restart !== trueearly return. Two subsequent Claude/Gemini deltas accepted the TTL-preservation fix and its eviction-only hardening with no surviving new finding. Human-Review-Need remains 4 for the policy choices above.Review-adjudication pass over all 14 threads at
da5de6772: 12 rulings verified against source, 2 (codex/cb1kenobion the failed dedicated start) found not to hold and fixed here.unitTests/server/threads/isolatedApplications.test.js,threadInfoTimeout.test.js,expiresAtAttribute.test.js,requestRestart.test.js— 38 passing;integrationTests/components/isolated-application.test.ts— 7 passing;npm run build, Prettier andoxlintclean on the changed files.npm run test:unit:resources— 2486 passing, 33 pending, 3 failing. The 3 arecrud.test.js"publishes and subscribes" (2 == 1), and they reproduce on this branch's merge basebba808e74with every PR source file swapped out, so they are inherited frommain, not from this change. The same file passes at6d725818c(Sep 8), which bounds the regression to6d725818c..bba808e74.Round 19 (delta, Claude graded + Gemini,
8e74b1b5e6a8): no new finding survived. Gemini'sblocker— a lateslot.readyrejection crashing the process — is wrong on JS semantics:Promise.racesubscribes a rejection reaction to every input at call time, so the losing input's late rejection is handled, not unhandled. Verified by running the exact shape (race loses to a timeout, the input rejects 50 ms later,process.on('unhandledRejection')never fires, exit 0). The graded leg re-raised itsmajorondrop_componentwithrestart: falsefor the third round; refuted again by exact count —components/operations.jshas exactly onereq.restart !== trueguard (line 1418) with an unconditionalreturn, and exactly onemanageThreads.restartWorkers(...)call (line 1441) after it, sorestartScopeis unreachable on that path andbranchedonly affects the message text and post-restart branch removal. Both legs' comment-narrationnitis declined: it targets comments already on the branch, not this delta, and the graded leg itself exempts the delta's comments as rationale rather than narration.Round 20 (delta, Claude graded + Gemini,
1d79483c1177): no new finding. Both legs traced the predicate extraction as behaviour-preserving and the eviction case as a true differential. The graded leg re-raised itsmajorondrop_componentwithrestart: falsefor the fourth time, this round on a new premise — that a branched application bypasses the!req.restartearly return. It does not:components/operations.jshas exactly onereq.restart !== trueguard (line 1418) whosereturn(line 1422) is unconditional, the onlyrestartWorkers(...)in that function is at line 1441 after it, andbranchedonly appendsBRANCH_STORAGE_RETAINEDto the message — text that says the branch storage is kept, the opposite of the claimed lock-clearing bypass. Both legs' comment-narrationnitis declined again: it names lines already on the branch, and the graded leg itself exempts the delta's comments as rationale.Test-coverage follow-up at
1d79483c1:unitTests/resources/expiresAtAttribute.test.js13 passing,unitTests/server/threads/isolatedApplications.test.js19 passing;npm run test:unit:resources2487 passing / 33 pending / 3 failing (the same 3crud.test.js"publishes and subscribes" cases inherited frommain);unitTests/server/**/*test.*js931 passing / 0 failing;integrationTests/components/isolated-application.test.ts7/7 withHARPER_UWS_UDS=1. Both new tests were verified differentially — removing|| evictionMsfails the eviction case.Review-adjudication pass over all 17 threads at
1d79483c1→74fb011cc: every thread on the PR was already resolved, so each ruling was re-verified against source rather than trusted. Fifteen hold as written; one (cb1kenobi's schema-TTL finding, first ruled no-change) had already been overturned and fixed in-PR;cb1kenobi'sdynamicThreadscounter-read was re-checked independently and is correct (poolSize = threadCountatserver/threads/socketRouter.ts:98is a sibling of theif/elseclosing at:97,origin/maincarries the same fall-through, and neitherbin/run.ts:225norbin/lite.ts:2passes the flag). The per-thread rulings are in this comment.One defect nobody had raised turned up beside them and is fixed here:
restartWorkers()'s unconditionalresetRestartNeeded()under the new restart scopes (item 9 above). Verified differentially — removing the gate fails the new case's first assertion.At
74fb011cc:npm run buildpassed; Prettier andgit diff --checkclean on both changed files;unitTests/server/**/*test*.js932 passing / 0 failing;unitTests/components/**/*test*.js1641 passing / 2 pending / 1 failing (gitCredentials.test.jscredential-scoping, a known local-environment failure unrelated to this diff); the focused isolation/TTL/restart set 41 passing.Rounds 21 and 22 (delta, Claude graded + Gemini). Round 21 raised one finding against the new gate's comment rather than its behaviour — the comment claimed the flag only ever stands for pool-loaded code, which the isolated case contradicts. The comment was rewritten to state the trade instead. Round 22 on
74fb011cc: no findings.@cb1kenobi then raised the symmetric case on the pushed head — a pool-scoped restart also leaves a running dedicated worker on its old modules — and it holds.
6868c4ebareplaces the gate with the covers-every-worker rule in item 9 rather than the suggested all-workers-only rule; the thread has the four-cell comparison. Verified differentially: removing theworkers.some(...)clause fails the running-dedicated-worker assertion.Rounds 23–25. Round 23 flagged the test's topology stand-in as a possible monitor-tick flake; round 24 self-corrected that finding (
manageThreads.js:1328backfillsrecentELUin the same tick, before the listener reads it), so the property was kept for self-containment and only its comment was corrected. Round 25 onb1d5cbc5cran a full whole-branch review: Gemini and Cursor Grok both completed with no surviving finding; the Claude graded leg failed on its review profile (exit 1) and the domain adjudication timed out, so that round's coverage is the two outside legs rather than three.unitTests/server/**/*test*.js— 932 passing / 0 failing at each of74fb011cc,6868c4ebaandb1d5cbc5c.CI on
b1d5cbc5c: 45 pass, 3 skipping, 1 failure — every required check is green (validate, runLinter, Format Check, Unit Test v24, all six Integration Tests shards), plus the non-required v22/v26/Windows/Bun/uWS matrices, Build Harper, Docker smoke and the Next.js downstream suite. The one failure is the non-requiredreview / reviewjob, which ran 25 minutes and then exited onSDK execution error: Claude Code returned an error result: Prompt is too long— a capacity failure of the review agent's own transcript on a 43-file diff at--effort max/--max-turns 96, not a finding about this code. No comment was posted because the run never reached one.unitTests/components/**/*test*.js— 1641 passing / 2 pending / 1 failing (gitCredentials.test.jscredential-scoping, a known local-environment failure unrelated to this diff).Complexity: complicated
Changed files
Review-Coverage: authored=codex; ran=gemini,cursor-grok; blocked=claude(exit-1),domain(timeout); declined=cursor-composer; rounds=25; full=4 @ b1d5cbc
Human-Review-Need: 4 @ b1d5cbc