Skip to content

spec: v0.1.25.14 — dual-auth on 6 tenant-scoped webhook endpoints (Stage 3) - #40

Merged
amavashev merged 1 commit into
mainfrom
spec/webhooks-dual-auth-v0.1.25.24
Apr 13, 2026
Merged

amavashev merged 1 commit into
mainfrom
spec/webhooks-dual-auth-v0.1.25.24

Conversation

@amavashev

Copy link
Copy Markdown
Contributor

Summary

Stage 3 of the admin-on-behalf-of rollout. Adds AdminKeyAuth alongside ApiKeyAuth on the 6 tenant-scoped /v1/webhooks/* endpoints that matter for admin incident response. Mirrors the pattern from #36 (v0.1.25.13 budgets/policies) and #37 (reservations).

Motivating use case — a tenant's webhook endpoint is flapping (auto-disabled, or spamming a shared downstream). Ops gets paged, needs to pause / force-delete / inspect deliveries without waiting for the tenant to rotate keys. Admin already owns /v1/admin/webhooks/* for admin-provisioned subscriptions; this closes the gap for tenant-provisioned ones.

Dual-auth added (6)

Method Path Operation
GET /v1/webhooks listTenantWebhooks — tenant query REQUIRED under admin
GET /v1/webhooks/{id} getTenantWebhook
PATCH /v1/webhooks/{id} updateTenantWebhook (pause/resume)
DELETE /v1/webhooks/{id} deleteTenantWebhook (force-remove)
POST /v1/webhooks/{id}/test testTenantWebhook
GET /v1/webhooks/{id}/deliveries listTenantWebhookDeliveries

Intentionally NOT dual-auth

  • POST /v1/webhooks (create) — URL / signing secret / event choice are tenant policy; admin creating on a tenant's behalf obscures provenance.
  • POST /v1/webhooks/{id}/replay — already admin-only at /v1/admin/webhooks/{id}/replay; tenant self-service replay remains tenant-key-only (bulk re-delivery is a capacity decision owed to the tenant).

Semantic rules

  • listTenantWebhooks — new tenant query param. REQUIRED under AdminKeyAuth, MUST NOT be set under ApiKeyAuth. Omitting under admin → 400 INVALID_REQUEST with the same message as listBudgets / listPolicies / listReservations.
  • Per-subscription endpoints resolve owning tenant from the subscription record under admin. 404 preserved across both auth types for consistency.
  • NORMATIVE audit requirement: admin-driven PATCH / DELETE MUST record actor_type=admin_on_behalf_of in the shared audit store so the governance audit-query surface surfaces them. Callers SHOULD populate a structured reason in request metadata (e.g., [WEBHOOK_PAUSE] INC-1234, [WEBHOOK_FORCE_DELETE] abuse-report-42).

Backward compatibility

Purely additive. No schemas modified. Existing ApiKeyAuth callers see no behavior change. New tenant query param is validation-only under ApiKeyAuth and MAY be omitted.

Downstream chain

  1. This PR — spec
  2. cycles-server-admin — AuthInterceptor allowlist + controller branching + audit tag on the 6 endpoints (runtime plane not involved; webhooks live on admin)
  3. cycles-dashboard — flip the 6 existing webhook wrappers to send X-Admin-API-Key against /v1/webhooks/*; extend WebhooksView with tenant filter under admin auth

Test plan

  • spectral lint cycles-governance-admin-v0.1.25.yaml --fail-severity=error → 0 errors
  • python scripts/merge_specs.py → merged artifact regenerated
  • CI spectral lint on both source and merged spec
  • Downstream server PR uses this spec revision for contract tests

Stage 3 of the admin-on-behalf-of rollout. Adds AdminKeyAuth alongside
ApiKeyAuth on the tenant-scoped /v1/webhooks/* endpoints that are
meaningful for admin incident response. Matches the pattern from
v0.1.25.13 (budgets/policies) and the 2026-04-13 reservations revision
in cycles-protocol-v0.

Dual-auth added to:
- GET    /v1/webhooks                           listTenantWebhooks
- GET    /v1/webhooks/{subscription_id}         getTenantWebhook
- PATCH  /v1/webhooks/{subscription_id}         updateTenantWebhook
- DELETE /v1/webhooks/{subscription_id}         deleteTenantWebhook
- POST   /v1/webhooks/{subscription_id}/test    testTenantWebhook
- GET    /v1/webhooks/{subscription_id}/deliveries listTenantWebhookDeliveries

Intentionally NOT dual-auth:
- POST /v1/webhooks (createTenantWebhook) — URL / secret / event choices
  are tenant policy; admin creating on a tenant's behalf obscures
  provenance.
- POST /v1/webhooks/{id}/replay — already admin-only at
  /v1/admin/webhooks/{id}/replay; tenant self-service replay remains
  tenant-key-only (bulk re-delivery is a capacity decision owed to the
  tenant).

Semantics:
- listTenantWebhooks gains `tenant` query param: REQUIRED under
  AdminKeyAuth (filter), MUST NOT be set under ApiKeyAuth. Same
  single-param dual-semantic pattern as listBudgets / listPolicies /
  listReservations.
- Per-subscription endpoints resolve the owning tenant from the
  subscription record under AdminKeyAuth. 404 preserved across both
  auth types for consistency.
- Admin-driven PATCH/DELETE MUST record actor_type=admin_on_behalf_of
  in the shared audit store. Callers SHOULD populate a structured
  reason (e.g., [WEBHOOK_PAUSE], [WEBHOOK_FORCE_DELETE]) for
  grep-ability.

Backward compatibility: purely additive. Existing ApiKeyAuth callers
see no behavior change. No schemas modified.

Gates: spectral lint clean (0 errors; pre-existing warnings unchanged).
Merged artifact (merged/cycles-openapi-admin-merged.yaml) regenerated.
@amavashev
amavashev merged commit 73f041b into main Apr 13, 2026
1 check passed
amavashev added a commit that referenced this pull request Apr 14, 2026
README header listed 'Governance base: v0.1.25.8'. After PR #40
(Stage 3 webhooks dual-auth) the governance-admin spec is at
editorial revision v0.1.25.14. The semantic_base is frozen at
0.1.25.9 per the PR #41 versioning model.

Updated to 'v0.1.25.14 (semantic_base 0.1.25.9)' — surfaces both
values so readers see the latest feature revision AND understand
wire-compatibility is tied to the 0.1.25.9 baseline.
amavashev added a commit that referenced this pull request Apr 14, 2026
…ard-compat)

Additive spec extension reserving the contract for tenant-role
introspect. No server implementation required in this revision;
no downstream repos touched. Purely forward-compatibility stake
so future multi-role clients don't re-litigate the capability
shape or drift across implementations.

Changes (all additive, zero wire impact on existing admin callers):

1. Security block on GET /v1/auth/introspect gains ApiKeyAuth
   alongside existing AdminKeyAuth. Same dual-auth pattern used
   for budgets/policies (#36), reservations (#37), and webhooks (#40).

2. AuthIntrospectResponse schema gains three fields, populated only
   when auth_type=tenant:
   - tenant_id (REQUIRED under tenant auth; MUST be absent under admin)
   - scope_filter (OPTIONAL; non-empty when key has scope restrictions)
   - auth_type enum gains value "tenant" alongside existing "admin"

3. Also surfaces the optional manage_* / view_reservations capability
   flags that servers have been returning for several releases (pre-
   existing wire behavior, now formally documented in the schema).

4. NORMATIVE capability-derivation table added to the capabilities
   schema description. Servers MUST derive the capabilities map from
   a tenant key's permissions per the published rules. Guarantees any
   future server or client produces identical capability maps from
   identical permission sets. Admin-plane views (view_tenants,
   view_api_keys, view_audit, view_overview) only fire on admin:*
   permissions, so default tenant keys cannot accidentally access
   admin-only pages.

5. Scope-filtered keys documented as NOT altering the capability map —
   filters constrain which records are visible at each endpoint, not
   whether a page is accessible. Enforcement stays server-side.

Non-goals (explicit to prevent scope creep when implementation lands):
- No tenant-scoped /v1/admin/overview (admin-only).
- No tenant-scoped /v1/admin/audit/logs (admin-only).
- No admin-on-behalf-of introspect of a specific tenant key.

Version: editorial 0.1.25.14 -> 0.1.25.15. semantic_base unchanged
at 0.1.25.9 (no wire-breaking change). cycles-spec-index.yaml
editorial pin bumped to match.

Gates:
- spectral lint: 0 errors (64 pre-existing warnings unchanged)
- merge_specs.py: idempotent (rerun produces no further diff)
- yaml.safe_load parses cleanly
- ApiKeyAuth count: +1 (41 total, was 40)

Implementation impact: none required. Compliant servers continue
returning admin-only introspect until they explicitly extend to
ApiKeyAuth. Rollout is opt-in; this revision just reserves the
shape.
@amavashev
amavashev deleted the spec/webhooks-dual-auth-v0.1.25.24 branch June 18, 2026 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant