Goal
Закрыть Wave 2 Chunk 6 — sweep по §7 Edge cases (Foundational Decisions раздел <div id="fd-edges">). Добавить 10 архитектурных bullets в 3 из 5 subsections (§7.1, §7.4, §7.5) — все decisions locked в CONV-27 interview. §7.2 GDPR / ADGM right-to-erasure + §7.3 Multi-jurisdiction compliance помечены как parking-lot до jurisdiction lock + подключения юриста + активации sub-plan plans/legal-multi-party-framework.md. Existing 5 subsections не переписываются — только additions / amendments.
Locked Decisions (CONV-27 interview)
§7.1 — User deactivation (4 additions)
| # | Тема | Решение |
|---|---|---|
| A | Buyer-records SA при deactivation | Buyer-records остаются в DB, attribution к Sales Agent'у preserved (commission через handoff per CONV-22 attribution rules). Link voiding только через GDPR erasure (см. §7.2). |
| B (revised) | Deactivated user UX | Деактивированный пользователь = анонимный посетитель. Логин fail'ится generic'ом («не получилось войти», без указания причины — security: не leak'аем «kicked by colleague»); прямые ссылки на проекты работают по правилам Public Visibility per §5 (Private 404, Discovery limited, Full sales full, PIN-protected gate). Никаких спецбаннеров про suspension. |
| C | Self-leave vs Owner-removes | Self-leave (Settings → Leave Organisation): confirmation modal с consequences (lose access to N projects, M assigned units cascade up to SM/Admin per §4.4). Same downstream flow что Owner-removes — разница только в trigger / actor в audit log. Owner self-leave disabled — single Owner per Organisation, требуется ownership transfer first per §3. |
| D (amend) | Re-invite no auto-restore | Amend existing bullet 4: при re-invite same email → fresh membership; assignments / buyer-records НЕ auto-restore (manual reassignment by SM/Admin). Защита от accidental restore'ов после suspicious deactivation'ов. |
§7.4 — Conflict resolution (3 additions; «Owner erasure» skipped)
| # | Тема | Решение |
|---|---|---|
| A | Race condition status-change в Open pool | First-click-wins. Два Sales Agent'а одновременно жмут «Mark as Reserved» на same unit (Open pool — оба видят и могут продать). Победитель — первый запрос дошедший до server. Loser получает modal «Только что зарезервировал Алексей в 14:23 — обнови страницу». Audit log пишет обе попытки + winner. Critical после CONV-26 §6.1 verification form (раньше формы не было — race condition новая). |
| B | Suspension Organisation с active buyer-tokens | Tokens продолжают работать read-only до 90-day expiry. CTA «Связаться со мной» disabled с tooltip «Project temporarily unavailable, contact your Sales Agent directly». Полный block backfire'ит на buyer experience и сорвёт active deal flow. |
| Skipped — defer to GDPR sub-plan (parking-lot §7.2). | ||
| D | Visibility flip с active tokens | Buyer-tokens = pre-auth'ed visitor, bypass'ят Visibility setting (consistent с §5.1 PIN bypass + §6 token mechanics). Visibility flip применяется только к новым anonymous посетителям; existing token holders работают по своему правилу до 90d expiry. |
§7.5 — Referral architecture (3 additions, all schema-level)
| # | Тема | Решение |
|---|---|---|
| A | Multi-Organisation referee | Только первая Organisation Client'а с заполненным referred_by_user_id триггерит payout. Schema: Organisation.referred_by_user_id (nullable, set один раз при create). Защита от gaming: Client создал 5 Organisations через одну signup → payout × 1, не × 5. |
| B | Cycle prevention | Self-ref (sponsor_id == new_organisation.owner_id) или reverse-ref (sponsor_id ранее уже был referee этого owner'а) → запись в referrals создаётся с payout_status: ineligible_cycle. Audit запись остаётся для visibility / detection. Не блокируем insert hard — храним для transparency. |
| C (expanded) | Sponsor visibility screen | Detailed spec — см. ниже. |
§7.5 C — Sponsor visibility detailed spec
Расположение: Settings → Referrals в Client'овой админке.
Top block (всегда виден):
- Свой ref-код и готовая URL для копирования:
https://app.offplan.online/?ref=<code>+ кнопка «Скопировать ссылку». - Агрегированные метрики: «приглашено: N · апгрейднулись: M · в платном статусе: K».
Таблица referee Organisations (sort: signup_at desc):
| Колонка | Что показано |
|---|---|
| Organisation | Anonymized identifier Org #abc12 (5-character hash от Organisation id) |
| Зарегистрирован | дата signup |
| Текущий статус | Free Guest / Trial / Paid Tier 1 / Paid Tier 2 |
| Апгрейд | дата первого upgrade на paid tier (или «—») |
| Payout | pending / paid / ineligible_cycle / refunded |
Что НЕ показываем (privacy):
- email и имя Owner'а referee
- настоящее имя Organisation и subdomain
- список проектов / юнитов / metrics referee
- activity log referee
Stage 1 не в scope:
- Генератор invite-ссылок (sponsor копирует свою ссылку вручную из top block)
- Ручной revocation / edit
payout_status(только система через триггеры signup → upgrade → payout) - Pagination таблицы (Stage 1 sponsors редко > 50 referrals; Stage 2 если накопится)
Cross-link: Phase 4.2 — payout-механика; §3 Billing — credit mechanics.
Parking-lot (deferred subsections)
§7.2 + §7.3 — DEFERRED до выполнения 3 условий:
- Jurisdiction lock — memory
project_legal_entity_cyprus.mdговорит «Cyprus decided», но user в CONV-27 сказал «есть вопрос где будет компания открываться» (под review). Нужна Roman ratification. - Юрист подключён (Cyprus / EU / UAE — зависит от jurisdiction lock'а).
- Sub-plan активирован —
plans/legal-multi-party-framework.md(status: stub) запущен отдельной /plan-сессией.
Visible parking-lot callout добавляется в начало §7 (после <h3> lead) — указывает что §7.2 + §7.3 не должны редактироваться до выполнения трёх условий.
Approach
Approach A — Surgical inline additions (pattern Chunks 3-5 для small additions, без новых HTML anchor'ов).
- Каждый принятый bullet appendится в свою существующую
<details>-секцию (§7.1 / §7.4 / §7.5) - §7.5 C — inline
<table>внутри bullet'а (pattern из §6.2 channels) - Parking-lot callout — небольшой stylized
<div>в начале §7 div'а после lead - 2 phase callouts (1.4 + 1.10) — append v4.10 bullets к existing callout
<ul>(как Chunks 3-5 делали v4.7/4.8/4.9) - Changelog v4.10 — entry поверх v4.9
Без новых <h4 id="fd-edges-X"> anchor'ов — §7 edge cases пока ниоткуда не cross-link'ятся (если в будущих Chunks 7-8 phase callouts начнут на них ссылаться — добавим anchor'ы тогда).
Steps
A. Parking-lot callout в начале §7
A1. Insert после existing <h3> (line 1096), перед first <details> (line 1098).
<div style="margin:14px 0; padding:12px 16px; background:var(--sand-50); border:1px solid var(--gold); border-left:3px solid var(--gold); border-radius:0 6px 6px 0; font-size:13px; color:var(--text-mid);">
<strong style="color:var(--navy);">⏸ Parking-lot (CONV-27):</strong> §7.2 GDPR / ADGM right-to-erasure + §7.3 Multi-jurisdiction compliance — <strong>DEFERRED</strong> до (1) lock'а jurisdiction (Cyprus default по memory, под review awaiting Roman ratification); (2) подключения юриста (Cyprus / EU / UAE); (3) активации sub-plan'а <code>plans/legal-multi-party-framework.md</code>. До этого момента не править. Архитектурные edge cases (§7.1 deactivation / §7.4 conflicts / §7.5 referrals) — closed in Chunk 6.
</div>
B. §7.1 — 4 additions
B1. В §7.1 <details> block — amend existing bullet 4 (line 1104) и append 3 new bullets перед closing </ul> (line 1105).
B1.1 — amend bullet 4 (D — re-invite no auto-restore):
- Existing:
<li><strong>Re-invite после удаления:</strong> new invitation flow (single-use token, full personal-invite process). «Restore deleted user» button → Stage 2.</li> - New:
<li><strong>Re-invite после удаления:</strong> new invitation flow (single-use token, full personal-invite process). «Restore deleted user» button → Stage 2. <strong>При re-invite того же email</strong> → fresh membership; assignments и buyer-records <strong>НЕ auto-restore</strong> (manual reassignment by Sales Manager / Admin) — защита от accidental restore'ов после подозрительных деактиваций.</li>
B1.2 — A bullet (Buyer-records persist):
<li><strong>Buyer-records деактивированного Sales Agent'а:</strong> остаются в DB, attribution к SA preserved (commission через handoff к другому SA per CONV-22 attribution rules). Link не воидится — удаление только через GDPR erasure (см. <a href="#fd-edges">§7.2</a>).</li>
B1.3 — B revised bullet (Deactivated as anonymous):
<li><strong>Login attempt после deactivation:</strong> деактивированный = анонимный посетитель. Логин fail'ится generic'ом («не получилось войти», без причины — security: не leak'аем «kicked by colleague»). Прямые ссылки на проекты работают по правилам Public Visibility per <a href="#fd-visibility">§5</a> (Private = 404, Discovery = limited, Full sales = full, PIN-protected = PIN gate). Никаких спецбаннеров про suspension.</li>
B1.4 — C bullet (Self-leave vs Owner-removes):
<li><strong>Self-leave (user-initiated):</strong> Settings → Leave Organisation — confirmation modal с consequences (lose access to N projects, M assigned units cascade up to SM/Admin per <a href="#fd-access">§4.4</a>). Same downstream flow что Owner-removes — разница только в trigger / actor в audit log. <strong>Owner self-leave disabled</strong> — single Owner per Organisation, требуется ownership transfer first per <a href="#fd-billing-transfer">§3</a>.</li>
C. §7.4 — 3 additions
C1. В §7.4 <details> block — append 3 new bullets перед closing </ul> (line 1135).
C1.1 — A (Race condition Open pool):
<li><strong>Race condition при Mark as Reserved (Open pool):</strong> два Sales Agent'а одновременно жмут «Mark as Reserved» на тот же unit (Open pool — оба видят и могут). <strong>First-click-wins:</strong> победитель — первый запрос дошедший до server. Loser получает modal «Только что зарезервировал <em>Алексей</em> в 14:23 — обнови страницу». Audit log записывает обе попытки + winner. Появилось после CONV-26 §6.1 verification form — раньше status-change формы не было.</li>
C1.2 — B (Org suspension active tokens):
<li><strong>Suspension Organisation с активными buyer-tokens:</strong> Organisation заморожена (неуплата / chargeback) — tokens продолжают работать <strong>read-only до 90d expiry</strong>. CTA «Связаться со мной» disabled с tooltip «Project temporarily unavailable, contact your Sales Agent directly». Полный block ударил бы по buyer experience и сорвал active deal flow.</li>
C1.3 — D (Visibility flip token bypass):
<li><strong>Visibility setting flip с активными tokens:</strong> Owner меняет Public Visibility (Discovery → Private или PIN-protected → Full sales) пока есть active buyer-tokens. Tokens = pre-auth'ed visitor, <strong>bypass'ят Visibility setting</strong> (consistent с <a href="#fd-visibility-pin">§5.1 PIN bypass</a> + <a href="#fd-buyer">§6 token mechanics</a>). Новый Visibility применяется только к новым anonymous посетителям; existing token holders работают до 90d expiry по своему правилу.</li>
D. §7.5 — 3 additions (включая expanded C с table)
D1. В §7.5 <details> block — append 3 new bullets перед closing </ul> (line 1145).
D1.1 — A (Multi-Org referee):
<li><strong>Multi-Organisation referee:</strong> Client создал несколько Organisations через одну signup-сессию (с <code>?ref=</code>). Только <strong>первая Organisation</strong> с заполненным <code>referred_by_user_id</code> триггерит payout. Schema: <code>Organisation.referred_by_user_id</code> (nullable, set один раз при create). Защита от gaming: создал 5 Org'ов → payout × 1, не × 5.</li>
D1.2 — B (Cycle prevention):
<li><strong>Cycle prevention:</strong> self-ref (<code>sponsor_id == new_organisation.owner_id</code>) или reverse-ref (sponsor_id ранее уже был referee этого owner'а) → запись в <code>referrals</code> создаётся с <code>payout_status: ineligible_cycle</code>. Audit запись остаётся для visibility / detection — не блокируем insert hard.</li>
D1.3 — C expanded (Sponsor visibility screen) — <li> с inline <table> + sub-blocks:
<li><strong>Sponsor visibility:</strong> отдельный экран Settings → Referrals в Client'овой админке.
<div style="margin:8px 0; padding:10px 14px; background:var(--sand-50); border-left:3px solid var(--gold); border-radius:0 6px 6px 0; font-size:13px;">
<strong style="color:var(--navy);">Top block:</strong> свой ref-код + готовая URL <code>https://app.offplan.online/?ref=<code></code> + кнопка «Скопировать ссылку». Агрегированные метрики «приглашено: N · апгрейднулись: M · в платном статусе: K».
</div>
<div style="margin:8px 0; padding:10px 14px; background:#fff; border:1px solid var(--border); border-radius:6px; font-size:13px;">
<strong style="color:var(--navy);">Таблица referees</strong> (sort: signup_at desc):
<table style="margin:6px 0 0; font-size:12.5px;">
<thead><tr><th style="width:140px;">Колонка</th><th>Что показано</th></tr></thead>
<tbody>
<tr><td>Organisation</td><td>Anonymized <code>Org #abc12</code> (5-char hash от Organisation id)</td></tr>
<tr><td>Зарегистрирован</td><td>дата signup</td></tr>
<tr><td>Текущий статус</td><td>Free Guest / Trial / Paid Tier 1 / Paid Tier 2</td></tr>
<tr><td>Апгрейд</td><td>дата первого upgrade на paid tier (или «—»)</td></tr>
<tr><td>Payout</td><td><code>pending</code> / <code>paid</code> / <code>ineligible_cycle</code> / <code>refunded</code></td></tr>
</tbody>
</table>
</div>
<div style="margin:8px 0; padding:10px 14px; background:#fff; border:1px solid var(--border); border-radius:6px; font-size:13px;">
<strong style="color:var(--navy);">НЕ показываем</strong> (privacy): email / имя Owner'а referee, имя Organisation / subdomain, проекты / юниты / metrics, activity log.
</div>
<div style="margin:8px 0 0; padding:10px 14px; background:#fff; border:1px solid var(--border); border-radius:6px; font-size:13px;">
<strong style="color:var(--navy);">Stage 1 не в scope:</strong> генератор invite-ссылок (sponsor копирует URL вручную из top block); ручной revocation / edit <code>payout_status</code>; pagination (Stage 2 если >50 referrals). Payout-логика → <a href="#fd-billing">§3 Billing</a> + Phase 4.2.
</div>
</li>
E. Phase 1.4 callout — operator visibility for new edges
E1. Append v4.10 <li> к existing Phase 1.4 callout <ul> (после v4.9 entry на line 1707).
<li><em>v4.10 (CONV-27):</em> <strong>Operator visibility для новых edge cases (§7.1 + §7.4 + §7.5).</strong> Operator dashboard читает audit log entries для self-leave events (отдельный actor в log: «User X self-left Organisation Y at TIMESTAMP» vs «Owner removed user X»). Suspension state для buyer-tokens — operator видит «Project P has N active read-only tokens» в Project drawer (after Org suspension). Visibility-flip с active tokens — operator видит «Visibility changed from X to Y, M existing tokens still bypass» в Project log. Referrals: operator видит aggregate counter «N referrals в <code>ineligible_cycle</code> state» в operator overview (anti-fraud signal — детект cycle gaming через несколько Organisations). См. <a href="#fd-edges">§7</a>.</li>
F. Phase 1.10 callout — sales-app routing для новых edge cases
F1. Append v4.10 <li> к existing Phase 1.10 callout <ul> (после v4.9 entry на line 2844).
<li><em>v4.10 (CONV-27):</em> <strong>Routing для §7 edge cases.</strong> Status-change form сабмит в Open pool: server-side optimistic lock (compare-and-swap на <code>unit.status</code>) — если first-click пробежал, second-click получает 409 Conflict + frontend modal «Только что зарезервировал X в TIMESTAMP — обнови». Suspension Organisation: middleware проверяет <code>organisation.status</code> — если suspended, anonymous + token paths render content read-only (CTA disabled), authed paths block с suspension banner. Visibility flip с active tokens: middleware sequence (Phase 1.10 PIN gate routing extended) — buyer-token bypass'ит новую Visibility setting, anonymous посетители получают новый preset. См. <a href="#fd-edges">§7.4</a>.</li>
G. Changelog v4.10 entry
G1. Insert v4.10 entry поверх v4.9 в launch-plan-changelog.html (insert after <div class="wrap"> opening, перед v4.9 <div>).
Structure (full content):
- Date: 2026-05-08
- Source: CONV-27 / Wave 2 Chunk 6
- Summary: §7 Edge cases sweep — 10 architectural additions + parking-lot для §7.2/§7.3 (deferred until jurisdiction + lawyer)
- Subsections: Parking-lot callout / §7.1 4 additions / §7.4 3 additions / §7.5 3 additions (с table) / Phase callouts 1.4 + 1.10 / Не закрыто (parking-lot expansion + Cyprus jurisdiction confirmation)
H. Workstream update
H1. В workstreams/stage1-roman-integration.md:
- Append CONV-27 entry в Session Log
- Update What's Next → Chunk 7 (Phase callouts deep rewrite, включая Phase 1.7 deep rewrite deferred из CONV-24) + parking-lot note про §7.2+§7.3+access expansion+legal alignment+Cyprus jurisdiction confirmation
I. Preview repo sync
I1. Sync 2 файла в ~/code/offplan-online/preview/plan/: launch-plan-stage-1.html + launch-plan-changelog.html. Commit + push.
Files
- Primary:
docs/rendered/launch-plan-stage-1.html(~80 lines added — parking-lot callout + 10 bullets + 2 phase callouts) - Secondary:
docs/rendered/launch-plan-changelog.html(v4.10 entry, ~50 lines) - Workstream:
workstreams/stage1-roman-integration.md - Sync:
~/code/offplan-online/preview/plan/
Dependencies
- Blocked by: nothing (Chunks 1-5 закрыты + commit'нуты + sync'нуты)
- Blocks: nothing (Chunks 7-8 могут идти параллельно или последовательно)
Testing
- Visual sanity check — открыть
launch-plan-stage-1.html:- §7 lead → parking-lot callout видна (golden border, ⏸ icon)
- 5
<details>блоков рендерятся: §7.1 расширен (4+4=8 bullets), §7.2 без изменений (4 bullets), §7.3 без изменений (3 bullets), §7.4 расширен (4+3=7 bullets), §7.5 расширен (4+3=7 bullets, последний с таблицей внутри) - §7.5 C — inline table + 3 sub-blocks (top block / table / NOT shown / not in scope) рендерятся nested внутри
<li>
- Cross-link sanity:
- §7.1 A →
#fd-edges(§7.2 placeholder; пока цель тот же блок) ✓ - §7.1 B →
#fd-visibility(§5) ✓ (anchor существует) - §7.1 C →
#fd-access(§4.4) +#fd-billing-transfer(§3) ✓ - §7.4 D →
#fd-visibility-pin+#fd-buyer✓ - §7.5 C →
#fd-billing✓
- §7.1 A →
- Cross-doc consistency: grep
deactivat— должен match: §7.1 (existing + new bullets) + §4.4 cascade row + Phase 1.4 v4.7 callout + new Phase 1.4 v4.10 callout. Без trespass'а в Stage 2/3/4. - Phase callout count: Phase 1.4 callout
<ul>должен иметь v4.4/v4.7/v4.8/v4.9/v4.10 (5 entries). Phase 1.10 callout<ul>— same. - Preview sync — после push проверить
https://offplan-online.github.io/preview/plan/launch-plan-stage-1.htmlрендерится правильно.
Risks
- Cyprus memory conflict с user'овским «under review» —
project_legal_entity_cyprus.mdговорит «decided», но user в CONV-27 сказал «есть вопрос где будет компания открываться». Mitigate: parking-lot callout формулирует мягко («Cyprus default по memory, под review awaiting Roman ratification») — не противоречит ни одной интерпретации, /handoff flag'нет вопрос для clarification в next session. - §7.5 C nested table может выглядеть тяжело внутри
<details>block'а — sub-blocks внутри<li>внутри<ul>внутри<details>. Mitigate: pattern такой же как §6.2 channels (CONV-26) — там работало; styling consistent (sand-50 + border-left gold). - Phase 1.4 + 1.10 v4.10 callouts могут дублировать v4.9 — все три (v4.8/4.9/4.10) затрагивают operator dashboard / sales-app routing. Mitigate: v4.10 фокусируется конкретно на §7 edges (self-leave audit, suspension token state, visibility-flip routing) — не дублирует v4.9 (status-flip / channel analytics).
- Parking-lot callout не активирует sub-plan автоматически —
plans/legal-multi-party-framework.mdостаётсяstatus: stub, нужен trigger в next session. Mitigate: explicit пометка в workstream's What's Next + flag в /handoff'е. - Cross-link
#fd-edgesдля §7.2 reference в §7.1 A bullet — §7.2 не имеет отдельного anchor'а (только parent#fd-edges). Pointer работает (jump к началу §7), но не precise. Mitigate: acceptable pre Approach A (no new anchors); если Chunk 7+ Phase callouts начнут точечно cross-link'аться на §7.2 — добавим anchor'ы тогда.
Workstreams
Updates workstreams/stage1-roman-integration.md:
- Append Session Log entry CONV-27 (Chunk 6 closed)
- Update What's Next → Chunk 7 + parking-lot summary (§7.2/§7.3 deferred + access expansion deferred + Cyprus confirmation pending)
Не создаём новый workstream — pattern Chunks 3-5 (single feat commit + sync, no separate workstream).
Evaluation
Done when:
- Parking-lot callout видна в начале §7
- §7.1 — 4 additions applied (3 new bullets + 1 amend)
- §7.4 — 3 additions applied
- §7.5 — 3 additions applied (включая C с inline table + 3 sub-blocks)
- Phase 1.4 + 1.10 — v4.10 callouts добавлены
- Changelog v4.10 entry добавлен поверх v4.9
- Workstream Session Log + What's Next обновлены
- Cross-link sanity passed
- Preview synced + pushed
Definition of NOT done (deferred):
- §7.2 GDPR / ADGM right-to-erasure proper sweep — sub-plan
legal-multi-party-framework.mdactivation - §7.3 Multi-jurisdiction proper sweep — same parking-lot
- Access expansion (новые роли / actions / invitation types из Roman call'а) — awaiting jurisdiction lock + lawyer
- Cyprus jurisdiction confirmation — clarify в /handoff'е
- Wave 2 Chunks 7 (Phase callouts deep rewrite, включая Phase 1.7 deep rewrite) + 8 (ADR placeholders 0008/0011/0012; 0005 v3 update)
- 6 sub-plans + 5 ADRs (отдельные /plan сессии после Wave 2 close)
Open Questions
Q1 — Cyprus jurisdiction status (clarification needed)
Memory project_legal_entity_cyprus.md говорит «Cyprus decided (EU member, 12.5% corp tax, GDPR-native — no EU Rep needed)». User в CONV-27 сказал «есть вопрос где будет компания открываться» — значит decision re-opened или новая ambiguity появилась. Resolution: confirm в next session — Cyprus locked? Re-opened? Alternatives (ADGM / DIFC / DMCC / Mainland UAE) under consideration?
Q2 — Access expansion (parked)
В Roman call (CONV-26 или earlier? — не зафиксировано в plan) обсуждались decisions «расширить кому какие доступы надо дать». User отправил parking-lot до jurisdiction lock. Resolution: после jurisdiction lock — отдельная /plan сессия для Chunk 9 (Access expansion + legal alignment) или включить в Phase 1.9 sub-plan.
Q3 — §7.5 C tabле sorting / pagination
Stage 1: sort signup_at desc, no pagination. Если sponsor накопит >50 referrals → pagination нужна. Stage 2 enhancement.
Q4 — Race condition implementation detail
§7.4 A — first-click-wins via optimistic lock (compare-and-swap на unit.status). HTTP 409 для loser'а. Frontend modal triggered by 409 response. Detail: Phase 1.10 build implementation — не plan-level.