Go · 3639 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package sigverify
4
5 import (
6 "context"
7 "fmt"
8
9 "github.com/jackc/pgx/v5/pgtype"
10
11 reposdb "github.com/tenseleyFlow/shithub/internal/repos/sqlc"
12 "github.com/tenseleyFlow/shithub/internal/worker"
13 )
14
15 // BackfillPayload is the on-the-wire schema for the gpg:backfill
16 // worker job. One job per repo; the handler walks every signed
17 // commit and tag on the repo's default branch and writes
18 // commit_verification_cache rows.
19 //
20 // The handler is intentionally NOT key-scoped — it verifies every
21 // signed object regardless of which user key signed it. The
22 // orchestrator returns ReasonUnknownKey for signatures it can't
23 // resolve; the cache row carries that state forward and the
24 // rendering path treats it as "Unverified". When new keys arrive
25 // later, those previously-unknown-key cache rows will get
26 // re-stamped by the next DispatchForKey or DispatchAll run.
27 type BackfillPayload struct {
28 RepoID int64 `json:"repo_id"`
29 }
30
31 // DispatchForKey enqueues backfill jobs for every repo owned by the
32 // user who just added a GPG key. This is the eager-backfill path:
33 // "user uploads key → existing signed commits in their own repos
34 // retroactively get Verified badges within minutes" — the gh
35 // behavior we're matching.
36 //
37 // We deliberately scope to OWNED repos rather than every repo the
38 // user has ever committed to. Owned repos are the common case
39 // (~90%); cross-repo authorship (PR contributions to others'
40 // repos) gets picked up by the next DispatchAll run. Without a
41 // commits-author index, walking every repo on every key upload
42 // would be O(repos × commits) per upload and is the wrong cost
43 // shape for the eager path.
44 //
45 // The caller passes (db) so this can run inside an existing
46 // transaction (typical pattern in the add-key handler: insert key
47 // row → insert subkey rows → dispatch backfill → commit). Notify
48 // is called inside the same tx so workers wake up on commit, not
49 // before.
50 func DispatchForKey(ctx context.Context, db reposdb.DBTX, userID int64) error {
51 rq := reposdb.New()
52 rows, err := rq.ListReposForOwnerUser(ctx, db, pgtype.Int8{Int64: userID, Valid: true})
53 if err != nil {
54 return fmt.Errorf("sigverify: list user repos: %w", err)
55 }
56 for _, row := range rows {
57 if _, err := worker.Enqueue(ctx, db, worker.KindGPGBackfill, BackfillPayload{
58 RepoID: row.ID,
59 }, worker.EnqueueOptions{}); err != nil {
60 return fmt.Errorf("sigverify: enqueue backfill for repo %d: %w", row.ID, err)
61 }
62 }
63 if err := worker.Notify(ctx, db); err != nil {
64 return fmt.Errorf("sigverify: notify workers: %w", err)
65 }
66 return nil
67 }
68
69 // DispatchAll enqueues backfill jobs for every active repo in the
70 // system. Called by `shithubd gpg-backfill-all`. Returns the number
71 // of jobs enqueued so the admin command can log progress.
72 //
73 // Unlike DispatchForKey this is NOT bounded to a single user; it
74 // walks every active repo. Use sparingly — typically once on
75 // initial S51 deploy, or after a known mass-key-upload event.
76 func DispatchAll(ctx context.Context, db reposdb.DBTX) (int, error) {
77 rq := reposdb.New()
78 rows, err := rq.ListAllActiveReposWithOwner(ctx, db)
79 if err != nil {
80 return 0, fmt.Errorf("sigverify: list all repos: %w", err)
81 }
82 for _, row := range rows {
83 if _, err := worker.Enqueue(ctx, db, worker.KindGPGBackfill, BackfillPayload{
84 RepoID: row.ID,
85 }, worker.EnqueueOptions{}); err != nil {
86 return 0, fmt.Errorf("sigverify: enqueue backfill for repo %d: %w", row.ID, err)
87 }
88 }
89 if err := worker.Notify(ctx, db); err != nil {
90 return 0, fmt.Errorf("sigverify: notify workers: %w", err)
91 }
92 return len(rows), nil
93 }
94