/speckit-specify output for 002-saas-integration (roadmap Phase 2): 3 user stories (authenticate/validate inbound requests, admin onboarding/rotation/revocation, rate limiting) and 12 functional requirements. Reserves an idempotency-key field on the inbound contract for the future ticketing feature (docs/11 gap A1) without implementing dedup here. Quality checklist passes with no NEEDS CLARIFICATION markers. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
13 KiB
Feature Specification: SaaS Product Integration & Inbound Request Trust
Feature Branch: 002-saas-integration
Created: 2026-08-21
Status: Draft
Input: User description: "Phase 2 of docs/10-implementation-roadmap.md: SaaS integration — Product/ProductIntegration models, credential validation, service-to-service auth (signed tokens/OAuth2/mTLS), inbound request contract, rate limiting. Per docs/02-integration-and-security.md."
User Scenarios & Testing (mandatory)
User Story 1 - Every inbound request is authenticated and trusted before anything happens (Priority: P1)
A registered SaaS product's backend calls SupportHub on a customer's behalf (e.g., a customer clicked "Help" inside the product). SupportHub validates the calling product's identity, the credential presented, the product's current status, and the accompanying user/tenant context against that product's registered integration and its allowed scope — before any downstream processing occurs. A request from an unregistered product, an invalid/expired/revoked credential, a suspended product, or context outside the credential's scope is rejected outright.
Why this priority: This is the trust boundary everything else in the system depends on. Without it, SupportHub cannot safely accept "this is customer X of tenant Y using product Z" as true, which every later phase (ticketing, AI, orchestration) relies on completely.
Independent Test: Send a request with a valid, correctly-scoped credential and confirm it is accepted and its product/tenant/user context is trusted; send the same request with an invalid, expired, or wrong-product credential and confirm it is rejected with no side effects.
Acceptance Scenarios:
- Given a product has a registered, active integration with a valid credential, When it sends a request with that credential and in-scope context, Then the request is accepted and the product/tenant/user identity it carries is treated as trusted.
- Given a request presents a credential that doesn't match any registered integration, When SupportHub validates it, Then the request is rejected and no ticket, session, or other record is created.
- Given a product's integration status is "suspended," When a request arrives for that product, Then it is rejected with a reason distinguishable from "invalid credential" (so the calling product can tell the difference between "you're not registered" and "you're registered but temporarily disabled").
- Given a request includes fields not defined in the inbound contract, When it is validated, Then the entire request is rejected rather than the unknown fields being silently ignored.
- Given a request's tenant/user context doesn't fall within the presented credential's allowed scope, When it is validated, Then the request is rejected even though the credential itself is valid.
User Story 2 - An admin can onboard, rotate, and revoke a product's integration credential (Priority: P2)
An operator/admin registers a new SaaS product as a SupportHub integration client, issuing it a credential scoped to that product alone. Later, the admin can rotate that credential (issue a new one while the old one keeps working for a defined transition window) or revoke it immediately (e.g., on suspected compromise), without any SupportHub downtime or a deploy.
Why this priority: Without this, User Story 1 has nothing to validate against, and there's no way to safely respond to a leaked credential — but it's second because a single seeded integration is enough to prove Story 1 works end to end before onboarding/rotation tooling exists.
Independent Test: Register a new product integration and confirm a request using its credential is accepted (Story 1); rotate the credential and confirm both old and new credentials work during the transition, then only the new one after; revoke a credential and confirm the very next request using it is rejected.
Acceptance Scenarios:
- Given an admin registers a new product integration, When they issue its credential, Then that credential is scoped to that product alone — it is never valid for any other product's requests.
- Given an active integration, When an admin rotates its credential, Then requests using either the old or new credential succeed until the transition window ends, after which only the new one works.
- Given an active integration, When an admin revokes its credential, Then the next request using that credential is rejected, and the revocation is recorded in the audit trail.
- Given any authentication attempt (success or failure) against any integration, When it occurs, Then it is recorded in an audit trail an admin can review — including which integration was involved and the outcome.
User Story 3 - No single product integration or end user can overwhelm the system (Priority: P3)
Inbound requests are rate-limited both per product integration and per end user within that integration, using limits an admin can change without a deploy. A product (or a single customer within it) sending requests far beyond its configured limit is throttled; other integrations and users are unaffected.
Why this priority: Important for production resilience and fairness across multiple integrated products, but the system is meaningfully useful (and Stories 1-2 fully testable) without it — this hardens an already-working trust boundary rather than enabling new behavior.
Independent Test: Send requests from one integration far beyond its configured rate limit and confirm later requests in the burst are throttled while a concurrent, well-behaved second integration's requests continue to succeed normally.
Acceptance Scenarios:
- Given an integration has a configured rate limit, When it is exceeded within the configured window, Then further requests from that integration are throttled until the window resets.
- Given two different end users under the same integration, When one exceeds their per-user limit, Then the other user's requests continue to succeed normally.
- Given an admin changes a rate limit value, When the change is saved, Then it takes effect without requiring a deploy or restart.
Edge Cases
- What happens when a credential is presented after its rotation transition window has fully elapsed? It MUST be treated identically to an already-revoked credential (rejected).
- What happens when the inbound request's signature/token appears valid but is a replay of a previously-used one (e.g., a captured and resent signed token)? It MUST be rejected — accepted service-to-service auth mechanisms must be replay-resistant (short-lived tokens with a nonce/timestamp check, or equivalent).
- What happens when a product has no integration configured at all (never registered)? Requests MUST be rejected the same way as an invalid credential, without leaking whether the product ID itself is known to SupportHub.
- What happens when clock skew between the calling product and SupportHub affects a time-bound signed token's validity window? A small, explicitly bounded tolerance is allowed; anything beyond it is rejected.
- What happens when an integration is rotated or revoked while a request is mid-flight? The in-flight request's outcome is decided by validation at the moment it's checked — no partial application, no race that lets a revoked credential's request complete after revocation is recorded.
- What happens when the same underlying customer problem is submitted twice in quick succession
(e.g., the calling product's own client retried after a timeout)? Out of scope for this
feature — de-duplicating retried problem reports into a single ticket depends on the
Ticketentity, which doesn't exist until the ticketing feature. This feature's contract MUST still reserve a field for an idempotency key so that later feature can use it without a contract change (see Assumptions).
Requirements (mandatory)
Functional Requirements
- FR-001: The system MUST require every SaaS product that wants support to be registered as a distinct integration, each with its own credential — never shared across products.
- FR-002: The system MUST validate, on every inbound request before any downstream processing: the calling product's identity, the presented credential, the product's current status (active/suspended/deprecated), the accompanying user/tenant context, and the credential's allowed scope.
- FR-003: The system MUST reject a request whose product, tenant, or user values are not corroborated by the validated integration and its scope — a caller-supplied ID is never trusted by itself.
- FR-004: The system MUST support at least one production-appropriate, replay-resistant service-to-service authentication mechanism (signed short-lived scoped tokens, OAuth2 client-credentials, or mTLS), selectable per integration.
- FR-005: The system MUST support rotating an integration's credential with a defined transition window in which both the old and new credential are valid, with zero downtime.
- FR-006: The system MUST support revoking an integration's credential with immediate effect on the next request.
- FR-007: The system MUST audit every authentication attempt (success and failure), recording which integration/credential was involved, without ever recording the raw credential value itself.
- FR-008: The system MUST reject any inbound request containing fields outside the defined contract, rather than silently accepting or ignoring them.
- FR-009: The system MUST rate-limit inbound requests per integration and independently per end user within an integration, with limit values configurable without a deploy.
- FR-010: The system MUST distinguish, in its rejection response, between "unregistered/ invalid credential," "suspended product," and "out-of-scope request" where doing so does not leak whether an unregistered product ID exists in the system.
- FR-011: The system MUST let an admin change an integration's status (active/suspended/ deprecated) and have that change take effect on the very next request, without a deploy.
- FR-012: The inbound request contract MUST include an optional idempotency-key field, reserved for the ticketing feature's future use, even though this feature does not implement deduplication against it.
Key Entities
- Product Integration: One SaaS product's registration with SupportHub — its identity, current status, credential reference, chosen authentication mechanism, allowed scope, and rotation/revocation timestamps. Exactly one per product; never shared.
- Customer Reference: The external user/tenant identifiers a validated request carries, scoped to the Product Integration that vouched for them — a reference into the SaaS's own identity system, never a second copy of it (per Constitution Principle I).
- Authentication Audit Event: A record of one authentication attempt (success or failure) against a Product Integration, including outcome and timestamp, but never the raw credential.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: 100% of inbound requests presenting an invalid, unregistered, expired, or out-of-scope credential are rejected before any downstream record is created.
- SC-002: Revoking a credential stops it from being accepted on the very next request after revocation — no propagation delay beyond normal request processing.
- SC-003: Rotating a credential causes zero failed requests for a well-behaved caller using either the old or new credential during the transition window.
- SC-004: An admin can retrieve a complete authentication audit trail (success and failure) for any given integration on demand.
- SC-005: An integration or user sending requests at 10x its configured rate limit is measurably throttled while unrelated integrations/users see no change in their own success rate.
- SC-006: Onboarding a new SaaS product as an integration requires no code change or deploy — it is a configuration/data action only.
Assumptions
- This feature covers the trust boundary and its own admin/audit surface only. It does not implement ticket creation, the AI agent, or any business logic beyond validating and scoping an inbound request — those are later features per the roadmap (Phases 3-5+).
- The idempotency key reserved in FR-012 is deliberately not enforced here (no
Ticketentity exists yet to deduplicate against) — this is a forward-compatibility placeholder so the ticketing feature doesn't need a breaking contract change later, per the gap noted indocs/11-architect-additions-gaps-and-recommendations.md§A1. - "Rate limiting... configurable without a deploy" follows Constitution Principle II
(configuration over hardcoding) — exact default limit values are a
REQUIRES BUSINESS CONFIRMATIONitem perdocs/10-implementation-roadmap.md, not invented here. - Choice of authentication mechanism (signed tokens vs. OAuth2 vs. mTLS) per integration, and the
credential rotation transition-window length, are technical decisions deferred to
/speckit-plan— this spec only requires that a production-appropriate, replay-resistant mechanism exists and that rotation/revocation behave as described.