offplan · online
Plan · permission-and-tenancy-model

Permission & Tenancy Model — Phase 1.3 Sub-plan

Approvedplanpermission-and-tenancy-modelpriority P0
Ratified
2026-05-11
Created
2026-05-04
Updated
2026-05-11
Priority
P0

Status

RATIFIED CONV-33 · SPEC-AMEND PATCHES v4.17 + v4.18 CONV-34. Part 1 (Behavioural Spec, 10 разделов) ratified CONV-32. Part 2 (Decision Log, 11 разделов) ratified CONV-33 pack-mode. Step 4.4 (Business review) + Step 4.5 (Ratification sweep) + workstream phase-1-3-implementation (P0) — completed during CONV-33. 21 HIGH SPEC-AMEND findings closed CONV-34 via v4.17 (Themes 1+2: token mechanics + session re-eval — 10 findings) + v4.18 (Themes 3+4: audit immutability + billing precision — 11 findings) patches. ADR 0004 v2 (audit retention) + ADR 0013 (proration policy) + ADR 0014 (MCP wrapper auth, placeholder shell) ratified. Sub-plan status remains ratified — amendments are inline patches, не re-ratification.

Goal

Phase 1.3 implementation-ready spec — UX flows + edge cases + open questions resolution. Не tech-level (DB schema / API contracts / library choice — implementation choice тех команды).

Success Criteria

Roma read → понимает все user-facing flows + edge cases → его scaffold не разойдётся с ratified decisions. Ilya read → может дать tech estimate без новых вопросов «что должно происходить».

Scope

IN: behavioural specs, edge cases, permission matrix as business rules, open question resolution.

OUT: DB schema, API contracts, library choice, deployment topology, UI mockups (отдельный track — Skeleton White design).

Anchored ADRs + Foundational sections


Part 1 — Behavioural Spec (RATIFIED CONV-32)

1.1 — Signup + onboarding

Three signup paths (Foundational §2 ratified):

Path A — Paid tier signup (Owner создаёт новую Organisation)

Trigger: visitor → offplan.online → «Get started» → tier selection на pricing page (T1/T2/T3, default T1).

  1. Tier selection на pricing page. Trial = T1 only (per ADR 0008); T2/T3 require commitment.
  2. Signup form — email + password OR Google SSO OR Microsoft OAuth.
  3. Email verification = magic link (для password signup; OAuth pre-verified). 60 min validity per Foundational §2.
  4. Welcome email — minimum content: verification link + ссылка обратно на entry-point screen (per Phase 1.2 line 1503).
  5. Entry-point choice screen (Foundational §2 line 725, ratified) — 3 опции, default-cursor on (1):
    • (1) «Start your first project» (primary CTA) → Object Builder (Phase 1.2.4). Single-project Clients = most common path.
    • (2) «Set up your organisation» → мини-визард: Organisation type multi-select (Studio/Agency/Developer) + brand (logo + accent colour) + invite команды. Затем landing на Organisation dashboard.
    • (3) «Skip» → empty dashboard, всё через menu.
  6. Trial = automatic (T1 14d, no card per ADR 0001). TRIAL — N days left badge в шапке.
  7. Только Object Builder доступен; Full Admin залочен до payment (per ADR 0008).

→ User = Owner новой Organisation на {slug}.offplan.online.

Path B — Guest signup (reverse invite, Free Guest tier)

Trigger: invitee получил reverse invitation от существующей Organisation (e.g. VV приглашает Italian developer).

  1. Click invite link → landing с context: «{Inviter Org} invited you as Free Guest для viewing project {X}».
  2. Signup form — email + password OR Google/Microsoft OAuth. (NO tier selection — Free Guest auto.)
  3. Email verification (magic link).
  4. NO entry-point choice screen — Free Guest не может create projects, поэтому direct landing → invited project с guest access.
  5. User создаёт own Free Guest Organisation; может invite users в свою Organisation (но они тоже видят только guest content).

→ User = Owner of Free Guest Organisation, с guest membership в inviter's project.

Path C — Team invite (existing Organisation, специфичная роль)

Trigger: existing Org Owner/Admin invites person в команду с role = Admin / Sales Manager / Content Editor / Sales Agent.

C.1 — Email new (no account):

  1. Invitee receives email с link /invite/{opaque_token} (Phase 1.3.7).
  2. Click → landing с context: «{Inviter Name} invited you to join {Org Name} as {Role}». Signup form (email pre-filled + locked) + password OR OAuth.
  3. After signup → user immediately joins target Org as specified role.

C.2 — Email уже имеет account:

  1. Click → если not logged in: login required ({slug}.offplan.online/login branded OR app.offplan.online/login central).
  2. After login → confirmation: «Accept invite to {Org Name} as {Role}? [Accept] [Decline]».
  3. Accept → membership added; multi-Org Client теперь (см. 1.3 workspace switcher).
  4. Decline → token consumed (single-use, ratified). Inviter sees decline event (audit log), может re-send.

Token mechanics (per Phase 1.3.7): opaque + 7d expiry + single-use (decline ALSO consumes).

Edge cases (1.1)

Deferred (1.1)


1.2 — Login surfaces + auth-scoped views

1.2.A — Две точки входа

(Foundational §2 «Login UX», утверждено.)

  1. Брендированный subdomain login (по умолчанию): {slug}.offplan.online/login — страница входа с брендингом Organisation (логотип, accent colour). Первая точка для приглашённых team members.
  2. Центральный fallback login: app.offplan.online/login — общий вход «не помню свой адрес». После успешного входа показывает список всех Organisations для этого email.

1.2.B — Маршрутизация после входа

Точка входа Одно членство Несколько членств
{slug}.offplan.online/login Сразу в эту Organisation В эту Organisation; переключатель в шапке (Slack-стиль)
app.offplan.online/login Автоматический редирект Страница выбора Organisation после входа

1.2.C — Иконка входа на sales-app страницах

Анонимный посетитель на {slug}.offplan.online/projects/{project-slug} видит иконку входа в правом верхнем углу (реализуется в Phase 1.10.0). Клик → modal с Google / Microsoft / email-пароль. После входа страница перерисовывается из state #1 в state #4 (1.2.G). Logout → возврат к анонимному виду.

1.2.D — Методы аутентификации

(Foundational §2 «Auth methods» + ADR 0005 v3, утверждено.)

Метод Описание Этап
Google OAuth «Войти через Google» — primary, рекомендуемый CTA Stage 1
Email + пароль Минимум 12 символов, NIST-compliant, проверка по haveibeenpwned Stage 1
Сброс пароля Magic link, 60 минут, single-use, invalidates active sessions Stage 1
Microsoft OAuth (Azure AD / Entra ID) Корпоративные пользователи Microsoft 365 / Azure AD Stage 2 (Tier 3 gate)
Custom SSO (SAML 2.0 / OIDC) Generic IdP integration через Okta / Entra ID / OneLogin Stage 2 (Tier 3 gate)

1.2.F — 2FA

2FA-loss recovery (v4.18 SPEC-AMEND — CS HIGH 2):

1.2.G — Auth-scoped views (4 viewer states)

(Foundational §5 line 964 — утверждено.)

Состояние Кто это Что видит
#1 Anonymous Нет логина, нет buyer-token Public Visibility preset Organisation: Private / Discovery / Full sales / PIN-protected
#2 Buyer с tokenised URL URL содержит ?b=<token>, токен валиден Полная unit-page: цены, availability, contact = атрибутированный SA, Heart + «Связаться» работают
#3 Buyer с истёкшим токеном Token есть, но >90 дней Редирект на main project page; attribution к SA сохранена в DB
#4 Organisation team member (logged in) Залогинен, открыл публичную страницу subdomain'а Всегда Full sales preset; S·1 stock allocation фильтрует юниты (SA — assigned only; CE/SM/Admin/Owner — всё)

Substate (PIN-protected): visitor #1 с valid pin_<project_id> cookie от предыдущего PIN entry → After-PIN preset (Discovery или Full sales) без blur. Cookie 30 дней. (§5.1)

1.2.H — Reserved subdomain blacklist

Зарезервированы: staff / app / api / admin / www. Полный список — Part 2.7.

Edge cases (1.2)

Deferred (1.2)


1.3 — Workspace switcher (Organisation + Project)

1.3.A — Когда Organisation switcher появляется

(Foundational §1 line 602 — утверждено.)

1.3.B — Где живёт переключатель

Surface Локация Реализация
Admin panel Шапка рядом с avatar Phase 1.3.12 + onboarding-trial-mode
Public sales-app Avatar dropdown в иконке логина (top-right) Phase 1.10.0
Operator dashboard (staff.offplan.online) Не применимо Operator — отдельная identity

1.3.C — Группировка

(Phase 1.2 line 1387 — утверждено: «Slack/Linear pattern с Guest orgs section».)

Две секции:

  1. Owned / paid Organisations — где Client = Owner или member платной Organisation (T1 / T2 / T3).
  2. Guest organisations — где Client = member Free Guest Organisation.

Внутри секций: Org name + tier badge + role badge.

1.3.D — Cross-Organisation login behaviour

(Phase 1.10.0 line 3039 — утверждено.)

1.3.E — Wrong-Organisation edge case

(Phase 1.10.0 line 3040 — утверждено дословно.)

1.3.F — Real-world пример (Foundational §1 line 628)

Иван (Content Editor + Sales Agent): один Client [email protected], два membership'а — Content Editor в VV и Sales Agent в Evgenia Realty. Switcher показывает обе.

1.3.G — Project switcher (внутри текущей Organisation)

(Phase 1.6.2 line 2178 + Phase 1.1.2 line 1321 — утверждено.)

Отдельный switcher — переключает фокус между проектами текущей Organisation (не путать с Organisation switcher).

Видимость по роли (permission decision этого sub-plan'а):

Роль Что видит в Project switcher
Owner / Admin Все проекты Organisation
Sales Manager Все проекты Organisation
Content Editor Все проекты Organisation
Sales Agent Только проекты где у него ≥1 assigned unit (S·1 Closed pool)
Free Guest member Только guest projects

Поведение:

UI deep-spec — делегирован в plans/onboarding-trial-mode.md.

Edge cases (1.3)

Deferred (1.3)


1.4 — Cross-entity invitations (forward + reverse)

1.4.A — 4 типа приглашений

(Foundational §4.1 line 854 — утверждено дословно.)

Type Описание Token
Personal invite Client → Client в свою команду. Role: Admin / SM / CE / SA. См. 1.1 Path C. Single-use, 7 дней
Organisation invite (forward) Developer's Org → Studio/Agency как guest organisation. Multi-use, 7 дней
Reverse invite Studio's Org → Developer как guest organisation (с Free Guest creation если нет аккаунта). Multi-use, 30 дней (v4.17 SPEC-AMEND — Developer B2B evaluation cycles 2-4 weeks)
Buyer email link SA → Buyer с tokenised unit links. Не invitation. См. 1.7. Per-unit token, 90 дней

1.4.B — Forward invite (Developer → Studio / Agency)

Use-case: Italian Developer создал project Cote (его Organisation платит), приглашает VV как guest organisation в роли Studio.

Flow:

  1. Инициатор: Owner / Admin Developer's Organisation. SM / CE / SA — нельзя.
  2. Form: Project Settings → Guest Organisations → «+ Invite organisation». Email + Role (Studio / Agency, default Studio) + scope = per-project always.
  3. Token: multi-use, 7d, opaque.
  4. Email: «{Developer Org Name} invites your organisation to join project {Project Name} as {Role}».
  5. Acceptance: existing Owner / Free Guest Owner → confirmation; нет аккаунта → signup Path A или B с pre-attached invitation.
  6. Result: guest organisation получает per-project access. Stock allocation = Closed pool default (0 юнитов до явного assignment).

1.4.C — Reverse invite (Studio → Developer)

Use-case: VV сделала Cote как готовый проект, отправляет Italian Developer'у. Developer регистрируется → Free Guest Organisation → видит Cote → решает upgrade на Tier 1 → получает full ownership.

Flow:

  1. Инициатор: Owner / Admin Studio's Organisation. Tier 1+ только (Free Guest не может приглашать guest organisations per Foundational §3 line 787).
  2. Form: Project Settings → Guest Organisations → «+ Invite developer». Email + Role: Developer (default) + scope = per-project always.
  3. Token: multi-use, 30d (v4.17 SPEC-AMEND — extended из 7d для Developer B2B evaluation cycles 2-4 weeks; see § 1.4.A row 3), opaque.
  4. Email: «{Studio Org Name} invites you to view project {Project Name} you can claim as your own».
  5. Acceptance: нет аккаунта → signup Path B (Free Guest tier creation); existing Owner → его Organisation получает guest membership.
  6. Conversion к платному: «+ Create project» в dashboard ИЛИ ownership transfer (1.5.F). Stage 1: ownership transfer self-serve до target Organisation's first_successful_payment_at (v4.17 SPEC-AMEND — Sales motion HIGH 4; см. § 1.5.F).

Funnel visibility surface (v4.17 SPEC-AMEND — Sales motion HIGH 2):

Studio видит «Invited Developers» tab в Project Settings → Guest Organisations со столбцами: Email · Sent at · Opened at · Signed up at · Free Guest tier · Upgraded at. Audit log events feed эту view (per § 2.4.A Membership lifecycle). Stage 2: per-step conversion rate metric. Stage 1 = list view + manual count.

Legal basis + IP-rights warranty (v4.17 SPEC-AMEND — Legal HIGH 3, deferred deep spec to legal sub-plan):

1.4.D — Token mechanics

(Foundational §4.1 line 867 — утверждено + v4.17 SPEC-AMEND — Security HIGH 4 + 6.)

1.4.E — Scope правила

(Foundational §4.x line 896–899 — утверждено дословно.)

  1. Personal invite — org-level по умолчанию.
  2. Personal invite — project-level через toggle в invite form.
  3. Organisation invite (guest org) — всегда project-level.
  4. Owner / Admin / Sales Manager своей Organisation — bypass scope.

Edge cases (1.4)

Deferred (1.4)


1.5 — Role transitions

1.5.A — Authority

(Foundational §1 line 616 + §4.2 line 876 — утверждено.)

Действие Owner Admin Sales Manager
Invite member
Promote / demote (Admin ↔ SM ↔ CE ↔ SA)
Remove member
Revoke personal invite (pending) ✅ (только им же отправленный)
Promote to Owner = ownership transfer
Demote / remove Owner ❌ — single Owner per Org, требуется ownership transfer first

1.5.B — Promote / Demote

1.5.C — Remove member (deactivation)

(Foundational §7.1 line 1196–1206 — утверждено + v4.18 SPEC-AMEND — CS HIGH 1.)

  1. Confirmation modal с consequences.
  2. Active sessions: immediate logout (server-side sessions.revoked_at = now() cross-subdomain, per § 1.5.B v4.17 amendment).
  3. Assignments cascade up к SM / Admin / Owner (с triage view + batch digest per § 1.5.E v4.17 amendment).
  4. Buyer-records preserved, attribution preserved.
  5. Login attempt после deactivation: generic «не получилось войти» (security — не leak). (v4.18 SPEC-AMEND — CS HIGH 1): operator dashboard (Phase 1.4.7) per-email lookup screen позволяет CS видеть actual reason (deactivated / wrong subdomain / unverified email / locked / 2FA-locked / not-member) на support tickets. Tenant-facing message остаётся generic; operator-facing actionable. Same applies к § 1.2 Edge cases generic wrong-Org error.
  6. Audit log: «{User} removed from {Org Name} by {Actor}».

1.5.D — Self-leave

(Foundational §7.1 line 1204 — утверждено дословно.)

1.5.E — Cascade rules

(Foundational §4.4 + line 921 — утверждено: «escalate up, never auto-revert в Internal pool».)

Кого удалили Куда переходят assignments
Sales Agent Sales Manager → Admin → Owner
Content Editor Не имеет assignments напрямую
Sales Manager Не имеет assignments напрямую (bypass scope)
Guest organisation member Owner-team owning Organisation
Auto-revert в Internal pool ❌ rejected

Audit log + email notification к новому target'у на каждом cascade event'е.

Cascade triage view (v4.17 SPEC-AMEND — Studios HIGH 1):

1.5.F — Ownership transfer

(Foundational §3 line 810–826 — утверждено + v4.17 SPEC-AMEND — Sales motion HIGH 4 + CS HIGH 3.)

Self-serve gate tied к target Organisation's first_successful_payment_at (NOT actor's payment state). Pre-payment (target Org in trial / Free Guest state) = self-serve flow. Post-payment (target Org has ≥1 successful Stripe charge) = [email protected] operator-mediated flow.

Field definition: organisations.first_successful_payment_at set on first Stripe webhook invoice.paid (or equivalent) with non-zero amount. Refunds / chargebacks do NOT roll back this field — once flipped, ownership transfer requires support route forever. Stage 1 = single timestamp; Stage 2 may model «payment dispute window» if abuse appears.

Self-serve flow (4 шага, pre-payment):

  1. Owner → Settings → Transfer Ownership → email получателя.
  2. Email-инвайт с одноразовым токеном (7d expiry, entropy + rate-limit per § 1.4.D).
  3. Получатель открывает email, регистрируется или logs in → acceptance screen с описанием Organisation.
  4. На acceptance: получатель выбирает что произойдёт со старым Owner — стать Admin или быть удалённым из команды. Confirm → ownership flips, audit log: «Ownership transferred from {X} to {Y}».

Operator-mediated flow (post-payment, v4.17 SPEC-AMEND — CS HIGH 3):

Owner clicks «Transfer Ownership» → modal: «Your Organisation has active billing. Transfer requires support assistance for fraud-prevention.» CTA «Open support ticket» → routes to [email protected] via в-app form pre-filling:

Operator SLA + identity-verification checklist (lives в Phase 1.4.7 operator playbook):

Stage 2: full self-service ownership transfer post-payment per ADR (TBD).

Edge cases:

Связь с reverse invite (1.4.C): главный sales motion — Studio создаёт project на trial → reverse invites Developer → Developer accepts ownership transfer (pre-payment self-serve) → Developer makes first payment.

1.5.G — Re-invite после deactivation

(Foundational §7.1 line 1201 — утверждено дословно.)

Edge cases (1.5)

Deferred (1.5)


1.6 — Stock assignment

1.6.A — Stock allocation mode (per-project setting)

(Foundational §4.4 line 904 — утверждено.)

1.6.B — Closed pool: 3 unit states

(Foundational §4.4 line 906–914 — утверждено дословно.)

State Кто видит Кто может продать
Internal pool (default) Owner / Admin / SM / CE / любой Internal SA. Guest org-members — НЕТ. Owner / Admin / SM / любой Internal SA
Assigned to guest org Только members этой guest org + Owner/Admin/SM owning Org + CE Только members этой guest org
Assigned to user Только этот user + Owner/Admin/SM owning Org + CE Только этот user

1.6.C — Open pool delta

(Foundational §4.4 line 917 — утверждено дословно.)

Единственное отличие от Closed mode: «Internal pool» юниты видят и могут продать ВСЕ Sales Agents с project access, включая members guest organisations. Assigned-to-user и Assigned-to-org остаются exclusive как в Closed mode.

1.6.D — Bulk assignment UX

(Foundational §4.4 line 920 — утверждено дословно.)

1.6.E — Per-unit assignment

(Phase 1.3.4 line 1556–1568 — утверждено.)

1.6.F — Authority

(Foundational §4.2 line 882 — утверждено.)

Действие Owner Admin SM CE SA
Manage stock allocation

1.6.G — S·1 visibility rules (3 sub-rules per ADR 0010)

  1. Owner / Admin / Sales Manager always sees all units.
  2. Content Editor sees all units regardless of allocation (content access ≠ sales access).
  3. On target removal — escalate up. Never auto-revert в Internal pool.

1.7 — Reserve unit flow + conflict modal

1.7.A — Status transitions

(Foundational §6.1 line 1121–1124 — утверждено.)

1.7.B — Trigger UX (button-driven)

(Foundational §6.1 line 1099–1106 — утверждено дословно.)

1.7.C — Smart-match logic (3-tier email lookup)

(Foundational §6.1 line 1110–1116 — утверждено дословно.)

  1. Lookup в buyer-records этого SA (org-scope) → match → auto-link.
  2. Lookup в Organisation-wide records → match → warning «attributed to {Other SA}, new record will be created».
  3. No match → form требует name + phone, новый record.

1.7.D — Reverse-flip policy

(Foundational §6.1 line 1122–1124 — утверждено дословно.)

1.7.E — Race condition (first-click-wins)

(Foundational §7.4 line 1234 — утверждено дословно.)

1.7.F — Pessimistic lock (per ADR 0010)

1.7.G — No reservation TTL (per CONV-18)

1.7.H — Buyer view sold/reserved

(Foundational §6 line 1074 — утверждено.)

Buyer с tokenised URL → unit-page открывается нормально, status badge «Sold» / «Reserved». Никаких 404 / hard blocks.

1.7.I — Audit log

(Foundational §6.1 line 1129–1133 — утверждено.)

1.7.J — Buyer tokenised URL mechanics (v4.17 SPEC-AMEND — Security HIGH 2)

(Замечание: anchors сразу к § 1.2.G row #2 «Buyer с tokenised URL» + Foundational §6 line 1074 + § 1.4.A row 4.)


1.8 — Tier transitions

1.8.A — Trial → Paid (Path A signup)

(Foundational §3 line 843 — утверждено.)

  1. Click on TRIAL — N days left badge → pricing modal → tier select → Stripe → first payment → trial badge исчезает, Full Admin открывается.

1.8.B — Trial expired без оплаты

(Foundational §3 line 804 — утверждено + v4.18 SPEC-AMEND — Finance HIGH 1.)

Stripe customer cleanup (v4.18 SPEC-AMEND — Finance HIGH 1):

1.8.C — Paid → Suspended (просрочка / chargeback)

(Foundational §3 line 832–836 — утверждено + v4.18 SPEC-AMEND — Finance HIGH 3.)

1.8.D — Suspended → Restore

(Foundational §3 line 836 — утверждено.)

1.8.E — Free Guest → Paid (Path B conversion)

(Foundational §1 line 629 — утверждено.)

  1. «+ Create project» в Free Guest dashboard.
  2. Upgrade modal → tier select → Stripe → tier flips на Tier 1+.
  3. Existing guest memberships preserved в Guest section workspace switcher'а.

1.8.F — Tier upgrade / downgrade (T1 ↔ T2 ↔ T3)

Proration policy (v4.18 SPEC-AMEND — Finance HIGH 2, anchored ADR 0013):

Cross-reference: ADR 0013 (Proration policy) ratified CONV-34 v4.18 SPEC-AMEND.


1.9 — Internal vs External Sales Agent

1.9.A — Same role, different operational context

(Foundational §4.5 line 934 — утверждено дословно.)

Sales Agent — одна role с двумя operational контекстами:

Базовые permissions в §4.2 (Section 1.10) одинаковы.

(Foundational §4.5 line 936–944 — утверждено дословно.)

Ось Internal SA External SA
Stock pool default (Closed mode) Видит Internal pool Не видит Internal pool — только assigned
Cross-team visibility Видит коллег-Internal SA в своей Organisation Видит только своих в guest organisation
Invitation source Personal invite от Owner / Admin / SM Член guest organisation, через org-invite
Removal authority Owner / Admin / SM (revoke personal invite) Admin owning Org (revoke guest-org membership) или Admin guest org
Reports / analytics Aggregate Org-level (own + team, без других guest orgs) Own продажи + own guest organisation aggregate

1.9.B — Membership_type derivation

(Phase 1.3 v4.7 callout line 1530 — утверждено.)

membership_type = internal | external — derived через organisation_id link на Sales Agent's home Organisation:

RBAC должен экспонировать обе variants через API + UI permissions filter.

1.9.C — UI distinction

Badge «External» рядом с именем external SA в Team list owning Organisation (admin UX clarity — кто сотрудник, кто guest-org member).


1.10 — Permission matrix (5 ролей × actions)

1.10.A — Hierarchy

(Foundational §4.2 line 870 — утверждено дословно.)

Owner ⊃ Admin ⊃ {Sales Manager, Content Editor, Sales Agent}

SM / CE / SA — параллельны (не subset друг друга). Один Client = одна role per Organisation (если нужно и контент, и продажи → Admin).

1.10.B — Полная permission matrix

(Foundational §4.2 line 873–890 — утверждено дословно.)

Действие Owner Admin Sales Manager Content Editor Sales Agent
Billing / cancel / transfer
Add / remove team members
Invite / remove Internal Sales Agents (own team)
Invite / remove External Sales Agents (via guest organisation)
Invite guest organisations
Edit content (gallery, renders, floorplates, descriptions, переводы)
Edit unit pricing
Manage stock allocation (assign / unassign units)
Create / edit buyer profiles ✅ (own)
Send presentation emails to buyers ✅ (own)
Change unit status (available → reserved → sold) ✅ (own assigned)
Public Visibility settings (Private / Discovery / Full sales / PIN-protected)
Branding / theme customisation (project-level)
Branding / theme customisation (Organisation-level)

Reverse-flip note (CONV-26): reverse направление status change (Sold → Reserved/Available, Reserved → Available) — same permissions как forward. Каждый reverse-flip → audit log + email notification одному уровню выше. Forward — audit only.

1.10.C — View-as-Agent (admin debug + training)

(Foundational §4.6 line 948–953 — утверждено дословно.)


Part 2 — Decision Log (RATIFIED CONV-33)

2.1 — Workspace switcher mechanics

Closed as cross-ref. Поведенческий спек workspace switcher закрыт в Part 1 § 1.3 (A когда появляется / B routing / C группировка / D cross-Organisation / E wrong-Organisation / G Project switcher). UI deep-spec (визуальный layout, dropdown rendering) делегирован в plans/onboarding-trial-mode.md. Нет открытых вопросов для Phase 1.3 implementation.


2.2 — Ownership transfer detail

Closed as cross-ref. Полностью покрыто в Part 1 § 1.5.F + Foundational §3 lines 811-826 (4-step flow + cross-link с reverse invite sales motion). Token mechanics = single-use + 7d (consistent с personal invite per § 1.4.D). Audit log entry («Ownership transferred from {X} to {Y} at {TIMESTAMP}. Old owner outcome: {Admin | Removed}») + email notifications (new Owner + old Owner + team broadcast; guest organisations silent) — details в § 2.4 (Audit log scope).


2.3 — Referral attribution rules

Real design. Scaffolding в Foundational §7.5 lines 1241-1273 даёт 7 ratified bullets; добавляем 6 + 2 v4.18 SPEC-AMEND picks для Stage 1 ratification:

  1. Credit accrual = dual condition. Запись в referrals появляется при signup referee (signup_at). payout_status: pending → eligible когда обе стороны выполнены: (a) referee's Organisation first upgrade на paid tier (upgraded_to_client_at), (b) sponsor на paid tier на момент upgrade'а referee. Если sponsor всё ещё Free Guest — статус остаётся pending, активируется когда sponsor upgrades. (v4.18 SPEC-AMEND — Finance HIGH 4): retroactive eligibility cap = 12 months from referee's upgraded_to_client_at. Если sponsor upgrades > 12mo после referee upgrade → payout_status = ineligible_expired (audit entry + sponsor-facing note «Referral expired — sponsor upgrade exceeded 12-month cap»). Prevents unbounded retroactive payouts.
  2. Payout amount calculation — Stage 2 / Phase 4.2. Stage 1 хранит payout_amount = null.
  3. Referral code generation — auto-generated при первом access экрана Settings → Referrals. Lazy creation (не при signup всех Client'ов — экономия записей). Format: 6 chars base32 (~1B variants — достаточно при <10k активных users в Stage 1). Stored в referral_codes(user_id, code) уникально.
  4. Self-ref + reverse-ref detection — already specified §7.5 line 1248: запись с payout_status: ineligible_cycle. Подтверждение: detection runs server-side на момент Organisation creation (cycle = sponsor попадает в свой же reverse-chain).
  5. ?ref= URL param scope — primary attribution; signup form backup field («Referred by code» optional). NO cookie tracking. Если оба заполнены и различаются → wins URL param (newer signal); audit log entry «conflicting referral params: URL={X}, form={Y}; URL chosen». (v4.18 SPEC-AMEND — Sales motion HIGH 3): existing-account signup attribution. Sponsor binding tied к Organisation created_at (НЕ user signup_at). Если referee — existing user creating new Organisation → reference ?ref= или form value attaches sponsor к new Organisation's created_at. Existing-user re-using account для new Org with valid ?ref= → referral row created (no longer drops silently). Audit: «Referral attributed на Organisation creation: sponsor={X}, referee_org={Y}, referee_user={Z}».
  6. Stage 1 visibility UI для Free Guest — экран Settings → Referrals показывается всем (включая Free Guest) с disabled top block «Upgrade to start referring» + locked tooltip на «Скопировать ссылку». Мотивация upgrade. Rejected: скрывать до upgrade (confusion «зачем мне это, я ещё не плачу»).

2.4 — Audit log scope для Phase 1.3 events

Real design. Phase 1.4.7 закрывает operator-side audit; здесь org-side enumeration для Phase 1.3.

2.4.A — Logged event categories

Категория События
Membership lifecycle invite sent · invite accepted · invite declined · invite revoked · invite superseded · member added · role changed · member removed · member self-left
Authentication login success · logout · failed login (per email + IP, anti-brute-force aggregate) · 2FA enabled / disabled · password reset triggered · password changed
Stock allocation unit assigned (to user / org) · unit unassigned · cascade event (escalate up) · pool mode flipped (Open ↔ Closed)
Ownership transfer initiated · accepted · declined / expired · completed
Tier transitions trial started · trial expired auto-downgrade · paid upgrade · downgrade attempt (blocked / completed) · suspension entered · suspension exited
Status flips (Phase 1.7) forward (Available → Reserved/Sold) · reverse (Sold → Reserved / Reserved → Available) — per Foundational §6.1 lines 1129-1133
View-as-Agent (§4.6) enter · exit · duration recorded
Public Visibility preset changed (Private / Discovery / Full sales / PIN) · PIN attempts (rate-limit aggregate)
Referrals code generated · referral_signup · cycle blocked (ineligible_cycle) · payout status change

2.4.B — Visibility per role

Категория Owner Admin Sales Manager Content Editor Sales Agent
Membership lifecycle own-team events own (re self)
Authentication ✅ (own + team) ✅ (team) own only
Stock allocation own assigned
Ownership transfer ✅ (read-only)
Tier transitions ✅ (read-only)
Status flips own actions
View-as ✅ (own session)
Public Visibility
Referrals ✅ (own + Org) ✅ (Org events) own only

2.4.C — Storage + retention

Immutability + tamper-evidence (v4.18 SPEC-AMEND — Security HIGH 3):

PII classification + GDPR-compliant retention (v4.18 SPEC-AMEND — Legal HIGH 2 + Legal HIGH 1):

2.4.D — UI surface для Phase 1.3

Deferred (2.4)


2.5 — Naming finalisation (Sales Manager + Content Editor)

Closed без изменений. Имена Sales Manager + Content Editor ratified в v4.7 (CONV-24); consistent across Foundational §1 lines 615-619, §4.2 line 870, Phase 1.3.9, permission matrix Part 1 § 1.10.B. Альтернатив не предложено. Закрыт.


2.6 — Email mismatch wording

Closed as verbatim. «Email приглашения не совпадает, свяжитесь с тем, кто пригласил» (Foundational §2 line 765, verbatim в Part 1 § 1.1 + § 1.4.E). EN/AR i18n — Stage 2 per ADR 0007.


2.7 — Subdomain reservation list

Real design. Полный list (58 entries, categorised):

2.7.A — Платформа (operator + auth)

staff · app · api · admin · www · auth · accounts · signup · login · signin · register · console · dashboard

2.7.B — Инфра / DNS

mail · email · smtp · mx · ftp · webhook · webhooks · cdn · assets · static · media · files

2.7.C — Environments

staging · dev · test · qa · preview · sandbox

2.7.D — Functional / brand

blog · news · press · docs · developers · kb · help · support · status · health · legal · terms · privacy · billing · payments · pay · checkout · analytics · metrics · pricing · about · contact

2.7.E — Brand / vendor (typosquat protection)

offplan · offplanonline · volumevision · stripe · paddle · notion

2.7.F — Validation rules

2.7.G — Storage + maintenance


2.8 — Free Guest → paid conversion

Real design. Триггер + миграционные правила.

2.8.A — Триггер (dual)

  1. Lazy — «+ Create project» CTA в Free Guest dashboard → upgrade modal → tier select → Stripe → conversion completes. Primary path per Foundational §3 line 844.
  2. Explicit — Settings → Billing → «Upgrade plan» button. Same modal.

2.8.B — Что мигрирует (preserved as-is)

2.8.C — Что новое (activated on conversion)

2.8.D — Edge cases

2.8.E — Backwards (paid → Free Guest)


2.9 — Sales Agent dual-Org membership

Real design. Подтверждение модели + home_org formalisation.

2.9.A — Allowed combinations

Один Client (один email) может быть members в N разных Organisations с разными ролями per Organisation (per Foundational §1 line 628 verbatim example: Иван CE в VV + SA в Evgenia Realty). Explicitly ratified:

2.9.B — Forbidden

2.9.C — home_org formalisation (new)

2.9.D — Switcher rendering (dual-Org SA)

2.9.E — Reports / analytics scope (per Foundational §4.5 line 943)


2.10 — Open/Closed pool reversibility

Real design. Per-project flip rules.

2.10.A — Both directions allowed

2.10.B — Permission

2.10.C — UI + confirmation

2.10.D — Audit + notifications

2.10.E — NO auto-assign на flip

2.10.F — Edge case: unit Reserved by External SA в Open mode, проект flipped to Closed

Variant A (chosen): Status preserved (Reserved сохранён в DB); External SA теряет visibility unless unit explicitly assigned to его guest org. Audit log warns: «External SA {X} from {Guest Org Y} loses visibility to unit {Z} after pool mode flip. If commission cycle ongoing — admin must manually assign unit to {Guest Org Y} OR handover to internal team.»

Rejected:

2.10.G — Universal status preservation


2.11 — Microsoft OAuth resolution

ADR 0005 v3 (2026-05-09, CONV-31) wins — более свежий + authoritative.

2.11.A — Authoritative Stage 1 auth methods

2.11.B — Tier 3 Stage 2 SSO

2.11.C — Patches применённые во время Part 2 commit


Risks & Edge Cases (RATIFIED CONV-33)

Architectural risks

Cross-stage dependencies

From Business Review

Process: Step 4.4 ratification — 6 parallel concern agents (Studios / Sales motion / Customer Success / Legal+Compliance / Security / Finance+Billing) reviewed full sub-plan на 2026-05-11. Consolidated finding counts: 21 HIGH · 29 MED · 15 LOW.

Treatment policy: HIGH findings that mark behavioural-spec gaps → Phase 1.3 implementation gates (Roma must address before scaffold ships); high findings flagged as (SPEC-AMEND) trigger Part 1 / Part 2 amendments в CONV-34 (separate session). MED → documented для post-launch follow-up. LOW → deferred (Stage 2 backlog).

HIGH findings (21)

Studios

Sales motion

Customer Success

Security

Finance + Billing

MED findings (29) — full table

Function Finding Suggested fix / owner
Studios No bulk team invite Stage 1 (§1.4 CSV defer) — pain for studios с 15-30 agents CS expectation: white-glove team-import для first 20 studios; defer acceptable
Studios Owner self-leave blocked без in-product «request transfer» path Add in-app «Request ownership transfer» form pre-filling support ticket
Studios home_org_orphaned flag (§2.9.C) — no Studio-side remediation UI Spec orphan-Client UX; can они convert guest membership в new home_org?
Sales motion Free Guest cannot reverse-invite (§1.4.C, §3 line 787) — blocks viral expansion Allow Free Guest forward-invite; keep reverse-invite Tier 1+
Sales motion Trial = T1 only (§1.1 Path A) — T2/T3 conversion blind T2/T3 demo sandbox OR tier-switch mid-trial
Sales motion SA cascade silent on commission impact (§1.5.E) Add «commission preserved to {departing SA}» line в cascade audit + notification
Sales motion No referral CTA в Free Guest onboarding Add post-Path-B welcome surface promoting referral after upgrade
CS Invite-decline events log'ed но inviter не receives proactive notification (§1.1 Path C.2) Email-to-inviter on decline + token expiry
CS Email mismatch error «свяжитесь с тем, кто пригласил» не actionable (§1.1, §1.4.E, §2.6) Show last-4 / masked hint of invited email (s***y@o***.online)
CS Trial auto-downgrade + 30-day public-site freeze без warning emails (§1.1, §1.8.B) Reminder schedule: T-7 / T-1 / day-of, then day-23 / day-29 freeze countdown
CS Operator workflow «resend fresh invite» missing (§1.4 edge cases) Operator dashboard one-click resend action
CS Cookie corruption mid-action loses unsaved form state (§1.2) Preserve form state в localStorage pre-redirect OR explicit «session expired, changes saved»
CS Role-change does not force logout (§1.5.B) — UI shows new permissions only on next action In-page toast «role updated, refresh to see changes»
Legal home_org_orphaned no defined controller-of-record fallback Define fallback rule + surface в privacy notice
Legal View-as-Agent не logged at buyer-PII access layer (§1.10.C) — GDPR Art. 30 gap Add per-record view event when View-as touches buyer PII
Legal Cross-jurisdiction subdomain hosting — no data-residency commitment / SCC Add residency disclosure + SCCs в multi-party agreement
Legal Referral table sponsor↔referee link retained indefinitely (§2.3) Retention = lifetime of payout claim + statutory limitation (Cyprus = 6yr)
Legal Trial auto-downgrade — buyer PII collected during trial остаётся indefinitely on unpaid Free Guest Dormant-account purge schedule (24mo inactivity → notice → delete)
Security Account enumeration on central login picker (§1.3.D) reveals Org-membership graph pre-auth Render generic response on login submit; show picker post-password+2FA
Security No anti-brute-force / lockout policy specified — only «aggregate» PIN attempts mentioned Per-account exponential backoff after 5 fails; CAPTCHA threshold; PIN 5/15min then 1h lockout
Security OAuth callback validation not specified — no state CSRF, PKCE, redirect_uri allowlist Mandate PKCE + signed state + strict redirect_uri allowlist; subdomain DNS lifecycle policy
Security Password reset invalidates sessions но role-change не invalidates (inconsistent) Align — role-change + member-removal invalidate all session records
Security Single *.offplan.online cookie scope = blast radius on subdomain XSS Strict CSP per subdomain; cookieless assets.offplan.online; separate staff. cookie scope
Security Invite token entropy / rate-limit unspecified ≥128-bit CSPRNG, constant-time DB lookup via hash, rate-limit per IP, alert on >N invalid attempts
Security 2FA optional Stage 1 для Owner с billing + transfer + downgrade access Step-up auth для sensitive actions OR mandate 2FA at Owner role assignment
Finance Ownership transfer pre-/post-payment boundary brittle для refund/chargeback edge cases Define first_successful_payment_at; refunds/chargebacks не roll back self-serve eligibility
Finance Free Guest Organisation cost accounting (storage / audit rows / API events) unaddressed Add Free Guest cost telemetry to Org record
Finance Tier upgrade Stripe proration unclear для trial-to-paid vs in-cycle Trial conversion = full new-tier period (no proration); paid-to-paid = Stripe default
Finance Reverse-invite ownership transfer creates billing-handover trial-clock ambiguity (§1.5.F + §1.4.C) Specify: transfer resets billing clock; new Owner adds card + new trial OR continues remaining days

LOW findings (15) — deferred Stage 2

Treatment plan

From Learnings DB


Workstreams (RATIFIED CONV-33)

Name Priority Depends On Tags Tasks
phase-1-3-implementation P0 architecture, ux, security, domain Все 12 numbered Phase 1.3.x tasks в docs/rendered/launch-plan-stage-1.html (1.3.1-1.3.12); реализация behavioural spec Part 1 + decision log Part 2

Evaluation

Field Description
Artefact Phase 1.3.x scaffold (Roma's code); Roma + Ilya self-report «понимает все user-facing flows + edge cases без новых вопросов»
2-min check Sergey + Ilya открывают plans/permission-and-tenancy-model.md, scan headings — все 21 раздел (10 Part 1 + 11 Part 2) присутствуют, status: ratified, audit-log table заполнена, edge cases для каждой section покрыты
Agent self-check Все 11 пунктов Part 2 имеют либо cross-ref close, либо real design с ratified picks. Все cross-refs (Foundational §X line Y / ADR NNNN / Part 1 § N.M) проверены grep'ом. Microsoft OAuth Foundational §2 patches применены

Changelog