Skip to content

feat: emit credly expiries from the badge state table with a daily sync - #5141

Merged
ulemons merged 2 commits into
mainfrom
feat/issue-873-credly-expiry-sync
Oct 28, 2026
Merged

ulemons merged 2 commits into
mainfrom
feat/issue-873-credly-expiry-sync

Conversation

@ulemons

@ulemons ulemons commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Closes linuxfoundation/lfx-cdp-insights-ops#873

What

Last piece of the badge state table: expirySync, a second sync on the Credly manifest, every 24 hours.

  • Reads the accepted badges past their expiry that were not emitted yet, in batches of 500, from integration."credlyBadges". No Credly call.
  • Builds expired-badge from the stored row (expiredBadgeActivityFromRow): timestamp expiresAt, template name and url from the row, badge url from its id, earner email and name of the first sight. Same member shape as the live badge, through one shared helper.
  • Emits per stored segment and integration, marks each group right after its emit (expiredEmittedAt). A badge is emitted as expired once; an expiry extended afterwards stays silent, as agreed.
  • Shadow run: emits the shadow records, marks nothing, reads one batch.
  • Returns complete: false when the run budget ends with rows still due, so the worker resumes in a minute rather than a day. Run log credly_expiry_sync_run with due, emitted, marked, skipped, drained.

Operations

The manifest change does not create the sync unit: pnpm script:seed-credly-sync-unit --segment-id <anchor segment> must be re-run once in staging and later in prod. It leaves the badgePoll unit untouched and creates expirySync with emitEnabled = false; switch it on like the poll's. There is one Credly organization, so one expirySync unit drains the whole table by design.

Code review triage

Applied: short defer instead of the daily cadence when the budget runs out; short-page check on the raw batch; mark per target group after its emit; groups carry only the target; comments trimmed.

Not applied: scoping the due list by integration (one organization, one unit); dropping the pre-emit schema parse (same pattern as the poll, it turns a bad row into a warn and skip).

Verification

169 Credly tests. Staging: re-run the seed, force the expirySync unit; the table already holds a CKAD badge expired on 2025-02-28, which must come out as one expired-badge on umbertoTest5.1 with expiredEmittedAt set; a second run must emit nothing.

No reviewer requested.

A daily expirySync reads the accepted badges past their date that were not emitted yet,
builds expired-badge from the stored row, emits it on the segment of the first sight and marks
the row. No Credly call. A shadow run emits shadow records and marks nothing.

Refs linuxfoundation/lfx-cdp-insights-ops#873

Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
@ulemons ulemons self-assigned this Oct 28, 2026
Copilot AI balanced review requested due to automatic review settings October 28, 2026 11:51
@cursor

cursor Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
New activity emission path and SQL join rules determine which badges become due; misconfiguration could skip or mis-route expiries, but changes are scoped to Credly badge state and existing emit patterns.

Overview
Adds a daily expirySync job on the Credly connector (alongside badgePoll) that emits expired-badge activities from integration."credlyBadges"—no Credly API calls.

The sync loads due rows in 500-row batches (accepted, past expiresAt, expiredEmittedAt null), maps them with new expiredBadgeActivityFromRow (timestamp expiresAt, first-sight segment/earner), groups emits by stored segmentId/integrationId, and sets expiredEmittedAt after each group. Shadow runs emit once without marking; complete: false when run budget is exhausted so the worker can retry soon.

Data layer: listDueCredlyExpiries now joins live Credly integrations (same segment, not deleted), supports an after cursor for pagination past stuck rows, and tests cover deleted/moved integrations. CredlyDeps wires the new list/mark helpers; mapper shares memberFromEarner between live and row-based activities.

Reviewed by Cursor Bugbot for commit d31d762. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI 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.

🟡 Changes recommended

Expiry processing can indefinitely block valid records behind 500 unmappable rows and publish and permanently mark expiries for soft-deleted integrations or integrations moved to another segment.

2 open issues
What changed in this PR

Adds a daily Credly expiry sync that generates expired-badge activities from stored badge state without calling Credly.

Changes:

  • Processes expiries in batches, emits to stored targets, and marks each group after emission.
  • Shares earner mapping between live and stored badges.
  • Adds coverage for batching, run-budget deferral, skipped rows, shadow mode, and dependency wiring.
File Description
services/​libs/​connectors/​src/​connectors/​credly/​syncs/​expirySync.ts Implements batched expiry emission and completion tracking.
services/​libs/​connectors/​src/​connectors/​credly/​syncs/​expirySync.test.ts Tests expiry mapping and sync behavior.
services/​libs/​connectors/​src/​connectors/​credly/​syncs/​badgePoll.test.ts Updates dependency mocks for the expanded interface.
services/​libs/​connectors/​src/​connectors/​credly/​mapper.ts Maps stored expiries and extracts shared earner mapping.
services/​libs/​connectors/​src/​connectors/​credly/​index.ts Registers the daily expiry sync.
services/​libs/​connectors/​src/​connectors/​credly/​discover.test.ts Verifies the new sync registration and cadence.
services/​libs/​connectors/​src/​connectors/​credly/​deps.ts Exposes due-expiry reads and emission marking.
services/​libs/​connectors/​src/​connectors/​credly/​deps.test.ts Tests expiry dependency delegation.
services/​apps/​connectors_worker/​src/​bin/​dry-run-credly-badge-poll.ts Adds expiry dependency stubs to the dry-run script.

Review effort: Balanced

Comment thread services/libs/connectors/src/connectors/credly/syncs/expirySync.ts Outdated
Comment on lines +59 to +61
await ctx.emit(activities, target)
counters.emitted += activities.length
if (ctx.emitEnabled) counters.marked += await ctx.deps.markExpiriesEmitted(badgeIds)
…tions

Due expiries are read only for badges whose integration is still live on their segment, and the
sync advances on a (expiresAt, badgeId) cursor so a row it could not mark never blocks the rest.

Refs linuxfoundation/lfx-cdp-insights-ops#873

Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
Copilot AI balanced review requested due to automatic review settings October 28, 2026 12:02

Copilot AI 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.

🟢 Approval recommended

No active review issues remain for the daily Credly expiry sync.

1 open issue
1 resolved since last review

Review effort: Balanced

@ulemons
ulemons merged commit 05dc16a into main Oct 28, 2026
15 of 16 checks passed
@ulemons
ulemons deleted the feat/issue-873-credly-expiry-sync branch October 28, 2026 12:24
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