Skip to content

[AIG-854] De-flake consensus expiry test with injectable clock/fake timers + CI repeat guard - #52

Merged
blackms merged 1 commit into
mainfrom
pm-agent/AIG-854-deflake-consensus
Jun 8, 2026
Merged

[AIG-854] De-flake consensus expiry test with injectable clock/fake timers + CI repeat guard#52
blackms merged 1 commit into
mainfrom
pm-agent/AIG-854-deflake-consensus

Conversation

@blackms

@blackms blackms commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Cosa cambia

Fix della flakiness time-based nei test expireCheckpoints di tests/unit/consensus-service.test.ts + nuovo guard CI.

1. Determinismo del tempo (fake timers)

I due test expireCheckpoints dipendevano dal wall-clock reale:

  • lo store calcola la scadenza con Date.now() (createConsensusCheckpoint, expireOldCheckpoints)
  • il service confronta new Date() > checkpoint.expiresAt in submitDecision

Su un runner CI lento il timeout poteva trascorrere tra createCheckpoint e approveCheckpoint, facendo finire un checkpoint approvato in stato expired (AssertionError: expected 'expired' to be 'approved', gia osservato 1 volta nel job coverage).

Fix: congelo il tempo con vi.useFakeTimers() + vi.setSystemTime() attorno a entrambi i test e avanzo il clock esplicitamente per superare la soglia di scadenza. I fake timers di vitest intercettano sia Date.now() (store) sia new Date() (service), quindi l'intera sequenza create -> approve -> expire diventa deterministica. Sostituiti gli UPDATE ... SET expires_at = Date.now() - 1 (fonte della corsa) con avanzamenti di tempo controllati.

Scelta fake timers vs clock iniettabile: il tempo e letto direttamente in due classi (service + store). Iniettare un clock attraverso entrambe sarebbe un refactor invasivo della logica di business; i fake timers ottengono lo stesso determinismo toccando solo il test, rispettando il vincolo "non alterare la logica di business".

2. Guard CI permanente (repeat 50x)

Nuovo job consensus-flake-guard in .github/workflows/ci.yml che esegue solo il file consensus, ripetuto 50 volte, come guard contro future regressioni di flakiness. Veloce: gira solo tests/unit/consensus-service.test.ts, non l'intera suite, e non rallenta gli altri job.

Nota: vitest 2.1.x non espone --repeat/--repeats come flag CLI (esiste solo come opzione per-test). Il repeat 50x e quindi realizzato con un loop shell for (set -euo pipefail, fail-fast alla prima run rossa).

Scope file

  • tests/unit/consensus-service.test.ts — fake timers sui test expireCheckpoints
  • .github/workflows/ci.yml — nuovo job consensus-flake-guard (repeat 50x, solo consensus)

Nessuna modifica alla logica di business.

Summary by CodeRabbit

  • Tests

    • Enhanced consensus service test determinism and reliability.
  • Chores

    • Added dedicated consensus flake detection to CI pipeline for improved test stability monitoring.

…t guard

The expireCheckpoints tests depended on the real wall-clock: expiry is
computed from Date.now() in the SQLite store and new Date() in the
service, so a slow CI runner could let the timeout elapse between
create and approve, flipping an approved checkpoint to 'expired'
(observed once in the coverage job).

- Freeze time with vi.useFakeTimers()/setSystemTime() around both
  expireCheckpoints tests and advance the clock explicitly to cross
  the expiry boundary, making the create -> approve -> expire sequence
  fully deterministic. No business logic changed.
- Add a dedicated 'consensus-flake-guard' CI job that runs only the
  consensus suite 50x as a permanent guard against future flakiness,
  without slowing the full test suite (vitest 2.x has no --repeat CLI
  flag, so the run is looped at the shell level).
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR improves consensus service test reliability by introducing deterministic time handling with Vitest fake timers and adds a CI job that runs those tests 50 times to detect intermittent failures. The test refactor centralizes timeout configuration and replaces time-based mocking with controlled time advancement, while the new CI job provides early signal of flaky test behavior.

Changes

Consensus Service Test Reliability

Layer / File(s) Summary
Test determinism with fake timers
tests/unit/consensus-service.test.ts
Vitest vi module is imported and expiry tests are refactored to use frozen system time (useFakeTimers/setSystemTime). A shared TIMEOUT_MS constant replaces hardcoded timeout values. Expiry boundary testing now advances frozen time past the expiry point rather than mutating the database. Non-pending checkpoint handling is verified by advancing time beyond expiry and asserting the checkpoint is skipped.
CI flake detection job
.github/workflows/ci.yml
New consensus-flake-guard job executes the consensus service test 50 times sequentially with fail-fast behavior (set -euo pipefail). Each iteration output is grouped into a collapsible CI log section for readability. The job runs before the existing build and coverage jobs.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A flake detector hops around,
Fifty times to find bugs underground!
With frozen time and timers fake,
Tests now steady, no more quake!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the main changes: de-flaking the consensus expiry test with fake timers and adding a CI repeat guard job.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pm-agent/AIG-854-deflake-consensus

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

tests/unit/consensus-service.test.ts

Parsing error: "parserOptions.project" has been provided for @typescript-eslint/parser.
The file was not found in any of the provided project(s): tests/unit/consensus-service.test.ts


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 72-98: Update the "consensus-flake-guard" job (and apply
file-wide) to pin third-party actions, restrict permissions, and disable
credential persistence: replace uses: actions/checkout@v4 and uses:
actions/setup-node@v4 with commit-pinned references (use the specific commit SHA
for actions/checkout and actions/setup-node), add an explicit permissions: block
granting least privilege (e.g., read-only for contents or only the permissions
actually needed by the job) at the job level, and set persist-credentials: false
on the checkout step; ensure these changes are applied similarly wherever
actions/checkout and actions/setup-node are used across the workflow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6c37e09b-1fc0-47fc-8a41-39bfaf5da8b5

📥 Commits

Reviewing files that changed from the base of the PR and between de30879 and ccaad37.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • tests/unit/consensus-service.test.ts

Comment thread .github/workflows/ci.yml
Comment on lines +72 to +98
consensus-flake-guard:
name: Consensus Flake Guard
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'

- name: Install dependencies
run: npm ci

# vitest 2.x exposes `repeats` only as a per-test option, not a CLI flag,
# so we loop the run 50x at the shell level. The whole loop fails fast if
# any single run fails (set -e + explicit exit on non-zero).
- name: Repeat consensus tests (50x)
run: |
set -euo pipefail
for i in $(seq 1 50); do
echo "::group::consensus run $i/50"
npx vitest run tests/unit/consensus-service.test.ts
echo "::endgroup::"
done

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Security: Pin action versions, restrict permissions, disable credential persistence.

The static analysis tool (zizmor) flagged three security concerns with this job that also apply to all other jobs in this workflow:

  1. Unpinned actions (flagged as error): actions/checkout@v4 and actions/setup-node@v4 use semantic version tags instead of commit hashes, creating a supply chain attack vector if the tags are moved.
  2. Overly broad permissions (flagged as warning): No permissions: block means the job inherits default workflow permissions (read/write), violating least privilege.
  3. Credential persistence (flagged as warning): The checkout step doesn't set persist-credentials: false, allowing subsequent steps to use the GitHub token.

These are pre-existing patterns across all jobs in this file—the new job correctly follows the existing structure. Consider addressing these file-wide in a follow-up PR.

🔒 Recommended fixes (apply to all jobs)

1. Pin actions to commit hashes:

-      - name: Checkout
-        uses: actions/checkout@v4
+      - name: Checkout  
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
 
-      - name: Setup Node.js
-        uses: actions/setup-node@v4
+      - name: Setup Node.js
+        uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0

2. Add permissions block to each job:

 consensus-flake-guard:
   name: Consensus Flake Guard
   runs-on: ubuntu-latest
+  permissions:
+    contents: read
   steps:

3. Disable credential persistence:

       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        with:
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
consensus-flake-guard:
name: Consensus Flake Guard
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
# vitest 2.x exposes `repeats` only as a per-test option, not a CLI flag,
# so we loop the run 50x at the shell level. The whole loop fails fast if
# any single run fails (set -e + explicit exit on non-zero).
- name: Repeat consensus tests (50x)
run: |
set -euo pipefail
for i in $(seq 1 50); do
echo "::group::consensus run $i/50"
npx vitest run tests/unit/consensus-service.test.ts
echo "::endgroup::"
done
consensus-flake-guard:
name: Consensus Flake Guard
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
# vitest 2.x exposes `repeats` only as a per-test option, not a CLI flag,
# so we loop the run 50x at the shell level. The whole loop fails fast if
# any single run fails (set -e + explicit exit on non-zero).
- name: Repeat consensus tests (50x)
run: |
set -euo pipefail
for i in $(seq 1 50); do
echo "::group::consensus run $i/50"
npx vitest run tests/unit/consensus-service.test.ts
echo "::endgroup::"
done
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 76-77: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[warning] 72-98: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)


[error] 77-77: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 80-80: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 72 - 98, Update the
"consensus-flake-guard" job (and apply file-wide) to pin third-party actions,
restrict permissions, and disable credential persistence: replace uses:
actions/checkout@v4 and uses: actions/setup-node@v4 with commit-pinned
references (use the specific commit SHA for actions/checkout and
actions/setup-node), add an explicit permissions: block granting least privilege
(e.g., read-only for contents or only the permissions actually needed by the
job) at the job level, and set persist-credentials: false on the checkout step;
ensure these changes are applied similarly wherever actions/checkout and
actions/setup-node are used across the workflow.

Source: Linters/SAST tools

@blackms
blackms merged commit 19f44b3 into main Jun 8, 2026
7 checks passed
@blackms
blackms deleted the pm-agent/AIG-854-deflake-consensus branch June 8, 2026 07:27
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.

1 participant