spec(governance): tenant webhook boundary covers event_categories (v0.1.25.38)#126
Merged
amavashev merged 1 commit intoJul 11, 2026
Merged
Conversation
….1.25.38) Prompted by a confirmed authorization gap in the reference admin server (fixed in parallel as cycles-server-admin 0.1.25.50): tenant-plane webhook validation rejected admin-only event_types but did NOT validate event_categories, and delivery matching treats categories as an ADDITIVE union with event_types — so a tenant subscription pairing one allowed type with event_categories:["api_key"] received admin-only events. The spec never stated the category half of the boundary, and its tenant-webhook prose had gone stale. governance v0.1.25.38: - createTenantWebhook gains a TENANT-ACCESSIBLE BOUNDARY (NORMATIVE) block: every event_categories entry MUST be a tenant-accessible category (budget, reservation, tenant); a category whose event types are admin-only (api_key, policy, webhook, system) MUST be rejected with 400 INVALID_REQUEST — matching the existing event_types rule. Rationale stated: categories are additive in matching, so an unvalidated category widens the subscription beyond the tenant-accessible set even when every event_types entry is allowed. - updateTenantWebhook: same boundary, partial updates covered explicitly (any provided event_types or event_categories array is validated in full). Both endpoints' 400 response descriptions gain "or event category". - WebhookSubscription.event_categories description gains the tenant-plane boundary note (the one schema-level place that documents the additive matching semantics). WebhookCreateRequest / WebhookUpdateRequest carry no per-field prose by design (shared admin+tenant shapes); the endpoint prose is the normative home. - Stale counts corrected: the EventType enum has 51 values (budget 17, reservation 6, tenant 6, webhook 7, api_key 7, policy 3, system 5); tenant-accessible = 29 of 51 (prose said "26 of 40"); the admin-only list now includes webhook.* (omitted since the category landed in 0.1.25.33). - Empty-both check: event_types + event_categories both empty (naive matcher = match-all) is NOT reachable via the documented API — event_types has minItems:1 on WebhookSubscription / WebhookCreateRequest / WebhookUpdateRequest and updates are partial — so no additional rule needed; recorded in the changelog. - Also fixes stale document-revision self-references (info.summary headline + SPEC FAMILY CONTEXT were stuck at 0.1.25.36 through the .37 bump) and extends the summary chronicle with .37/.38. Prose/documentation only — no schema shapes, operations, or status codes change; semantic base stays 0.1.25.9. Changelog entry added; spec-index pins bumped (base_governance -> 0.1.25.38, spec_index -> 0.1.37); admin merged artifact regenerated (byte-stable second run; source metadata picks up v0.1.25.38 dynamically). Validation: YAML parse OK, validate_changelogs.py green. Spectral runs in CI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Governance spec revision v0.1.25.38: the tenant self-service webhook boundary now normatively covers
event_categories, and the stale tenant-webhook prose (counts + admin-only list) is corrected. Prose/documentation only — no schema shapes, operations, or status codes change; semantic base stays 0.1.25.9.The gap (verified in the reference admin server; fixed in parallel as cycles-server-admin 0.1.25.50)
WebhookTenantController.validateTenantEventTypesrejects admin-onlyevent_typesfor tenant keys, butevent_categorieswas not validated — andWebhookRepository.matchesEventTypetreats categories as an additive union withevent_types. A tenant subscription pairing one allowed type withevent_categories: ["api_key"]therefore received admin-only events. The spec never stated the category half of the boundary, so the server gap was also a spec gap.Normative text added
createTenantWebhook:updateTenantWebhook(first line): "Restricted to tenant-accessible event types AND event categories: any provided event_types or event_categories array is validated in full against the tenant-accessible boundary … Admin-only entries MUST be rejected with 400 INVALID_REQUEST." Both endpoints' 400 response descriptions now say "restricted event type or event category".WebhookSubscription.event_categoriescarries a matching schema-level note (the one place documenting the additive matching semantics); the Create/Update request schemas carry no per-field prose by design (shared admin+tenant shapes) — the endpoint prose is the normative home.Count corrections (recounted against the enum)
The EventType enum has 51 values: budget 17, reservation 6, tenant 6, webhook 7, api_key 7, policy 3, system 5. Tenant-accessible (budget./reservation./tenant.) = 29 of 51 — the prose said "26 of 40" (pre-0.1.25.29 numbers). The admin-only list omitted
webhook.*entirely (the category landed at revision 0.1.25.33); it now reads api_key., policy., webhook., system.*.listTenantEvents' boundary sentence was checked and is already correct (no counts).Empty-both check (requested)
event_types+event_categoriesboth empty (which a naive matcher would treat as match-all) is not reachable through the documented API:event_typescarriesminItems: 1onWebhookSubscription,WebhookCreateRequest, andWebhookUpdateRequest, and updates are partial — a provided array must satisfyminItems, an omitted field is unchanged — soevent_typescan never become empty. No additional normative rule needed; recorded in the changelog.Also fixed
Stale document-revision self-references: the
info.summaryheadline and SPEC FAMILY CONTEXT were stuck at 0.1.25.36 through the 0.1.25.37 bump — both now read 0.1.25.38 and the summary chronicle gains .37/.38 clauses.Bookkeeping / validation
v0.1.25.38 — 2026-07-10inchangelogs/cycles-governance-admin.md(format precedent).base_governance→ 0.1.25.38,spec_index→ 0.1.37.merged/cycles-openapi-admin-merged.yamlregenerated (byte-stable second run — merge-check passes; source metadata picks up v0.1.25.38 dynamically). Protocol merged artifact unaffected.validate_changelogs.pygreen. Spectral runs in CI.