feat: git sync — webhook, prune, manual flow (Plan C of issue #16)#140
Conversation
Adds disabled_at column to workflow_definitions and git_repo_workflows join table to support prune behavior for GitOps sync (issue #16). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Pruned field to Report, call RecordSeen+Reenable per file, and drive Disable+audit+RemoveSeenRecords for stale workflows when Prune is set. Use DB clock for syncStart to avoid Go/Postgres time skew. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Registers POST /hooks/git/<repo-id>: HMAC-SHA256 verifies against webhook_secret, emits git.push.received audit event, fires SyncRepo in a goroutine for immediate 202 response. Adds Store.GetByID (no team_id filter) and wires RepoStore/GitDriver into Server. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a cloneBasePath parameter to Store.Delete so the CLI removes the on-disk clone directory (filepath.Join(cloneBasePath, repoID)) after the DB transaction commits. Filesystem failures are logged via slog.Warn but never fail the call — an orphan directory is preferable to an orphan row. The CLI resolves the path from cfg.Storage.Path (same fallback as serve). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds PlanRepo (dry-run: pull + discover + classify without any DB writes or audit events), and exposes it via `mantle repos plan <name>`. Also adds `mantle repos apply <name>` as an explicit manual-sync alias for operators using auto_apply:false repos. Refactors newReposSyncCommand to share buildSyncDriver so all three commands use identical driver-selection logic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add --webhook-secret flag to repos add and repos update (blocker) - Remove stray fmt.Println from webhook test - Clarify plan/apply/update help text, mention commands in repos Long - Sanitize last_sync_error for pull/discover failure paths Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gosec G118 (CWE-400) flags goroutines that use context.Background() when a request-scoped context is available. Two fixes: - emitGitAudit: called synchronously while the request is still in flight, so r.Context() is correct and preserves trace/deadline info. - background SyncRepo goroutine: context.WithoutCancel(r.Context()) propagates request values (e.g. trace IDs) without tying the goroutine's lifetime to the HTTP request, so it survives after the 202 response is sent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add WebhookSecret field to Repo struct (was dropped during rebase) - Restore reposCtx to return (context.Context, *config.Config); Plan C tests call store.Get with a real context — the 1-return form was wrong - Update govulncheck exclusions to include go-git transitive dep vulns (matching feat/git-sync-engine) and refactor with KNOWN variable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 14 minutes and 11 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (21)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f07f8df758
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| `INSERT INTO git_repos | ||
| (team_id, name, url, branch, path, poll_interval, credential, auto_apply, prune) | ||
| VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) | ||
| (team_id, name, url, branch, path, poll_interval, credential, auto_apply, prune, webhook_secret) | ||
| VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) | ||
| RETURNING id, name, url, branch, path, poll_interval, credential, | ||
| auto_apply, prune, enabled, created_at, updated_at`, | ||
| auto_apply, prune, enabled, webhook_secret, created_at, updated_at`, |
There was a problem hiding this comment.
Add the webhook_secret migration before using it
This insert now references git_repos.webhook_secret, but the only migrations in this commit still leave that column absent (019_git_repos.sql explicitly says it is not created, and 020_git_sync_prune.sql only adds prune metadata). On a freshly migrated database, mantle repos add and the new webhook-secret tests fail with column "webhook_secret" does not exist, so repo registration is broken until a migration adds the column.
Useful? React with 👍 / 👎.
| Metadata: map[string]string{"name": repoRow.Name}, | ||
| }) | ||
|
|
||
| // Fire sync in the background so the provider gets its 202 fast. |
There was a problem hiding this comment.
Preserve the repo team when syncing from webhooks
For webhooks on repos owned by any non-default team, this goroutine calls SyncRepo with the unauthenticated HTTP request context after GetByID deliberately bypasses team scoping. SyncRepo/workflow.Save derive team_id from auth.TeamIDFromContext, which falls back to the default team without an authenticated user, so a push to another team's repo writes or prunes workflow definitions in the default team instead of the repo's team.
Useful? React with 👍 / 👎.
Migration 019 intentionally deferred this column to Plan C. The column is required by store.Create and store.Update which pass webhook_secret as parameter $10 in the INSERT/UPDATE statements. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
store.Get selected 15 columns but did not include webhook_secret, so the field always read back as empty string. Added it to the SELECT and Scan alongside the other nullable fields, matching store.GetByID. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Plan C (final) for issue #16 — completes the core GitOps loop. Plans A (#133) and B (#134) are already merged.
workflow_definitions.disabled_atand agit_repo_workflowsjoin table. The sync engine records each seen workflow per repo, then disables any that disappeared whenprune: true. Reappearing files cleardisabled_aton the next sync. History preserved.POST /hooks/git/<repo-id>verifies HMAC-SHA256 viaX-Hub-Signature-256, emitsgit.push.received, and firesSyncRepoin a goroutine so the provider gets its 202 fast. Body size capped at 1 MiB. Constant-time HMAC comparison.mantle repos plan <name>previews pending changes without writing;mantle repos apply <name>syncs on demand forauto_apply: falserepos.repo.Store.Deletenow removes the cloned working tree under<artifact>/git/<repo-id>/when a repo is unregistered. DBON DELETE CASCADEhandles the join-table rows automatically.sanitizeURLstripsuser:password@from URLs in audit metadata andlast_sync_error, so go-git errors echoing a clone URL can't leak inline credentials.mantle repos update— wires the existingStore.Updateinto a CLI subcommand for in-place edits.--webhook-secretflag onrepos addandrepos updateso operators can arm HMAC verification without raw SQL.git.sync.pruned,git.push.received.What operators can do with A + B + C
git_sync.repos:inmantle.yaml)mantle serve— reconciler materializes config, poller syncs each enabledauto_apply: truerepo atpoll_intervalPOST /hooks/git/<id>+X-Hub-Signature-256) for immediate syncsplan), manually apply (apply), or force-sync (sync) on any repoCloses the core GitOps loop of #16.
Test plan
go test ./...passes in CIgo test ./internal/repo/sync/— prune, URL sanitization, PlanRepogo test ./internal/repo/— GetByID, Delete cleanup, webhook_secret persistencego test ./internal/server/— webhook handler (202 / 403 / 404)go test ./internal/cli/— repos plan, apply, update, webhook-secret🤖 Generated with Claude Code