[Feature] Scheduled GitHub source drift-check job (#1176) - #1184
Merged
Conversation
resolveRefHeadSha now classifies a GitHub rate-limit response (429, or 403 with X-RateLimit-Remaining:0 / a Retry-After header) into a typed GitHubRateLimitError carrying the recommended wait (from Retry-After or X-RateLimit-Reset). Other non-OK statuses stay generic 'transient' errors. This lets the batch drift scheduler back off cleanly instead of hammering a limited API. Part of #1176
Extract the probe-result-to-verdict mapping (classifyProbeResult) and the settings>env>anonymous token resolution (pickSourceSyncToken) out of the single-skill drift path so the upcoming batch job can reuse them verbatim — the batch probes once per (repo,ref) group and classifies each member against its own lastSyncedCommit. No behavior change: runSourceDriftCheck and SkillService.resolveSourceSyncToken now delegate to the shared helpers. Part of #1176
Add findGithubSourcedSkills({ notCheckedSince }) — enumerates github-sourced skills due for a check (excluding pinned 40-hex refs via a $not-regex, and skills checked more recently than the cutoff), returning light { guid, source, ownerId } rows. Back it with a partial index over github sources ordered by lastCheckedAt. Extract the source-coercion out of mapDoc into a shared coerceSkillSource helper so both read paths stay consistent.
Part of #1176
Add the skill.source_broken category to the canonical NOTIFICATION_CATEGORIES vocabulary (so the boot migration's allow-list can't wipe it) and a notifySourceBroken() method that tells a skill's owner their linked GitHub source could not be resolved, deep-linking to the skill so they can re-link it. Part of #1176
) runSourceDriftJob enumerates due github-sourced skills, coalesces them by (repo,ref) so a shared upstream costs exactly one probe, and persists each skill's drift verdict. A broken source marks the skill broken and notifies its owner; a rate-limit signal short-circuits the rest of the tick (the next fire retries); pinned refs are skipped; probes run under a bounded concurrency with optional jitter. No single skill's persist/notify/probe failure is allowed to abort the run. Records state only — no publish (that is #1177). Part of #1176
createSourceSyncScheduler clones the mirror scheduler's multi-pod-safe Agenda pattern (per-fire row lock on agendaJobs) for the inbound drift job: a source-drift-check job whose cron cadence is driven by settings.sourceSync.pollSchedule (Asia/Singapore), plus a 1-minute sync tick that re-registers it cluster-wide when an admin changes the cadence. Exposes getScheduledRunStatus() for parity with the mirror scheduler. Part of #1176
Construct + start the source-sync scheduler next to the mirror scheduler, injecting the drift job with the skill repo, settings, env token fallback, and the notification service. A start failure logs and leaves this pod without the scan rather than crashing boot; shutdown stops it before the mirror scheduler. Part of #1176
…e-drift-scheduler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 1 of 5 for automatic sync of GitHub-sourced skills (
Depends on #1175, now merged). Adds the scheduled inbound drift-check job — the pull-based baseline that discovers when a linked upstream repo has moved, with no manual trigger. It records drift state only; automatic re-publish is #1177.What this does
sourceSyncScheduler.ts) cloned from the mirror scheduler's proven pattern (per-fire row lock onagendaJobs). Cadence fromsettings.sourceSync.pollSchedule(Asia/Singapore); a 1-minute tick re-registers it cluster-wide on an admin cadence change.sourceDriftJob.ts) — enumerates github-sourced skills due for a check, coalesces by(repo, ref)so a shared upstream costs exactly one probe, and persists each skill's verdict against its ownlastSyncedCommit. Bounded concurrency + jitter; pinned 40-hex refs skipped.404/private/deleted) marks the skillbrokenand notifies its owner (newskill.source_brokennotification); a GitHub rate-limit signal (typedGitHubRateLimitErrorfrom429/403+Retry-After) short-circuits the rest of the tick; no single skill's probe/persist/notify failure aborts the run.findGithubSourcedSkills({ notCheckedSince })(excludes pinned refs + freshly-checked skills, returns{ guid, source, ownerId }), backed by a partial index over github sources bylastCheckedAt.Testing
bun test src/domains/skills/crud src/domains/notifications src/domains/settings→ 649 pass, 0 fail.classifyProbeResult+pickSourceSyncToken; rate-limit classification; and repository query + dot-path setter against realmongodb-memory-server.Ornn@developprobed exactly once for two skills, stale commit →drifted(real HEAD persisted), real HEAD →in_sync, missing repo →broken+ notified once, counts checked=2/drifted=1/broken=1.tsc --noEmitclean;eslintclean.Commits (dependency-ordered, buildable per commit)
feat(api)typedGitHubRateLimitErrorrefactor(api)shareclassifyProbeResult+pickSourceSyncTokenfeat(api)repository query + setter + partial indexfeat(api)skill.source_brokennotificationfeat(api)source-drift batch job (coalesce + backoff)feat(api)source-sync Agenda schedulerfeat(api)wire scheduler into bootstrapdocschangesetCloses #1176