Spec requirements from awais786/sso-rules-moneta that this e2e suite does NOT cover, with the reason for each.
Categories:
- Infra-only — verifiable from infra config / IaC, not from a browser. The bundle-side
audit-sso.shcovers these via grep. - Cognito-side — requires Cognito console access or admin API; not reachable from CI.
- Policy/doc — negative or documentation requirement; verified by reasoning, not test.
- Needs infra access — would require an echo endpoint, access token exposure, or similar plumbing the bundle does not provide today.
- Genuine test gap — could be tested but not yet written. Tracked for follow-up.
Format: <module>#<requirement> — <category> — short rationale.
(no remaining gaps in this module — see Categories Summary below for the breakdown of what's covered.)
email-shape detection on header values SHALL avoid polynomial-backtracking regex— Covered elsewhere — per-forksso-audit.shRow 21 catches static regression; live ReDoS probe (adversarial input + response-time bound) is a genuine test gap if you want behavioural coverage.
gateway SHALL use OIDC Discovery against the Cognito issuer— Infra-only — bundle'saudit-sso.shgreps forOIDC_ISSUER_URLconfig; behavioural verification would require breaking discovery to confirm.gateway SHALL pass access token to downstream apps when requested— Needs infra access — requirespass_access_token = trueconfig and a downstream endpoint that echoes the token; not exposed in current bundle.gateway SHALL use the configurable identity claim— Infra-only — config-level.single shared callback URL— Infra-only — Traefik config.
backend ports SHALL be bound to 127.0.0.1 only— Infra-only — only verifiable from inside the host; bundle's audit checksdocker-composeport bindings.auth-response headers SHALL include exactly the three required headers— Infra-only — Traefik config; bundle audit greps forauthResponseHeaders.
Layer 1 SHALL refresh transparently against OIDC— Genuine test gap — would need to fast-forward time or shorten TTL in a test bundle; not yet wired.Layer 1 expiry while Layer 2 is valid SHALL re-auth transparently— Genuine test gap — same TTL constraint.Layer 1 expiry shall force a fresh Cognito login— Genuine test gap — same TTL constraint (would need a short-TTL bundle or time-fast-forward to expire Layer 1 deliberately).mPass-side session revocation SHALL be honoured on next refresh— Cognito-side — requires admin API call to revoke a refresh token; not in CI scope today.per-app session TTLs SHALL be uniformly configurable— Infra-only.Layer-2 session renewal SHALL be guarded against three regression paths— Partially covered —tests/auth/layer2-renewal-suppressed-on-4xx.spec.tspins the Penpot/auth-tokencase (4xx response must carry no fresh session cookie). Extending to Outline (accessToken) and Plane (sessionid) is the same shape with different per-app cookie names.bridge state TTL SHALL be 3 minutes— Genuine test gap — would need to wait 3 minutes or mock the TTL.
identity claim SHALL be configurable when email is unreliable— Partially covered —tests/auth/email-domain-consistency.spec.tsproves cross-appDEFAULT_EMAIL_DOMAINagreement (the load-bearing operational invariant); testing the configurability axis directly is infra-only.claim mapping SHALL be the same across the cookie flow and the JWT-bearer flow— Needs infra access — same blocker asid_token vs access_token audiencebelow: the Cognito access token is not exposed via oauth2-proxy on the current bundle (requirespass_access_token = trueplus a downstream echo endpoint). The e2e test shape would log in via cookie, capture the access token, and replay it asAuthorization: Beareragainst an app that accepts both paths; without token exposure the bearer leg can't be reached.id_token vs access_token audience claim SHALL both be accepted— Needs infra access — requires the JWT-bearer endpoint AND access token exposure.display name SHALL be derived without round-trip when possible— Infra-shaped — the "when possible" qualifier makes this conditional on bundle config (header propagation ofX-Auth-Request-Preferred-Usernameetc.) rather than on observable app behaviour. Both Outline and Twenty's SPAs make a first-paint user-info round-trip regardless of header presence, so the e2e signal collapses to "headers exist on the upstream response" — which is config-only.
per-app "Logout" SHALL NOT be relied on for security— Policy/doc — negative requirement; verified by reasoning + the cross-cuttingsession-lifecyclereap test.Cognito SSO teardown is operator-callable but not surfaced as a user action— Policy/doc — operator-only convention.Cognito allowlist SHALL include the portal main page— Cognito-side — Cognito app client config.
auto-join SHALL run on every login, not just on user creation— Needs admin mutation — the test shape requires removing the user from the workspace out-of-band (admin API or direct DB) between logins, then asserting the next login re-joins them. Without that mutation step, the observable collapses to "user is in workspace" — already covered bytests/auth/workspace-auto-join-independence.spec.tsfor the static case. Admin removal needs either a workspace-Admin SSO identity (not currently provisioned) or instance-admin DB access from CI.auto-join SHALL skip when no workspace exists yet— Genuine test gap — fresh bundle with no workspace; assert user is created but unbound.auto-join target SHALL be the oldest workspace— Genuine test gap — create two workspaces with distinct creation times, log in as a fresh user, assert they land in the oldest.auto-join role SHALL be the app's regular-member role, not Admin or Guest— Partially covered —tests/apps/{outline,twenty,penpot,surfsense,pm}-admin.spec.tseach assert NORMAL_USER (the auto-joined identity) lands without admin/owner rights on the respective app. Tags now in place. A direct DB-role probe is still future work.per-app workspace model SHALL be documented in workspaces.md— Policy/doc — doc requirement; verified by file existence inawais786/sso-rules-moneta.auto-join SHALL NOT leak across apps— Covered —tests/auth/workspace-auto-join-independence.spec.tsprobes each app's primary-workspace endpoint and asserts the returned identifier matches the bundle-configured value inconstants.ts(PLANE_WORKSPACE_ID,OUTLINE_TEAM_ID,PENPOT_TEAM_ID). A regression where app A's storage backend gets pointed at app B's workspace store would surface as a mismatch. SurfSense omitted — its/users/mereturns a per-user PK, not a workspace identifier; SurfSense membership is covered bytests/apps/surfsense-admin.spec.ts. (Prior shape — "no two apps share an identifier" — was vacuous against 4 independent UUID generators and is replaced with this positive-correlation shape.)
After issues #32 and #34, every contract-bearing test in the suite carries an @spec tag pointing at a vendored requirement. The bidirectional gate in tests/meta/playwright-practices.spec.ts enforces this.
What's left here is the soft allowlist — files that don't pin a contract and have a documented reason. Maintained in tests/meta/playwright-practices.spec.ts as UNTAGGED_ALLOWLIST; reproduced here for visibility:
| Spec file | Why exempt |
|---|---|
tests/apps/pm-project-create.spec.ts |
Plane project creation exercises Plane's API + storage layer (AWS / SeaweedFS access-key alignment + browser-reachable presigned-URL hostname). Storage / deployment concern, not the SSO contract. |
tests/apps/{outline,penpot,pm,surfsense,twenty}.spec.ts |
Per-app shell files just call registerLinkCoverage() — the parameterised assertions live in tests/lib/link-coverage.ts. Will fold into per-app skills (or a new link-coverage skill) when that work lands. |
By-directory exemptions (also in the meta spec):
tests/bugs/**— bug tests pin a scenario via plan.md, not a contract requirementtests/zap/**— DAST scan drivers, not assertionstests/meta/**— the gates themselves
| Category | Count | Notes |
|---|---|---|
| Infra-only | 8 | Covered by bundle-side audit-sso.sh |
| Cognito-side | 2 | Out of CI scope |
| Policy/doc | 3 | Verified by reasoning |
| Needs infra access | 2 | Blocked on bundle exposing additional state |
| Genuine test gap | 8 | Open work — candidates for the next coverage PR |
Live counts from scripts/check-spec-coverage.sh (against current upstream):
59 ✅ Covered + 25
The Categories table above is a manual rationale breakdown and may lag module growth (especially when vendored skill modules add requirements). Use make audit as the source of truth for live counts.