Суть урока
Большинство фаундеров на старте делают одну тихую ошибку: строят "платформу" в которой все проекты переплетены. На бумаге это выглядит как экономия — один репозиторий, одна база данных, один config. На деле — через год это превращается в клубок проводов где нельзя выдернуть один кабель не выключив весь дом.
Потом приходит момент: проект X надо продать. Или закрыть. Или передать команде партнёра. И выясняется что нельзя — потому что Worker проекта X читает секрет проекта Y, бренд-голос лежит в IDENTITY.md платформы, а половина прод-таблиц проекта X — это shared schema со всеми остальными.
Portfolio Detachability — это инженерный принцип который защищает от этой ловушки с дня 1. Идея простая: платформа отдельно, проекты отдельно. Платформа долгоживущая (10-50 лет). Проекты приходят и уходят. Удаление одного проекта не должно ломать ни платформу, ни остальные проекты, ни сам удаляемый проект (его новый владелец должен получить рабочий артефакт).
В этом уроке разберём как такое спроектировать, как тестировать detachability на постоянной основе и какие 4 красные линии нельзя пересекать никогда.
Ключевые концепции
- Portfolio Pattern — архитектурная модель: платформа = уровень инфраструктуры (engineering charter + shared services), проекты = независимые стартапы (свой бренд, своя аудитория, своя экономика)
- Detachability — свойство проекта: его можно вырезать из платформы за разумное время (часы, не недели) без ломания платформы и без потери работоспособности проекта
- Namespace — изолированное пространство данных и кода с собственными правилами доступа. В Portfolio Pattern их обычно 4: CORE / SHARED / PROJECT-OWNED / PRIVATE
- Coupling (связанность) — степень зависимости одной части системы от другой. В контексте проектов: hard-coupling (нельзя удалить не сломав) vs loose-coupling (можно удалить, остальное работает)
- Anti-coupling rules — формальные ограничения которые не дают coupling появиться (например: project-specific data запрещено класть в CORE)
- Strangler Fig pattern — паттерн постепенного разрыва coupling: новый код пишем правильно, старый — переезжает в правильный namespace по одному компоненту за раз
- Detachment artifact — комплект файлов и инструкций которые передаются новому владельцу при продаже/архивации проекта (код, бренд, customer base, runbook, secrets handoff)
- Sale-readiness — формальное состояние проекта: detachable + документирован + secrets изолированы + независимый билд проходит
Теория
Откуда берётся проблема
Coupling редко появляется одним решением. Это десятки маленьких "так быстрее" которые копятся.
Типичная история:
- Месяц 1. Запускаем платформу. Проект один — агентство недвижимости. Бренд-голос пишем в
IDENTITY.mdплатформы. "Потом вынесем, пока так". - Месяц 4. Появился второй проект — онлайн-школа. Делаем
shared/brand-voice.ts. Но половина строк там специфичны для агентства недвижимости ("спокойный консультант, не продавец"). Школа наследует и переопределяет. Работает. - Месяц 9. Третий проект — подписочный сервис. У него своя аудитория. Но он использует тот же
shared/auth.tsкоторый читает Cloudflare KV namespaceREALTY_USERS. Все юзеры лежат в одной таблице. "Потом разделим". - Месяц 14. Появляется покупатель на агентство недвижимости. Готов заплатить серьёзные деньги. Спрашивает: что покупаю — папку или платформу целиком? Ты понимаешь что отделить нельзя за неделю. Покупатель уходит.
Каждое отдельное решение было разумным. Сумма — катастрофа.
Portfolio Pattern: Startup Studio и независимые стартапы
Самая чистая модель — это Startup Studio (инкубатор нового поколения).
| Уровень | Аналогия | В AI Startup Platform | Что там лежит |
|---|---|---|---|
| Platform Core | Устав и правила Studio (для всех) | CORE | CLAUDE.md, IDENTITY.md, VISION.md, правила разграничения контекста, engineering charter |
| Platform Services | Общая инфра Studio (DevOps, auth, legal) | SHARED | departments/security/, departments/finance/, tools/ |
| Portfolio Project | Независимый стартап в портфеле | PROJECT-OWNED | projects/realty/, projects/school/ |
| Founder's Vault | Личный сейф основателя | PRIVATE | личная папка владельца, secrets, keys |
Правила Portfolio Pattern:
Platform не меняется ради одного стартапа. YC взял Airbnb в свою программу — устав YC не переписывался под Airbnb. Твоя платформа запустила новый продукт —
IDENTITY.mdплатформы остался прежним.Стартапы автономны. У каждого свой бренд, своя customer base, свой
CLAUDE.md, своя БД (или хотя бы свой namespace в общей). Стартап сам принимает продуктовые решения внутри архитектурных рамок платформы.Стартапы могут выйти из портфеля. Продажа, закрытие, передача команде — архитектура должна выдержать любой exit. Если один стартап продан, платформа и остальные стартапы продолжают работать без единого изменения.
Platform Services общие. Auth, мониторинг, security — на всех стартапах. Это нормально — именно в этом смысл Platform. Но services не должны содержать project-specific логику. Они универсальны по дизайну.
4 namespace в деталях
CORE (federal, уровень базовых правил)
Что лежит: CLAUDE.md, IDENTITY.md, VISION.md, MEMORY.md, правила разграничения контекста, правила безопасности, каталог навыков, strategy/engineering-charter.md.
Правила:
- Меняется только через формальную поправку к уставу (с паузой на обдумывание, например 7 дней)
- Не содержит ничего project-specific
- Не содержит ни одного имени проекта в business logic (имена в примерах ОК)
- Универсальные принципы, vetoes, identity платформы
Тест чистоты CORE: открыл IDENTITY.md, поискал названия проектов (например, "realty", "school"). Если нашлось вне раздела "примеры проектов" — это утечка. Чинить.
SHARED (federal services)
Что лежит: departments/, tools/, infrastructure/ (если используется несколькими проектами), .claude/agents/ (общие subagents), общие skills.
Правила:
- Используется минимум 2 проектами одновременно
- Универсально по дизайну (interface-first, не "вот код под агентство недвижимости, остальные адаптируются")
- Изменение требует impact assessment: на какие проекты повлияет?
Тест: если код используется только одним проектом — он не в SHARED. Перенести в projects/<X>/.
PROJECT-OWNED (state)
Что лежит: projects/<name>/ целиком.
Внутри обычно:
CLAUDE.md(проектный контекст, ссылается на CORE через@~/platform/CLAUDE.md)BRAND-VOICE.md(бренд только этого проекта)code/илиapp/(исходники)customers/илиdb/(изолированные данные)deployments/(свой wrangler.toml, свои Cloudflare secrets с префиксом проекта)decisions/(журнал решений проекта)HANDOFF.md(что нужно знать новому владельцу)
Правила:
- Всё специфичное для проекта живёт здесь
- Не зависит от других
projects/<Y>/ - Может зависеть от CORE и SHARED — но через документированные интерфейсы
PRIVATE (personal)
Что лежит: личная папка владельца, зашифрованный файл с ключами, настройки аппаратного ключа, письмо преемнику.
Правила:
- Никогда не передаётся при продаже проекта
- Не пересекается с PROJECT-OWNED
- Доступ — только владельцу платформы с аппаратным ключом
Detachability test (главный инструмент)
Один вопрос определяет здоровье архитектуры:
Если завтра я удалю папку
projects/X/целиком — что сломается?
Варианты ответов и что они значат:
| Что сломалось | Диагноз | Что делать |
|---|---|---|
| Ничего. Платформа работает, остальные проекты работают, deploy остальных проходит | ✅ Detachable. Проект готов к продаже | Документировать handoff |
| Свалилась сборка платформы (build error в shared code) | ❌ Hard coupling в SHARED | Перенести project-specific код из SHARED в PROJECT-OWNED |
Свалился другой проект Y потому что он импортирует из projects/X/ |
❌ Cross-project coupling | Вынести общий код в SHARED или дублировать |
| Сломались Cloudflare Workers других проектов | ❌ Shared infrastructure без боундари | Изолировать namespaces, отдельные KV / D1 per project |
| Test runner проходит, но runtime падает в продакшене через час | ❌ Hidden runtime coupling (cron, queue, webhook) | Аудит всех async dependencies |
IDENTITY.md или CLAUDE.md платформы теперь содержат битые ссылки на удалённый проект |
❌ Project-specific data в CORE | Чистить CORE, переносить в projects/<X>/ |
Принцип: тест прогоняется не один раз, а на каждом релизе. Желательно автоматизированно (CI step: симулировать удаление проекта в эфемерной ветке и прогнать билды остальных).
4 Red Lines (anti-coupling правила)
Это формальные запреты. Не "best practice" — именно red lines, как в безопасности. Их нарушение = технический долг который растёт экспоненциально.
Red Line 1: Не пихать project-specific data в CORE
❌ Нельзя:
- Биография лица бренда агентства недвижимости в
IDENTITY.mdплатформы - Список конкурентов онлайн-школы в общем
COMPETITORS-PLAYBOOK.mdв CORE - Брендовый голос агентства недвижимости в
CLAUDE.mdплатформы - Customer персоны проекта в
MEMORY.mdплатформы
✅ Правильно:
- Project-specific данные →
projects/<X>/ - В CORE — только универсальные принципы и identity самой платформы
Red Line 2: Не менять CORE при работе над проектом
Если сессия объявлена как "работаем над агентством недвижимости" — общий план платформы, IDENTITY.md, VISION.md, личную папку владельца не трогаем. Даже если "по пути увидел опечатку". Особенно если "по пути увидел опечатку" — это самый частый канал утечки.
Если опечатка важная — отдельная сессия "работаем над платформой", отдельный коммит, отдельный journal entry.
Red Line 3: Не дублировать данные между namespaces
Один источник правды per data type.
❌ Нельзя:
- Брендовые правила и в личных правилах владельца платформы, и в
projects/realty/BRAND-VOICE.md - Решения по проекту и в
journals/decisions.mdплатформы и вprojects/<X>/decisions/ - Customer list в SHARED и в PROJECT-OWNED
✅ Правильно:
- Один файл — один источник правды
- В проектной папке — ссылка
@~/platform/IDENTITY.md, если нужен CORE контекст
Red Line 4: Не путать platform owner с project personas
❌ Нельзя:
- Применять стиль бренда проекта к самому платформодержателю
- Применять биографию project persona (например, лица бренда агентства недвижимости) к контенту другого проекта (например, онлайн-школы)
- В коммуникации платформы использовать voice одного из проектов
✅ Правильно:
- Платформодержатель = хранитель системы, а не продукт
- Каждая project persona = своя identity внутри
projects/<X>/ - Онлайн-школа (если запустится) = ещё одна отдельная persona
Sale scenario walkthrough
Допустим, к тебе пришёл покупатель на проект realty. Готов заплатить серьёзную сумму. Что передаёшь?
Передаётся (PROJECT-OWNED):
- Папка
projects/realty/целиком - Кодовая база проекта
- Бренд:
BRAND-VOICE.md, ассеты, лого - Customer base (с передачей по применимому закону о персональных данных, например GDPR, если он действует, или с pseudonymization если контракт это требует)
- Domain (
realty-example.com, если зарегистрирован под проект) - Cloudflare account для проекта (если изолированный) ИЛИ migration plan на новый аккаунт покупателя
- Telegram bot tokens, API keys специфичные для проекта (через secure handoff, не email)
HANDOFF.md— runbook для нового владельца- Журнал решений
decisions/
Остаётся (CORE + SHARED + PRIVATE):
- Платформа целиком
- Все остальные проекты
- Личные правила и данные владельца платформы
- Engineering Charter, IDENTITY, VISION
- Зашифрованный файл с ключами, аппаратный ключ
Что обязательно прописать в контракте:
- Срок миграции secrets (обычно 7-14 дней)
- Срок передачи domain (DNS propagation 48-72ч)
- Что покупатель НЕ получает: бренд платформы, методологию, code в SHARED (которым проект только пользовался)
- Non-compete на время transition (если применимо)
- Поддержка покупателя в первый месяц (часов, формат)
Что должно произойти технически за один день:
- Эфемерная ветка
sale/realty-detach - Удалить
projects/realty/из main ветки (git history оставляем — она показывает что проект существовал) - Прогнать билды всех остальных проектов: должны пройти
- Прогнать тесты платформы: должны пройти
- Если что-то упало — есть hidden coupling. Сделка ставится на паузу до исправления.
Strangler Fig pattern: разрыв coupling постепенно
Что делать если уже всё переплетено и detachability test показывает катастрофу?
Не делать big bang refactoring. Strangler Fig — паттерн постепенного разрыва.
Идея: старый coupling не трогаем, но новый код пишем правильно. Постепенно компоненты переезжают в правильные namespaces, старые умирают.
Пошагово:
- Замораживаем coupling. В CI добавляем hook: "новые файлы в
shared/не могут содержать имена проектов". Не позволяем мусору расти дальше. - Каталогизируем существующее. Один документ:
coupling-inventory.md. Что где переплетено, severity, owner. - Атакуем по приоритету. Самое опасное — coupling который блокирует продажу. Менее опасное — coupling который раздражает разработку.
- Один coupling — один PR. Не "почистил половину системы за неделю". Чистим точечно, регрессионные тесты на каждом шаге.
- Detachability test после каждого PR. Метрика сдвигается? Считается ли проект ближе к detachable? Если нет — что-то не так с подходом.
Реалистичный темп: один значимый разрыв coupling в неделю. За полгода — система переезжает в чистое состояние.
Антипаттерны (детально)
Monorepo coupling
Симптом: все проекты в одном npm/cargo/Python workspace, shared package.json с проектно-специфичными зависимостями.
Почему плохо: при продаже проекта покупатель получает либо весь monorepo (ему 90% не нужно), либо приходится месяц расплетать зависимости.
Лечение: проектные workspaces (projects/<X>/package.json) + минимальный shared core с явным interface.
Shared database без боундари
Симптом: одна Cloudflare D1 база, все проекты пишут в одни таблицы, разделяются по project_id колонке.
Почему плохо: при продаже проекта надо экспортировать строки с project_id = X, чистить foreign keys, проверять что ничего не сломалось у остальных. И всё это под нагрузкой продакшена.
Лечение: namespace per project. В Cloudflare — отдельный D1 / KV namespace per project. В Postgres — отдельная schema или отдельный database. Прирост стоимости минимальный, прирост detachability огромный.
Project-specific config в platform-wide files
Симптом: в wrangler.toml платформы прописаны routes всех проектов. В .env платформы — секреты всех проектов с префиксами.
Почему плохо: новый владелец проекта не может просто скопировать config — там чужие секреты и routes.
Лечение: один wrangler.toml per project. Один .env.<project> per project. На уровне платформы — только инфраструктура которая обслуживает всех (мониторинг, бэкапы).
"Временное" имя проекта в CORE
Симптом: "пока что платформа = агентство недвижимости, потом разделим". Через год — IDENTITY.md платформы говорит про продажу квартир.
Почему плохо: когда второй проект появится, эта строка станет ложью. Чинить её придётся когда у тебя уже три проекта и десять переплетений. Дешевле — никогда не писать project-specific строки в CORE с самого начала.
Лечение: при сомнении — пиши в projects/<X>/. Поднимешь в CORE когда увидишь паттерн в 2+ проектах. Это правило "rule of three" применённое к namespace promotion.
🧪 Практика
Шаг 1: Audit namespaces текущего проекта (15 минут)
Открой свой проект (тот над которым работаешь сейчас) и сделай инвентаризацию. Создай файл coupling-inventory.md в корне проекта:
# Coupling Inventory — <project-name> **Дата:** YYYY-MM-DD **Цель:** определить что в каком namespace и где coupling ## CORE-уровень (платформа) Файлы в которых упоминается ИМЯ ТЕКУЩЕГО ПРОЕКТА (это утечка): - [ ] CLAUDE.md — упоминается ли? - [ ] IDENTITY.md — упоминается ли? - [ ] VISION.md — упоминается ли? - [ ] MEMORY.md — упоминается ли? ## SHARED-уровень Файлы в shared/ или tools/ которые используются ТОЛЬКО этим проектом: - [ ] список ## PROJECT-OWNED Что у проекта есть своего: - [ ] BRAND-VOICE.md - [ ] CLAUDE.md проектный - [ ] Изолированный wrangler.toml - [ ] Изолированный D1/KV namespace - [ ] Изолированные secrets с префиксом проекта ## Cross-project dependencies Импорты из других projects/<Y>/ в текущем проекте: - [ ] список ## Hidden coupling - [ ] Cron / queue / webhook которые касаются нескольких проектов - [ ] Shared environment variables - [ ] Общие webhooks
Заполни честно. Не "ну вроде нет" — а прогрепай по именам:
# В корне платформы
PROJECT_NAME="realty" # подставь свой
# Где упоминается имя проекта вне его собственной папки
grep -r "${PROJECT_NAME}" . \
--include="*.md" --include="*.ts" --include="*.js" --include="*.toml" \
--exclude-dir="projects/${PROJECT_NAME}" \
--exclude-dir="node_modules" \
--exclude-dir=".git" \
--exclude-dir="_archive"Каждый матч — потенциальная утечка. Разбирай по одному.
Шаг 2: Detachability test (10 минут)
Симулируем удаление проекта без реального удаления.
# Создаём эфемерную ветку
git checkout -b detachability-test/$(date +%Y%m%d)
# "Удаляем" проект
PROJECT_NAME="realty"
git rm -r projects/${PROJECT_NAME}/
# Прогоняем билды остальных проектов
for project in projects/*/; do
name=$(basename "$project")
echo "Building: $name"
cd "$project"
# Подставь свой build command
npm run build 2>&1 | tail -5
cd - > /dev/null
done
# Прогоняем тесты платформы
npm test 2>&1 | tail -10
# Прогоняем lint / type check платформы
npm run lint 2>&1 | tail -5Что должно произойти:
- Все билды остальных проектов проходят
- Тесты платформы проходят
- Lint чистый
- Ни одного broken import
Если что-то упало — это и есть твой coupling. Записывай в coupling-inventory.md под "Hidden coupling".
Главное: после теста — git checkout main && git branch -D detachability-test/.... Это была симуляция, ничего реально не удаляли.
Шаг 3: Чистим один coupling (15 минут)
Возьми один найденный coupling — самый дешёвый по усилию. Применяем Strangler Fig к нему.
Пример: в shared/brand-voice.ts есть строки, специфичные для агентства недвижимости.
// БЫЛО — shared/brand-voice.ts
export const brandVoice = {
tone: "спокойный консультант, не продавец", // специфично для агентства недвижимости!
avoidPhrases: [
"я эксперт", // правило этого проекта
"лучшие цены в городе", // правило этого проекта
"успейте купить" // правило этого проекта
]
}Шаг 1. Переносим project-specific в проект:
// СТАНОВИТСЯ — projects/realty/brand-voice.ts
import { BaseBrandVoice } from "@platform/shared/brand-voice"
export const realtyVoice: BaseBrandVoice = {
tone: "спокойный консультант, не продавец",
avoidPhrases: [
"я эксперт",
"лучшие цены в городе",
"успейте купить"
]
}Шаг 2. SHARED становится универсальным interface:
// СТАНОВИТСЯ — shared/brand-voice.ts
export interface BaseBrandVoice {
tone: string
avoidPhrases: string[]
// ... другие универсальные поля
}
export function validateContent(content: string, voice: BaseBrandVoice): ValidationResult {
// универсальная логика валидации
}Шаг 3. Регрессионный тест:
# Должно пройти как раньше
npm test
# Detachability test должен показывать сдвиг
git checkout -b detachability-test/iter2
git rm -r projects/realty/
npm test # теперь shared/brand-voice.ts проходит без строк проекта недвижимости
git checkout main && git branch -D detachability-test/iter2Один coupling разорван. Записываем в decisions:
## YYYY-MM-DD: Разорван coupling shared/brand-voice → realty
**Context:** shared/brand-voice.ts содержал project-specific строки
**Decision:** вынесли interface в shared, имплементацию в projects/realty/
**Impact:** detachability проекта realty повышен
**Tag:** detachability-iter1Шаг 4: Документируем HANDOFF.md (опционально, 15 минут)
Каждый PROJECT-OWNED должен иметь HANDOFF.md в корне. Это документ для гипотетического нового владельца. Пишется так, как будто завтра ты передаёшь проект незнакомому человеку.
Минимальный шаблон:
# HANDOFF — <Project Name>
## Что это
1-2 параграфа. Что за проект, какую проблему решает, кто аудитория.
## Стек
- Frontend: ...
- Backend: ...
- Hosting: ...
- БД: ...
- Платежи: ...
## Как запустить локально
```bash
cd projects/<name>
npm install
cp .env.example .env # заполнить secrets — см. ниже
npm run dev
```
## Secrets (где взять)
- `STRIPE_SECRET_KEY` — Stripe dashboard
- `CLOUDFLARE_API_TOKEN` — Cloudflare → My Profile → API Tokens
- ... (без значений! только инструкция где взять)
## Deploy
1 параграф. Что делать чтобы выкатить в продакшн.
## Customer base
Где лежит, как экспортировать, соответствует ли структура закону о персональных данных (например, GDPR).
## Domain & DNS
Где зарегистрирован, у кого учётка, как перенести.
## Известные риски
3-5 пунктов чего опасаться (deprecated APIs, скоро истекает сертификат, тех долг в модуле X).
## Контакт прежнего владельца
Email / часы поддержки в первый месяц.Этот документ — главный артефакт detachability. Если ты можешь написать честный HANDOFF.md за час — проект готов к продаже. Если нет — есть hidden knowledge которое надо вытащить из головы в файл.
⚠️ Антипаттерны
❌ "Потом разделим" — никогда не делается. Coupling, который не разорвали на старте, разрывают только под давлением (продажа, выгорание, конфликт с партнёром). В этот момент он стоит в 10 раз дороже.
❌ Один Cloudflare account на все проекты без изоляции — при продаже одного проекта надо мигрировать его на новый аккаунт. Если все Workers / KV / D1 в одном аккаунте без префиксов проекта — миграция занимает недели.
❌ Project-specific routes в wrangler.toml платформы — каждый проект должен иметь свой wrangler.toml. Один файл, описывающий routes всех проектов, превращается в неразделимый клубок.
❌ Customer base в общей таблице с project_id колонкой — экспортировать строки с project_id = X теоретически можно. На практике — там foreign keys на shared таблицы, JSON колонки с ссылками на платформенные сущности, и миграция занимает спринт.
❌ Бренд платформы и бренд проекта смешаны — если новый владелец агентства недвижимости получает BRAND-VOICE.md, где половина правил — про саму платформу, он не сможет работать без переписывания.
❌ Перенос coupling в _archive/ вместо разрыва — частая ошибка. "Старый код переехал в архив, теперь чисто". Но импорты из основного кода в архив остались. Это не разрыв coupling, это перекладывание.
❌ Платформенные journals содержат project decisions — journals/decisions.md платформы должен содержать только CORE-решения. Решения проекта — в projects/<X>/decisions/. Иначе при продаже проекта новый владелец не получит контекст принятых решений.
❌ Hardware key или master passphrase лежат в проектной папке — PRIVATE остаётся PRIVATE всегда. Если что-то секретное проникло в projects/<X>/ — это нарушение и потенциальная утечка при продаже.
🔗 Связано с
- Claude.md — системный промпт твоего проекта — как разделять платформенный и проектный контекст
- Философия папок — PARA система — папочная структура поддерживающая Portfolio Pattern
- Безопасность в Claude Code — .env, secrets — изоляция секретов per project как фундамент detachability
- Деплой — Cloudflare Workers — отдельный wrangler.toml per project, namespace isolation
- 3-Tier Templates — solo, mid, corporate — общий контекст production patterns
✅ Checkpoint
Прежде чем идти дальше — пройдись по списку. Каждый пункт должен быть честным "да".
Если есть пробелы — вернись к теории. Это не урок который проходится "по диагонали". Coupling который не пресёк сейчас — год спустя будет блокировать твою свободу действий.
Источники
- Martin Fowler — Strangler Fig Pattern (martinfowler.com)
- Sam Newman — "Monolith to Microservices" — главы про database decomposition
- AWS Well-Architected Framework — Reliability Pillar, fault isolation principles
- Y Combinator — Startup Studio model, portfolio company independence
- Cloudflare — Namespace isolation best practices (Workers KV, D1, R2)
Дальше по теме → Production Observability — что мерить и куда смотреть когда что-то идёт не так.
Отметка хранится только в этом браузере и никуда не отправляется. Мой прогресс