Skip to content

feat!: mirror member roles and per-sign-in sessions in the fake tenant - #84

Merged
KyleJune merged 2 commits into
mainfrom
feat/fake-tenant-member-roles-sessions
Oct 7, 2026
Merged

KyleJune merged 2 commits into
mainfrom
feat/fake-tenant-member-roles-sessions

Conversation

@KyleJune

@KyleJune KyleJune commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Summary

createFakeTenant now mirrors three behaviours of a Udibo Identity tenant that apps building organization and account screens rely on:

  1. Member application roles. The organization API answers the manager-tier GET …/organizations/:organizationId/members/:userId/roles, POST …/members/:userId/roles ({ roleId }) and DELETE …/members/:userId/roles/:roleId. member-roles now names each tenant-defined role by the id the grant takes; built-in tiers carry no id. A granted role's permissions answer in /api/check and introspection inside that organization only. Refusals follow the tenant: a non-manager gets the 404 an unknown organization gets; a pending member, a non-member and an unknown user share one 404; an unknown role is 404; 400 for a grant naming no role; 403 for revoking a built-in role, which no one in the fake holds as an application role.
  2. The end of a last membership. When the HTTP member revoke removes someone's last accepted role in an organization, everything that membership carried there ends with it: their application roles, the permissions addMember seeded, and the offers still open to them (pending memberships, and unaccepted invitations to their address). Rejoining grants only what the new offer names. Revoking one of several accepted roles, or withdrawing a pending offer from someone who is not a member, ends nothing else.
  3. A login session per sign-in. Each authorization request is a sign-in that starts a new login session and sets an HttpOnly cookie on the tenant's origin naming it. A request that sends the cookie back continues that session while it is live and belongs to the person signInAs chose, as the same browser does on a tenant, so a person can have several devices in the account sessions list.

Changes

  • defineOrganizationRole accepts id and permissions and returns the role's id; redefining a slug keeps its id, and naming a new id for a defined slug throws. A resource grant naming a defined role lists that role's id.
  • removeMember also ends the application roles the membership held; deleting an organization forgets its roles.
  • Contract suite: new steps for the three role verbs, the last-membership cascade and its non-cases, that a revoke and a membership's end stay with the person and organization they name, and a second browser's sign-in leaving the first credential live. TenantContractFixture gains a required addRole(permissions) hook.
  • Testing guide and llms-full.txt updated.

Testing

  • deno task check, deno task test --parallel --reporter=dot (239 passed) and deno task test:all all green.
  • The new contract steps pass against the fake and against the real identity service.
  • An adversarial review found four scoping gaps the suite did not pin (cascade limited to the leaving person, invitation withdrawal and role revoke limited to the organization, a cross-organization check that could not fail); the added steps kill all four mutants and pass against the real service (96 steps).
  • With the fake's change reverted, the new fake and contract tests fail. 25 focused mutants of the new code (cascade conditions, each cascade effect, manager gate, membership gate, organization scoping, re-grant idempotency, role-id stability, cookie continuation and its person check) each fail at least one test at its behavioural assertion.

Closes

Nothing; there is no tracking issue in this repository.

BREAKING CHANGE: Login sessions follow the browser, not signInAs. Repeated authorization requests after one signInAs no longer share a session; each starts its own unless it sends back the session cookie the tenant set, so a test that relied on a second sign-in revoking the first credential must carry that cookie. Conversely, calling signInAs again no longer starts a new browser: a browser that carries the cookie continues its session for the same person, so a test modelling two devices needs two cookie jars (browser contexts). Revoking someone's last accepted role in an organization through DELETE …/members/:userId/:role now also withdraws their pending memberships and unaccepted invitations there and drops the permissions addMember seeded, so a later accept of such an offer answers invalid and rejoining restores nothing. TenantContractFixture requires a new addRole(permissions) hook that defines a tenant-wide role and returns its id.

🤖 Generated with Claude Code

KyleJune and others added 2 commits October 6, 2026 21:56
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@KyleJune

KyleJune commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

test: pin role and cascade scoping in the tenant contract — adds a contract step proving a role revoke and a last-membership cascade stay with the person and organization they name, makes the cross-organization check seat the member in the second organization so it can fail, asserts the grant's created flag, and refuses a new id for an already-defined role slug. Passes against the fake and the real service; the body's breaking-change footer now also covers the revoke cascade and cookie-driven session continuation.

@KyleJune
KyleJune merged commit e94a8d7 into main Oct 7, 2026
12 checks passed
KyleJune pushed a commit that referenced this pull request Oct 7, 2026
## [0.14.0](0.13.0...0.14.0) (2026-10-07)

### ⚠ BREAKING CHANGES

* Login sessions follow the browser, not `signInAs`.
Repeated authorization requests after one `signInAs` no longer share a
session; each starts its own unless it sends back the session cookie the
tenant set, so a test that relied on a second sign-in revoking the first
credential must carry that cookie. Conversely, calling `signInAs` again
no longer starts a new browser: a browser that carries the cookie
continues its session for the same person, so a test modelling two
devices needs two cookie jars (browser contexts). Revoking someone's
last accepted role in an organization through `DELETE
…/members/:userId/:role` now also withdraws their pending memberships
and unaccepted invitations there and drops the permissions `addMember`
seeded, so a later accept of such an offer answers `invalid` and
rejoining restores nothing. `TenantContractFixture` requires a new
`addRole(permissions)` hook that defines a tenant-wide role and returns
its id.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

### Features

* mirror member roles and per-sign-in sessions in the fake tenant ([#84](#84)) ([e94a8d7](e94a8d7))
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 0.14.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant