Skip to content

feat(sync_jobs): prep sync_job_id for int4 → bigint widening (NAN-5491 Phase 0)#6260

Merged
pfreixes merged 3 commits into
masterfrom
pau/nan-5491-phase0-bigint-prep
Jun 1, 2026
Merged

feat(sync_jobs): prep sync_job_id for int4 → bigint widening (NAN-5491 Phase 0)#6260
pfreixes merged 3 commits into
masterfrom
pau/nan-5491-phase0-bigint-prep

Conversation

@pfreixes

Copy link
Copy Markdown
Contributor

Phase 0 groundwork for NAN-5491 — widening sync_jobs.id from int4 (exhausting ~July 2026) to bigint. Everything here is backward-compatible and a no-op until the columns are actually widened in later phases. Full plan: Analysis: migration of sync_job_id.

  • records upsert casts sync_job_id as bigint (was integer) so a value above the int4 ceiling won't be rejected at the cast once the column is widened. Safe today: bigint → int4 is an implicit assignment cast for in-range values, and all current ids are in range.
  • job.service coerces sync_jobs.id to a JS number on read (node-pg returns bigint columns as strings), keeping Job.id / nangoProps.syncJobId real numbers after the widening.
  • Caps syncJobId at Number.MAX_SAFE_INTEGER in the persist param schemas and the nangoProps / lambda-runner schemas. We keep number (no global pg type parser), so 2^53−1 is the enforced ceiling — still ~4M× the current 2^31 limit.

Deliberately deferred: narrowing getRecordsToUpdate's SELECT * to drop sync_job_id — it'd require changing decryptRecordData's type and the fetched value is never read, so it's harmless; left as optional follow-up.

Test plan

  • tsc -b — no type errors in the changed packages (shared, records, persist, jobs, lambda-runner)
  • No behavior change for current (< 2^31) ids — Job.id stays a number
  • After Phase 1 widens the records column: a sync_job_id > 2^31 round-trips through the records upsert

…1 Phase 0)

Backward-compatible groundwork; all no-ops until the columns actually widen in
later phases.

- records upsert: cast sync_job_id as ::bigint (was ::integer) so values > 2^31
  won't fail at the cast once the column is widened.
- job.service: normalize sync_jobs.id to a JS number on read (node-pg returns
  bigint as a string) and assert it stays within Number.MAX_SAFE_INTEGER, so
  Job.id / nangoProps.syncJobId stay real numbers.
- cap syncJobId at Number.MAX_SAFE_INTEGER in the persist param schemas and the
  nangoProps / lambda-runner schemas.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@linear

linear Bot commented May 27, 2026

Copy link
Copy Markdown

NAN-5491

@superagent-security superagent-security Bot added the contributor:verified Contributor passed trust analysis. label May 27, 2026
@pfreixes pfreixes requested a review from a team May 27, 2026 13:58
@superagent-security superagent-security Bot added the pr:verified PR passed security analysis. label May 27, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 42cada2da7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/records/lib/models/records.ts Outdated
Comment on lines +494 to +495
? '(?::integer, ?::text, ?::uuid, ?::text, ?::text, ?::uuid, ?::bigint, ?::timestamptz, ?::timestamptz)'
: '(?::integer, ?::text, ?::uuid, ?::text, ?::text, ?::uuid, ?::bigint, ?::timestamptz)'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Widen records_seen with the upsert path

When this path receives a sync_job_id above the int4 ceiling after records.sync_job_id is widened, the upsert still calls insertSeenEntry for every non-empty batch, and records_seen.sync_job_id is still declared as integer in packages/records/lib/db/migrations/20260512000100_create_records_seen.ts. That insert will reject the same >2^31 id even though this CTE now accepts it as bigint, so the promised large-id records upsert still fails unless the seen table/path is widened at the same time.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 7 files

Confidence score: 3/5

  • There is a concrete regression risk in packages/shared/lib/services/sync/job.service.ts: coercing bigint to number can round values above Number.MAX_SAFE_INTEGER, which may cause updates to affect the wrong sync job.
  • Given the issue is medium severity (6/10) with high confidence (8/10) and directly user-impacting data behavior, this sits in a moderate-risk zone rather than a safe-to-merge baseline.
  • Pay close attention to packages/shared/lib/services/sync/job.service.ts - guard bigint-to-number conversion or enforce a DB-side cap to prevent ID precision loss.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/shared/lib/services/sync/job.service.ts">

<violation number="1" location="packages/shared/lib/services/sync/job.service.ts:16">
P2: Guard this bigint→number coercion. Without a database-side cap, ids above `Number.MAX_SAFE_INTEGER` will be rounded here and later updates can target the wrong sync job.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

// consumers (Job.id, nangoProps.syncJobId) expect a JS number. Coerce on read. id is a NOT NULL
// PK and the sequence is capped at Number.MAX_SAFE_INTEGER, so the value always fits exactly.
function normalizeSyncJobId<T extends { id: number }>(job: T): T {
return { ...job, id: Number(job.id) };

@cubic-dev-ai cubic-dev-ai Bot May 27, 2026

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.

P2: Guard this bigint→number coercion. Without a database-side cap, ids above Number.MAX_SAFE_INTEGER will be rounded here and later updates can target the wrong sync job.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/shared/lib/services/sync/job.service.ts, line 16:

<comment>Guard this bigint→number coercion. Without a database-side cap, ids above `Number.MAX_SAFE_INTEGER` will be rounded here and later updates can target the wrong sync job.</comment>

<file context>
@@ -9,6 +9,13 @@ import type { ConnectionJobs, SyncResultByModel } from '@nangohq/types';
+// consumers (Job.id, nangoProps.syncJobId) expect a JS number. Coerce on read. id is a NOT NULL
+// PK and the sequence is capped at Number.MAX_SAFE_INTEGER, so the value always fits exactly.
+function normalizeSyncJobId<T extends { id: number }>(job: T): T {
+    return { ...job, id: Number(job.id) };
+}
+
</file context>
Suggested change
return { ...job, id: Number(job.id) };
const id = Number(job.id);
if (!Number.isSafeInteger(id)) {
throw new Error(`sync_jobs.id ${job.id} exceeds Number.MAX_SAFE_INTEGER`);
}
return { ...job, id };
Fix with Cubic

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

that's a good point.

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.

Thanks!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If we ever reach that situation we are fuck and we will require to change the contract and no longer use Number, also we should consider that took us 4 years to reach the limit of 2^32, the 2^53 will most likely never happen in the following 8.4 million years.

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.

Thanks for the context—understood.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Im expecting that all of us will gone or have colonized Mars by that time

records.sync_job_id is unused (write-only since records_seen took over the
generation logic), so it will be decoupled (write NULL) and dropped rather than
widened — the cast is no longer needed. Keeps this PR to sync_jobs prep only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/records/lib/models/records.ts">

<violation number="1">
P1: `sync_job_id` is being cast back to `integer` here, so this upsert will still reject ids above the int4 limit once writers start producing bigint job ids.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

// consumers (Job.id, nangoProps.syncJobId) expect a JS number. Coerce on read. id is a NOT NULL
// PK and the sequence is capped at Number.MAX_SAFE_INTEGER, so the value always fits exactly.
function normalizeSyncJobId<T extends { id: number }>(job: T): T {
return { ...job, id: Number(job.id) };

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

that's a good point.

@superagent-security superagent-security Bot removed contributor:verified Contributor passed trust analysis. pr:verified PR passed security analysis. labels Jun 1, 2026
@pfreixes pfreixes added this pull request to the merge queue Jun 1, 2026
Merged via the queue into master with commit 619a1a0 Jun 1, 2026
28 checks passed
@pfreixes pfreixes deleted the pau/nan-5491-phase0-bigint-prep branch June 1, 2026 07:28
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.

3 participants