Skip to content

fix(config): compare resolve checked_at at the database's microsecond precision - #1252

Draft
IAmJSD wants to merge 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-692-checked-at-precision
Draft

IAmJSD wants to merge 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-692-checked-at-precision

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

Fixes the red Integration Tests job on main (a14dc8f2, run 39878015423), caused by our #1109.

Closes #1251

Root cause

checkRIExchangeCloudCheck refuses a cloud check that doesn't postdate the exchange's last change, using !c.CheckedAt.After(row.updatedAt). But row.updatedAt comes back from Postgres truncated to microseconds, while CheckedAt is a Go time with nanoseconds. So a check stamped at the change's own instant reads as a few hundred nanoseconds after it and is accepted.

TestPostgresStoreDB_ResolveStaleRIExchange_ValidatesCloudCheck/checked_at_the_change covers exactly this case. The bad resolve also releases the shared hold, so the remaining subtests fail after it. That's why CI shows all 8 subtests failing.

The test only started running on main recently. On #1109's branch it skipped unless RI_EXCHANGE_TEST_DATABASE_URL was set, and #1165 then moved it onto the shared testcontainers Postgres. Our pre-merge verification for #1109 ran go test -short without the integration tag, so it didn't catch this either.

Fix

  • New helper checkPostdatesDBTime(checked, last) in internal/config/store_postgres_ri_exchange_resolve.go. It truncates checked to microseconds before the strict "after" comparison, so a check taken within the same microsecond as the change can't be ordered after it.
  • Used in checkRIExchangeCloudCheck and in validateLadderUnverifiedFail (store_postgres_ladder_resolve.go). The ladder path has the same pattern, because its lastChange is the newest chain node's updated_at from Postgres.
  • The "checked_at is in the future" checks are unchanged.

Real-world impact is small. The handlers parse checked_at as RFC 3339, normally whole seconds, so only sub-microsecond inputs were affected. The fix makes the boundary match what the comment and the test already promise.

Verification

Local, Linux, Docker Postgres via testcontainers, AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null:

  • Fail before / pass after (unit, no DB): TestCheckPostdatesDBTime, TestCheckRIExchangeCloudCheck_CheckedAtTheChangeIsRefused and TestValidateLadderUnverifiedFail_CheckedAtTheChangeIsRefused all fail with main's comparisons swapped back in, and pass with the fix.
  • Integration: go test -tags integration -count=1 -run 'ResolveStaleRIExchange|LadderResolve|ResolveLadder' ./internal/config/ passed on 3 consecutive runs, including ValidatesCloudCheck and the concurrency tests (ConcurrentResolvesOneWins, RaceWithLateSettlementKeepsHold). On origin/main the same ValidatesCloudCheck run fails with the CI error above.
  • Build and unit tests: go build ./...; go vet with and without -tags integration on ./internal/config/; go test ./internal/config/ ./internal/mocks/.
  • Lint: golangci-lint v2.10.1 found 0 issues with and without --build-tags integration. gocyclo -over 10 is silent on both changed files.
  • git-secrets: the repo's scripts/setup-git-secrets.sh patterns, run in a fresh repo with an isolated HOME, are clean on the 4 changed files, and a planted key was flagged. scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.

I didn't rerun the full internal/config integration package. The two resolve suites above are the ones that touch the changed code.

Out of scope

Main's other red checks: the GO-2026-6809 govulncheck steps (#1236), and a transient sum.golang.org TLS failure while installing actionlint in pre-commit.

Labels: #1251 has type/bug; please add priority/severity as you see fit.

🤖 Generated with Claude Code

… precision

checkRIExchangeCloudCheck and validateLadderUnverifiedFail refuse a cloud
check that does not postdate the row's last change, but compared the
operator's nanosecond checked_at against an updated_at that Postgres had
truncated to microseconds. A check stamped at the change's own instant read
as a few hundred nanoseconds after it and was accepted, which is what
TestPostgresStoreDB_ResolveStaleRIExchange_ValidatesCloudCheck catches now
that LeanerCloud#1165 runs it against the shared container.

Truncate checked_at to microseconds before the "after the last change"
comparison in both paths (checkPostdatesDBTime), and add unit tests that
pin the boundary without a database.

Closes LeanerCloud#1251

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added the type/bug Defect label Oct 23, 2026
@coderabbitai

coderabbitai Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

This was referenced Oct 23, 2026
This was referenced Oct 23, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/bug Defect

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(config): resolve cloud-check compares checked_at at nanosecond precision against microsecond DB timestamps (main integration test red)

1 participant