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
- 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.
- 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).
- 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.
- 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.
Problem
VectorService's retry backoff is a hardcoded2 ** retries * 1000at 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 shrinkingmaxRetriesinstead of fixing the cause. The unit suite has crossed 16 CI-minutes; the operator has ruled coverage stays in CI (NEO_TEST_SKIP_CIis the rejected alternative), so the wall-clock tax must go, not the tests.Acceptance Criteria
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.setTimeout(resolve, N)with N >= 1000 that lacks an explicitwall-clock-under-test:justification comment — so the class cannot regrow silently.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.kb.embedding.backoffBaseMs(ai/mcp/server/knowledge-base/configBase.mjs)base * 2 ** n. Default1000, owned by the knowledge-base per-server configBase, not the rootnonNegativeIntfallback covered inconfig.template.spec.mjsNEO_KB_EMBEDDING_BACKOFF_BASE_MS(env projection)nonNegativeInt, matching the adjacentbatchDelaydelay leaf rather than the looser'number'its two WAL namesakes use1000;0is legitimate (retry with no delay)'number'the refusal case receives-1VectorServicecarried-prefix write retry (:1131)2 ** retries * 1000lint-retry-boundssite hashVectorServiceembed batch retry (:1235)backoffBaseMswas already destructured at:1017, so this site needed noticing rather than plumbinglint-retry-boundssite hashUNIT_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)config.template.spec.mjsasserts the1ms override resolvesKnown adjacent defect, deliberately not fixed here:
memoryWal.backoffBaseMsandmessageWal.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.