Browse documentation
Testing and launch

Test your integration and go live

Test both the happy path and deliberate failures on stable HTTPS staging domains. The repository's tenant_system reference app includes controls for the core scenarios.

Test data

Prepare three identities:

IdentityNanaade stateExpected result
Returning pilot userActive membership, matching subjectSuccessful 303 launch
First-time userNo Nanaade identity or membershipJIT user + membership creation, then successful 303
Inactive userInactive/revoked target membershipSSO_MEMBERSHIP_INACTIVE
Conflicting userActive member of another tenantBranded transfer/conflict page

Do not use real production credentials in local fixtures or automated test output.

Contract scenarios

ScenarioHow to produce itExpected result
ValidFresh times, nonce, active memberCookie + 303 to approved workspace
First launchFresh unknown sub and email with nameUser and membership created once; cookie + 303
ReplaySubmit identical signed fields twiceSecond returns 409 SSO_ASSERTION_REPLAYED
ExpiredSet iat 10 minutes ago and exp 5 minutes ago401 SSO_ASSERTION_EXPIRED
Invalid signatureSign with a different test secret401 SSO_SIGNATURE_INVALID
Future clockSet iat more than 60 seconds ahead401 SSO_ASSERTION_EXPIRED
Bad originSubmit from unregistered staging origin403 SSO_ORIGIN_NOT_ALLOWED
Bad destinationSign unregistered redirect origin403 SSO_REDIRECT_NOT_ALLOWED
Missing launch codeOmit launchCode400 SSO_LAUNCH_CODE_REQUIRED
Unknown launch codeSign admin.open or another invented value400 SSO_LAUNCH_CODE_INVALID
Unlicensed launchRequest a registered module the tenant does not own403 SSO_LAUNCH_FEATURE_NOT_LICENSED
Unsafe deep linkPut a path or URL in launchContext.contextId400 SSO_LAUNCH_CONTEXT_INVALID
Subject mismatchChange sub after first binding409 SSO_SUBJECT_MISMATCH
Missing first-launch nameUnknown identity without name400 SSO_NAME_REQUIRED
Unsafe portal returnSign an unregistered portal origin403 SSO_PORTAL_RETURN_NOT_ALLOWED
Cross-tenant identityUse an email active under Tenant A to launch Tenant BConflict page; no duplicate active membership

Generate destructive cases only in staging. Never intentionally expose or corrupt a production secret.

Authorization scenarios after SSO

  1. Licensed sidebar entries and dashboard cards appear.
  2. Unlicensed entries are omitted.
  3. Direct unlicensed page navigation renders Feature Unavailable.
  4. Direct API requests return 403 FEATURE_NOT_LICENSED with the required code.
  5. Removing a parent module blocks dependent add-ons.
  6. Revoking membership blocks all subsequent tenant access.
  7. Tenant A cannot read Tenant B roster, analytics, uploads, notifications, or background-job output.

Usage and expiry scenarios

  1. Set a low Gemini tenant threshold and confirm only Gemini-backed write operations return TENANT_USAGE_LIMIT_REACHED.
  2. Confirm Job Board browsing, profile, tenant configuration, and SSO remain available.
  3. Set a per-member daily threshold and confirm another member is unaffected.
  4. Verify licensed calls create bypassed FeatureUsage records without deducting retail credits.
  5. Issue a short-lived test credential and confirm an expired exchange returns SSO_CREDENTIAL_EXPIRED.
  6. End a non-renewing test agreement and run the credential sweep; confirm tenant suspension, credential revocation, and audit events.
  7. Display an unlicensed navigation item and confirm it is disabled as an upgrade prompt while its direct route still returns Feature Unavailable.

Branding and hosted UI scenarios

  1. Verify the tenant display name, browser title, and favicon on the final tenant hostname.
  2. Test wide, square, and tall transparent logos in the desktop sidebar and mobile header.
  3. Confirm logos preserve aspect ratio and remain visible in specialized module sidebars.
  4. Verify primary buttons, selected navigation, focus rings, dialogs, and toast indicators use the tenant theme.
  5. Confirm destructive and warning states retain their semantic meaning and accessible contrast.
  6. Configure a dark logo and confirm it is used on dark surfaces.
  7. Change branding, refresh tenant configuration, and confirm the next workspace load uses the new version.
  8. Move between a tenant hostname and retail Nanaade; confirm branding does not leak across contexts.

Rotation rehearsal

Rotate with a short overlap. Confirm the new secret works immediately, old secret works only during overlap, and old secret fails afterward. Then update the operational runbook with the actual deployment time required.

Production checklist

  • Production portal, workspace, and API use HTTPS.
  • Exact origins and minimal return paths are registered.
  • Production credential differs from staging.
  • Secret is server-only and access-controlled.
  • Assertions derive identity from authenticated server state.
  • Servers use synchronized time.
  • Handoff responses are no-store with restrictive CSP.
  • Logs redact payloads, signatures, cookies, and personal data.
  • First-time JIT provisioning creates exactly one user and membership.
  • Returning launches reuse the subject-bound identity.
  • Conflict and transfer-request flows preserve the original membership until approval.
  • Every portal module button uses a registered launch code.
  • Nanaade—not the assertion—selects the destination route and enforces licensing.
  • Replay, expiry, signature, origin, redirect, and membership failures pass.
  • Module, permission, and cross-tenant isolation tests pass.
  • Rotation and emergency revocation are rehearsed.
  • DNS, TLS, redirects, and cookies pass on final hostnames.
  • Technical and incident contacts are documented.

What success looks like

The user clicks once in the tenant portal, briefly sees the secure handoff page, lands in the correctly branded Nanaade workspace, and sees only licensed capabilities. No Nanaade password is requested, no tenant secret reaches the browser, and repeated or altered assertions fail with stable diagnostic codes.