LearnNewsExamplesServices
Frontmatter
id17124
titleRetry backoff bases are hardcoded seconds, so every retry spec pays real wall-clock — make them leaves and lint fixed sleeps out of unit specs
stateClosed
labels
aitestingperformance
assigneesneo-opus-grace
createdAtAug 14, 2026, 4:44 PM
updatedAtAug 15, 2026, 4:30 PM
githubUrlhttps://github.com/neomjs/neo/issues/17124
authorneo-opus-vega
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 15, 2026, 4:30 PM

Retry backoff bases are hardcoded seconds, so every retry spec pays real wall-clock — make them leaves and lint fixed sleeps out of unit specs

Closed Backlog/active-chunk-16 aitestingperformance
neo-opus-vega
neo-opus-vega commented on Aug 14, 2026, 4:44 PM

Problem

VectorService's retry backoff is a hardcoded 2 ** retries * 1000 at two sites (the ordinary batch retry arm and the carried-prefix write retry, ai/services/knowledge-base/VectorService.mjs ~1128/~1229). Any spec that exercises a retry pays a real 2s/4s per attempt — today's work-conservation falsifier arms alone added ~15-20 seconds of genuine sleep to the unit suite, and the leaseYield spec documented the same friction weeks ago ("a maxRetries-5 mutation run exhausts the 30s test timeout") and worked around it by shrinking maxRetries instead of fixing the cause. The unit suite has crossed 16 CI-minutes; the operator has ruled coverage stays in CI (NEO_TEST_SKIP_CI is the rejected alternative), so the wall-clock tax must go, not the tests.

Acceptance Criteria

  1. The backoff base becomes an ADR-0019-compliant config leaf (e.g. kb.retryBackoffBaseMs-class, default 1000, env-projected per the behavior-binding-clock projection contract from #17115), consumed at both VectorService sites; specs pin it to 1ms.
  2. The existing retry/falsifier specs keep their exact assertion coverage and drop their real-sleep cost (the four VectorService invariant spec files' combined wall-clock drops measurably; state before/after).
  3. A mechanical lint (same gate family as check-block-alignment / lint-retry-bounds) fails a unit spec containing a bare fixed setTimeout(resolve, N) with N >= 1000 that lacks an explicit wall-clock-under-test: justification comment — so the class cannot regrow silently.
  4. A sweep enumerates any other production retry/backoff constants specs currently pay for real (grep receipts in the PR body), fixing or ticketing each.

Contract Ledger

Backfilled 2026-08-15 per @neo-gpt's review of PR #17126. This ticket introduces one externally-reachable surface — an env-projected config leaf — and shipped without declaring it; the row below is the declaration, verified against the head at ed55ef5223.

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
kb.embedding.backoffBaseMs (ai/mcp/server/knowledge-base/configBase.mjs) ADR-0019 leaf placement; this ticket's AC-1 Base for the embedding retry's exponential backoff, base * 2 ** n. Default 1000, owned by the knowledge-base per-server configBase, not the root leaf JSDoc both consumers read it; nonNegativeInt fallback covered in config.template.spec.mjs
NEO_KB_EMBEDDING_BACKOFF_BASE_MS (env projection) ADR-0019 env layer — the sanctioned override seam Operator/harness override. Domain nonNegativeInt, matching the adjacent batchDelay delay leaf rather than the looser 'number' its two WAL namesakes use Negative, fractional, or unparseable → falls back to 1000; 0 is legitimate (retry with no delay) leaf JSDoc mutation-verified: under 'number' the refusal case receives -1
Consumer: VectorService carried-prefix write retry (:1131) AC-1 "consumed at both sites" Reads the leaf instead of a hardcoded 2 ** retries * 1000 lint-retry-bounds site hash
Consumer: VectorService embed batch retry (:1235) AC-1 "consumed at both sites" Same; backoffBaseMs was already destructured at :1017, so this site needed noticing rather than plumbing lint-retry-bounds site hash
Test override AC-1 "specs pin it to 1ms" Pinned in the unit Playwright config beside UNIT_TEST_MODE, never in the leaf and never per-spec — an inline branch bakes env-resolution into the declarative SSOT, and a per-spec write mutates a singleton every other spec shares (ADR-0019 test isolation) unit config config.template.spec.mjs asserts the 1ms override resolves

Known adjacent defect, deliberately not fixed here: memoryWal.backoffBaseMs and messageWal.backoffBaseMs (ai/mcp/server/memory-core/configBase.mjs:500,582) carry the same role with the looser 'number' domain and accept negative and fractional values. They are a different server's contract surface, outside this ledger, and get their own ticket rather than a silent widening of this PR's diff.

Evidence class

Source census of the two backoff sites + measured spec-run deltas, 2026-08-14. Complements #17123 (wake daemon spec de-sleep) — together they own the suite's return to single-digit minutes.

tobiu referenced in commit 6905741 - "feat(ai): leaf the retry backoff base and require fixed waits to name what they wait for (#17124) (#17126) on Aug 15, 2026, 4:30 PM
tobiu closed this issue on Aug 15, 2026, 4:30 PM