# Big picture — __APP_NAME__

> **What this app IS** — the durable architecture an agent should not have to
> re-derive. Generated from the module registry at scaffold time; keep it true
> as the app grows.
>
> **Trigger files** (any change here ⇒ update this doc in the same change):
> `modules.config.js`, `schema/sync.json`, `models/**`, `client/src/routes.jsx`.
>
> Active work: [`plan.md`](plan.md) · Bugs & ideas: [`backlog.md`](backlog.md)

Scaffolded `__GENERATED_AT__` with ai-builder `__AI_BUILDER_VERSION__` ·
korm-js `__KORM_VERSION__` · twreact-ui `__TWREACT_VERSION__` · db `__DB_DIALECT__`.

---

## Shape

```
Express API  ──  @dreamtree-org/korm-js   ──  __DB_DIALECT__
     │              (all data access)
     │
     └── POST /api/crud/:Model  ◄── @api registry ◄── Vite/React SPA
                                                       └── @dreamtree-org/twreact-ui
```

- **One registry drives four things.** `modules.config.js` is the source of truth
  for permissions, nav, routes, and the model→module map. Never author them apart.
- **`BaseModel.validate()` is the single enforcement gate** — not route middleware.
  `BaseTenantModel` adds branch scoping and row-level grant filtering.
- **Per-request tenant instance.** `middleware/tenantResolver.js` puts a
  tenant-bound korm/knex on `req.context`. The module singleton is boot/CLI only.
- **SPA, no SSR.** The client composes twreact-ui; theming flows through CSS
  variables (`client/src/theme/tokens.css`), never raw colors.

## Modules (`__MODULE_KEYS__`)

| Key | Label | Models | Actions | Row-level (`instanceScoped`) |
| --- | --- | --- | --- | --- |
__MODULE_ROWS__

## Models (__MODEL_COUNT__)

| Model | Table | Module | Base |
| --- | --- | --- | --- |
__MODEL_TABLE_ROWS__

## RBAC

`(module, action, scope)` — `scope ∈ own | branch | tenant | instance`, enforced
identically on the API and the client (`<Can>` / `useCan`). `user_permissions`
allow/deny overrides a role (deny wins). `resource_grants` gives row-level access
to the `instanceScoped` models above, default-deny.

Seeded by `npm run seed:rbac`, which reads `modules.config.js` — so adding a
module or action to the registry is what creates the permission.

## Adding to this doc

Record what a future session would otherwise have to rediscover: a new module
and why it exists, a non-obvious model relationship, an intentional deviation.
Do not record what the code already says plainly.

---

_Last reviewed: __GENERATED_AT__ · by: scaffold_
