Architecture
Contracts first, rendering second
The framework's value is not its components. It is the set of contracts every Ottili product agrees to: how a session resolves, how a company is proven, how a manifest declares a route, how a policy becomes a rendered state and how an action reaches the backend.
Bootstrap · SoT §10
One deterministic startup path
Every Foundation product boots the same way. The order is not a suggestion — rendering the shell before the company is proven is the bug the sequence exists to prevent.
Session resolve
The app asks Ottili Auth who the user is. No token, no bootstrap — the shell renders the unauthenticated state instead of a half-populated dashboard.
Company resolve
The company in the URL is validated against the verified token server-side. A slug in the path is a hint, never an authority.
Bootstrap payload
One call to /api/v1/foundation/bootstrap returns product manifests, navigation, widgets, commands, settings, capabilities, extensions, preferences, customization and the context revision.
Contribution merge
Product, extension and platform contributions are merged deterministically. Conflicts are resolved by declared precedence, not by import order.
Feature-state resolution
Permission, entitlement and rollout collapse into one feature state so the UI renders visible, hidden, locked, upgrade-required or degraded consistently.
Shell render
Only then does the shell mount, already themed by the company's published customization — no flash of the default brand.
One request, one revision number. When the revision changes — a company switch, a published branding change, an installed extension — caches invalidate together instead of drifting apart.
Unified API · /api/v1/foundation
32 routes carry the whole runtime
Foundation does not own a second backend. It speaks to the Unified API like every other Ottili consumer, through a generated client with response validation, cursor pagination, SSE and streaming uploads.
| Method | Route |
|---|---|
| GET | /api/v1/foundation/bootstrap |
| GET | /api/v1/foundation/products |
| GET | /api/v1/foundation/companies |
| GET | /api/v1/foundation/manifest-revisions |
| GET | /api/v1/foundation/navigation |
| GET | /api/v1/foundation/widgets |
| GET | /api/v1/foundation/commands |
| GET | /api/v1/foundation/settings |
| GET | /api/v1/foundation/capabilities |
| GET | /api/v1/foundation/extensions |
| GET | /api/v1/foundation/help/{page_id} |
| GET | /api/v1/foundation/translations/{locale} |
| GET | /api/v1/foundation/preferences |
| PATCH | /api/v1/foundation/preferences |
| GET | /api/v1/foundation/customization/effective |
| GET | /api/v1/foundation/customization/draft |
| PATCH | /api/v1/foundation/customization/draft |
| POST | /api/v1/foundation/customization/preview |
| POST | /api/v1/foundation/customization/publish |
| POST | /api/v1/foundation/customization/rollback |
| GET | /api/v1/foundation/customization/history |
| POST | /api/v1/foundation/customization/export |
| POST | /api/v1/foundation/customization/import |
| GET | /api/v1/foundation/dashboards/{dashboard_id} |
| PUT | /api/v1/foundation/dashboards/{dashboard_id} |
| GET | /api/v1/foundation/favorites |
| POST | /api/v1/foundation/favorites |
| DELETE | /api/v1/foundation/favorites/{favorite_id} |
| GET | /api/v1/foundation/recents |
| POST | /api/v1/foundation/recents |
| POST | /api/v1/foundation/actions/{action_id}/preflight |
| POST | /api/v1/foundation/actions/{action_id}/execute |
Product manifest · SoT §9
A product declares itself, it does not wire itself
Routes, navigation, widgets, commands, settings and capability configuration are declared. The runtime resolves them, so navigation, search, the palette and the Workbench all see the same truth.
Non-negotiables
Rules the framework enforces
| Rule | Why it exists |
|---|---|
| Company scope comes from the verified token | Inferring it from the first membership or a URL segment is how cross-tenant leaks happen. |
| Frontend permissions are never authoritative | They exist to render the right state. Every action is re-checked server-side before it executes. |
| localStorage is not a session or tenant authority | It is a convenience cache for preferences. Losing it must never change who you are or which company you see. |
| Product code never reads raw palette values | Semantic tokens are the only interface, which is precisely what makes deep white label possible. |
| Third-party backend code never runs in the Unified API process | Extensions get a sandboxed UI and scoped RPC, not a foothold in the platform runtime. |
| Audit records are produced server-side only | A compliance history a client can write is not a compliance history. |
Forbidden · SoT §42
Anti-patterns that fail review
Written down so they can be pointed at, rather than rediscovered in a post-mortem.
Do not
- localStorage as session or tenant authority
- Raw palette values in product code
- Product code reaching into another product's database
- Frontend permissions treated as authoritative
- Duplicating Console or Auth inside a product
- Unrestricted SQL or shell behind a customer-facing surface
- Circular dependencies between Foundation packages
- Copying the shell instead of consuming it