Skip to content

fix: normalise time.Time writes to UTC across auth + tiering (closes #460) - #461

Merged
xe-nvdk merged 2 commits into
mainfrom
fix/utc-audit-timestamps
May 26, 2026
Merged

xe-nvdk merged 2 commits into
mainfrom
fix/utc-audit-timestamps

Conversation

@xe-nvdk

@xe-nvdk xe-nvdk commented May 25, 2026

Copy link
Copy Markdown
Member

Summary

Why

Go's SQLite driver text-encodes time.Time using the value's location, so byte-for-byte equality (WHERE created_at = ?) only matches if both sides agree on timezone. PR #459 fixed the RBAC path. The rest of the codebase had the same shape — no current bug because the other paths don't do equality readback, but the latent shape would bite the next time a readback got added.

What changed

Timestamps normalised to UTC (11 sites)

File Sites Risk class
internal/auth/cluster_apply.go 3 (Phase A token FSM applier) Latent — no current readback, but symmetric with the RBAC bug PR #459 fixed
internal/auth/rbac_manager.go 7 (RBAC OSS direct-SQLite path) Latent — cluster path already UTC via nextProposerTimestamp; OSS now matches for symmetry
internal/tiering/migrator.go 1 (MigrateFile startTime) Cosmetic — time.Since(startTime) is location-independent, but matches the rest of internal/tiering/metadata.go

New regression tests (3)

Each forces time.Local = America/Argentina/Buenos_Aires and exercises the affected path. Each was verified to catch a re-introduced bug via temp-revert experiment:

  • TestProposer_CreateRole_NonUTCRoundTrip — RBAC cluster round-trip (proposer + applier UTC parity). Fails with sql: no rows in result set if either side drops .UTC().
  • TestOSS_RBACWrites_StampUTC — OSS-path RBAC Create* + Update* — asserts CreatedAt.Location() == time.UTC. Covers the 7 OSS sites this PR fixes.
  • TestApply_TokenCreate_NonUTC — Phase A token applier — reads raw SQLite text via CAST(... AS TEXT), asserts UTC offset suffix. Covers the 3 cluster_apply.go sites.

Tests skip gracefully via t.Skipf if tzdata is missing (e.g. minimal Alpine).

Internal review process

  • Step 1: configuration matrix (11 rows) — every cell traced.
  • Step 2: single deep reviewer agent surfaced 2 Mediums (test coverage gap on OSS path + token applier) + 1 Style nit (t.Cleanup ordering). All 3 addressed in this commit before opening.
  • Audit dispatched to a read-only Explore agent first to enumerate every time.Time → SQLite site across auth/, audit/, cluster/, database/, license/, tiering/. The auditor undercounted RBAC OSS sites (said 4, actually 7) — I caught the miss during the matrix step.

Test plan

  • go build ./cmd/... ./internal/... clean
  • go test -race -count=1 ./internal/auth/... ./internal/tiering/... ./internal/cluster/... clean
  • 3 new regression tests verified to catch real regressions via temp-revert
  • gofmt -l + go vet clean on touched files
  • Internal review: configuration matrix + single deep reviewer; 2 Mediums + 1 Style addressed before PR open

🤖 Generated with Claude Code

…460)

PR #459 fixed a `WHERE created_at = ?` round-trip bug in the RBAC FSM
apply path by making both proposer and applier UTC. Issue #460 audited
the rest of the codebase for the same shape.

This PR normalises 11 timestamp sites:
- internal/auth/cluster_apply.go (3 sites): Phase A token FSM applier
  now writes UTC. No current WHERE-equality readback pairs with these,
  but the latent shape matches the one PR #459 fixed for RBAC.
- internal/auth/rbac_manager.go (7 sites): RBAC OSS direct-SQLite path
  now writes UTC. Cluster path already UTC via nextProposerTimestamp;
  OSS now matches for symmetry and to prevent a future regression if
  any OSS readback adds equality matching.
- internal/tiering/migrator.go (1 site): MigrateFile's startTime now
  UTC. time.Since(startTime) is location-independent so duration math
  is unaffected.

Adds 3 regression tests, each verified to catch a re-introduced bug
by temp-revert experiment:
- TestProposer_CreateRole_NonUTCRoundTrip — exercises the RBAC
  cluster round-trip under TZ=America/Argentina/Buenos_Aires, fails
  with "sql: no rows in result set" if either proposer or applier
  drops .UTC() (proposer/applier UTC parity check).
- TestOSS_RBACWrites_StampUTC — exercises every OSS-path RBAC Create*
  + Update* under non-UTC TZ, asserts CreatedAt.Location() ==
  time.UTC; covers the 7 OSS sites this PR fixes.
- TestApply_TokenCreate_NonUTC — exercises ApplyCreateToken under
  non-UTC TZ, reads the raw SQLite text storage via CAST(... AS TEXT),
  asserts a +00:00 or Z suffix; covers the 3 cluster_apply.go sites.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request standardizes database timestamp writes to UTC across both cluster and OSS-path operations (such as token creation, organization updates, and file migrations) to prevent timezone round-trip issues in SQLite. It also introduces comprehensive regression tests that simulate a non-UTC local timezone environment. The reviewer recommended explicitly checking errors returned by GetOrganization and GetTeam in the tests to avoid silent failures if those lookups fail.

Comment thread internal/auth/cluster_rbac_proposer_test.go Outdated
Gemini PR #461 round 1: the `if got != nil && ...UpdatedAt.Location() != time.UTC` shape silently skipped the assertion if the GetX call returned (nil, err) — a readback failure would make the test pass by accident instead of fail loudly. Now t.Fatalf on the error first, then assert directly on `got.UpdatedAt.Location()`.
@xe-nvdk

xe-nvdk commented May 25, 2026

Copy link
Copy Markdown
Member Author

@gemini-code-assist please review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request ensures that timestamps are consistently handled and stored in UTC across the authentication, RBAC, and tiering components to prevent timezone-related round-trip issues with SQLite. Specifically, it updates various database insertion and update operations to use UTC time, and introduces comprehensive regression tests to verify this behavior when running under non-UTC local timezones. I have no feedback to provide as there are no review comments.

@xe-nvdk
xe-nvdk merged commit ac1278f into main May 26, 2026
7 checks passed
@xe-nvdk
xe-nvdk deleted the fix/utc-audit-timestamps branch May 26, 2026 00:02
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.

audit: normalise timestamps to UTC across auth + cluster (follow-up to PR #459)

1 participant