Skip to content

test(cluster): pin three node-lifecycle invariants — hdb_nodes reload auth, clone readiness, remove_node blast radius - #615

Open
kriszyp wants to merge 11 commits into
mainfrom
kris/qa-promote-hdbnodes-sendauth
Open

test(cluster): pin three node-lifecycle invariants — hdb_nodes reload auth, clone readiness, remove_node blast radius#615
kriszyp wants to merge 11 commits into
mainfrom
kris/qa-promote-hdbnodes-sendauth

Conversation

@kriszyp

@kriszyp kriszyp commented Jul 25, 2026

Copy link
Copy Markdown
Member

What

Pins three node-lifecycle invariants and fixes the production full-replication remove/rejoin path they exposed:

  • remove_node_back is registered under its real operation name (the previous trailing semicolon made it unroutable).
  • Reciprocal removal targets are limited to the authenticated peer or the receiving node itself.
  • Explicitly unsubscribed database entries retain their URL/iterator cleanup state, but restored membership bypasses the stale-entry fast path and schedules a fresh subscription.
  • Reciprocal removal passes the full peer record to the operation transport, preserving stored CA trust and SNI identity on secure meshes.
  • Deleted-node cleanup resolves the peer URL from nodeMap, so cleanup still works if worker reassignment emptied the per-database map.

Regression coverage

  • hdbNodesReloadSendAuth.test.mjs: requires a B-local post-join reload marker and continuously requires the expected A↔B sockets to remain present and connected; status errors and missing sockets fail.
  • cloneReadinessKeySet.test.mjs: uses a deterministic receive throttle and ~8 MB dataset, requires a readable partial key set while production reports Unavailable, then requires the first confirmed Available key-set snapshot to be complete.
  • removeNodeBlastRadius.test.mjs: exercises A/B/C full replication. Removing B deletes B's self row and disconnects B from both A and C; authenticated add_node restores both peer subscriptions without restarting B; a second removal remains durable across B restart.
  • removeNodeBackAuthorization.test.mjs: rejects unrelated targets and requests without authenticated peer identity.

The three-node test documents the production semantics: full-replication reciprocal removal deletes the removed node's self row, so it ejects that node from every peer, not only the remover.

Validation

  • Full unit suite: 561 passing.
  • Focused integration tests: hdb_nodes reload 2/2, clone readiness 1/1, remove/rejoin 5/5.
  • Changed-file Prettier, oxlint, Node syntax checks, and git diff --check: clean.
  • npm run build: no errors in changed files; the repository currently has unrelated baseline TypeScript errors in analytics/profile.ts and replication/replicationConnection.ts.
  • Independent pre-push review completed at the pushed head with Claude and Harper-domain adjudication; Grok provided a supporting leg and the final Gemini leg returned no output. Secure-transport and unit-test findings were fixed; remaining design concerns are captured in the draft review. The clone timing comment was not applied because the original review specifically requires the deterministic partial-while-Unavailable precondition, and that throttled assertion passed repeatedly.

Squash on merge is recommended because the branch preserves the review/audit sequence.

…ive peers

Integration-level regression anchor for #602, which shipped with unit tests
only (resolveNodeForSendAuth / shouldCloseSendAuthWatch). Nothing in
integrationTests/cluster/ drives an actual hdb_nodes reload marker, so the
end-to-end property #602 restored — a base copy of the system database must
not tear down unrelated live replication connections — was unpinned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a new integration test file, hdbNodesReloadSendAuth.test.mjs, to serve as a regression anchor ensuring that hdb_nodes reload markers do not incorrectly de-authorize live peers. The review feedback highlights two key improvements: replacing the CommonJS-specific module.path fallback with an ESM-safe alternative to avoid runtime errors, and assigning started test instances within their individual promise chains inside Promise.all to prevent resource leaks if one of the startup promises fails.

Comment thread integrationTests/cluster/hdbNodesReloadSendAuth.test.mjs Outdated
Comment thread integrationTests/cluster/hdbNodesReloadSendAuth.test.mjs Outdated
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found.

…le key set

Integration-level regression anchor for the readiness gate reported in #611
("cloneNode's checkSyncStatus passes mid-copy, so a clone declares itself
complete while still holding partial data").

addNodeFullCopy.test.mjs asserts POST-convergence state; nothing in
integrationTests/cluster/ asserted the readiness PREDICATE itself. This drives
the real cloneNode CLI bootstrap (HDB_LEADER_TOKEN/HDB_LEADER_URL), polls
production's own get_status(availability), and at the FIRST Available tick takes
a bidirectional leader/clone key-set diff -- never a count, never cluster_status
alone. One assertion: Available implies 0 missing keys.

Forced onto storage.engine 'lmdb' deliberately. Under the default RocksDB
engine, RocksTransactionLogStore.getKeys() is an unimplemented stub, so
schemaDescribe's fallback assigns a compound index key to last_updated_record,
findMostRecentTimestamp compares an array to a number, and the watermark stays 0
-- which checkSyncStatus treats as "no target, skip". The predicate is therefore
vacuously true from tick one on RocksDB, so a test there would exercise nothing.
On LMDB the watermark is genuinely computed (observed non-zero on both runs), so
the comparison logic actually runs. That vacuity is tracked separately; this
anchor covers the path where the gate is real.

Runs in ~25s (2 nodes, 250k records). requires-isolation: drives a real clone
bootstrap and mutates topology, so it must not share an instance.

Oracle is armed: a self-test feeds the comparator a truncated set with an
injected extra key and asserts it reports exactly that gap, so the 0/0 result is
a proven-working comparator rather than a broken one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kriszyp kriszyp changed the title test(cluster): pin that hdb_nodes reload markers don't de-authorize live peers test(cluster): pin two node-lifecycle invariants — hdb_nodes reload auth + clone readiness key set Jul 26, 2026
Comment thread integrationTests/cluster/hdbNodesReloadSendAuth.test.mjs
@kriszyp kriszyp changed the title test(cluster): pin two node-lifecycle invariants — hdb_nodes reload auth + clone readiness key set test(cluster): pin three node-lifecycle invariants — hdb_nodes reload auth, clone readiness, remove_node blast radius Jul 26, 2026
…-join recovery

`remove_node` on A deletes A's own hdb_nodes row for B, and best-effort sends
`remove_node_back` to B. Nothing in integrationTests/cluster/ asserted what the
remover looks like afterwards, that its answer survives a restart, or that a
node can be re-joined once it has been fully removed.

Asserted (4 tests, 2 nodes, ~13s) — all fix-agnostic invariants:

- A drops B promptly: A reports B disconnected and A's persisted hdb_nodes no
  longer carries B.
- That removal survives a restart of A with its ORIGINAL config. Re-passing the
  config is load-bearing: omitting it wipes replication.databases, which would
  make the assertion vacuous.
- Recovery: from the fully-removed end state, an authenticated `add_node` re-join
  restores a live connection and a fresh write on A replicates to B.

Deliberately NOT asserted, only logged:

- Whether B stops receiving writes after being removed. Today it does not (the
  reciprocal removal does not reach B), so asserting the observed behavior would
  pin a bug and turn this anchor red when that is fixed; asserting the corrected
  behavior would make it red today. Both legs print what the build did so a CI
  reader can still see it.
- Any claim about whether SUBSCRIPTION_REQUEST performs a synchronous membership
  check. An authenticated add_node is a deliberate operator re-join, so its
  acceptance cannot distinguish "no check exists" from "the check is satisfied".

From QA-758. requires-isolation: kills and restarts nodes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
kriszyp and others added 4 commits August 3, 2026 08:18
Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Keep explicitly unsubscribed entries for iterator and URL cleanup while allowing restored membership to schedule a fresh subscription. Exercise the lifecycle without restarting the removed node and pin the three-node blast radius.

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Resolve reviewed edge cases in deleted-node iterator cleanup, reset rejoin timing state, and constrain remove_node_back to authenticated peer targets.

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Pin fail-closed behavior for unrelated targets and missing authenticated peer identity.

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
@kriszyp
kriszyp marked this pull request as ready for review August 3, 2026 17:21
@kriszyp
kriszyp requested a review from a team as a code owner August 3, 2026 17:21
Comment thread integrationTests/cluster/removeNodeBlastRadius.test.mjs Outdated
Comment thread integrationTests/cluster/cloneReadinessKeySet.test.mjs
Comment thread unitTests/replication/removeNodeBackAuthorization.test.mjs
kriszyp and others added 4 commits August 3, 2026 11:54
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>

@DavidCockerill DavidCockerill left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name: 'remove_node_back;' good find! 🥇

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants