Skip to content

fix(config-cache): has() no longer inflates hit/miss statistics#498

Closed
nikolasdehor wants to merge 2 commits intoSynkraAI:mainfrom
nikolasdehor:fix/config-cache-has-stats-inflation
Closed

fix(config-cache): has() no longer inflates hit/miss statistics#498
nikolasdehor wants to merge 2 commits intoSynkraAI:mainfrom
nikolasdehor:fix/config-cache-has-stats-inflation

Conversation

@nikolasdehor
Copy link
Contributor

@nikolasdehor nikolasdehor commented Feb 24, 2026

Resumo

  • Reimplementa has() no ConfigCache para não chamar get() internamente
  • Antes: has() delegava para get(), que incrementava contadores de hits/misses — uma simples verificação de existência inflava as estatísticas do cache
  • Depois: has() verifica diretamente no Map sem tocar nas métricas

Closes #497

Plano de teste

  • 35 testes unitários passam localmente
  • Testes de regressão verificam que has() não altera stats de hits/misses
  • Teste confirma que has() seguido de get() conta exatamente 1 hit

Summary by CodeRabbit

  • Bug Fixes

    • Corrected cache existence checks so hit/miss statistics are no longer inflated; expired and missing entries are cleaned up without altering stats.
  • Tests

    • Added a comprehensive test suite for cache behavior: TTL/expiration, set/get, invalidation, size/keys/entries, stats tracking and reset, serialization, and singleton defaults.

Copilot AI review requested due to automatic review settings February 24, 2026 20:42
@vercel
Copy link

vercel bot commented Feb 24, 2026

@nikolasdehor is attempting to deploy a commit to the Pedro Valério Lopez's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

Warning

Rate limit exceeded

@nikolasdehor has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 49 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 157a4c92-36f3-4016-b71d-74e9a2dea575

📥 Commits

Reviewing files that changed from the base of the PR and between 1f7063e and 68f2f76.

📒 Files selected for processing (3)
  • .aiox-core/core/config/config-cache.js
  • .aiox-core/install-manifest.yaml
  • tests/core/config/config-cache.test.js

Walkthrough

Adds a comprehensive Jest test suite for ConfigCache and fixes ConfigCache.has() to check existence/expiration directly instead of calling get(), preventing unintended increments to hit/miss statistics.

Changes

Cohort / File(s) Summary
ConfigCache Test Suite
tests/core/config/config-cache.test.js
New comprehensive Jest tests covering constructor, set/get, expiration, has(), invalidation, clear, clearExpired, size, stats (hits/misses), keys/entries, TTL updates, serialization, and global singleton behavior using fake timers.
ConfigCache Implementation Fix
.aiox-core/core/config/config-cache.js
Reimplemented has() to perform a direct existence and expiry check on the internal maps and to clean up expired entries without calling get(), avoiding changes to hit/miss statistics (fixes #497).
Install manifest update
install-manifest.yaml (root), core/orchestration/swarm-intelligence.js (added)
Manifest updated (new file added: core/orchestration/swarm-intelligence.js) and metadata/hash/size updates including core/config/config-cache.js.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes changes to install-manifest.yaml adding a new file (swarm-intelligence.js) and updating file hashes, which appears unrelated to the has() statistics fix. Remove the swarm-intelligence.js manifest entry and unrelated hash updates; keep only changes to config-cache.js and its test file that directly address issue #497.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main fix: preventing has() from inflating hit/miss statistics, which directly addresses the core issue in the PR.
Linked Issues check ✅ Passed The PR successfully reimplements has() to check cache/timestamps directly without calling get(), eliminating the double-counting bug described in issue #497.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes ConfigCache.has() so it no longer inflates hit/miss counters by avoiding delegation to get(), and adds regression coverage to prevent the double-counting pattern.

Changes:

  • Reimplemented ConfigCache.has() to check presence/TTL directly without mutating statistics.
  • Added comprehensive unit tests (including regression tests for #497) covering cache behavior and stats.
  • Updated install manifest metadata/hashes to reflect the modified core file(s).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
.aios-core/core/config/config-cache.js Updates has() to avoid incrementing cache hit/miss stats while still enforcing TTL + cleanup.
tests/core/config/config-cache.test.js Adds unit + regression tests to validate TTL behavior and ensure has() does not affect stats.
.aios-core/install-manifest.yaml Updates generated manifest timestamp and file hashes/sizes for changed artifacts.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +77 to +85
if (!this.cache.has(key)) return false;

const timestamp = this.timestamps.get(key);
if (Date.now() - timestamp > this.ttl) {
this.cache.delete(key);
this.timestamps.delete(key);
return false;
}
return true;
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

has() only checks this.cache.has(key) but assumes a corresponding timestamp exists. If the maps ever become out-of-sync (e.g., timestamp missing), Date.now() - timestamp becomes NaN and the entry will be treated as non-expired, returning true. Consider treating a missing timestamp as invalid (cleanup + false) or checking this.timestamps.has(key) alongside this.cache.has(key).

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +85
// Check directly instead of delegating to get(), which would
// increment hits/misses and inflate cache statistics (fixes #497)
if (!this.cache.has(key)) return false;

const timestamp = this.timestamps.get(key);
if (Date.now() - timestamp > this.ttl) {
this.cache.delete(key);
this.timestamps.delete(key);
return false;
}
return true;
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

This duplicates expiration/cleanup logic that likely also exists in get(), which can drift over time (e.g., boundary conditions like > vs >=, cleanup behavior, or time source). A concrete way to prevent divergence is to extract a shared internal helper that validates/cleans a key without touching stats, and have both get() and has() rely on it.

Copilot uses AI. Check for mistakes.
* Fixes #497 — has() was inflating hit/miss stats by calling get()
*/

jest.useFakeTimers();
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The suite enables fake timers at module scope but never restores real timers. To reduce cross-suite coupling (especially if this file is ever refactored/merged), add an afterAll(() => jest.useRealTimers()) (or equivalent) in this test file.

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/core/config/config-cache.test.js (1)

12-12: Restore real timers after the suite.

jest.useFakeTimers() is module-scoped with no corresponding teardown. If the suite ever runs --runInBand (or in a shared worker), the fake timer state can leak into subsequently loaded modules. A single afterAll guard is cheap insurance.

🛡️ Proposed fix
 jest.useFakeTimers();
 
 const { ConfigCache, globalConfigCache } = require('../../../.aios-core/core/config/config-cache');
+
+afterAll(() => {
+  jest.useRealTimers();
+});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/config/config-cache.test.js` at line 12, Add a teardown to restore
real timers after the suite: the test file calls jest.useFakeTimers() but never
reverts the timer implementation, so add an afterAll hook that calls
jest.useRealTimers() (i.e., include an afterAll(() => jest.useRealTimers()) near
the top-level of the test file) to prevent fake timer state leaking across
modules or when running with --runInBand.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/core/config/config-cache.test.js`:
- Line 12: Add a teardown to restore real timers after the suite: the test file
calls jest.useFakeTimers() but never reverts the timer implementation, so add an
afterAll hook that calls jest.useRealTimers() (i.e., include an afterAll(() =>
jest.useRealTimers()) near the top-level of the test file) to prevent fake timer
state leaking across modules or when running with --runInBand.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9425a35 and f9fa146.

📒 Files selected for processing (3)
  • .aios-core/core/config/config-cache.js
  • .aios-core/install-manifest.yaml
  • tests/core/config/config-cache.test.js

coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 24, 2026
coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 7, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In @.aiox-core/core/config/config-cache.js:
- Around line 79-91: The get() method must mirror the new invalidation rule in
has(): if a key exists in this.cache but has no corresponding timestamp in
this.timestamps it should be treated as invalid — delete the stale cache entry
and return undefined. Update the get(key) implementation to first check the
timestamp presence/age (reuse or call has(key)) and on missing/expired timestamp
perform this.cache.delete(key) and this.timestamps.delete(key) as appropriate
before returning; also add a regression unit test that inserts a value into
cache without a timestamp and asserts get(key) returns undefined and the cache
entry is removed, keeping behavior unchanged for valid entries (respect this.ttl
and backwards compatibility).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5493c279-28e1-4d4f-800b-159fcd62d39f

📥 Commits

Reviewing files that changed from the base of the PR and between e5d620e and 1f7063e.

📒 Files selected for processing (3)
  • .aiox-core/core/config/config-cache.js
  • .aiox-core/install-manifest.yaml
  • tests/core/config/config-cache.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/core/config/config-cache.test.js

Comment on lines +79 to +91
// If timestamp is missing, maps are out-of-sync — treat as invalid
if (!this.timestamps.has(key)) {
this.cache.delete(key);
return false;
}

const timestamp = this.timestamps.get(key);
if (Date.now() - timestamp > this.ttl) {
this.cache.delete(key);
this.timestamps.delete(key);
return false;
}
return true;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep get() aligned with the new invalidation rule.

Line 79 now treats a missing timestamp as an invalid entry, but get() still returns the cached value in that same out-of-sync state because Date.now() - undefined > this.ttl is false. That makes has(key) and get(key) disagree on validity and can still record a hit for corrupted entries.

Suggested fix
  get(key) {
    if (!this.cache.has(key)) {
      this.misses++;
      return null;
    }

+   if (!this.timestamps.has(key)) {
+      this.cache.delete(key);
+      this.misses++;
+      return null;
+   }
+
    const timestamp = this.timestamps.get(key);
    const now = Date.now();

    // Check if expired
    if (now - timestamp > this.ttl) {
      this.cache.delete(key);
      this.timestamps.delete(key);
      this.misses++;
      return null;
    }

Please add a matching regression for get() with an out-of-sync cache/timestamp pair as well.

As per coding guidelines, Ensure backwards compatibility — core modules are consumed by all agents.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/core/config/config-cache.js around lines 79 - 91, The get()
method must mirror the new invalidation rule in has(): if a key exists in
this.cache but has no corresponding timestamp in this.timestamps it should be
treated as invalid — delete the stale cache entry and return undefined. Update
the get(key) implementation to first check the timestamp presence/age (reuse or
call has(key)) and on missing/expired timestamp perform this.cache.delete(key)
and this.timestamps.delete(key) as appropriate before returning; also add a
regression unit test that inserts a value into cache without a timestamp and
asserts get(key) returns undefined and the cache entry is removed, keeping
behavior unchanged for valid entries (respect this.ttl and backwards
compatibility).

has() delegated to get(), which increments this.hits or this.misses.
The common has()+get() pattern double-counted every lookup.

Re-implement has() with its own TTL check so cache statistics stay
accurate.

35 unit tests added including 3 regression tests for this fix.

Closes SynkraAI#497
…ra timers

- has() agora verifica this.timestamps.has(key) além de this.cache.has(key)
- Timestamp ausente trata como inválido (cleanup + return false)
- Adiciona afterAll com jest.useRealTimers() para evitar vazamento entre suites
- Adiciona teste para cenário de mapas dessincronizados
- Corrige caminho de import .aios-core → .aiox-core no teste
@nikolasdehor nikolasdehor force-pushed the fix/config-cache-has-stats-inflation branch from 1f7063e to 68f2f76 Compare March 11, 2026 02:27
@codecov
Copy link

codecov bot commented Mar 11, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@nikolasdehor
Copy link
Contributor Author

@Pedrovaleriolopez @oalanicolas, esse fix do ConfigCache.has() tá aberto desde 24/fev. Ontem o #581 do @rafaelscosta foi mergeado em menos de 24h com fixes que eu já tinha pronto há semanas (#500, #514, #523). Esse aqui (#498) é outro fix original nosso que ainda tá esperando. Qualquer feedback, estou pronto pra ajustar.

@nikolasdehor
Copy link
Contributor Author

Fechando — o fix do ConfigCache.has() foi mergeado via #582. Nosso PR era o original (aberto 24/fev, 2 semanas antes do #582).

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.

bug: ConfigCache.has() inflates hit/miss stats by calling get() internally

2 participants