From 586187ec2fcedcfa535283496fdd70a7a257e0d9 Mon Sep 17 00:00:00 2001 From: khaliqgant Date: Sun, 20 Sep 2026 18:18:29 -0700 Subject: [PATCH 1/3] fix(cli): surface webhook subscription health on list List webhook subscriptions now forwards lastDeliveryAt/lastSuccessAt and githubPrIdentityAuthorized from relayfile-cloud so operators can see whether a binding is authorized to follow PR identity and when it last delivered. --- cmd/relayfile-cli/control_plane.go | 31 +++++++++++++------ cmd/relayfile-cli/control_plane_test.go | 4 +++ .../relayfile-control-plane-v1.openapi.yaml | 19 ++++++++++++ packages/client/src/client.test.ts | 14 +++++++++ .../client/src/generated/control-plane.ts | 10 ++++++ 5 files changed, 69 insertions(+), 9 deletions(-) diff --git a/cmd/relayfile-cli/control_plane.go b/cmd/relayfile-cli/control_plane.go index 6d45f2a0..28e2f9a9 100644 --- a/cmd/relayfile-cli/control_plane.go +++ b/cmd/relayfile-cli/control_plane.go @@ -154,10 +154,19 @@ type listWebhookSubscriptionsResponse struct { WorkspaceID string `json:"workspaceId,omitempty"` } +type webhookSubscriptionHealth struct { + LastDeliveryAt *string `json:"lastDeliveryAt"` + LastSuccessAt *string `json:"lastSuccessAt"` + LastError *string `json:"lastError"` + ConsecutiveFailures int `json:"consecutiveFailures"` +} + type webhookSubscriptionSummary struct { - SubscriptionID string `json:"subscriptionId"` - URL string `json:"url"` - PathGlobs []string `json:"pathGlobs"` + SubscriptionID string `json:"subscriptionId"` + URL string `json:"url"` + PathGlobs []string `json:"pathGlobs"` + GithubPrIdentityAuthorized *bool `json:"githubPrIdentityAuthorized,omitempty"` + Health *webhookSubscriptionHealth `json:"health,omitempty"` } func runControlPlane(args []string, stdout io.Writer) error { @@ -514,9 +523,11 @@ func handleControlPlaneWebhookSubscription(w http.ResponseWriter, r *http.Reques return } var upstream []struct { - ID string `json:"id"` - URL string `json:"url"` - PathGlobs []string `json:"pathGlobs"` + ID string `json:"id"` + URL string `json:"url"` + PathGlobs []string `json:"pathGlobs"` + GithubPrIdentityAuthorized *bool `json:"githubPrIdentityAuthorized"` + Health *webhookSubscriptionHealth `json:"health"` } if err := commandClient.client.getJSON( r.Context(), @@ -529,9 +540,11 @@ func handleControlPlaneWebhookSubscription(w http.ResponseWriter, r *http.Reques subscriptions := make([]webhookSubscriptionSummary, 0, len(upstream)) for _, item := range upstream { subscriptions = append(subscriptions, webhookSubscriptionSummary{ - SubscriptionID: item.ID, - URL: item.URL, - PathGlobs: item.PathGlobs, + SubscriptionID: item.ID, + URL: item.URL, + PathGlobs: item.PathGlobs, + GithubPrIdentityAuthorized: item.GithubPrIdentityAuthorized, + Health: item.Health, }) } writeControlPlaneJSON(w, http.StatusOK, listWebhookSubscriptionsResponse{ diff --git a/cmd/relayfile-cli/control_plane_test.go b/cmd/relayfile-cli/control_plane_test.go index 7a574018..bf795242 100644 --- a/cmd/relayfile-cli/control_plane_test.go +++ b/cmd/relayfile-cli/control_plane_test.go @@ -381,6 +381,10 @@ func TestControlPlaneCloudIntegrationConformance(t *testing.T) { listedSubscriptions.Subscriptions[0].PathGlobs[0] != "/github/repos/acme/widgets/issues/**" { t.Fatalf("unexpected webhook subscription list: %#v", listedSubscriptions) } + if listedSubscriptions.Subscriptions[0].Health == nil || + listedSubscriptions.Subscriptions[0].Health.ConsecutiveFailures != 0 { + t.Fatalf("expected webhook subscription health to pass through: %#v", listedSubscriptions.Subscriptions[0].Health) + } var deleted map[string]bool status = controlPlaneJSON(t, client, http.MethodDelete, baseURL+"/v1/integrations/webhook-subscriptions", deleteWebhookSubscriptionRequest{ diff --git a/openapi/relayfile-control-plane-v1.openapi.yaml b/openapi/relayfile-control-plane-v1.openapi.yaml index 28005d46..4110d4cd 100644 --- a/openapi/relayfile-control-plane-v1.openapi.yaml +++ b/openapi/relayfile-control-plane-v1.openapi.yaml @@ -617,6 +617,25 @@ components: type: array items: type: string + githubPrIdentityAuthorized: + type: boolean + description: Whether `/pulls//**` identity expansion (title slugs, reviews, comments, checks via resource_ref) is enabled for this subscription. + health: + type: object + properties: + lastDeliveryAt: + type: string + format: date-time + nullable: true + lastSuccessAt: + type: string + format: date-time + nullable: true + lastError: + type: string + nullable: true + consecutiveFailures: + type: integer ErrorEnvelope: type: object required: [error] diff --git a/packages/client/src/client.test.ts b/packages/client/src/client.test.ts index 54e2d971..82980149 100644 --- a/packages/client/src/client.test.ts +++ b/packages/client/src/client.test.ts @@ -611,6 +611,13 @@ describe('RelayfileControlPlaneClient integration webhook subscriptions', () => subscriptionId: 'whsub_123', url: 'https://cast.test/v1/integrations/relayfile/inbound/ws/ch', pathGlobs: ['/github/repos/acme/widgets/issues/**'], + githubPrIdentityAuthorized: true, + health: { + lastDeliveryAt: '2026-09-21T00:00:00.000Z', + lastSuccessAt: '2026-09-21T00:00:00.000Z', + lastError: null, + consecutiveFailures: 0, + }, }, ], }); @@ -622,6 +629,13 @@ describe('RelayfileControlPlaneClient integration webhook subscriptions', () => subscriptionId: 'whsub_123', url: 'https://cast.test/v1/integrations/relayfile/inbound/ws/ch', pathGlobs: ['/github/repos/acme/widgets/issues/**'], + githubPrIdentityAuthorized: true, + health: { + lastDeliveryAt: '2026-09-21T00:00:00.000Z', + lastSuccessAt: '2026-09-21T00:00:00.000Z', + lastError: null, + consecutiveFailures: 0, + }, }, ], }); diff --git a/packages/client/src/generated/control-plane.ts b/packages/client/src/generated/control-plane.ts index 83f92051..891dba18 100644 --- a/packages/client/src/generated/control-plane.ts +++ b/packages/client/src/generated/control-plane.ts @@ -334,6 +334,16 @@ export interface components { /** Format: uri */ url: string; pathGlobs: string[]; + /** Whether `/pulls//**` identity expansion (title slugs, reviews, comments, checks via resource_ref) is enabled for this subscription. */ + githubPrIdentityAuthorized?: boolean; + health?: { + /** Format: date-time */ + lastDeliveryAt?: string | null; + /** Format: date-time */ + lastSuccessAt?: string | null; + lastError?: string | null; + consecutiveFailures?: number; + }; }; ErrorEnvelope: { error: { From fe7fcbffc070e7dc2baf87a39ad668d06463282e Mon Sep 17 00:00:00 2001 From: khaliqgant Date: Sun, 20 Sep 2026 19:16:59 -0700 Subject: [PATCH 2/3] fix(cli): omit absent webhook consecutiveFailures on list encoding/json left the int at 0 when upstream omitted the field, so clients could not tell an absent count from a confirmed zero. --- cmd/relayfile-cli/control_plane.go | 2 +- cmd/relayfile-cli/control_plane_test.go | 35 ++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/cmd/relayfile-cli/control_plane.go b/cmd/relayfile-cli/control_plane.go index 28e2f9a9..c4bdb9c8 100644 --- a/cmd/relayfile-cli/control_plane.go +++ b/cmd/relayfile-cli/control_plane.go @@ -158,7 +158,7 @@ type webhookSubscriptionHealth struct { LastDeliveryAt *string `json:"lastDeliveryAt"` LastSuccessAt *string `json:"lastSuccessAt"` LastError *string `json:"lastError"` - ConsecutiveFailures int `json:"consecutiveFailures"` + ConsecutiveFailures *int `json:"consecutiveFailures,omitempty"` } type webhookSubscriptionSummary struct { diff --git a/cmd/relayfile-cli/control_plane_test.go b/cmd/relayfile-cli/control_plane_test.go index bf795242..d1de51cb 100644 --- a/cmd/relayfile-cli/control_plane_test.go +++ b/cmd/relayfile-cli/control_plane_test.go @@ -382,7 +382,8 @@ func TestControlPlaneCloudIntegrationConformance(t *testing.T) { t.Fatalf("unexpected webhook subscription list: %#v", listedSubscriptions) } if listedSubscriptions.Subscriptions[0].Health == nil || - listedSubscriptions.Subscriptions[0].Health.ConsecutiveFailures != 0 { + listedSubscriptions.Subscriptions[0].Health.ConsecutiveFailures == nil || + *listedSubscriptions.Subscriptions[0].Health.ConsecutiveFailures != 0 { t.Fatalf("expected webhook subscription health to pass through: %#v", listedSubscriptions.Subscriptions[0].Health) } @@ -425,6 +426,38 @@ func TestControlPlaneCloudIntegrationConformance(t *testing.T) { } } +func TestWebhookSubscriptionHealthOmitsAbsentConsecutiveFailures(t *testing.T) { + var omitted webhookSubscriptionHealth + if err := json.Unmarshal([]byte(`{"lastDeliveryAt":null,"lastSuccessAt":null,"lastError":null}`), &omitted); err != nil { + t.Fatalf("unmarshal omitted consecutiveFailures: %v", err) + } + if omitted.ConsecutiveFailures != nil { + t.Fatalf("expected omitted consecutiveFailures to stay nil, got %#v", omitted.ConsecutiveFailures) + } + raw, err := json.Marshal(omitted) + if err != nil { + t.Fatalf("marshal omitted consecutiveFailures: %v", err) + } + if strings.Contains(string(raw), "consecutiveFailures") { + t.Fatalf("omitempty should drop absent consecutiveFailures: %s", raw) + } + + var present webhookSubscriptionHealth + if err := json.Unmarshal([]byte(`{"consecutiveFailures":0}`), &present); err != nil { + t.Fatalf("unmarshal zero consecutiveFailures: %v", err) + } + if present.ConsecutiveFailures == nil || *present.ConsecutiveFailures != 0 { + t.Fatalf("expected confirmed zero consecutiveFailures, got %#v", present.ConsecutiveFailures) + } + raw, err = json.Marshal(present) + if err != nil { + t.Fatalf("marshal zero consecutiveFailures: %v", err) + } + if !strings.Contains(string(raw), `"consecutiveFailures":0`) { + t.Fatalf("confirmed zero consecutiveFailures should serialize as 0: %s", raw) + } +} + func TestControlPlaneProviderStatusUsesCloudTokenFromEnvironment(t *testing.T) { t.Setenv("HOME", t.TempDir()) clearRelayfileEnv(t) From 9fc3ea4be6ff4896fa57aca6eaf7c5e2a0a86862 Mon Sep 17 00:00:00 2001 From: khaliqgant Date: Sun, 20 Sep 2026 23:10:59 -0700 Subject: [PATCH 3/3] fix(client): regenerate subscription health contract comments Session-Id: 01a0c27d-bf21-78d0-a18b-062250fa617a --- packages/client/src/generated/control-plane.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/generated/control-plane.ts b/packages/client/src/generated/control-plane.ts index 891dba18..c30aa37a 100644 --- a/packages/client/src/generated/control-plane.ts +++ b/packages/client/src/generated/control-plane.ts @@ -334,7 +334,7 @@ export interface components { /** Format: uri */ url: string; pathGlobs: string[]; - /** Whether `/pulls//**` identity expansion (title slugs, reviews, comments, checks via resource_ref) is enabled for this subscription. */ + /** @description Whether `/pulls//**` identity expansion (title slugs, reviews, comments, checks via resource_ref) is enabled for this subscription. */ githubPrIdentityAuthorized?: boolean; health?: { /** Format: date-time */