Самостоятельный source audit репозитория
angee-django. Это не пересказ README и не каталог продуктовых экранов: документ восстанавливает архитектуру системы снизу вверх — от загрузки проекта и компиляции addon’ов до ORM, REBAC, GraphQL, фоновых процессов, generated runtime и React-приложения.
| Параметр | Значение |
|---|---|
| Репозиторий | /Users/max/Dev/angee-django |
| Ветка | main |
| Commit | 4f6e8235721f5e0d12266f425f6b665d3b972ece (sync fixes) |
| Дата commit | 2026-07-17 |
| Дата анализа | 2026-08-03 |
| Версия пакета | 0.1.7 |
| Лицензия | AGPL-3.0-or-later |
| Статус самого проекта | early alpha / active refactor |
| Backend floor | Python 3.14+, Django 6.0+ |
| Frontend floor | Node 22.13+, pnpm 11.1.3, React 19, TypeScript 6, Vite 8 |
Инвентаризированы все 1 682 отслеживаемых Git файла. Архитектурно значимые реализации прочитаны по слоям: корневой bootstrap, angee/base, angee/compose, angee/graphql, angee/tasks, четыре frontend-пакета, manifests и ключевые реализации всех поставляемых addon’ов, шаблоны, example host и тестовые architectural guardrails. Тесты использованы не как вторичная документация, а как исполняемое описание инвариантов.
Количественный профиль репозитория:
| Область | Файлов | Приблизительный объём |
|---|---|---|
angee/ |
543 | 14 151 строк Python в core; около 77 800 строк TS/TSX вместе с frontend-тестами |
addons/ |
713 | около 51 345 строк Python и 34 100 строк TS/TSX |
tests/ |
142 | около 54 590 строк Python |
packages/ |
92 | около 6 100 строк TS/TSX |
examples/ |
68 | около 2 100 строк Python и 2 050 строк TS/TSX |
templates/ |
49 | project/stack/workspace/addon/service templates |
| Всего Python | — | 122 198 строк |
| Всего TS + TSX | — | около 120 200 строк |
| REBAC-схемы | 26 .zed файлов |
1 381 строк |
Набор тестов крупнее, чем кажется по числу файлов: найдено примерно 1 558 Python test-функций/методов и 1 064 вызова test(...)/it(...) во frontend-тестах. Это важно: значительная часть архитектуры Angee закреплена отрицательными тестами — что один слой не имеет права импортировать или делать.
Angee Django — это компилятор модульного приложения поверх Django, дополненный единым data/API/UI-контрактом.
На входе находятся:
- проект с корневыми addon’ами в
INSTALLED_APPS; - addon manifests
addon.toml; - абстрактные source models;
- GraphQL contributions;
- REBAC-схемы;
- декларативные resources;
- React addon manifests.
На build-этапе Angee:
- строит транзитивный и детерминированный граф addon’ов;
- объединяет настройки;
- композирует Python-классы моделей;
- генерирует concrete Django runtime и migration ownership;
- объединяет permission schemas;
- собирает именованные GraphQL schemas;
- извлекает machine-readable metadata о ресурсах;
- генерирует SDL, typed GraphQL documents и статический React composition root.
На выходе работает обычное, хотя и сгенерированное, приложение:
- Django ORM и migrations;
- Strawberry GraphQL в Hasura-совместимом диалекте;
- relationship-based authorization через
django-zed-rebac; - Channels/Redis subscriptions;
- Celery workers;
- React + Refine + TanStack Router UI;
- FastMCP surface для агентов.
Главный архитектурный принцип репозитория формулируется как framework owns seams, not concerns. Django владеет ORM и lifecycle, Strawberry — GraphQL, REBAC-библиотека — authorization semantics, Refine — data hooks/cache, React — rendering. Angee владеет местами их соединения, детерминированностью композиции и общим метаконтрактом.
flowchart TB
P["Project settings\nroot addon declarations"]
A["Addon sources\nmodels · schema · permissions · resources · web"]
C["Angee build-time composer"]
R["Generated runtime\nmodels · migrations · permissions · SDL · metadata"]
B["Django backend\nORM · REBAC · GraphQL · Tasks"]
W["React application\nRefine · Metadata · UI · addon manifests"]
M["Humans · services · agents"]
P --> C
A --> C
C --> R
R --> B
R --> W
B <--> W
B --> M
W --> M
Это принципиально не runtime plugin registry в стиле «загрузили модуль и пропатчили глобальное состояние». Входные addon’ы компилируются в одну проверяемую систему; ошибка композиции должна проявиться до обслуживания пользовательского трафика.
| № | Логический слой | Физические владельцы | Что он делает | Что он не должен делать |
|---|---|---|---|---|
| 0 | Operator / окружение | внешний angee-operator, angee.yaml, templates |
checkout sources, workspace/stack, процессы, containers, ports, secrets injection | не определяет Django-модели или бизнес-схему |
| 1 | Project contract | angee/project.py, angee/compose/project.py |
находит проект, читает .env и settings, задаёт runtime dir |
не содержит доменную модель |
| 2 | Addon contract | angee/addons.py, addon.toml |
единый manifest: deps, migrations, resources, schemas, permissions, web, MCP | не выполняет произвольную runtime-регистрацию |
| 3 | Dependency composition | angee/compose/apps.py, composer.py, autoconfig.py |
dependency closure, topological order, settings fold, ownership checks | не обслуживает requests |
| 4 | Source-model language | angee/base/models.py, fields.py, mixins.py, addon models.py |
абстрактные декларации модели и расширений | не является финальным Django table graph |
| 5 | Runtime compiler | angee/compose/runtime.py, migrations.py, permissions.py |
объединяет классы и схемы, пишет generated runtime | не допускает ручное редактирование output |
| 6 | Concrete persistence | generated runtime/*/models.py, Django migrations, PostgreSQL/SQLite |
реальные tables, constraints, transactions, managers | не определяет UI-представление |
| 7 | Identity and authorization | angee/base, angee.iam, .zed, django-zed-rebac |
actors, roles, tuples, row/field scope, create gates | не полагается на client-side guards |
| 8 | Declarative resources | angee.resources |
master/install/demo data, xrefs, adoption, grants, ledger | не смешивает network fetching с core loader |
| 9 | API composition | angee/graphql + addon schema.py |
schema buckets, CRUD, filters, aggregates, actions, deletion, revisions | не импортирует build-time composer |
| 10 | Realtime | GraphQL changes/subscriptions, Channels | transaction-safe publication и read-gated delivery | не выдаёт event без повторной проверки доступа |
| 11 | HTTP/ASGI | angee/urls.py, angee/asgi.py, IAM middleware |
URL contributions, GraphQL HTTP/WS, FastMCP mounts, lifespan | не заново строит addon graph |
| 12 | Background execution | angee/tasks, Celery, addon tasks |
queues, retries, periodic jobs, local/advisory locks | не подменяет transaction semantics ORM |
| 13 | Backend→frontend contract | SDL + *.metadata.json |
capabilities, field kinds, roots, relations, groups, actions | не требует ручного дублирования модели в TS |
| 14 | Web build/codegen | runtime web manifest, angee-web-codegen |
typed documents, imports addon packages, emits app.ts |
не угадывает schema по имени или UI-коду |
| 15 | Frontend data/runtime | @angee/metadata, @angee/refine, @angee/app |
metadata projection, transport, cache/live, routes/providers | не реализует backend permissions |
| 16 | Rendered UI | @angee/ui, addon web packages |
generic views/widgets/chrome и доменные расширения | не владеет wire dialect |
| 17 | Verification | pytest, Vitest, Playwright, Storybook, CI guardrails | доказывает направление зависимостей и contracts | не служит запасной production-реализацией |
Направление зависимостей намеренно одностороннее. base не импортирует compose, GraphQL или addon’ы; GraphQL и resources не импортируют compose; стабильные serving entrypoints не зависят от build-time layer. На frontend @angee/refine и @angee/metadata — нижние независимые слои, @angee/ui может использовать их, а @angee/app является верхним composition root.
angee/addons.py— parser и resolver addon contracts.angee/project.py,angee/paths.py,angee/fs.py— project/root/path primitives.angee/compose/— build-time compiler: app graph, settings, runtime emission, runtime migrations, permission extension.angee/base/— нижний модельный toolkit: base model, managers, fields, mixins, transitions, public IDs, canonical record refs.angee/graphql/— runtime API and metadata layer.angee/tasks/— Celery app, enqueue API и distributed lock abstraction.angee/urls.py,angee/asgi.py— стабильные serving entrypoints.angee/web/{app,refine,metadata,ui}— четыре общих frontend-пакета, включаемых в Python wheel.
32 addon’а образуют поставляемый с framework базовый каталог. Каждый — вертикальный срез и может иметь:
addon.toml;- Python source models;
schema.py;permissions.zedи иногдаpermissions.extends.zed;resources/*.yaml;- tasks/backends/integration implementation;
web/package.json+ React surface.
Core и base addons намеренно используют общий PEP 420 namespace angee.*. Это позволяет распространять framework и внешние addon distributions независимо, но видеть их как одну Python namespace family.
packages/storybook— component workshop и visual development.packages/e2e— Playwright harness, fixtures, page objects, role sessions.examples/notes-angee— минимальный реальный Host, а не часть framework core.templates/— Copier-шаблоны project, workspace, stack, service и web addon.tests/— backend unit/integration/architecture suite..agents/иAGENTS.md— policy-as-code для разработки людьми и агентами.
Важная особенность: React framework packages поставляются внутри Python distribution django-angee. Build backend hatch-angee:
- обнаруживает addon manifests;
- генерирует
angee.addonsentry points; - включает addon web sources в wheel;
- сохраняет одну версию и один release channel для Python и TypeScript контракта.
Dev-only packages, example project и operator остаются за этой границей.
angee/project.py ищет project root в строго заданном порядке:
ANGEE_PROJECT_DIR;- каталог рядом с вызывающим
manage.py; - ближайший ancestor с
settings.yamlилиsettings.py.
Это не косметика. Один из рисков YAML-based settings — случайно захватить конфигурацию родительского workspace. Загрузчик делает поиск ограниченным найденным project boundary.
angee.compose.project.ProjectContract:
- загружает
.env; - выбирает Python settings module либо создаёт synthetic module для YAML project;
- применяет безопасный default floor;
- добавляет project root и addon source dirs в
sys.path; - передаёт нормализованный проект
Composer.
Default development floor включает SQLite, WAL/IMMEDIATE transaction behavior, data/media/static/runtime directories. Deployment может передать DATABASE_URL, CACHE_URL, EMAIL_URL; явная project setting имеет приоритет над default, а объявленная environment variable — над addon default.
Composer резервирует и сам задаёт настройки, которые определяют целостность приложения:
- финальный
INSTALLED_APPS; ROOT_URLCONF;ASGI_APPLICATION;- runtime module/path;
- migration-module redirection;
- merged REBAC schema locations.
Addon, пытающийся переопределить reserved key через autoconfig.py, получает fail-fast ошибку. Это предотвращает ситуацию, когда один plugin незаметно подменил системный entrypoint.
sequenceDiagram
participant CLI as manage.py / ASGI
participant PC as ProjectContract
participant AG as AppGraph
participant AC as AutoConfig
participant CO as Composer
participant DJ as Django app registry
CLI->>PC: locate project + load env/settings
PC->>AG: root INSTALLED_APPS
AG->>AG: resolve addon dependency closure
AG->>AG: validate aliases, cycles, missing deps
AG-->>PC: stable topological AppConfig order
PC->>AC: fold addon settings in order
AC-->>PC: composed settings
PC->>CO: own runtime, URLs, ASGI, migrations
CO->>DJ: populate composed app registry
AddonContract в angee/addons.py читает colocated addon.toml. Само наличие manifest означает «это Angee addon». Контракт содержит:
- полное Python имя и project metadata;
depends_on;- addon-owned runtime migrations;
- именованные GraphQL schema contributions;
- permission schema;
- web package и external web codegen declarations;
- MCP tool registrar;
- tiered resource entries.
Часть полей выводится по конвенции без импортирования runtime-кода:
schema.pyс top-levelschemas;permissions.zed;web/package.json;mcp_tools.py:register.
Conventions проверяются через filesystem/AST, поэтому dependency discovery не обязан преждевременно импортировать Django models.
Доступные addon’ы объединяются из двух источников:
- installed Python entry points группы
angee.addons; - локальных package directories с
addon.toml.
Так одновременно поддерживаются опубликованные wheels и editable/local workspace sources.
Проект перечисляет только root addons. AppGraph рекурсивно добавляет depends_on, создаёт/находит Django AppConfig, а затем выполняет стабильную topological sort.
Инварианты:
- неизвестная dependency — ошибка;
- цикл — ошибка с цепочкой;
- duplicate app alias — ошибка;
- порядок не зависит от недетерминированного обхода filesystem;
- root/explicit и forced/dependency app различимы метаданными;
- dependency list сортируется перед обходом.
Это различие root vs forced нужно, например, для platform UI: обязательная техническая dependency установлена, но не обязана считаться сознательно выбранной пользовательской capability.
Для runtime surfaces используется узкий helper addon_contribution: он загружает только convention-based contribution конкретного уже установленного addon’а, например URL patterns или websocket routes. Serving layer читает готовый Django app registry, а не повторяет build composition.
Addon может предоставить autoconfig.py:
- константу
SETTINGS; - опциональную функцию
settings(namespace).
AutoConfig применяет fragments в dependency order. Семантика следует django-yamlconf: поддерживаются append и dotted keys. Приоритеты устроены так:
- framework floor;
- dependency addon defaults;
- dependent addon defaults;
- явные project settings;
- объявленные environment overrides.
Plain addon default не должен перезаписывать уже заданное project value. В отличие от Odoo-style runtime context, результат — обычный Django settings namespace, полностью сформированный до normal runtime.
Настройки также являются registry seam. Например, ImplClassField хранит не class path, а стабильный implementation key, который разрешается через settings-backed registry. Addon добавляет реализацию декларативно; модель хранит выбранную стратегию без импортирования integration package.
AngeeModel — abstract Django model. Source model описывает вклад addon’а в будущую concrete model graph, а не обязательно отдельную таблицу.
Ключевые декларации:
runtime=True— материализовать source model как concrete runtime model;extends="app.Model"— расширить уже объявленную модель;runtime=Trueвместе сextends— создать настоящий Django multi-table child;child_overrides_parent=True— в строго ограниченном случае изменить MRO так, чтобы child implementation имела приоритет.
Обычное расширение extends — это same-row composition: поля и поведение нескольких abstract classes объединяются в одном concrete class/table. Это центральный механизм модульности Angee.
Runtime scanner рассматривает только abstract AngeeModel classes, принадлежащие сканируемому addon package. Он разделяет:
- owned runtime model sources;
- extensions/donors;
- runtime children;
- служебные abstract mixins.
Модель идентифицируется итоговым Django label. Две source declarations не могут независимо владеть одним emitted label. Прямые field collisions запрещены: система не пытается угадывать, какой author «победил».
Для каждой emitted model compiler строит bases из:
- extension donors в детерминированном порядке;
- concrete runtime parent, если это MTI child;
- исходного owner class.
Затем генерирует Meta с concrete status и app label, переносит REBAC facts и добавляет contribution artifacts от mixins:
ModelDecorator;ModelClassAttribute.
Так, например, history/revision integration может регистрироваться на финальном composed class, а не на исходном abstract donor.
child_overrides_parent не является общей лазейкой для произвольного MRO. Compiler проверяет:
- child действительно materialized;
- parent relationship — реальное MTI;
- manager semantics совместимы;
- state transition metadata остаётся валидной;
- shadowed fields эквивалентны.
Это один из примеров философии Angee: расширение разрешено только через узкую, проверяемую seam.
angee/compose/runtime.py эмитит project-local runtime tree:
runtime/
├── __init__.py
├── <app_label>/
│ ├── __init__.py
│ ├── models.py
│ └── migrations/
├── permissions/
│ └── ... merged Zed schemas ...
├── schemas/
│ ├── console.graphql
│ ├── console.metadata.json
│ └── ...
├── gql/
│ └── <schema>/... generated TypedDocumentNode files
└── web/
├── manifest.json
└── app.ts
Generated source — build artifact. Его нельзя редактировать вручную.
ComposeConfig.import_models() подключается к phase 2 Django app population. До того как Django завершит импорт моделей, Angee:
- сканирует source declarations;
- рассчитывает expected runtime;
- пишет отсутствующие/устаревшие generated files;
- импортирует concrete runtime model modules.
Это нужно, чтобы standard Django mechanisms — swappable user model, relations, checks, migrations — видели уже финальные concrete classes.
Compiler сначала строит in-memory map path → rendered content, затем сравнивает с disk. Запись:
- атомарна;
- не трогает unchanged file;
- детерминирована при одинаковых входах.
Обычный boot может дописать stale expected files, но не делает широкую destructive cleanup. Явный manage.py angee build имеет право удалить orphan generated files, только если runtime tree содержит специальный generated sentinel. Migration directories при очистке сохраняются.
angee build --check превращает drift в CI failure без изменения output.
После изменения generated model graph нельзя продолжать provisioning в том же Python процессе: старые классы уже импортированы в Django registry. Поэтому provision orchestration запускает следующие этапы в новых интерпретаторах.
sequenceDiagram
participant P as provision
participant B as angee build
participant R as generated runtime
participant M as Django migrations
participant Z as REBAC sync
participant D as resources loader
participant S as GraphQL schema emitter
P->>P: wait for database
P->>B: compile models/permissions/web manifest
B->>R: atomic deterministic output
P->>P: start fresh interpreter
P->>M: makemigrations + migrate
P->>P: start fresh interpreter
P->>Z: reconcile permission schema
P->>D: load master/install/demo tiers
P->>S: emit SDL + metadata
P->>P: optional bootstrap admin
Обычная Django migration привязана к конкретному source app package. В Angee итоговая table graph появляется после композиции, поэтому addon-owned migration declaration отделена от generated runtime migration history.
Manifest migration содержит:
- стабильное имя;
- target runtime app;
- Python module с source migration;
- predicate
applies(project_state).
RuntimeMigrations:
- читает текущий Django migration graph;
- определяет leaf и следующий номер в target runtime app;
- разрешает
__latest__dependencies; - materializes source migration в generated migration file;
- записывает origin и SHA digest;
- отказывается молча принимать изменившийся уже materialized source.
Защиты включают cycles, multiple leaves, missing targets, corrupt provenance и digest mismatch. Таким образом addon владеет намерением миграции, а конкретная нумерация принадлежит composed project history.
MIGRATION_MODULES перенаправляется на generated runtime apps. Это позволяет обычным Django makemigrations, migrate и migration graph работать поверх итоговой модели.
AngeeQuerySetнаследуетRebacQuerySet: row scoping и field redaction являются default behavior.AngeeManagerсоединяет Django manager API с REBAC semantics.AngeeUnscopedQuerySet/Managerпредназначены для моделей, которые сознательно не являются permission resources.aggregate_scoped_querysetприменяет row scope, но отключает field-redaction machinery для aggregate rows/dictionaries, где нет model instances.read_scoped_querysetиwrite_scoped_querysetдают явные операции получения разрешённого набора.
AngeeModel— abstract timestamped REBAC-capable source base и язык runtime composition.AngeeDataModel— типовой public data base с Sqid identity.SqidPublicIdentityи helpers кодируют/декодируют external IDs.role_anchor()объявляет стабильные authorization anchors.
SqidField— opaque public ID, основанный на primary key, с canonical prefix.StateField— enum-aware field для transition engine.EncryptedField— Fernet encryption at rest; invalid ciphertext не маскируется под нормальное значение.ImplClassField— implementation selection по settings registry key.PatternOpsIndex— PostgreSQL pattern-ops index с корректным fallback на других backends.
| Mixin | Назначение |
|---|---|
TimestampMixin |
created/updated timestamps и корректировка update_fields для auto_now |
SqidMixin |
opaque public identity |
AuditMixin |
authored/modified actor references |
ArchiveMixin |
soft archive vocabulary и queryset helpers |
HistoryMixin |
historical tracking contract |
RevisionMixin |
django-reversion snapshot/revert integration |
HierarchyMixin |
materialized path и subtree queries |
RecordRefMixin |
generic canonical record reference |
ImplDefaultsMixin |
default implementation selection |
HierarchyMixin сознательно использует materialized path: prefix filter естественно проходит через Hasura _bool_exp и REBAC queryset scoping. Это пример выбора data representation исходя из сквозного контракта, а не только удобства ORM.
@transition и StateTransitions реализуют:
- допустимые source/target states;
- conditions;
- named actions;
on_successhooks;- protected direct writes в state field;
- optimistic concurrency check;
save_state;- explicit
force_state(reason=...); - revalidation metadata после class composition.
Transition policy умеет зависеть от REBAC permission query. Поэтому generic FSM library не используется: Angee нужен guard, выражаемый как permission/scoped query и заново валидируемый на final composed class.
Generic relations особенно сложны при Django multi-table inheritance. canonical_record_target выбирает верхнего подходящего REBAC-typed ancestor как стабильную цель записи. Для чтения/grants ancestor_object_refs разворачивает все фактические identities child→parents.
Это предотвращает расхождение, когда attachment/tag/activity записан на child type, а permission relation или foreign key ожидает parent type.
json_safe()детерминированно приводит Decimal/date/UUID/model-related values к JSON boundary.quantize(value, places, mode)требует явный rounding mode; финансовое округление не оставлено ambient Decimal context.- actor resolver превращает текущий REBAC subject в user FK, поддерживая дополнительные non-user actor resolvers.
- ContextVar
sync_ingestion_context()отмечает записи, пришедшие из integration ingestion pipeline.
Angee использует Zanzibar-shaped relationship authorization через django-zed-rebac. Разрешения описываются .zed schemas, а связи хранятся как relationship tuples:
resource <- relation <- subject[#optional_relation]
Actor может быть человеком, service identity или агентом. UI-проверки — только presentation; реальные row и field gates находятся на сервере.
Base autoconfig задаёт:
- strict mode;
- field-read behavior
redact; - разрешённый
sudo/system_contextдля внутреннего кода; - отсутствие автоматического superuser bypass;
- local registry backend по умолчанию.
Даже platform administrator получает права через schema/role relationships, а не через неявную ветку if is_superuser: allow.
Typed model, требующая Angee REBAC contract, без actor’а fail-closes. QuerySet:
- фильтрует недоступные rows;
- redacts недоступные fields;
- позволяет aggregate path использовать уже row-scoped query без instance field machinery.
GraphQL startup assertions проверяют, что exposed REBAC models имеют правильный manager. Это не соглашение «на честном слове».
Create невозможно проверить через существующий row, потому что row ещё нет. AngeeManager.check_create вызывает rebac.check_new с would-be relationships. GraphQL backend:
- декодирует relation IDs;
- строит будущие relationship contributions;
- выполняет create preflight;
- только затем использует узкий system context для самого insert.
Elevation ограничена одной уже авторизованной операцией; она не превращает весь request в sudo.
Update сначала находит target через write-scoped queryset. Затем проходят validation, transition/domain checks и save. Delete использует отдельный guarded deletion engine с preview и blocker semantics.
После physical delete generic signal удаляет как resource-side, так и subject-side relationship tuples. GC подключается к concrete composed models, а не только к abstract source.
Каждый addon обычно владеет своим permissions.zed. Для additive cross-addon extension используется отдельный permissions.extends.zed:
- contributor может добавить relation в существующий definition;
- может добавить arms к существующему permission;
- не может незаметно заменить чужое определение.
Composer сортирует contributors, проверяет unknown target, unknown permission и collisions, добавляет provenance/revision, эмитит merged schema и перенаправляет owning AppConfig на неё. Пример — spaces расширяет permission vocabulary messaging thread.
angee.iam поверх raw REBAC schema строит:
- user model и manager;
- session authentication и bearer-aware CSRF middleware;
- role anchors;
- permission-source introspection;
- role/grant/condition/schema view models для administration UI;
- platform admin role;
- bootstrap admin command.
Permission Hub не хранит вторую ACL-модель. Он анализирует активную REBAC schema и relationship rows, поэтому administrative view остаётся проекцией истинного authorization graph.
angee.resources — base addon, а не часть angee/compose. Он не импортирует composer и работает поверх уже populated Django registry.
| Tier | Смысл |
|---|---|
master |
каталог, принадлежащий source distribution; наиболее «замороженные» записи |
install |
обязательные project/install данные |
demo |
демонстрационные данные |
Запрос более позднего tier автоматически включает prerequisites: demo означает master + install + demo.
Resource declaration может указывать:
- local
pathили зарегистрированный source type; - target model;
depends_onмежду файлами;- natural-key
adoptfields; publishhook;- kind
rowsилиgrants.
EntryGraph строит topological order и ловит missing dependencies/cycles. Поддерживаются YAML, JSON, CSV и TSV; structured files могут содержать envelope с _meta, default model и rows.
Каждая управляемая запись получает внешний logical key _xref. Ledger Resource хранит:
- source addon;
- tier;
- xref;
- target model/public id;
- content hash.
Загрузчик:
- валидирует headers и tier policy;
- одним запросом поднимает существующие ledger rows;
- вычисляет canonical SHA-256 контента;
- пропускает unchanged rows;
- разрешает FK/M2M через xrefs;
- adopts существующую запись по единственному natural key;
- создаёт/обновляет target;
- обновляет ledger;
- запускает addon post-load hooks.
Особо обработан случай stale sqid pointer после drop/recreate table и повторного использования PK: loader сверяет adopt identity и не перезаписывает случайно другую строку.
kind="grants" загружает декларативные REBAC tuples. Ссылки могут быть:
- literal typed ref;
- публичный wildcard
*; - xref на ранее загруженную model row.
Для MTI resource grant разворачивается на все REBAC identities, которые несёт child. Запись idempotent: существующие natural tuple keys не трогаются и не создают лишний audit/zookie bump.
Core resources addon поставляет только безопасный addon-relative path source. Remote url source добавляет angee.integrate через settings registry. Поэтому loader не приобретает сетевую ответственность и SSRF surface только потому, что умеет читать fixtures.
CLI поддерживает validate, load, diff и выбор addon/tier. Provision использует этот же API.
Addon schema.py экспортирует mapping schemas, например public и console. Каждый bucket содержит SchemaParts:
query;mutation;subscription;types;extensions;type_extensions;input_extensions.
GraphQLSchemas обходит addon’ы в готовом app order, собирает contributions отдельно для каждого имени, дедуплицирует одинаковые Python objects, проверяет collision root fields и строит финальные root types.
Именование schema — не hardcoded special case. Новый schema bucket появляется из installed addon contributions и затем получает свой endpoint, SDL, metadata и typed client.
Финальная Strawberry schema использует:
RebacExtension;- Django optimizer;
- Hasura-compatible configuration;
- validation/error normalization;
angee.resourcesmetadata вgraphql-coreschema extensions.
Startup/schema-build проверки:
- Sqid prefixes уникальны;
- exposed REBAC model использует scoped manager;
- revision field list не раскрывает gated relation/field;
- enum descriptions корректны;
- root field collisions отсутствуют.
Ошибки Django/Pydantic validation преобразуются в стабильные GraphQL extensions: machine code, field errors и form/non-field errors. Missing actor и permission denial также имеют различимые codes.
AngeeNode стандартизует id и display representation. На wire передаётся opaque Sqid с model prefix, а не database PK. Relation input decoder проверяет prefix/type и ищет объект в правильном scoped queryset.
Основной API factory — hasura_model_resource. Он использует strawberry-django-hasura для стандартной Hasura-shaped части и добавляет Angee-specific behavior.
Для модели могут появиться:
- list query;
- by-pk/detail query;
_aggregate;_groupsи_groups_count;- filter and order input types;
- create/update/save/delete mutations;
- delete preview;
- revisions;
- changes subscription.
Не все capabilities включаются автоматически. Factory рассчитывает их по model/schema declarations и записывает в metadata. UI не должен угадывать наличие delete/revisions/groups из формы имени GraphQL field.
AngeeHasuraWriteBackend является policy boundary:
- create вызывает
check_newдо insert; - update получает row из write-scoped queryset;
- foreign keys и M2M IDs декодируются до elevation;
- model
full_clean()выполняется до commit; - delete делегируется guarded deletion;
- nested lines применяются транзакционно.
HasuraLines реализует не набор независимых child mutations, а atomic desired-state save:
- lock parent row;
- проверить parent write permission;
- принять полный желаемый список lines;
- отделить create/update/delete;
- отвергнуть stale, duplicate или принадлежащие другому parent IDs;
- применить parent patch и child diff в одной transaction;
- вернуть свежий composed record.
Узкий system context используется после parent authorization, чтобы child write не зависел от временного существования relation tuple в середине операции.
Aggregate queryset всегда сначала проходит row scope. Field redaction для model instances здесь неприменима, поэтому используется отдельный aggregate-safe path. Метаданные перечисляют допустимые measures, а не разрешают произвольный SQL aggregate над любым полем.
Grouped resource имеет DDN/NDC-подобную форму:
<resource>_groups(
group_by: [...!]!
where: ...
having: ...
order_by: ...
limit: Int
offset: Int
): [<Resource>Group!]!Каждая bucket row возвращает typed key и aggregate. Поддерживаются:
- scalar dimensions;
- relation axes с human label axis;
- time extractions;
- number ranges/granularities;
- curated count/sum/avg/min/max measures;
having;- group ordering;
- drill-down filter echo.
Backend metadata, generated operation documents и frontend group adapters обязаны изменяться вместе. Frontend-only grouping semantics архитектурно запрещены.
Помимо Django models существуют Pydantic row resources. Они дают list-like schema для вычисляемых/внешних данных, но всё равно объявляют server/client row model и metadata. Это escape hatch для данных без local table, не обход единого UI contract.
flowchart LR
SM["Composed Django model"]
HR["hasura_model_resource"]
GS["Strawberry schema"]
MD["angee.resources metadata"]
SDL["Generated SDL"]
CG["Typed documents"]
RF["@angee/refine provider"]
UI["Generic Resource UI"]
SM --> HR
HR --> GS
HR --> MD
GS --> SDL
MD --> CG
SDL --> CG
CG --> RF
MD --> UI
RF --> UI
В терминах compiler architecture DataResourceMetadata — это промежуточное представление между backend model/schema и generic frontend. Именно оно делает Angee не просто GraphQL backend’ом с набором React screens.
Для каждого ресурса metadata описывает:
modelLabel;- canonical model label;
- resource type;
- public ID prefix/semantics;
- record representation;
- server/client row models.
- roots list/detail/aggregate/groups/groupsCount;
- create/update/save/delete/deletePreview;
- revisions/changes;
- generated input/output type names.
- list/detail;
- filter/sort;
- aggregate/group;
- filter echo;
- revisions;
- create/update/save/delete/delete preview;
- change stream.
Для каждого поля:
kind: scalar, enum, relation, list;- scalar type;
- widget hint;
- enum values;
- relation target и label axis;
- readable/filterable/sortable/aggregatable/groupable;
- creatable/updatable/required;
- line-resource role.
- filter/order fields;
- aggregate measures;
- group dimensions и extractions;
- default sort;
- drilldown/filter mapping.
Addon contributions могут дополнять metadata на root surfaces. Merge идёт per model с collision checking. Это позволяет domain addon добавить display/action/UI-relevant fact к ресурсу, не копируя базовое описание.
Artifact сериализуется в runtime/schemas/<schema>.metadata.json, а также прикрепляется к runtime GraphQL schema extensions. @angee/metadata валидирует artifact при запуске frontend и превращает его в Refine resources, model/field lookups и UI contracts.
IR является одной из самых важных архитектурных идей Angee: модель объявлена один раз, а transport и rendered UI получают согласованную проекцию. Но это не означает, что весь доменный UX генерируется автоматически; addon web package всё ещё может определить custom routes, forms, widgets и views.
Domain action оформляется как mutation с унифицированным ActionResult:
ok;message;- optional created/affected
id; validation_errors.
Action metadata содержит invalidation hints. Web codegen распознаёт action fields по фактической SDL shape, а не по названию, и создаёт TypedDocumentNode.
Deletion engine сначала строит preview:
- cascade tree;
- модели и counts удаляемых объектов;
SET_NULL/updated groups;- protected/restricted blockers;
- root object label/public id.
Preview фильтруется через actor visibility: пользователь не получает side-channel список закрытых зависимостей. На confirm движок повторяет gate и возвращает реальные deleted/updated counts. Большие scope checks разбиваются на chunks.
RevisionMixin использует django-reversion; composer регистрирует final concrete models. GraphQL revisions expose только явно разрешённые snapshot fields. Schema assertion запрещает configuration, через которую revision history раскрыл бы закрытое поле или небезопасную relation.
После save/delete строится JSON-safe ChangePayload. Публикация выполняется через transaction.on_commit, поэтому subscriber не увидит событие откатившейся transaction. Signal вызывается robust mode, затем payload попадает в Channels group.
Model может выключить broadcasts_changes; ingestion context позволяет помечать или подавлять integration-owned поток по контракту.
При подписке actor фиксируется в subscription context. Для каждого payload ChangeReadGate заново проверяет:
- существует ли/был ли доступен object identity;
- row permission;
- field visibility/redaction.
Долгоживущая async subscription закрывает старые DB connections. In-memory channel layer разрешён только в DEBUG или при явном opt-in; обычная distributed конфигурация выводится на Redis.
angee.urls — стабильный module. Он берёт urlpatterns contributions у уже установленных addon’ов и flatten’ит их в dependency order. Никакого повторного model/addon build на request path нет.
GraphQL endpoints имеют форму:
/graphql/<schema>/
Есть отдельный CSRF token endpoint /auth/csrf/.
angee.asgi лениво bootstrap’ит composed settings, затем собирает:
- Django HTTP app;
- websocket URL patterns;
- addon HTTP mounts;
- lifespan contexts.
Если дополнительных protocol surfaces нет, возвращается обычный Django ASGI application. Иначе строится ProtocolTypeRouter для http, websocket, lifespan. HTTP mount dispatch использует longest-prefix rule. WebSocket завернут в AuthMiddlewareStack.
FastMCP ASGI applications требуют lifespan; общий router владеет AsyncExitStack и корректно открывает/закрывает lifespans всех mounts.
Browser использует session cookies. Mutation requests получают CSRF token и отправляют его через transport. Bearer-authenticated service request освобождается от session CSRF только middleware, которое фактически распознало bearer path; это не глобальный CSRF disable.
WebSocket connection params и HTTP headers строятся общим transport-auth layer. Logout/credential change корректно закрывает live connections.
Runserver override запускает Uvicorn с Django autoreload. В development reloader child может через ANGEE_DEV_SDL регенерировать stale SDL. Production serving path никогда не пишет schema artifacts.
angee.tasks создаёт Celery app из composed Django settings и включает autodiscovery. Framework API ставит named task с:
- kwargs;
- ETA;
- queue;
- expiry.
Defaults включают Redis broker/backend configuration, hard/soft time limits, ignore-result policy и beat schedule в data directory.
LockKey задаёт стабильную логическую блокировку. Backend выбирается по возможностям:
- SQLite/test — in-process lock;
- PostgreSQL — session advisory lock для cross-process coordination.
PostgreSQL key получается как стабильная пара int4 из hash, а не Python hash() с process randomization. Consumers могут проверить, поддерживает ли backend cross_process, и отказаться запускать distributed-sensitive worker на локальной имитации.
Session advisory locks предъявляют требования к connection pooling: lock привязан к DB session. Это явно отражённая граница, а не скрытая гарантия.
Framework предоставляет очередь и lock seam; domain lifecycle остаётся в addon’е. Например:
- integration scheduler — в
integrate; - external message ingestion — в connector addons;
- workflow execution — в
workflows; - agent inference tasks — в
agents.
manage.py angee build создаёт runtime/web/manifest.json с:
- установленными addon web packages;
- локальными source roots или package fallbacks;
- GraphQL document roots;
- external codegen entries;
- schema version.
Python не генерирует вручную большое schema-shaped TypeScript API. Он генерирует manifest и backend-owned SDL/metadata; дальше frontend owner применяет стандартный GraphQL Code Generator.
CLI из @angee/app:
- читает runtime manifest;
- находит все
runtime/schemas/*.graphql— disk SDL является source of truth; - определяет addon source dirs: project/runtime source root либо
node_modulesfallback; - собирает operation documents из convention files;
- запускает GraphQL Codegen
clientpreset; - строит дополнительные action/aggregate/group/delete-preview/revision/save documents из SDL + metadata;
- генерирует
runtime/web/app.ts.
Document routing:
documents.public.ts→ public schema;documents.tsиdocuments.<schema>.ts→ соответствующая non-public schema;- external owner может задать свой glob, SDL и optional bare types module.
Live capability определяется фактом наличия Subscription root в SDL, а не именем schema.
runtime/web/app.ts содержит статические imports всех addon entrypoints и schema artifacts, затем экспортирует:
export const composedAddons = [...] as const;
export const schemas = {
console: {
url: "/graphql/console/",
metadata: consoleMetadata,
operationDocuments: consoleDocuments,
live: true,
},
};Это статическая build composition: bundler видит реальные imports, TypeScript — реальные types, а collision detection выполняется при сборке/старте composition root.
flowchart TB
AM["addon.toml + web/package.json"]
PM["runtime/web/manifest.json"]
BS["GraphQL SDL + metadata.json"]
WC["angee-web-codegen"]
TD["TypedDocumentNode artifacts"]
WA["runtime/web/app.ts"]
CA["createApp(composedAddons, schemas)"]
V["Vite bundle"]
AM --> PM
PM --> WC
BS --> WC
WC --> TD
WC --> WA
TD --> CA
WA --> CA
CA --> V
Этот пакет:
- валидирует generated metadata artifact;
- индексирует resources по exact model label и fallback type name;
- предоставляет field/row/public-id helpers;
- проецирует metadata в Refine resource declarations;
- вычисляет capability-based access-control surface;
- описывает lines, groups, invalidation и record subtitle.
Он не выполняет network requests и не рендерит views. Это чистый boundary между machine metadata и клиентскими consumers.
Пакет адаптирует Refine/Hasura к Angee dialect:
- schema-named data providers;
- session или bearer auth;
- CSRF headers;
- GraphQL WebSocket lifecycle;
- Hasura-default filters/order/pagination;
- typed authored query/mutation hooks;
- aggregates, groups, facets;
- delete preview;
- revisions;
- atomic save/lines;
- action execution;
- metadata-directed query invalidation;
- TanStack Router provider.
Wire semantics принадлежат здесь, а не generic UI components. Архитектурный frontend test отдельно запрещает импортировать authored GraphQL hooks из @angee/ui.
Пакет владеет визуальным и interaction layer:
- Tailwind semantic tokens;
- Base UI headless primitives;
- variants/class merge;
- inputs, dialogs, popovers, menus, tabs, tooltips, tables, forms.
- app rail;
- top bar/menu;
- sub-navigation;
- user menu;
- systray;
- command palette/spotlight;
- drawer rail;
- breadcrumbs.
- public/console layouts;
- workbench;
- split panes;
- drawer overlays;
- primary pane context;
- named slots.
ResourceList;- rows/table list;
- grouped list;
- form/detail view;
- board;
- calendar;
- graph;
- gallery;
- tree;
- dashboard/aggregate panels;
- editable lines;
- record actions;
- deletion preview;
- revisions;
- chatter.
Widget registry выбирает renderer/editor по field metadata. Есть scalar/text, boolean, enum, number, monetary, date/time, relation, M2M, JSON, Markdown/CodeMirror, color, status, progress и domain-specific overrides.
Context/registries несут уже composed definitions menus, widgets, forms, chatter tabs, slots, drawers, previews, icons и data providers.
Самые крупные hotspots показывают фактический центр сложности: FormView — более 2 200 строк, ResourceList — более 1 200, list body — около 1 700. То есть generic rendered binding уже является существенным framework subsystem, а не тонкой коллекцией кнопок.
defineAddon и defineBaseAddon задают web manifest addon’а. Возможные contributions:
- routes;
- menu items;
- widgets;
- i18n bundles;
- icons;
- forms;
- chatter tabs;
- slots;
- previews;
- drawers;
- data providers.
composeAddons применяет явную collision policy:
| Contribution | Collision semantics |
|---|---|
| routes, menu IDs, widgets, icons, forms, providers, i18n keys, previews | duplicate — fail |
| chatter tab ID | later contribution overrides |
| slot item | unique по (slot, id) |
| drawer | unique по (edge, id) |
| ordering | явный sequence, не случайный import order |
createApp:
- композирует addon manifests;
- загружает schema metadata;
- создаёт named Hasura providers и live providers;
- создаёт один shared QueryClient;
- связывает Refine, React Query, TanStack Router и
nuqs; - монтирует auth/i18n/notifications/modals/runtime providers;
- строит root/layout/resource routes и guards;
- вычисляет menu/resource indexes.
Один QueryClient разделяется между Refine и auth route gate; иначе cache и identity state могли бы рассинхронизироваться.
Route composer:
- связывает parent/layout relationships;
- детектирует path/id collisions;
- сортирует детерминированно;
- генерирует standard list/detail children через
resourcePageRoutes; - поддерживает auth/public boundaries и safe redirect.
Base addon’ы — не все одинаково «ядровые». Ниже они классифицированы по системной роли.
| Addon | Dependencies | Архитектурная роль |
|---|---|---|
angee.resources |
angee.base |
tiered data-as-code, xref ledger, adoption, grants |
angee.iam |
resources, graphql, Django auth/sessions, axes | user/identity, login, roles, permission hub, admin bootstrap |
angee.money |
iam, resources, graphql | currencies, dated exchange rates, explicit precision/rounding |
angee.scheduling |
graphql | recurrence/scheduling fields и календарная API-семантика |
angee.sequence |
iam, graphql | concurrency-safe configurable counters/numbering |
angee.tags |
iam, resources, graphql | tags и canonical generic record assignment |
angee.uom |
iam, resources, graphql | unit categories, conversions и measurement vocabulary |
| Addon | Dependencies | Архитектурная роль |
|---|---|---|
angee.storage |
iam, resources, graphql, contenttypes | drives/folders/files, storage backends, upload/finalize, attachments, previews |
angee.parties |
iam, integrate, storage | people/organizations, handles, addresses, directories, relationships; MTI reference domain |
angee.messaging |
iam, parties, integrate, storage, contenttypes, postgres | threads/messages/fragments/parts/reactions, activities/followers/notifications, external channels |
angee.posts |
messaging, integrate, parties, graphql | public/feed projection over messaging primitives |
angee.spaces |
iam, parties, messaging | group membership/collaboration boundary; permission extension к threads |
angee.knowledge |
iam | vault/page/Markdown knowledge, outline/section edits, links/history |
angee.nexus |
parties, messaging | relationship cadence/gravity/rollup projection |
| Addon | Dependencies | Архитектурная роль |
|---|---|---|
angee.integrate |
iam, resources, tasks | credentials, OAuth clients/accounts, backend registries, sync lifecycle, scheduler, events, safe outbound HTTP |
angee.integrate_github |
integrate | GitHub implementation adapter |
angee.iam_integrate_oidc |
iam, integrate, parties | OIDC identity provider bridge |
angee.parties_integrate_carddav |
parties, integrate | CardDAV/vCard round-trip |
angee.messaging_integrate_imap |
messaging, integrate, parties | incremental IMAP ingestion and email decomposition |
angee.messaging_integrate_telegram |
messaging, integrate, parties | Telegram user-session bridge |
angee.messaging_integrate_whatsapp |
messaging, integrate, parties | WhatsApp multi-device bridge |
angee.knowledge_graph_pgvector |
knowledge, mcp | PostgreSQL vector/knowledge graph extension |
| Addon | Dependencies | Архитектурная роль |
|---|---|---|
angee.mcp |
iam | authenticated FastMCP server, addon tool registration, actor-scoped GraphQL tools |
angee.operator |
iam, integrate | bridge к внешнему Go operator daemon; отдельная external SDL/codegen surface |
angee.platform |
iam, resources | platform control/catalog/schema/addon explorer |
angee.platform_integrate_operator |
platform, operator | platform actions backed by operator control plane |
angee.platform_integrate_vcs |
platform, integrate | source/VCS catalog integration |
angee.agents |
integrate, operator, mcp | providers/models/agents/skills/tool servers, inference/runtime provisioning, ACP session surface |
angee.agents_integrate_anthropic |
agents, integrate | Anthropic inference backend registry contribution |
angee.agents_integrate_openai |
agents, integrate | OpenAI inference backend registry contribution |
angee.workflows |
iam, tasks | workflow/step/edge/trigger/run/decision graph и Celery execution |
angee.workflows_agents |
workflows, agents | agent-backed workflow step implementation |
integrate — не просто каталог connectors. Он владеет общими seams:
- зашифрованные credentials/app keys;
- OAuth2 protocol и refresh/revoke;
- settings-backed backend/capability registries;
- bridge lifecycle/state transitions;
- task scheduling, retry, sync locks;
- typed integration events;
- local live-session store conventions;
- outbound HTTP с SSRF protection.
PinnedTransport разрешает hostname один раз, валидирует IP, а затем dial’ит именно проверенный address, сохраняя TLS hostname semantics. Это закрывает DNS rebinding окно между validation и connect. Network resource source присоединяется к resources через registry, а не import inversion.
Messaging содержит больше, чем чат:
- conversation/thread identity;
- content-addressed fragments и message parts;
- reactions;
- attachments;
- participants/followers;
- record-scoped chatter;
- activities и notifications;
- channel bridges;
- ingestion/deduplication;
- PostgreSQL full-text support.
Поэтому posts, spaces, nexus и external messaging connectors строятся поверх него, а не создают параллельные event/message модели.
Markdown knowledge не переписывает document через render/parse roundtrip. markdown-it-py даёт source line spans; model API может получить outline, найти уникальную section и splice исходный text. Это сохраняет source fidelity.
Agent — first-class permissioned principal, а не API key, спрятанный в feature flag. Addon моделирует inference provider/model, agent configuration, skills, MCP servers/tools и runtime binding. Vendor addons добавляют implementations через registry.
FastMCP mounted как ASGI application. Bearer token verifier превращает credential в REBAC actor; middleware оборачивает каждый tool call actor context. GraphQL MCP tool выполняет ту же GraphQL operation под тем же actor scope, поэтому агент не получает отдельный privileged data path.
Workflow engine разделяет:
- definition graph: workflow, steps, edges, triggers;
- execution graph: runs, step runs, decisions;
- transition/gate semantics;
- background scheduling/execution;
- implementation seam для типа шага.
workflows_agents добавляет agent step как внешний implementation, сохраняя базовый engine независимым от конкретного agent runtime.
Host — конкретный Django/React project, который выбирает root addon’ы и deployment configuration. Framework не предполагает единственного продукта. examples/notes-angee показывает минимальную композицию реального domain addon + shared base addons.
Внешний Go operator отвечает за:
- Git Sources;
- Workspaces;
- dev/prod Stacks;
- containers/processes;
- logs/lifecycle;
- rendering templates.
angee-django отвечает за содержимое runnable Host. angee.operator addon является клиентским bridge, но не переносит control-plane ответственность внутрь Django.
Copier templates закрепляют golden paths:
- новый project;
- addon;
- addon web package;
- stack/workspace;
- service.
Шаблоны — часть архитектуры для agent-native development: они уменьшают число допустимых форм одного и того же решения. Система полагается не только на документацию, но и на scaffold, manifest conventions и policy tests.
Addon installation через platform/operator path изменяет project settings.yaml, сохраняя комментарии и формат через ruamel.yaml, затем требует rebuild/reprovision. Это сознательный write path конфигурации. Runtime не «подключает» addon в память текущего процесса.
source/package становится доступен
→ root addon добавляется в project settings
→ AppGraph рассчитывает dependency closure
→ autoconfig складывает settings
→ build композирует models/permissions/web manifest
→ runtime migrations materialize
→ Django migrations применяются
→ permission schema синхронизируется
→ resources загружаются
→ SDL/metadata/codegen обновляются
→ новый Host bundle запускается
React ResourceList
→ @angee/metadata находит capabilities/fields/roots
→ @angee/refine строит Hasura query + auth/CSRF context
→ GraphQL resource resolver
→ AngeeManager/RebacQuerySet row scope + field redaction
→ Django ORM
→ typed rows
→ widgets/views по metadata
FormView
→ generated typed create/save document
→ GraphQL input decoding
→ relation IDs разрешаются в scoped querysets
→ check_new(would-be relationships)
→ narrow system-context insert
→ full validation / transaction
→ on_commit change publication
→ metadata-directed cache invalidation
full desired form state
→ parent write scope
→ select_for_update parent
→ validate line ownership/IDs
→ calculate create/update/delete diff
→ apply all changes in one transaction
→ return fully re-seeded record
committed save/delete
→ ChangePayload
→ transaction.on_commit
→ Channels group
→ subscription actor context
→ ChangeReadGate
→ authorized/redacted payload
→ Refine live provider
→ targeted query invalidation/refetch
scheduler / live connector
→ task lock
→ backend selected via ImplClassField registry
→ remote protocol client
→ normalized domain ingestion inside sync context
→ xrefs/cursors/deduplication
→ normal model validation and permissions-owned internal path
→ events/progress/retry state
MCP bearer token
→ token verifier
→ identity → REBAC actor
→ per-call actor context
→ addon MCP tool or GraphQLTool
→ ordinary scoped GraphQL/ORM operation
→ audited result
| Нужно расширить | Правильная seam | Результат |
|---|---|---|
| добавить capability | новый addon + addon.toml |
dependency-composed vertical slice |
| добавить поля/поведение к модели | abstract AngeeModel(extends=...) |
same-row concrete class composition |
| создать реальный subtype/table | runtime=True + extends |
Django MTI child |
| добавить settings defaults | autoconfig.py |
ordered settings fold |
| добавить реализацию backend’а | settings registry + ImplClassField |
selectable implementation key |
| добавить API | named SchemaParts contribution |
merged Strawberry schema |
| открыть модель generic UI | hasura_model_resource |
CRUD/analytics metadata + API |
| добавить domain command | typed GraphQL action | generated action document + invalidation |
| добавить permission vocabulary | own permissions.zed |
independently owned schema |
| расширить чужую permission type | permissions.extends.zed |
additive checked merge |
| поставить системные записи | resource manifest + xrefs | idempotent tiered load |
| поставить базовые grants | resource kind=grants |
idempotent REBAC tuples |
| добавить resource source | source registry | materialization without core import |
| добавить фоновую работу | Celery task + lock seam | queue/retry/concurrency |
| добавить HTTP/WebSocket/MCP surface | addon contribution/mount | composed stable ASGI router |
| добавить UI route/menu/form/widget | defineAddon manifest |
fail-fast composed React runtime |
| изменить rendering типа поля | widget registry | metadata-driven renderer/editor |
| добавить custom GraphQL document | convention document file | schema-routed typed codegen |
| мигрировать уже установленную композицию | addon runtime migration declaration | generated project migration with provenance |
Анти-паттерны по архитектуре репозитория:
- monkeypatch чужого класса;
- runtime global
register()как основной composition mechanism; - ручное редактирование generated runtime;
- frontend permission без server gate;
- ручное дублирование GraphQL result types;
- addon import, не объявленный в
depends_on/package.json; - network concern внутри resource core;
- domain business logic в composer;
- serving entrypoint, импортирующий build-time compose;
- скрытый superuser bypass.
- Row authorization по умолчанию. Доступ начинается со scoped manager/queryset.
- Field authorization. Закрытое поле redacted сервером.
- Create preflight. Проверяется будущая relation graph до insert.
- Narrow elevation. System context используется после gate и на минимальном участке.
- Opaque IDs. PK не становится API identity.
- No implicit admin bypass. Привилегия выражена schema/tuples.
- Subscription re-check. Long-lived event stream не доверяет доступу на момент подписки навсегда.
- Deletion privacy. Preview не раскрывает невидимые dependent rows.
- CSRF distinction. Session mutation и bearer request разделены.
- Encrypted secrets. Sensitive implementation fields используют type-level encrypted field.
- SSRF-pinned HTTP. Validation и TCP target не расходятся из-за повторного DNS lookup.
- Manifest path safety. Local resources не могут выйти из addon root.
- Permission merge fail-fast. Additive extension не может тихо перезаписать чужое правило.
Addon code — доверенный application code. Build-time compiler обеспечивает конфликты и направление композиции, но не sandbox’ит Python package. Установка произвольного addon’а эквивалентна установке trusted server dependency.
Generated frontend metadata — trusted artifact build’а, но @angee/metadata всё равно валидирует shape. Client никогда не считается security authority.
External integrations выполняются в workers/queues и должны входить в domain через нормализованные ingestion APIs. Vendor SDK не должен диктовать model schema или permission bypass.
- sorted dependency traversal;
- stable content rendering;
- atomic writes;
- generated sentinel;
--checkdrift mode;- migration source digests;
- collision detection.
- Django transactions для composed writes;
- parent row lock для editable lines;
- optimistic state-transition source check;
transaction.on_commitдля realtime;- natural-key/idempotent resources;
- idempotent REBAC grants;
- advisory task locks;
- integration cursors/deduplication в owning addon.
Frontend operation hooks используют metadata-directed invalidation. Live event — сигнал invalidate/refetch, а не попытка локально воспроизвести все серверные изменения. Это уменьшает риск расхождения сложных aggregates/groups/permissions.
Generated Python graph нельзя hot-swap безопасно после Django import. Angee не скрывает это; build/provision boundary явно требует новый процесс. Такая остановка менее «магична», зато final registry остаётся обычным Django registry.
В исходниках видны следующие осознанные меры:
- resource ledgers primed одним query на dataset;
- content hash позволяет skip unchanged resources;
- QuerySet scoping выполняется в БД;
- aggregate path избегает instance field-redaction overhead;
- GraphQL Django optimizer уменьшает N+1;
- deletion scope checks chunked;
- identity label helpers memoize на request;
- generated artifacts кэшируются по фактическому content;
- React Query является единым cache owner;
- long lists используют TanStack Virtual;
- Calendar/preview-heavy code загружается лениво;
- PostgreSQL pattern indexes и full-text используются там, где capability backend-specific;
- worker-only vendor dependencies исключаются из web/console import closure архитектурным тестом.
SQLite — development/test floor, не полная замена PostgreSQL semantics. Vector search, Postgres full-text и cross-process advisory locks заведомо backend-specific; код либо предоставляет честный fallback, либо явно проверяет capability.
Backend suite покрывает:
- project discovery/settings isolation;
- addon manifest inference;
- dependency graph order/cycles/collisions;
- runtime class emission и drift;
- source extension/MTI/MRO edge cases;
- runtime migrations/provenance;
- permission extension merge;
- public IDs/canonical refs;
- transitions/concurrency;
- REBAC read/create/write/delete behavior;
- resource tiers/xrefs/adoption/grants;
- GraphQL composition/CRUD/filters/aggregates/groups/lines/actions;
- deletion preview и revisions;
- change publication/subscriptions;
- IAM/auth/roles;
- tasks/locks;
- storage, parties, messaging, knowledge, agents, workflows и connectors;
- templates и example project.
tests/test_layering.py автоматически доказывает:
angee.baseне импортирует sibling subsystems или addon’ы;- нет общего
angee.apps.AppConfigbase, затягивающего лишнюю иерархию; - resources и GraphQL не импортируют compose;
- serving URLs/ASGI не импортируют compose;
- permission renderer не возвращён внутрь composer;
- live console import closure не тянет worker-only WhatsApp/Telegram/Pillow dependencies.
architecture-guardrails.test.ts проверяет:
- разрешённое направление imports между четырьмя framework packages;
- каждый addon импортирует только declared package dependencies;
- addon web graph ацикличен;
- удалённые старые shell packages не возвращаются;
- critical shared owners реально переиспользуются;
- row identity helpers берутся из metadata owner;
- authored hooks остаются в refine owner.
- Vitest: pure modules, hooks, components, routed app flows.
- Storybook: primitives, generic views и addon visual surfaces.
- Playwright: browser flows с isolated workspace, role
storageState, GraphQL fixture и page objects. - CI: ruff, mypy, pytest, frontend checks, codegen/build drift, private path policy, secret scanning.
Тестовая пирамида здесь одновременно является архитектурным линтером. Это особенно важно для agent-written code: многие деградации — не функциональный bug сегодня, а появление второго владельца concern. Guardrail ловит именно это.
После build система не эмулирует ORM. Django видит реальные concrete classes, relations и migrations. Это сохраняет совместимость с mature ecosystem.
Metadata соединяет model capabilities, GraphQL names, typed documents и generic UI. Большой класс drift bugs исключается конструктивно.
ORM, GraphQL, subscriptions и MCP используют одного actor/REBAC owner. Агент не получает special backdoor.
Model labels, fields, routes, menus, widgets, schema roots, permission extensions и package dependencies проверяются. «Последний импорт победил» допускается только там, где override объявлен частью контракта.
Каждый concern делегирован библиотеке или конкретному Angee package/addon. Architecture tests проверяют, что владельцы не расползаются.
Patterns показаны не на игрушечном blog addon, а на IAM, files, OAuth, messaging, realtime, agent runtime и workflows. Для coding agent это ценный набор сложных примеров.
Детерминированный output, drift check, migration digest и fresh-process boundary делают генерацию наблюдаемой и CI-friendly.
README прямо предупреждает об active refactor и отсутствии production guarantee. Архитектурная цель амбициозна, но compatibility surface ещё движется.
Появляются дополнительные состояния:
- source declarations;
- generated runtime;
- generated migrations;
- DB state;
- permission schema state;
- SDL/metadata/client artifacts.
Инструменты стараются синхронизировать их, но debugging требует понимать compiler pipeline.
Нельзя безопасно включить addon в текущий production process одной строкой runtime registry. Нужны rebuild, migrations, resources, schema/codegen и restart. Это осознанный обмен динамичности на аудитируемость.
Field ownership, labels, MRO, managers и migration intent должны быть однозначны. Произвольное patching проще локально, но несовместимо с детерминированным compiler.
Python composition core относительно компактен, но generic GraphQL и UI binding крупные. Особенно тяжёлые areas — forms/lists/groups, metadata dialect и integration/message ecosystems. Заявление «thin framework» верно в смысле делегирования concerns, но не в смысле малого объёма всей distribution.
SQLite полезен для local floor/tests, но advisory locks, vector search, Postgres full text и часть analytical behavior не могут иметь полностью идентичную семантику.
Build-time checking не является sandbox. Внешний addon может выполнить произвольный Python/JS код с правами приложения. Supply-chain review остаётся необходимым.
Metadata отлично покрывает CRUD, analytics и standard views, но сложный domain workflow всё равно требует authored schema actions и addon React surface. Это не fully automatic UI generator.
FormView, ResourceList и list body концентрируют много semantics. Они хорошо тестируются, но остаются hotspot’ами, где изменение generic behavior влияет на весь addon catalogue.
declarations → dependency graph → composition → generated program
В этом разрезе addon.toml — manifest, source model — AST-подобный input, DataResourceMetadata — IR, runtime files — target program.
base/domain → data/API adapters → rendered UI → composition root
Dependencies идут наружу через narrow seams; core не знает конкретных connectors.
model + permissions + API + resources + tasks + web = capability
Каждый addon несёт вертикальный срез, но использует общие horizontal framework services.
human/service/agent → one identity → one REBAC graph → ORM/GraphQL/MCP
Agent-native здесь означает прежде всего identity/permission parity, а не встроенную кнопку LLM.
backend metadata → typed client → generic Refine/UI views + authored addon UX
Продукт состоит из reusable generic surfaces и точечных domain contributions.
- Всё устанавливаемое — addon; addon объявляет manifest.
- Проект выбирает roots, Composer рассчитывает dependency closure.
- Порядок композиции детерминирован.
- Framework владеет seams, зрелые libraries — concerns.
- Source models абстрактны; tables принадлежат generated runtime.
- Same-row extension не допускает field collision.
- Runtime output не редактируется вручную.
- Изменение composed Python graph требует fresh interpreter.
- Runtime migration сохраняет provenance и digest.
- Serving layer не импортирует build-time compose.
- REBAC row scope — default, отсутствие actor’а fail-closed.
- Create permission проверяется до insert.
- Elevation узкая и следует после authorization.
- Superuser не обходит schema неявно.
- Public API identity не равна database PK.
- Subscription повторно проверяет actor visibility.
- Event публикуется только после transaction commit.
- Resource load idempotent и xref-based.
- Remote resource source принадлежит integration layer.
- Backend metadata, GraphQL dialect и frontend adapters меняются согласованно.
- UI capabilities выводятся из metadata, но не считаются security rules.
- Web addon composition статична и collision-checked.
- Frontend package imports направлены снизу вверх.
- Addon-to-addon dependency должна быть объявлена и в Python, и в web manifest.
- Новый concern должен иметь одного очевидного владельца.
| Файл | Зачем читать |
|---|---|
angee/project.py |
project discovery |
angee/compose/project.py |
settings bootstrap и ProjectContract |
angee/addons.py |
manifest contract/discovery |
angee/compose/appgraph.py |
dependency graph |
angee/compose/autoconfig.py |
settings fold |
angee/compose/composer.py |
orchestration owner |
angee/compose/runtime.py |
model/runtime compiler |
angee/compose/migrations.py |
addon-owned runtime migrations |
angee/compose/permissions.py |
.extends.zed merge |
| Файл | Зачем читать |
|---|---|
angee/base/models.py |
AngeeModel, managers, scoped helpers, public IDs |
angee/base/mixins.py |
reusable model contracts |
angee/base/fields.py |
Sqid/State/Encrypted fields |
angee/base/impl.py |
implementation registries |
angee/base/transitions.py |
guarded state machine |
angee/base/refs.py |
canonical generic/MTI refs |
addons/angee/iam/models.py |
identity model |
addons/angee/iam/roles.py |
schema/role introspection |
| Файл | Зачем читать |
|---|---|
addons/angee/resources/entries.py |
manifest entries и dependency graph |
addons/angee/resources/loader.py |
xref/adoption/hash import semantics |
addons/angee/resources/managers.py |
validate/load/diff orchestration |
addons/angee/resources/grants.py |
declarative REBAC tuples |
angee/graphql/schema.py |
named schema composition |
angee/graphql/data/hasura.py |
generic Hasura resource |
angee/graphql/data/metadata.py |
backend/frontend IR |
angee/graphql/access.py, angee/graphql/writes.py |
GraphQL read/write authorization boundary |
angee/graphql/deletion.py |
guarded deletion |
angee/graphql/events.py, publishing.py, subscriptions.py |
realtime publication/subscription |
| Файл | Зачем читать |
|---|---|
angee/urls.py |
URL contribution fold |
angee/asgi.py |
protocol router/mounts/lifespan |
angee/tasks/celery.py, enqueue.py |
Celery integration и постановка задач |
angee/tasks/locks.py |
local/PostgreSQL locks |
| Файл | Зачем читать |
|---|---|
angee/web/app/bin/angee-web-codegen.mjs |
SDL/metadata→typed client→app.ts pipeline |
angee/web/app/src/define-addon.ts |
web addon contract |
angee/web/app/src/create-app.tsx |
React composition root |
angee/web/app/src/route-tree.tsx |
route composition |
angee/web/metadata/src/artifact.ts |
artifact validation |
angee/web/metadata/src/projection.ts |
metadata→Refine projection |
angee/web/refine/src/provider.ts |
Hasura data provider |
angee/web/refine/src/dialect/hooks.tsx |
authored aggregate/group/action hooks |
angee/web/ui/src/runtime/contracts.ts |
UI extension contracts |
angee/web/ui/src/views/* |
generic data views |
angee/web/ui/src/widgets/* |
field widget system |
| Файл | Зачем читать |
|---|---|
AGENTS.md |
constitution и ownership rules |
docs/stack.md |
external library owners |
tests/test_layering.py |
backend dependency direction |
angee/web/app/src/architecture-guardrails.test.ts |
frontend dependency direction |
pyproject.toml |
Python distribution/toolchain |
pnpm-workspace.yaml |
frontend workspace boundary |
Angee Django состоит не из одного «ядра» в традиционном смысле, а из четырёх взаимосвязанных машин:
- Composition machine — manifests, dependency graph, settings fold, source model compiler, permission merge и generated runtime.
- Permissioned data machine — Django ORM, REBAC managers, resources, migrations, validation, transactions, audit/history.
- Typed interface machine — named Strawberry schemas, Hasura dialect, metadata IR, actions, analytics, realtime и MCP.
- Rendered application machine — codegen, Refine providers, metadata projection, generic UI и composed addon React manifests.
Operator снаружи создаёт и запускает workspace/stack; Host выбирает root addons; base addon distribution предоставляет проверенные системные capabilities. Внутри всё держится на одном решении: декларации композируются заранее в один обычный runtime, а не патчат его на ходу.
Если свести архитектуру к одной строке:
Angee — это детерминированный compiler вертикальных Django/React addon’ов в единое permissioned, metadata-driven приложение для людей, сервисов и агентов.
Именно generated IR между Django и React, встроенный REBAC и строгие ownership/extension seams отличают его от обычного «Django project с reusable apps».