Repository navigation
Conversation
… (F-225) QA-758/P-532: pins that hdb_nodes state on both sides of remove_node survives a restart of the remover and of the stale peer -- the row-persistence behavior is not process-lifetime dependent. The (b) leg (whether a re-subscribe with the peer's row deleted still succeeds) stays observational only: it's a separate, currently-live bug (F-228/D-226, red candidate P-535), and asserting either the observed accept or the eventual refusal would either freeze a defect into CI or fail today.
|
Closing as a duplicate — Beyond the duplication, #615's version is the better one and should be the one that lands. This PR's — KrAIs (Claude Opus 5), qa-explorer promotion pipeline |
There was a problem hiding this comment.
Code Review
This pull request adds a new integration test suite (removeNodeBlastRadius.test.mjs) to verify the durability and blast radius of the remove_node_back bug (F-225). The feedback suggests avoiding CommonJS globals like module.path in ES modules by using an ESM-safe fallback, and assigning asynchronous process instances within their respective promise chains inside Promise.all to ensure reliable cleanup during teardown if a startup fails.
| import { sendOperation } from './clusterShared.mjs'; | ||
|
|
||
| process.env.HARPER_INTEGRATION_TEST_INSTALL_SCRIPT = join( | ||
| import.meta.dirname ?? module.path, |
There was a problem hiding this comment.
In ES modules (ESM), avoid using CommonJS-specific globals like module (e.g., module.path) as a fallback when import.meta.dirname is undefined. Use an ESM-safe fallback such as new URL('.', import.meta.url).pathname instead.
| import.meta.dirname ?? module.path, | |
| import.meta.dirname ?? new URL('.', import.meta.url).pathname, |
References
- In ES modules (ESM), avoid using CommonJS-specific globals like
module(e.g.,module.path) as a fallback whenimport.meta.dirnameis undefined. Use an ESM-safe fallback such asnew URL('.', import.meta.url).pathnameinstead.
| await Promise.all([ | ||
| startHarper(ctxA, nodeStartOptions(ctxA.harper)), | ||
| startHarper(ctxB, nodeStartOptions(ctxB.harper)), | ||
| ]); | ||
| ctx.nodeA = ctxA.harper; | ||
| ctx.nodeB = ctxB.harper; |
There was a problem hiding this comment.
When starting multiple asynchronous processes in parallel using Promise.all, assign the successfully started instances within their respective promise chains (e.g., using .then()) rather than waiting for Promise.all to resolve. This ensures that if one process fails, the already started processes are still recorded and can be properly cleaned up during teardown.
| await Promise.all([ | |
| startHarper(ctxA, nodeStartOptions(ctxA.harper)), | |
| startHarper(ctxB, nodeStartOptions(ctxB.harper)), | |
| ]); | |
| ctx.nodeA = ctxA.harper; | |
| ctx.nodeB = ctxB.harper; | |
| await Promise.all([ | |
| startHarper(ctxA, nodeStartOptions(ctxA.harper)).then(() => { ctx.nodeA = ctxA.harper; }), | |
| startHarper(ctxB, nodeStartOptions(ctxB.harper)).then(() => { ctx.nodeB = ctxB.harper; }), | |
| ]); |
References
- When starting multiple asynchronous processes in parallel using Promise.all, assign the successfully started instances within their respective promise chains (e.g., using .then()) rather than waiting for Promise.all to resolve. This ensures that if one process fails, the already started processes are still recorded and can be properly cleaned up during teardown.
|
PR was self-closed by the author as a duplicate of #615, which carries the same spec with a fix-agnostic assertion set. No action needed. |
One exploratory-QA spec promoted into the tracked cluster suite. Test-only — no product code touched.
What it pins
integrationTests/cluster/removeNodeBlastRadius.test.mjs(from QA-758, candidate P-532 — regression anchor for finding F-225)F-225 established that the reciprocal remove-node operation is registered under the literal string
'remove_node_back;'(stray semicolon, corereplication/setNode.ts), so it can never match an incomingoperation: 'remove_node_back'. The consequence is asymmetric removal: when A callsremove_node(B), A drops its ownhdb_nodesrow for B and best-effort-sendsremove_node_backto B — which B can never handle — so B's row for A, and B's live replication connection to A, survive.This spec pins the durability half of that blast radius, in both directions, as hard assertions:
replication.databasesfilter isn't silently wiped).hdb_nodesis a persisted system-database table, so both answers should be restart-invariant — this is a row-persistence property, not a process-lifetime one. Pinning it means a future fix to the operation name can't quietly regress into "removal works until you restart."One leg is deliberately observational, not asserted
The
(b)leg simulates the fixed end-state (delete B'shdb_nodesrow for A directly) and then has B re-subscribe to A. It still succeeds today, becauseSUBSCRIPTION_REQUESThandling gates on the connection-levelauthorizationcaptured at connect time and then falls through to a reactive watch on futurehdb_nodeschange events — there is no synchronous point-read ofhdb_nodesfor the requesting peer at that moment.That is a separate, currently-live bug (tracked as F-228 / D-226). So this leg computes the observation and
console.logs it, with no assertion: asserting the observed accept would freeze a known defect into CI, and asserting the refusal it should eventually produce would fail today. Its own asserted regression test lands with the fix. The leg's setup preconditions (row exists → row deleted) remain hard-asserted, since those aren't the disputed behavior. This follows the same precedent ashdbNodesReloadSendAuth's positive-control leg in #615.Verification
prettier --checkclean andoxlint --deny-warningsexit 0, both run from inside the repo tree (not at the snapshot path, where prettier would silently fall back to defaults).teardownHarperruns unconditionally for both nodes.requires-isolation: yes — it kills and restarts nodes and mutates topology, so it stays its own file rather than joining a shared instance.Not duplicative:
deleteResurrectionOnRejoincovers record resurrection on rejoin, andaddNodeLeaderNoMeshLeak/systemDbExcludedPeerChurnassert different properties of node membership. Nothing inintegrationTests/cluster/asserted restart-durability of the removal on either side.Opened as a single-spec PR: the sibling qa-promote PRs (#612 replication/failover/blob-copy, #615 node-lifecycle auth) are both at their batch cap and this is the only currently gate-passing candidate in the cluster bank.
—
Reviewed and opened by KrAIs (Claude Opus 5) as part of the qa-explorer promotion pipeline.
🤖 Generated with Claude Code