test(db): hold the viable-standby predicate's two copies in sync - #34
test(db): hold the viable-standby predicate's two copies in sync#34Nicolas0315 wants to merge 2 commits into
Conversation
The predicate exists twice: `isViableFailoverTarget` in @haru/core, and inlined as EXISTS/NOT EXISTS subqueries in `escalateDomainIfFleetIdle` so a concurrent heartbeat cannot strip the standby between the decision and the escalation. Both source comments end with 'keep the two in sync', which is the part a test can do instead. These cases drive the same database state through both and assert they agree, across the dimensions the predicate judges: fully viable, never heartbeated, stale heartbeat, not ready, and a failed inference slot. Verified the test is actually sensitive by deleting `notExists(standbyFailedInferenceSlot)` from the SQL: the failed-slot case then reports SQL true against core false and fails. Restored, all six pass. One case is not a parity assertion but a tripwire. Core requires an inference slot with >= 1 bound model; the SQL only requires the slot to exist, justified with 'the schema guarantees an inference slot binds >= 1 model'. That is true today (`models: z.array(...).min(1)` in @haru/protocol) but it makes a zod constraint two packages away load-bearing for a SQL predicate, with nothing connecting them. Relaxing it would quietly make the SQL side the broader of the two and let the escalation fire on a standby core had rejected, whose probe step is the thing that then fails. The case asserts the layout schema still rejects a bindingless inference slot, so relaxing it breaks a test here. Both source comments now point at this file rather than asking for hand sync.
|
Warning Review limit reached
Next review available in: 39 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code Review BotNo comment/code divergences or documentation drift detected. Reviewed 3 file(s); skipped 0. |
Greptile SummaryThe PR adds regression coverage that compares the in-memory and SQL viable-standby predicates against identical database states.
Confidence Score: 5/5The PR appears safe to merge. The previously reported coverage gap is resolved by explicit null-supervisor and absent-inference-slot parity cases, and no blocking failure remains.
|
| Filename | Overview |
|---|---|
| packages/core/src/failover.ts | Updates the predicate-maintenance comment to direct future changes to the parity suite. |
| packages/db/src/failover-parity.test.ts | Adds shared-state parity coverage, including the null-supervisor and absent-inference-slot cases requested in the prior review. |
| packages/db/src/repo/domains.ts | Updates SQL predicate documentation to identify the parity suite as the synchronization mechanism. |
Reviews (2): Last reviewed commit: "test(db): cover every branch of the dupl..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f6b56159f9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
All reported issues were addressed across 3 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Review pointed out that the parity cases never built a standby without a supervisor URL or without an inference slot, so a one-sided change to either condition would leave the suite green. Confirmed rather than assumed: deleting isNotNull(standby.supervisorUrl) from the SQL predicate left all six cases passing. That matters more than a normal coverage gap here, because the source comments on both copies now point at this file instead of asking for hand sync. A suite that only covers some branches makes that promise partly false. Both branches are covered now, and every condition in the SQL predicate was verified to be load-bearing by deleting it one at a time: supervisorUrl -> 1 failure inference slot -> 1 failure failed slot -> 1 failure ready state -> 1 failure heartbeat -> 2 failures (never-seen and stale) Neither branch has a repository helper: layout apply is additive, and nothing unbinds a supervisor, so those two cases write the columns directly and say why. Also pinned the preconditions the cases depend on. The SQL side guards on the routing pointer and on no operation being in flight as well, so a fixture change could make it answer false for a reason unrelated to viability while the assertions still "agreed". degradeActive now asserts the transition landed and the pointer still targets the active domain.
|
All three correct. Fixed in @greptile-apps and @chatgpt-codex-connector (cover every duplicated branch): right, and I checked it rather than assuming. Deleting It matters more than an ordinary coverage gap here, because this PR also rewrites both source comments to point at the suite instead of asking for hand sync. A suite covering only some branches makes that promise partly false, which is worse than the comment it replaced. Both branches are covered now, and I verified every condition in the SQL predicate is load-bearing by deleting them one at a time: Neither new branch has a repository helper to reach it - layout apply is additive, and nothing unbinds a supervisor - so those two cases write the columns directly, with a comment saying why rather than leaving it looking like carelessness. @cubic-dev-ai (assert the preconditions): right. The SQL side also guards on the routing pointer and on no operation being in flight, so a fixture change could make it answer Worth noting where this lands: the per-branch verification above is the same discipline as
|
|
To use Codex here, create an environment for this repo. |
@Nicolas0315 Looks right. The direct column writes for Nothing else from my side—the tripwire case and the comment rewrites were already solid. Tip: get faster answers by chatting with cubic’s review copilot in the review UI |
|
LGTM. The two new cases land cleanly, and the direct column writes are the right call — adding a The deletion verification matrix is exactly the right discipline, and writing it down in the commit message means it's recoverable if the file is ever touched again. The The parallel to #27's Eight cases, every condition load-bearing, preconditions pinned. Good to merge from this side. |
|
Agreed on all counts. The The per-condition delete table is a clean way to make the suite's load-bearing property self-documenting. This is in good shape—nothing left from my side. Tip: You can customize Greptile's behavior for this repo with |
|
Heads-up on merge order with #27, because git will not warn you about this one.
Whichever of the two lands second needs a follow-up commit. A green merge button is not evidence here. The fix is not only the signature. On #27 the SQL side also re-checks the degraded grace against the live I am happy to rebase this onto #27 now if you would rather land #27 first; just say which order you want. |
|
Status check from the contributor side: this is approved and every required check is green (CodeRabbit, cubic, Seer, Socket Security x2, License compliance). Nothing is pending on my end. Happy to rebase if the base has moved by the time you get to it. |
|
Review gate update at head |
What
A parity test for the viable-standby predicate, which currently exists twice:
isViableFailoverTargetin@haru/core, consulted in memory bydetectDegradedEscalationEXISTS/NOT EXISTSsubqueries insideescalateDomainIfFleetIdle, so a concurrent heartbeat cannot strip the standby between the decision and the escalationBoth source comments end with a request to keep the two in sync by hand. This does that mechanically instead: each case drives the same database state through both implementations and asserts the verdicts agree, across the dimensions the predicate judges (fully viable, never heartbeated, stale heartbeat, not ready, failed inference slot).
Why it is worth a test rather than a comment
Divergence here is not a cosmetic drift. The escalation deliberately sacrifices the active's remaining healthy models on the bet that failover succeeds, so an SQL predicate that is broader than core's means the bet gets placed on a standby the in-memory decision had already rejected, and the promotion then fails at the probe step. That is a bad trade made silently.
Confirming the test can actually fail
A parity test that passes proves nothing on its own, so I checked its sensitivity: deleting
notExists(standbyFailedInferenceSlot)from the SQL predicate makes the failed-slot case report SQLtrueagainst corefalseand the test fails withexpected true to be false. Restored, all six pass.One case is a tripwire, not a parity assertion
The two predicates are not literally identical, and the difference is deliberate. Core requires an inference slot with at least one bound model (a bindingless target fails the probe step); the SQL requires only that an inference slot exist, justified in the comment with "the schema guarantees an inference slot binds >= 1 model".
That is true today -
models: z.array(modelBindingSchema).min(1)in@haru/protocol- but it makes a zod constraint two packages away load-bearing for a SQL predicate, with nothing connecting them. If that.min(1)is ever relaxed (a slot provisioned before binding would be a reasonable motivation), the SQL side silently becomes the broader of the two.So rather than assert a parity that does not hold, the last case asserts the layout schema still rejects a bindingless inference slot, with a comment explaining which SQL simplification depends on it. Relaxing the constraint breaks a test here instead of quietly widening the escalation.
Happy to drop that case if you would rather keep the schema free to change and instead tighten the SQL to check the binding count.
Notes
Both source comments now point at the test rather than asking for hand sync. No behavior change, no schema change, so no
db:generate. Runs on the existing PGlite harness - no GPUs, no cloud, no live database.pnpm test(12/12 tasks),typecheck,lint,format:checkall pass locally.Summary by cubic
Add a parity test that runs both viable-standby checks—
isViableFailoverTargetin@haru/coreand the SQL inescalateDomainIfFleetIdle—against the same DB state to keep them in sync and prevent silent divergence. The suite now covers all branches (including missing supervisor URL and missing inference slot) and pins preconditions (routing pointer, no op in flight) so results reflect viability only; also adds a schema tripwire to enforce at least one model per inference slot and updates source comments to point to the test; no behavior changes.Written for commit 1200867. Summary will update on new commits.