Skip to content

fix(db): keep tokenExpiresAt when a connection is created - #11368

Open
ntdatt812 wants to merge 2 commits into
diegosouzapw:release/v3.8.51from
ntdatt812:fix/create-connection-tokenexpiresat
Open

fix(db): keep tokenExpiresAt when a connection is created#11368
ntdatt812 wants to merge 2 commits into
diegosouzapw:release/v3.8.51from
ntdatt812:fix/create-connection-tokenexpiresat

Conversation

@ntdatt812

Copy link
Copy Markdown
Contributor

Summary

createProviderConnection copies optional fields onto the row through an allowlist (src/lib/db/providers.ts:583). tokenExpiresAt is not on it, and the initial object at :568 does not set it either — so _insertConnectionRow always binds

tokenExpiresAt: conn.tokenExpiresAt || null,   // providers.ts:707

as NULL on every insert, however good the payload was.

That makes #5326 a no-op end to end. buildOAuthConnectionCreatePayload mirrors the computed expiry into tokenExpiresAt (connectionPersistence.ts:105) precisely so the dashboard badge does not flash "Token Expired" before the first background refresh — and the value is discarded one layer down. The update path already carries it (providers.ts:762), so a connection only gained the field after its first refresh.

One string on the allowlist.

Related Issues

Validation

  • Change type: DB
  • Focused tests and category gates from the golden path
  • npm run lint0 net new on the changed file (4 pre-existing no-unused-vars, same before and after)
  • Reconciled with the current active release base (release/v3.8.50, dafb4ae80)
  • Production-code changes include a new or updated automated test in this PR
tests/unit/oauth-connection-tokenexpiresat-5326.test.ts   3 pass, 0 fail
  + oauth-refresh-connection-dedup-8059                   7 pass, 0 fail

Written test-first: the new case fails on the base with tokenExpiresAt: null, and passes with the one-string change. Mutation-checked, after confirming the edit applied — removing the string again gives 2 pass / 1 fail, exactly the new case.

Tests Added Or Updated

  • tests/unit/oauth-connection-tokenexpiresat-5326.test.ts — one case that persists the payload with createProviderConnection and reads it back through getProviderConnections.

The two existing cases are unchanged. Worth noting why they did not catch this: both assert the payload object and neither touches the database, so the create path could drop the field for as long as it did with the regression test green. The file now covers both halves.

Coverage Notes

src/lib/db/providers.ts — the added allowlist entry is exercised by the round-trip case, and the mutation confirms it is the line under test rather than incidental.

Reviewer Notes

  • One-string change to a create-path allowlist; no schema change, no migration. The column and its binding already existed.
  • I did not touch the re-auth path. connectionPersistence.ts:146 and the four route sites spread ...tokenData + expiresAt but omit tokenExpiresAt, which looks like the same omission on update — but both consumers prefer tokenExpiresAt and the create path is now correct, so I would rather that be judged separately than widened into this PR.

ntdatt812 added a commit to ntdatt812/OmniRoute that referenced this pull request Aug 24, 2026
@ntdatt812

Copy link
Copy Markdown
Contributor Author

The red shard is not from this diff.

Unit Tests fast-path (1/4) fails on one case, the repo-wide OpenAPI ratchet:

✖ openapi.yaml does not regress documented-operation coverage below the agreed floor
  AssertionError: OpenAPI operation coverage regressed: 34.5% < floor 34.6%

This PR adds no route and touches no spec — the diff is one string on a create-path allowlist in src/lib/db/providers.ts, one test, and a changelog fragment. PR #11370 fails the identical case, so the floor is simply above what the base currently achieves.

Build (advisory) and dast-smoke are likewise unrelated to a DB allowlist entry.

Green here, and these are the ones that would have caught a mistake in this change: Unit Tests fast-path (2/4), (3/4), (4/4), Vitest, Merge integrity, No new ESLint warnings, Docs Gates, Change Classification, semgrep.

The new case is in shard 3 and passes there. Locally: oauth-connection-tokenexpiresat-5326 3 pass / 0 fail, and reverting the one string gives 2 pass / 1 fail.

@diegosouzapw
diegosouzapw changed the base branch from release/v3.8.50 to release/v3.8.51 August 24, 2026 23:01
@diegosouzapw

Copy link
Copy Markdown
Owner

Re-homed to release/v3.8.51: v3.8.50 entered its release freeze, so the branch now belongs to the release captain and development continues on the next cycle. Nothing is wrong with this PR — it just needed a live base. No action needed from you; CI will re-run against the new base.

@diegosouzapw

Copy link
Copy Markdown
Owner

Great catch and exemplary validation — the round-trip test plus your own mutation check is exactly how this should be done, and we independently confirmed the bug still exists on release/v3.8.51 and that no equivalent fix landed there. One structural blocker: your branch sits on top of release/v3.8.50 tip, so the PR currently carries ~19 unrelated v3.8.50-only commits toward v3.8.51. Please rebase your two commits onto the current release/v3.8.51 tip — we verified zero conflicts (both touched files are byte-identical between the two bases) and the diff will collapse to just the allowlist string, the test, and the changelog fragment.

`createProviderConnection` copies optional fields onto the row through an
allowlist. `tokenExpiresAt` was not on it and the initial object does not set it
either, so `_insertConnectionRow` always bound `conn.tokenExpiresAt || null` —
NULL on every insert.

That makes diegosouzapw#5326 a no-op end to end. `buildOAuthConnectionCreatePayload` mirrors
the computed expiry into `tokenExpiresAt` precisely so the dashboard badge does
not flash "Token Expired" before the first background refresh, and the value was
discarded one layer down. The update path already carries it, so a connection
only gained the field after its first refresh.

The existing diegosouzapw#5326 test asserts the payload object and never persists it, which
is why it stayed green throughout. The new case creates the connection and reads
it back through `getProviderConnections`.
@ntdatt812
ntdatt812 force-pushed the fix/create-connection-tokenexpiresat branch from 745303a to 041a436 Compare August 25, 2026 06:57
@ntdatt812

Copy link
Copy Markdown
Contributor Author

Rebased onto the current release/v3.8.51 tip (b39e5ecb2).

The branch had been cut before v3.8.51 was branched, so this PR was showing 21 commits / 131 files — nineteen of them other people's already-merged work — and the merge had gone dirty. It is now 2 commits / 3 files, and mergeable again.

The defect is still present on the new base. optionalFields in createProviderConnection reads

"accessToken",
"refreshToken",
"expiresAt",
"tokenType",
"scope",
"idToken",

with no tokenExpiresAt, so a created row stores NULL however good the payload is — while the update path already carries data.tokenExpiresAt.

Re-verified after the rebase, tests/unit/oauth-connection-tokenexpiresat-5326.test.ts:

✔ buildOAuthConnectionCreatePayload mirrors expiresAt into tokenExpiresAt (#5326)
✔ buildOAuthConnectionCreatePayload keeps tokenExpiresAt null when expiry is unknown
✔ a created connection keeps tokenExpiresAt through the database
pass 3, fail 0

Mutation — deleted the single "tokenExpiresAt", line from the allowlist (verified the file went from 1 occurrence to 0) and re-ran the same file:

✔ buildOAuthConnectionCreatePayload mirrors expiresAt into tokenExpiresAt (#5326)
✔ buildOAuthConnectionCreatePayload keeps tokenExpiresAt null when expiry is unknown
✖ a created connection keeps tokenExpiresAt through the database
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal
pass 2, fail 1

The two pre-existing assertions stay green under the mutation, and that is the whole reason this survived: they check the object buildOAuthConnectionCreatePayload returns, never the row that reaches SQLite. The third test persists through createProviderConnection and reads back through getProviderConnections, so it fails the moment the allowlist drops the field again.

Restored, and the diff is back to +52/-5 across the three files.

@ntdatt812

Copy link
Copy Markdown
Contributor Author

CI came back red on 9 checks after the rebase. All nine are the base, and the rebase is what pulled them in — the branch had been sitting on a pre-v3.8.51 cut where they did not exist yet.

The failing set is identical on unrelated PRs cut from the same base.

PR failing checks
#11470 Build (advisory), Docs Gates (fast-path), Fast Quality Gates, No new ESLint warnings, Unit Tests fast-path (1–4/4), Vitest (fast-path)
#11471 the same nine
this PR the same nine

Unit Tests fast-path (1/4) — the eight failing tests here are byte-for-byte the ones failing on #11470, which contains none of this diff:

✖ GET /.well-known/agent.json returns 6 skills
✖ Agent Card includes list-capabilities skill entry
✖ list-capabilities entry has required tags [discovery, capabilities]
✖ list-capabilities entry has at least one example question
✖ Agent Card includes all 5 original skills
✖ hard-lease credential, executor, and connection-query inventory has no unclassified site
✖ GOLDEN provider.ts translate-path is stable across all providers
✖ LEDGER-4: minimax-m3 vision metadata matches each provider

Agent-card skill counts, a hard-lease inventory sweep, a golden translate-path snapshot and a minimax-m3 metadata ledger. None of them reads createProviderConnection, and oauth-connection-tokenexpiresat-5326.test.ts does not appear anywhere in those logs.

No new ESLint warnings fails with

There are suppressions left that do not occur anymore.
Consider re-running the command with `--prune-suppressions`.

Stale entries in config/quality/eslint-suppressions.json — this diff adds lines, it does not remove the last occurrence of any suppressed warning. The same gate is red right now on #11473, #11471, #11470, #11469 and #11468.

This PR's own tests pass in CI, from the fast-path shard logs on this head:

✔ buildOAuthConnectionCreatePayload mirrors expiresAt into tokenExpiresAt (#5326)
✔ buildOAuthConnectionCreatePayload keeps tokenExpiresAt null when expiry is unknown
✔ a created connection keeps tokenExpiresAt through the database

The third is the new one, and it is the one that goes red the moment "tokenExpiresAt" leaves the allowlist.

I have deliberately not folded a base fix into this PR — the diff stays 3 files. Happy to send the suppressions prune separately if that is useful.

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.

2 participants