This page is the domain contract. Every modeling, endpoint, or test decision in the project must be justifiable by a rule listed here. When the code disagrees with the rule, the code is wrong (or the rule needs an RFC before it changes).
Severity convention
MUST = domain invariant, violation is a bug.
SHOULD = default behavior, override requires justification.
MAY = intentional opening, decided case by case.
Anyone MUST be able to sign up via POST /auth/signup without authentication.
U-02
Email MUST be unique (case-insensitive, stored lowercase).
U-03
Password MUST be ≥ 12 characters and hashed with bcrypt (PasswordUtils.hash).
U-04
Signup MUST return 201 with {user_id, access_token, refresh_token}.
U-05
Login (POST /auth/login) MUST accept email+password and return both tokens.
U-06
Refresh (POST /auth/refresh) MUST validate refresh_token and emit a new pair.
U-07
User MAY update name / photo via PATCH /users/me. Email is immutable.
U-08
Soft-delete (DELETE /users/me) MUST flip is_active=False and revoke issued tokens via jti blacklist.
Rationale: public signup is the funnel trigger. Unique email prevents duplicates; bcrypt prevents password leak on breach. Soft-delete preserves referential integrity (old orders keep pointing at the now-inactive user).
Any user MUST be able to create an organization via POST /organizations.
O-02
A user MUST NOT create more than 2 active organizations simultaneously (count Organization rows with owner_id = user.id AND is_active = true).
O-03
Creator becomes the OWNER automatically — Membership(role=OWNER) in the same create transaction.
O-04
Each org MUST have exactly 1 OWNER. Transfer via POST /organizations/{id}/transfer-ownership (target must already be a member).
O-05
OWNER MAY delete the organization (soft-delete). Deletion does not free the slot until the async cleanup (TaskIQ) zeroes out members and converts open orders to cancelled.
O-06
Org slug MUST be globally unique (acme-supplies).
Rationale: the 2-org cap blocks mass-creation abuse for quota resets. Single OWNER eliminates governance conflicts.
OWNER/ADMIN MAY create an invitation via POST /organizations/{id}/invitations with {email, role}.
I-02
Invitation generates an opaque token (generate_opaque_token(48)), stores the hash (hash_opaque_token), and sends the link via email (Jinja2 via EmailUtils.render_template).
I-03
Invitation MUST expire in 7 days (expires_at).
I-04
POST /invitations/{token}/acceptMUST validate: invitation valid + not expired + logged-in user has the same email.
I-05
Acceptance creates Membership(role=invite.role) in the same transaction that marks the invitation ACCEPTED.
I-06
Duplicate invite for the same email MUST invalidate the previous one before creating the new one (status=SUPERSEDED).
I-07
OWNER/ADMIN MAY revoke via DELETE /invitations/{id} before acceptance (status=REVOKED).
I-08
Acceptance MUST fail with 409 when the org already maxed out at 10 members.