···4040 Value string `json:"value" cborgen:"value"`
4141}
42424343+// Pipeline_ScheduleTriggerData is a "scheduleTriggerData" in the sh.tangled.pipeline schema.
4444+type Pipeline_ScheduleTriggerData struct {
4545+ // cron: The cron expression that triggered this pipeline
4646+ Cron string `json:"cron" cborgen:"cron"`
4747+}
4848+4349// Pipeline_PullRequestTriggerData is a "pullRequestTriggerData" in the sh.tangled.pipeline schema.
4450type Pipeline_PullRequestTriggerData struct {
4551 Action string `json:"action" cborgen:"action"`
···6167 Manual *Pipeline_ManualTriggerData `json:"manual,omitempty" cborgen:"manual,omitempty"`
6268 PullRequest *Pipeline_PullRequestTriggerData `json:"pullRequest,omitempty" cborgen:"pullRequest,omitempty"`
6369 Push *Pipeline_PushTriggerData `json:"push,omitempty" cborgen:"push,omitempty"`
7070+ Schedule *Pipeline_ScheduleTriggerData `json:"schedule,omitempty" cborgen:"schedule,omitempty"`
6471 Repo *Pipeline_TriggerRepo `json:"repo" cborgen:"repo"`
6572}
6673
+10
appview/db/db.go
···676676 create index if not exists idx_webhooks_repo_at on webhooks(repo_at);
677677 create index if not exists idx_webhook_deliveries_webhook_id on webhook_deliveries(webhook_id);
678678 create index if not exists idx_site_deploys_repo_at on site_deploys(repo_at);
679679+680680+ create table if not exists pijul_change_pushes (
681681+ change_hash text not null,
682682+ committer_did text not null,
683683+ repo_at text not null,
684684+ channel text not null default '',
685685+ pushed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
686686+ primary key (change_hash, repo_at)
687687+ );
688688+ create index if not exists idx_pijul_change_pushes_repo on pijul_change_pushes(repo_at);
679689 `)
680690 if err != nil {
681691 return nil, err
+48
appview/db/pijul_changes.go
···11+package db
22+33+import "strings"
44+55+// StorePijulChangePush records that a change was pushed by a specific committer.
66+func StorePijulChangePush(e Execer, changeHash, committerDid, repoAt, channel string) error {
77+ _, err := e.Exec(`
88+ insert or ignore into pijul_change_pushes (change_hash, committer_did, repo_at, channel)
99+ values (?, ?, ?, ?)
1010+ `, changeHash, committerDid, repoAt, channel)
1111+ return err
1212+}
1313+1414+// GetVerifiedPijulChanges returns the set of change hashes that have a verified
1515+// push record (i.e., were pushed by a known committer via an authenticated path).
1616+// Returns a map of change_hash → committer_did.
1717+func GetVerifiedPijulChanges(e Execer, repoAt string, hashes []string) (map[string]string, error) {
1818+ if len(hashes) == 0 {
1919+ return make(map[string]string), nil
2020+ }
2121+2222+ placeholders := make([]string, len(hashes))
2323+ args := make([]any, 0, len(hashes)+1)
2424+ args = append(args, repoAt)
2525+ for i, h := range hashes {
2626+ placeholders[i] = "?"
2727+ args = append(args, h)
2828+ }
2929+3030+ rows, err := e.Query(`
3131+ select change_hash, committer_did from pijul_change_pushes
3232+ where repo_at = ? and change_hash in (`+strings.Join(placeholders, ",")+`)
3333+ `, args...)
3434+ if err != nil {
3535+ return nil, err
3636+ }
3737+ defer rows.Close()
3838+3939+ result := make(map[string]string)
4040+ for rows.Next() {
4141+ var hash, did string
4242+ if err := rows.Scan(&hash, &did); err != nil {
4343+ return nil, err
4444+ }
4545+ result[hash] = did
4646+ }
4747+ return result, rows.Err()
4848+}
+7-33
appview/ingester.go
···11351135}
1136113611371137// ingestPijulRefUpdate handles sh.tangled.pijul.refUpdate records published to
11381138-// the committer's PDS after a pijul push or merge. It updates the contributor
11391139-// punchcard using the number of changes pushed.
11381138+// the committer's PDS after a pijul push or merge.
11391139+// Punchcard updates are handled by the knotstream handler (ingestPijulRefUpdate
11401140+// in state/knotstream.go) to avoid double-counting.
11401141func (i *Ingester) ingestPijulRefUpdate(e *jmodels.Event) error {
11411141- if e.Commit.Operation != jmodels.CommitOperationCreate {
11421142- return nil
11431143- }
11441144-11451145- committerDid := e.Did
11461146-11471147- var record tangled.PijulRefUpdate
11481148- if err := json.Unmarshal(e.Commit.Record, &record); err != nil {
11491149- return fmt.Errorf("invalid pijulRefUpdate record: %w", err)
11501150- }
11511151-11521152- if record.Repo == "" || len(record.Changes) == 0 {
11531153- return nil
11541154- }
11551155-11561156- repoAt, err := syntax.ParseATURI(record.Repo)
11571157- if err != nil {
11581158- return fmt.Errorf("invalid repo AT URI %q: %w", record.Repo, err)
11591159- }
11601160-11611161- if _, err := db.GetRepoByAtUri(i.Db, repoAt.String()); err != nil {
11621162- // Unknown repo — ignore rather than error; the repo may not be
11631163- // registered on this appview instance.
11641164- return nil
11651165- }
11661166-11671167- return db.AddPunch(i.Db, models.Punch{
11681168- Did: committerDid,
11691169- Date: time.Now(),
11701170- Count: len(record.Changes),
11711171- })
11421142+ // No-op: the knotstream event path is the authoritative source for
11431143+ // punchcard updates. This handler exists so the firehose consumer
11441144+ // doesn't log unknown-NSID warnings.
11451145+ return nil
11721146}