Files
support_backend/specs/002-saas-integration/spec.md
T

219 lines
13 KiB
Markdown
Raw Normal View History

# 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**:
1. **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.
2. **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.
3. **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").
4. **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.
5. **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**:
1. **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.
2. **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.
3. **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.
4. **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**:
1. **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.
2. **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.
3. **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 `Ticket`
entity, 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 `Ticket` entity
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 in
`docs/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 CONFIRMATION` item per `docs/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.