Skip to content

Commit df71b46

Browse files
committed
spec(governance-admin): v0.1.25.33 — webhook lifecycle EventTypes + per-row event emission
Closes the v0.1.25.32 deferral that explicitly carved out webhooks from the bulk-action Event emission contract because no `webhook.*` lifecycle EventTypes existed. This revision adds the EventTypes and wires per-row Event emission through `bulkActionWebhooks`, the three single-op webhook operations, and the dispatcher's auto-disable transition. Changes to `cycles-governance-admin-v0.1.25.yaml`: - EventType enum: six new values under a new "Webhook lifecycle" group between the tenant and api_key blocks — webhook.created / webhook.updated / webhook.paused / webhook.resumed / webhook.disabled / webhook.deleted. Naming follows the tenant.suspended / tenant.reactivated precedent: each event name mirrors the resulting status, rather than the v0.1.25.32 note's provisional webhook.enabled / webhook.disabled pairing which would have conflated operator PAUSE with dispatcher auto-DISABLE. webhook.disabled is now reserved exclusively for the health-driven transition. - New schema EventDataWebhookLifecycle — mirrors EventDataTenantLifecycle; carries subscription_id, tenant_id, previous_status, new_status, changed_fields, disable_reason. - bulkActionWebhooks: added EVENTS block documenting the per-row emission contract (PAUSE → webhook.paused, RESUME → webhook.resumed, DELETE → webhook.deleted), skipped/failed rows produce no Event, correlation_id = webhook_bulk_action:<action>:<request_id>. bulkActionWebhooks explicitly never emits webhook.disabled. - createWebhookSubscription / updateWebhookSubscription / deleteWebhookSubscription: EVENTS blocks documenting the single-op emission contract and correlation_id patterns. updateWebhookSubscription disambiguates status-change PATCH (emits webhook.paused/resumed) from pure property-change PATCH (emits webhook.updated). No-op PATCH emits nothing. - WebhookSubscription.FAILURE HANDLING: documents that the dispatcher MUST emit webhook.disabled on auto-transition with correlation_id = webhook_auto_disable:<subscription_id>:<failure_batch_id> and populate disable_reason. Emission happens in the dispatcher component, not the admin API. - info.version 0.1.25.32 → 0.1.25.33; info.summary trailer extended. Also prepends a v0.1.25.33 changelog entry (Keep-a-Changelog) and regenerates merged/cycles-openapi-admin-merged.yaml. Additive-only: new enum values (existing clients MUST ignore unrecognized values per the EventType extensibility note), new optional schema, new prose sections. No removals, no renames, no wire-breaking changes. Reference servers compile against the regenerated model without code changes; a follow-up cycles-server-admin patch release will wire the emits into WebhookAdminController and the dispatcher's auto-disable path. The existing cascade variant webhook.disabled_via_tenant_cascade is unchanged — the tenant-close cascade retains its v0.1.25.29 Rule 1 identity (tenant_close_cascade:<tenant_id>:<request_id>) and is distinct from the new standalone webhook.disabled event.
1 parent d081a9a commit df71b46

3 files changed

Lines changed: 321 additions & 7 deletions

File tree

changelogs/cycles-governance-admin.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,90 @@ New entries are added directly to this file. See `scripts/validate_changelogs.py
66

77
---
88

9+
## v0.1.25.33 — 2026-04-23
10+
11+
- Adds webhook lifecycle EventTypes and documents per-row Event
12+
emission on `bulkActionWebhooks`, the three single-op webhook
13+
operations (`createWebhookSubscription`, `updateWebhookSubscription`,
14+
`deleteWebhookSubscription`), and the dispatcher's auto-disable
15+
transition. Closes the v0.1.25.32 deferral, which explicitly carved
16+
out webhooks because no `webhook.*` lifecycle EventTypes existed.
17+
18+
New EventType enum values (six):
19+
20+
* `webhook.created` — new subscription provisioned
21+
* `webhook.updated` — subscription properties modified (url,
22+
event_types, etc.) with no status change
23+
* `webhook.paused` — subscription status ACTIVE → PAUSED (operator)
24+
* `webhook.resumed` — subscription status PAUSED → ACTIVE (operator,
25+
or DISABLED → ACTIVE when operator re-enables a dispatcher-disabled
26+
subscription)
27+
* `webhook.disabled` — subscription status → DISABLED (auto, after
28+
failure threshold; never produced by the admin API)
29+
* `webhook.deleted` — subscription record removed
30+
31+
Naming follows the `tenant.suspended` / `tenant.reactivated`
32+
precedent — each event name mirrors the resulting status. This
33+
intentionally diverges from the v0.1.25.32 note's provisional
34+
`webhook.enabled` / `webhook.disabled` pairing: webhook status has
35+
three values (ACTIVE / PAUSED / DISABLED), and conflating a user
36+
PAUSE with the dispatcher's auto-DISABLE under one event name would
37+
hide a meaningful operational distinction. `webhook.disabled` is
38+
now reserved exclusively for the health-driven transition.
39+
40+
`bulkActionWebhooks` — server MUST emit one Event per
41+
successfully-mutated row:
42+
43+
* PAUSE → `webhook.paused`
44+
* RESUME → `webhook.resumed`
45+
* DELETE → `webhook.deleted`
46+
47+
Skipped and failed rows MUST NOT produce an Event. Emission is
48+
bound to actual state transition, matching the single-op contract.
49+
50+
Correlation identity for bulk: `webhook_bulk_action:<action>:<request_id>`
51+
(mirrors `tenant_bulk_action:...` / `budget_bulk_action:...` from
52+
v0.1.25.32).
53+
54+
Single-op endpoints — correlation identities:
55+
56+
* `createWebhookSubscription``webhook_create:<subscription_id>`
57+
* `updateWebhookSubscription``webhook_update:<subscription_id>:<request_id>`
58+
* `deleteWebhookSubscription``webhook_delete:<subscription_id>`
59+
60+
Dispatcher auto-disable — when `consecutive_failures` exceeds
61+
`disable_after_failures` and status flips to DISABLED, the
62+
dispatcher MUST emit `webhook.disabled` with correlation_id
63+
`webhook_auto_disable:<subscription_id>:<failure_batch_id>` and
64+
populate `disable_reason` in the payload
65+
(e.g. `consecutive_failures_exceeded_threshold`). Documented
66+
under `WebhookSubscription.FAILURE HANDLING`; emission happens in
67+
the dispatcher component, not the admin API.
68+
69+
New payload schema: `EventDataWebhookLifecycle` (mirrors
70+
`EventDataTenantLifecycle`) carrying `subscription_id`, `tenant_id`,
71+
`previous_status`, `new_status`, `changed_fields`, and
72+
`disable_reason`. `previous_status` is omitted on `webhook.created`;
73+
`new_status` is omitted on `webhook.deleted`; `changed_fields` is
74+
populated only on `webhook.updated`; `disable_reason` is populated
75+
only on `webhook.disabled`.
76+
77+
The existing cascade variant
78+
`webhook.disabled_via_tenant_cascade` (referenced in the tenant
79+
close cascade prose) is unchanged — the cascade path retains its
80+
v0.1.25.29 Rule 1 identity
81+
`tenant_close_cascade:<tenant_id>:<request_id>` and is distinct from
82+
the new standalone `webhook.disabled` event.
83+
84+
Additive-only spec change: six new EventType enum values (existing
85+
clients MUST ignore unrecognized values per the EventType
86+
extensibility note at line 1575), one new schema, new prose sections
87+
on four operations plus FAILURE HANDLING. No removals, no renames,
88+
no wire-incompatible changes. Reference servers continue to compile
89+
against the regenerated model without code changes; a follow-up
90+
cycles-server-admin patch release will wire the emits into
91+
`WebhookAdminController` and the dispatcher's auto-disable path.
92+
993
## v0.1.25.32 — 2026-04-22
1094

1195
- Editorial: adds normative `EVENTS` sections to `bulkActionBudgets`

cycles-governance-admin-v0.1.25.yaml

Lines changed: 129 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
openapi: 3.1.0
22
info:
33
title: Complete Budget Governance Admin API
4-
version: 0.1.25.32
4+
version: 0.1.25.33
55
license:
66
name: Apache 2.0
77
url: https://www.apache.org/licenses/LICENSE-2.0
88
summary: >-
9-
Stable governance base — document revision 0.1.25.32; semantic base
9+
Stable governance base — document revision 0.1.25.33; semantic base
1010
frozen at 0.1.25.9. This file is self-sufficient for the current
1111
v0.1.25 conformance target (runcycles' reference servers implement
1212
this today — adds a normative tenant-close cascade contract at
1313
document revision 0.1.25.31; document revision 0.1.25.32 adds
1414
per-row Event emission requirements to bulkActionBudgets and
15-
bulkActionTenants for observability parity with single-op paths)
15+
bulkActionTenants for observability parity with single-op paths;
16+
document revision 0.1.25.33 introduces webhook lifecycle EventTypes
17+
— webhook.created / webhook.updated / webhook.paused /
18+
webhook.resumed / webhook.disabled / webhook.deleted — and wires
19+
per-row Event emission into bulkActionWebhooks, the single-op
20+
create / update / delete webhook operations, and the dispatcher's
21+
auto-disable path, closing the v0.1.25.32 deferral)
1622
and also serves as the base for the upcoming v0.1.26
1723
spec family. Covers tenant management, authentication, and runtime
1824
enforcement. NOTE: This file alone is NOT sufficient for the
@@ -1612,6 +1618,14 @@ components:
16121618
- tenant.closed # Tenant status → CLOSED (irreversible)
16131619
- tenant.settings_changed # Default overage policy, TTL, or overdraft limit changed
16141620

1621+
# --- Webhook lifecycle events (document revision 0.1.25.33) ---
1622+
- webhook.created # New subscription provisioned
1623+
- webhook.updated # Subscription properties modified (url, event_types, etc.) with no status change
1624+
- webhook.paused # Subscription status ACTIVE → PAUSED (operator action)
1625+
- webhook.resumed # Subscription status PAUSED → ACTIVE (operator action)
1626+
- webhook.disabled # Subscription status → DISABLED (auto, after failure threshold)
1627+
- webhook.deleted # Subscription record removed
1628+
16151629
# --- API key events ---
16161630
- api_key.created # New API key provisioned
16171631
- api_key.revoked # Key revoked (manual or automated)
@@ -2101,6 +2115,47 @@ components:
21012115
List of fields that were modified (e.g., ["name", "default_commit_overage_policy"]).
21022116
Empty for create events.
21032117
2118+
EventDataWebhookLifecycle:
2119+
type: object
2120+
description: >-
2121+
Payload for webhook.created, webhook.updated, webhook.paused,
2122+
webhook.resumed, webhook.disabled, webhook.deleted events
2123+
(document revision 0.1.25.33). Mirrors EventDataTenantLifecycle
2124+
in shape — carries the subscription identity, the status
2125+
transition, and the fields modified (for update events).
2126+
properties:
2127+
subscription_id:
2128+
type: string
2129+
description: Subscription identifier (e.g., "whsub_01abc...").
2130+
tenant_id:
2131+
type: string
2132+
description: >-
2133+
Tenant owning the subscription. Use "__system__" for
2134+
system-wide subscriptions.
2135+
previous_status:
2136+
type: string
2137+
enum: [ACTIVE, PAUSED, DISABLED]
2138+
description: >-
2139+
Status before the transition. Omitted on webhook.created.
2140+
new_status:
2141+
type: string
2142+
enum: [ACTIVE, PAUSED, DISABLED]
2143+
description: >-
2144+
Status after the transition. Omitted on webhook.deleted.
2145+
changed_fields:
2146+
type: array
2147+
items:
2148+
type: string
2149+
description: >-
2150+
Fields modified (e.g., ["url", "event_types"]). Populated on
2151+
webhook.updated; empty on create, pause, resume, disable,
2152+
and delete events.
2153+
disable_reason:
2154+
type: string
2155+
description: >-
2156+
Only set on webhook.disabled. Human-readable reason
2157+
(e.g., "consecutive_failures_exceeded_threshold").
2158+
21042159
EventDataApiKey:
21052160
type: object
21062161
description: >-
@@ -2192,7 +2247,14 @@ components:
21922247
- On non-2xx response: retry with exponential backoff (1s, 2s, 4s, ... up to max_retries).
21932248
- After max_retries exhausted: mark delivery as FAILED, emit system.webhook_delivery_failed event.
21942249
- After consecutive_failures exceeds disable_after_failures: status → DISABLED automatically.
2195-
- Disabled subscriptions can be re-enabled via PATCH.
2250+
The dispatcher MUST emit one `webhook.disabled` Event on this
2251+
auto-transition (document revision 0.1.25.33). Payload conforms
2252+
to EventDataWebhookLifecycle with `previous_status` = ACTIVE
2253+
or PAUSED, `new_status` = DISABLED, and `disable_reason`
2254+
populated (e.g., "consecutive_failures_exceeded_threshold").
2255+
correlation_id = `webhook_auto_disable:<subscription_id>:<failure_batch_id>`.
2256+
- Disabled subscriptions can be re-enabled via PATCH — that path
2257+
emits `webhook.resumed` (see updateWebhookSubscription).
21962258
required: [subscription_id, tenant_id, url, event_types, status, created_at]
21972259
additionalProperties: false
21982260
properties:
@@ -5955,6 +6017,12 @@ paths:
59556017
- Omit tenant_id to create a system-wide subscription (receives events from all tenants).
59566018
- System-wide subscriptions still respect scope_filter if provided.
59576019
6020+
EVENTS (document revision 0.1.25.33):
6021+
- On success, server MUST emit one `webhook.created` Event.
6022+
Payload conforms to EventDataWebhookLifecycle with
6023+
`new_status` = ACTIVE and `previous_status` omitted.
6024+
correlation_id = `webhook_create:<subscription_id>`.
6025+
59586026
AUTHORIZATION:
59596027
- Requires admin key (X-Admin-API-Key)
59606028
security:
@@ -6185,6 +6253,27 @@ paths:
61856253
- Pause delivery: set status to PAUSED
61866254
- Rotate signing secret: provide new signing_secret value
61876255
- Change event types: provide new event_types array (replaces, does not merge)
6256+
6257+
EVENTS (document revision 0.1.25.33):
6258+
- On every successful PATCH the server MUST emit exactly one
6259+
Event, typed by the nature of the change:
6260+
* Status unchanged, any other field(s) mutated →
6261+
`webhook.updated`. `changed_fields` lists the mutated
6262+
field names.
6263+
* Status ACTIVE → PAUSED → `webhook.paused`.
6264+
* Status PAUSED → ACTIVE → `webhook.resumed`.
6265+
* Status DISABLED → ACTIVE (operator re-enable) →
6266+
`webhook.resumed`.
6267+
When a single PATCH both changes status AND mutates other
6268+
fields, the server emits the status-typed event
6269+
(`webhook.paused` / `webhook.resumed`) and `changed_fields`
6270+
lists the non-status mutations for operator visibility.
6271+
A no-op PATCH (zero fields mutated) MUST NOT emit an Event.
6272+
- Payload conforms to EventDataWebhookLifecycle.
6273+
correlation_id = `webhook_update:<subscription_id>:<request_id>`.
6274+
- Auto-disable after failure threshold produces
6275+
`webhook.disabled` via the dispatcher, not via this PATCH
6276+
path. See WebhookSubscription.FAILURE HANDLING.
61886277
security:
61896278
- AdminKeyAuth: []
61906279
parameters:
@@ -6254,6 +6343,13 @@ paths:
62546343
description: >-
62556344
Permanently removes the subscription. Pending deliveries are cancelled.
62566345
This operation is irreversible.
6346+
6347+
EVENTS (document revision 0.1.25.33):
6348+
- On success, server MUST emit one `webhook.deleted` Event.
6349+
Payload conforms to EventDataWebhookLifecycle with
6350+
`previous_status` set to the subscription's status at time
6351+
of deletion and `new_status` omitted.
6352+
correlation_id = `webhook_delete:<subscription_id>`.
62576353
security:
62586354
- AdminKeyAuth: []
62596355
parameters:
@@ -6820,6 +6916,35 @@ paths:
68206916
- Overall HTTP 200 even when some rows fail; response envelope
68216917
reports success/failure counts.
68226918
6919+
EVENTS (document revision 0.1.25.33):
6920+
- Server MUST emit one Event per successfully-mutated row,
6921+
typed by action: PAUSE → `webhook.paused`,
6922+
RESUME → `webhook.resumed`, DELETE → `webhook.deleted`.
6923+
Skipped rows (ALREADY_IN_TARGET_STATE, MISSING) and failed
6924+
rows MUST NOT produce an Event — emission is bound to actual
6925+
state transition, matching the single-op
6926+
createWebhookSubscription / updateWebhookSubscription /
6927+
deleteWebhookSubscription contract.
6928+
- Each emitted Event carries
6929+
`correlation_id = webhook_bulk_action:<action>:<request_id>`
6930+
(action lowercased, e.g.
6931+
`webhook_bulk_action:pause:req_abc`). Operators retrieve the
6932+
full bulk fan-out via
6933+
GET /v1/admin/events?correlation_id=…; the shared request_id
6934+
also ties every emitted row to the invocation's single
6935+
AuditLogEntry.
6936+
- Event payload conforms to EventDataWebhookLifecycle.
6937+
- `webhook.disabled` is NOT produced by bulkActionWebhooks —
6938+
that EventType is reserved for the auto-health-driven
6939+
transition described in
6940+
WebhookSubscription.FAILURE HANDLING. Operator PAUSE actions
6941+
always emit `webhook.paused` regardless of prior status.
6942+
6943+
AUDIT LOG:
6944+
- One AuditLogEntry per bulk-action invocation (not per row).
6945+
Per-row Events (above) are the operator-visible audit trail;
6946+
the AuditLogEntry is the compliance-facing invocation record.
6947+
68236948
AUTHORIZATION:
68246949
- AdminKeyAuth only.
68256950
security:

0 commit comments

Comments
 (0)