docs: spec for SaaS integration & inbound request trust feature
/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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
38148a97f9
commit
0e92faf615
@@ -0,0 +1,40 @@
|
||||
# Specification Quality Checklist: SaaS Product Integration & Inbound Request Trust
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2026-08-21
|
||||
**Feature**: [spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Auth mechanism choice (signed tokens/OAuth2/mTLS) and rotation-window length are deliberately
|
||||
left to `/speckit-plan`, not decided here — see spec.md Assumptions.
|
||||
- Idempotency-key enforcement is explicitly deferred to the future ticketing feature (FR-012
|
||||
reserves the field only); this is a scope boundary, not a gap.
|
||||
- Exact rate-limit values and auth-mechanism-per-integration defaults are
|
||||
`REQUIRES BUSINESS CONFIRMATION` per docs/10-implementation-roadmap.md — not invented here.
|
||||
- All items pass; no revision iterations were needed.
|
||||
@@ -0,0 +1,218 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user