Skip to content

neighbors: follow-ups from #541 (graph-age clamp, cache/publish tests, atomic.Pointer) - #550

Merged
dborup merged 1 commit into
masterfrom
fix/545-neighbor-graph-followups
Oct 24, 2026
Merged

dborup merged 1 commit into
masterfrom
fix/545-neighbor-graph-followups

Conversation

@dborup

@dborup dborup commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Relates to #545

Plan / summary

Follow-ups from the #541 review. Server-only, no schema, API, dependency or workflow change; cmd/server stays read-only.

Item Change Test Mutant
F1 The response cache still refreshes every cacheRecomputeIntervalSeconds, but the graph rebuild now runs every ceil(neighborGraphTTL / interval) ticks (neighborGraphRebuildEvery). Default 300 s: every tick, unchanged. Intervals above the TTL: graph age equals the interval (documented). TestNeighborGraphRebuildEvery, TestNeighborGraphFastCacheCadenceDoesNotRebuildGraphEveryTick rebuild on every tick
F2 Test pins that both cached responses come from the graph the same tick produced, and that a skipped-rebuild tick keeps the published graph. TestNeighborGraphCacheBuiltFromSameTickGraph, TestNeighborGraphCacheTickWithoutRebuildKeepsGraph build the response before the rebuild (M2), rebuild between the two builds
F3/F7 Server.neighborGraph is now atomic.Pointer[NeighborGraph]; neighborMu is removed, so the mutex-less mutant cannot be written. neighborBuildMu still serializes builds. TestNeighborGraphFieldIsAtomicPointer, TestNeighborGraphPublishIsRaceFree plain-pointer field fails the type pin
F4 #1481 P0-1 / #1483 restored in the config.go and config.example.json comments, which also describe the new semantics. TestNeighborCacheIntervalDocsKeepIssueRefs strip the reference
F6 Wiring tests for NeighborMaxEdgeKm() and for DebugAffinity / MaxEdgeKm reaching the build. TestConfigNeighborMaxEdgeKm, TestBuildNeighborGraphWiresMaxEdgeKm, TestBuildNeighborGraphWiresDebugAffinity hardcode DefaultMaxEdgeKm; force EnableLog=false; offset the returned value

Existing #541 tests were adapted to the atomic.Pointer field (.Store(...) instead of assignment under the mutex).

Performance

The request path is unchanged (an atomic load, cheaper than the former mutex). Background work goes down for intervals below 300 s: one ~1.3 s graph build per TTL instead of one per tick. At the default there is no change. No new unbounded structure.

Config / customizer

No new knob. cacheRecomputeIntervalSeconds semantics are documented; the graph TTL stays a constant.

Verification

See the report comment on this PR. Draft only; no merge or ready.

🤖 Generated with Claude Code

…, atomic.Pointer)

Relates to #545

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@dborup

dborup commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

Rapport — CS-Minimax PR#550 #545 — head 4572e1e

Status: Draft, all 4 requirements implemented with tests and mutants; local suites and all CI jobs green; no local E2E run (see residuals).

Evidence tags: [T] = test or tool run, [A] = analysis, [K] = known and unchanged.

F1 — graph rebuild clamped to the TTL

Test Mutant Result
TestNeighborGraphRebuildEvery, TestNeighborGraphFastCacheCadenceDoesNotRebuildGraphEveryTick rebuild on every tick killed [T]

Default 300 s still rebuilds every tick, so behaviour is unchanged [T][A]. Intervals above the TTL make the graph as old as the interval; this is documented in config.go and config.example.json.

F2 — response built from this tick's graph

Test Mutant Result
TestNeighborGraphCacheBuiltFromSameTickGraph (default and unfiltered shapes), TestNeighborGraphCacheTickWithoutRebuildKeepsGraph M2 (response before rebuild), and rebuild between the two builds both killed [T]

My first M2 variant survived because only the default shape was checked. I fixed the test and re-ran.

F3/F7 — atomic.Pointer

Test Mutant Result
TestNeighborGraphFieldIsAtomicPointer, TestNeighborGraphPublishIsRaceFree plain pointer plus no mutex unrepresentable [A]; type pin fails if reverted [T]

neighborMu is removed. neighborBuildMu still serializes builds.

F4 / F6

Item Test Mutant Result
F4 refs restored TestNeighborCacheIntervalDocsKeepIssueRefs strip #1481 P0-1 killed [T]
F6 NeighborMaxEdgeKm TestConfigNeighborMaxEdgeKm offset result killed [T]
F6 maxEdgeKm wiring TestBuildNeighborGraphWiresMaxEdgeKm hardcode default killed [T]
F6 DebugAffinity wiring TestBuildNeighborGraphWiresDebugAffinity force EnableLog=false killed [T]

Note: F2 and F6 pass on the old code (they are coverage-gap tests), so their red evidence is the mutants. F1 and F3 are red on the old code because the new API does not exist there.

Local runs

  • go test ./... in cmd/server: pass [T]
  • go test -race -timeout 40m ./... in cmd/server: pass, 819 s, 0 races, includes TestNoSQLUnderStoreLock [T]. A first run with the default 10 min timeout hit the timeout in the unrelated TestObserverComparisonPerf_RealisticFixtureEndToEnd (no race report); the re-run with a longer timeout passed.
  • sh test-all.sh: 263 passed, 0 failed [T]
  • node test-frontend-helpers.js: 719 passed, 0 failed [T]
  • scripts/check-xss-sinks.sh --diff origin/master: clean (no public/ changes) [T]
  • New map[string]interface{}: 0 [T]. Workflow files untouched, so the fork-guards are unchanged [K].

CI (run 40083681089)

Job Result
Go Server Tests 1/3, 2/3, 3/3 pass [T]
Go Ingestor Tests, Go Libraries & CLI, Go Build & Test pass [T]
JavaScript Unit Tests & Lint pass [T]
Script & Container Unit Checks, Verify Go Test Assignments pass [T]
Playwright E2E 1/3, 2/3, 3/3 pass [T]
Verify E2E Shards & Coverage, Validate Docker Image, Build & Publish Docker Image pass [T]
Publish Badges, Release Artifacts, Deploy Staging skipped

No job failed, so no rerun and no #271 flake.

Residuals

  • I did not run the E2E suite against a local Go server with e2e-fixture.db. The change is server-only with no public/ or API-shape change, and the CI Playwright shards passed. [A]
  • I did not do a manual browser check. The change has no visual effect. [A]
  • Intervals above 300 s still age the graph by the interval, by design and now documented. Making the TTL configurable in the customizer is left for a later milestone.

@dborup
dborup marked this pull request as ready for review October 24, 2026 10:46
@dborup
dborup merged commit a4126c6 into master Oct 24, 2026
18 checks passed
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