Ottili Foundation Lab

The conformance report for the Ottili Foundation Framework. Every scenario below is a real configuration of the shipped packages — nothing here is a mock or a screenshot. The application itself lives at /c/acme/dashboard.

Coverage

56
Exercisable
3
Partly exercisable
0
Not yet built
59
Total scenarios

Product accents (SoT §14.5)

Each swatch paints its label in the foreground the Foundation selects for that accent. LD3's green reaches only 3.77:1 against white, so it resolves to dark text rather than shipping an inaccessible button.

auth

#0B0B0F#ffffff (19.64:1)

console

#0B0B0F#ffffff (19.64:1)

hq

#7C3AED#ffffff (5.70:1)

ld3

#059669#111318 (4.93:1)

ai-platform

#1D4ED8#ffffff (6.70:1)

coder

#C2521B#ffffff (4.65:1)

cloud

#B91C1C#ffffff (6.47:1)

Density (SoT §14.11)

compact

topbar 48px · row 36px · field 32px

comfortable

topbar 56px · row 44px · field 36px

spacious

topbar 64px · row 52px · field 40px

Feature states (SoT §13.2)

All nine states, each explaining itself rather than rendering a silent disabled control.

Available

available

Read only

read_only

Locked by plan

locked_by_plan

Missing permission

missing_permission

Missing connection

missing_connection

Coming soon

coming_soon

Disabled by policy

disabled_by_policy

Degraded (retryable)

degraded

Unavailable

unavailable

Scenarios

Light, dark and system themes

Every semantic token rendered in all three modes, including high contrast.

Exercisable §14.3, §14.4, §14.17

All five product accents

Each accent with its automatically-selected button foreground. LD3's green resolves to dark text because it only reaches 3.77:1 against white.

Exercisable §14.5 vs §14.17

Compact, comfortable and spacious

Topbar, row, field, button and sidebar metrics at each density.

Exercisable §14.11

All six breakpoints

Shell layout from xs through 2xl, including the mobile navigation drawer.

Exercisable §16.2, §16.4

Keyboard and screen-reader paths

Skip links, focus return, roving sidebar navigation, live-region announcements.

Exercisable §14.17

Session lifecycle

Every §12.3 transition, including that a blocked write is never replayed.

Exercisable §12.3, §12.4

Enterprise sign-in states and draft recovery

§12.6's twelve states are not twelve ways of saying "sign in again". /states steps through all of them and shows the split that decides whether a person gets help: MFA and step-up the user clears themselves, an IP allowlist or device approval only an administrator can, and a removed membership nobody can — so it offers a company switch rather than a retry, and drops the deep link, because returning someone to a page they no longer have access to repeats the refusal. §12.4's draft rule rides along: work is offered back, never resubmitted, discarded when the account changed, when the company changed, or across a password reset.

Exercisable §12.4, §12.6

Company switch

Route carry-over, record-route fallback, unsaved-changes guard and the post-switch invalidation effects.

Exercisable §11.3

Account switch

Routed through Auth with select_account — not a company switch.

Exercisable §11.4

Locked and suspended products

A locked product never links into the product itself.

Exercisable §11.5

All nine feature states

Every §13.2 state rendered, each explaining itself.

Exercisable §13.2, §14.16

Navigation customization

Company, role and user layers — including that customization can never reveal an entry the actor lacks permission for.

Exercisable §15.4, §18.7

Degraded capability states

Each shared capability reported degraded, with the honest UI that follows.

Exercisable §32.4

Metric, and what its colour means

§47.5's Metric existed as stylesheet classes in the design system with no component to use them, so the Lab wrote its own markup against `.ott-widget__metric` — the one §47 component any app had hand-rolled. Promoting it fixed the rule those styles encoded: `[data-direction="up"]` was green and `[data-direction="down"]` red, unconditionally. For churn, error rate, cost per order or open incidents a fall is the win, so that rule paints a genuinely good number red — identically in every product, because the design system decides. Direction and sentiment are now separate inputs, and the dashboard's open-orders widget is the case in point: −4 open orders now reads as good news. The change is also announced in words, since colour and an arrow are the same information twice and neither survives a screen reader.

Exercisable §47.5, §14.17

§16.2 breakpoints, honestly applied

§16.2's table already existed in `tokens.ts` — with no consumers. The stylesheets used three hand-picked numbers instead: `1024` and `720` in shell.css, `760` in tokens.css. `max-width: 1024px` includes 1024, which §16.2 puts in `lg`, so at exactly the width a 1024x768 tablet reports in landscape the sidebar became a fixed drawer while the layout still reserved desktop space for it. All three are now the chapter's boundaries, and `__tests__/breakpoints.test.ts` reads the shipped CSS so the constant and the stylesheet cannot drift again. /states lists the ranges and resolves probe widths through the contract.

Exercisable §16.2

Container queries for reusable components

§16.2 asks for container queries by name, and the distinction is real: a capability surface can be mounted in a page, a workbench panel, an inspector rail or a widget slot, and one inside a 360px inspector on a 2560px monitor needs the narrow layout that a viewport query would deny it. The shared capability surface is now a container and lays out narrow by its own width. The shell chrome deliberately stays on media queries — `container-type: inline-size` implies containment, which would reposition the fixed drawer and scrim relative to the container instead of the viewport, and a drawer really does overlay the viewport.

Exercisable §16.2

Mobile baseline and forced viewport

§16.5 lists what must work on a phone, and `__tests__/mobile-baseline.test.ts` checks each entry has a route — built for the owner role, since a permission-filtered navigation would make the check pass by hiding things. Reachability is the floor rather than the whole requirement; §16.4's "keine versteckten Kernaktionen" is not something a test can settle. The Lab's force-mobile scenario now sets the design system's own `data-ott-viewport` attribute instead of re-implementing the drawer CSS, which it did until this wave — meaning the reference app was demonstrating a layout the framework did not ship.

Exercisable §16.3, §16.4, §16.5

ICU messages, plural rules and locale-aware formatting

§17.2's runtime, which was entirely absent. /localization renders every example through the shipped formatter for whichever locale is selected — switch to `ru` and the plural table changes *category*, which is the thing a screenshot of German copy cannot prove. Plural categories come from `Intl.PluralRules` rather than `count === 1`, because Russian is one of §17.1's five locales and has four of them. Money keeps the company's currency in every locale while the locale decides the shape: inferring the currency from the locale gives a number that is correctly formatted and factually wrong.

Exercisable §17.1, §17.2

Missing keys as release errors

§17.2 says missing keys are release errors and forbids raw keys in production — one rule from two sides, because a translator that falls back to the key turns a missed translation into a defect nobody notices. `createTranslator` throws under the development policy and, in production, degrades to a humanised label rather than the identifier, reporting every miss. The enforcement is a build-time gate: `__tests__/i18n-catalogues.test.ts` checks all five locales against English, and a gap fails. An extension catalogue reaching outside its namespace is refused rather than silently trimmed, because the catalogue is merged once per locale — one extension defining `actions.save` would change the word on every Save button in the product.

Exercisable §17.2

Locale resolution order

§17.3's five steps encoded once instead of reassembled at each call site — the previous helper took an unordered candidate array, which is correct everywhere or wrong in one place with nothing to say which. The ordering that surprises is on the page: an account-wide preference loses to the company default, while an explicit user preference beats it, because those two are different things. `de-AT` resolves to `de` rather than to English, which is what keeps "weitere Locales ohne Rebuild" true.

Exercisable §17.3

Telemetry emission, without a transport

§32.1's twelve event names, the safety check and the route templater existed for several waves with nothing calling them. `TelemetrySink` makes them reachable and deliberately ships no transport: a console emitter would let the framework claim telemetry while producing nothing anybody reads. What is worth having before a collector is the policy — `assertTelemetrySafe` enforced at the emit boundary rather than at each call site, batching, flush on page-hide (`visibilitychange`, not `unload`, which does not fire when a mobile browser kills a tab), per-*trace* sampling so a trace is never shredded into fragments, and a failed flush that is counted rather than retried. /observability emits real events through it and can take the collector down.

Exercisable §32.1, §32.2

Trace context and telemetry safety

§32.2 lists seven fields every request must carry and the transport was sending three. The four it was missing are the four that turn a report into a diagnosis: which attempt failed, which server span served it, which product asked, and which company context revision it ran against — the last being the one that answers "it worked this morning". /observability shows all seven as headers, and exercises the chapter's closing rule: a route is templated before it is recorded, and an event carrying a customer name or a search term is refused at runtime, because that mistake is invisible until somebody reads the telemetry store.

Exercisable §32.1, §32.2

Performance and bundle budgets

§32.3's six timing targets, checked and reported with how far over rather than just that something is slow. A breach is an engineering signal, not an error shown to the customer — they already lived through the slow interaction. Product and extension bundle budgets stay separate because they fail differently: a product over budget is its own team's decision, while an extension over budget slows a page belonging to somebody who never installed it.

Exercisable §32.3

Circuit breaker

§28.4's item, and the join §32.4 was built without: the degradation runtime could say what to do when Files is down while nothing said *when* Files is down. The shipped `FoundationClient` now keeps failure accounting per service and refuses a request locally once a breaker is open, so a call to a failing backend fails fast instead of hanging for the whole timeout — doing that on every retry is how one unhealthy service becomes a page that never finishes loading. /observability drives a real client against a stub transport, so what is demonstrated is the integration rather than the reducer, and shows the degradation plan that follows. Two rules decide whether a breaker helps or hurts, and both are exercisable: a 4xx does not count against the service, because otherwise one person repeatedly hitting a permission boundary trips it for everybody; and it trips on a failure *ratio* rather than a streak, because alternating failure and success never produces two in a row and would leave a service erroring on half its requests running forever. Half-open lets exactly one probe through.

Exercisable §28.4

Declared and observed health as one answer

The capability manifest carries a `health` the *server* declares; a circuit breaker carries what *this client* has observed. Neither subsumes the other, and two health models would be the worst outcome — a surface reading one while the status bar reads the other. `resolveCapability` takes the worse of the two, which is right in both directions: a network partition or a bad edge node looks perfectly healthy from the server while the person at the screen cannot use it, and a replica returning stale reads succeeds on every request and is still wrong. Observed degradation reaches the capability's `state`, not just a badge — left `available`, a Files surface renders "No files yet" for a backend it simply cannot reach, which is the most misleading thing a data surface can say.

Exercisable §28.4, §32.4

Maintenance windows

Existed only as a notification category, which is a different thing: a notification is read later by whoever opens the inbox, and planned downtime has to reach the person about to start a two-hour import now. /observability shows the banner at five moments across one window. Two of them carry the argument — it warns *before* the window opens, because telling somebody the system is unavailable while they are already staring at a failed save is a caption rather than a warning; and it has a word for overrunning that carries no new estimate, because a banner still reading "back by 14:00" at 14:30 was a promise and is visibly false, and a second guess only spends the trust the first one lost.

Exercisable §28.4

Per-service degraded mode

§32.4 gives five services five different answers, and /observability drives them from the scenario switcher's own backend setting. AI is hidden because a visibly failing assistant is worse company than an absent one; Files is reduced rather than removed because a vanished file panel reads as deleted files; Notifications falls back to a deliberately slow poll so every open tab does not join the incident; Search hands its job to navigation rather than stranding people; Flows keeps history readable and refuses to start a run whose outcome could not be reported. None of them takes the product down, which is the point of the chapter.

Exercisable §32.4

Command palette

Cmd/Ctrl+K across the §20.2 search domains.

Exercisable §20

Workbench command execution

The full §21.5 pipeline, including that medium risk and above never executes without an explicit decision.

Exercisable §21.5, §21.7

Notification inbox and transport

Grouping, optimistic read with rollback, and the honest SSE→polling fallback.

Exercisable §23

White-label publish gates

Contrast, font, asset and injection gates — publish blocked, with reasons.

Exercisable §18.8, §48

Dashboard grid

Drag, resize, keyboard move, collision handling and the responsive fallback.

Exercisable §22.3

Widget runtime and legacy import

Widget states plus an import that reports what it cannot migrate.

Exercisable §22.2, §22.7

Settings composition

Personal settings with effective-value explanation and reset-to-inherited.

Exercisable §24

Ottili AI surface

Real SSE streaming, incremental rendering, Stop and Regenerate, tool calls with their real execution and approval states, and citations.

Exercisable §25.1

Ottili Files surface

File browser against the real storage service. Upload is hidden, not broken, when the actor cannot write.

Exercisable §25.2

Ottili Flows canvas

Canvas, node palette, insertion, selection, edges, zoom and validation — fully keyboard operable. Node types match the real engine.

Exercisable §25.3

Help surface

Page-aware help from the route registry, documentation search against the real docs service, shortcut reference, status link and support ID.

Exercisable §25.7

Approvals inbox

Approval inbox with the no-self-approval and view-only states (§13.3).

Exercisable §25.4

Activity timeline

Read-only history; audit is produced server-side and is never client-mutable.

Exercisable §25.5

Task Center

Background work with determinate progress only where the job reports it.

Exercisable §25.6

Extension runtime

Manifest validation, compatibility, deterministic activation, conflict rollback, lifecycle and the kill switch. /settings/extensions runs a real activation pass over eight demonstration extensions.

Exercisable §27

Sandboxed host and RPC bridge

The host mounts a sandboxed iframe without allow-same-origin and answers postMessage through the permission-checked bridge. /c/{slug}/apps/{extensionId} mounts it and logs every bridge call.

Partly exercisable §27.5

No third-party extension origin serves a working iframe here, so the Lab exercises the mount, the load timeout and the failure states — not a successful round trip. The bridge itself is covered by 40 unit tests including forged origins and borrowed extension ids.

Version blocklist

§33.4's ninth control, and not the kill switch under another name: the kill switch stops an extension, the blocklist stops *versions*. /settings/extensions applies Ottili's published list to what is installed — Paused Connector is blocked with a fix, so the remedy is an update and the company's own Enable is refused until it happens; Quarantined Tools is withdrawn with no fix, so the remedy is removal. A third entry is malformed on purpose and the validator drops it before it is applied, because a range that matches nothing leaves a vulnerable version running while the record says it was handled.

Exercisable §33.4

Update policy and data retention

§34.1's Console list named both and neither had runtime — and retention was already making a promise, since `describeExtensionState` tells the customer their data is kept until the period ends. /settings/extensions now answers with a date and says what a reinstall would actually get back. The update table shows the two cases where the platform decides instead of the company: a security fix is applied over a pin and says it overrode the policy, and an automatic update stops at a major version rather than breaking a working integration unattended.

Exercisable §34.1

Permission-aware download

§33.5 asks downloading separately from previewing, because rendering a document and handing over its bytes are different acts — once the file leaves, nothing Ottili does governs it. Each row in /files resolves access against the file's *owning* root before offering the link, so a file in a nested root the actor cannot see offers no download; a still-processing file offers none either, and an export policy that blocks one is named rather than hinted at.

Exercisable §33.5

Version history and rollback

§18.8's flow ends `publish → active → rollback` and the editor could only publish. /settings/branding lists every version with what it changed, and rolling back publishes a *new* version rather than erasing the one being replaced. One fixture version was published under the old rules and is refused today, because a rolled-back document is re-validated against the gates as they stand now.

Exercisable §18.8

Panels and customer-safe logs

§21.2's seven core panels plus two Lab ones, with permission gating and arrow-key cycling. The Logs panel is the one that matters: the Lab hands it four lines that must never reach a customer — a private IP, an internal hostname, a stack frame and a connection string — and the panel withholds all four and says how many without naming them.

Exercisable §21.2, §10.5, §33.5

Favorites, recents and navigation badges

§19.3/§19.4 in the sidebar: favorites are company-scoped so another company's pinned page is never rendered, the current page can be pinned and unpinned, and recents are pruned, scoped and **re-checked against permission on read** — switch to viewer and the Permissions entry disappears rather than showing as locked. The approvals badge shows nothing at all once the backend is degraded, because a zero would claim nothing is waiting.

Exercisable §19.1, §19.3, §19.4

Scoped views, upload safety and preview rules

§25.2's governing rule made real: virtual roots decide every question, a file in a nested root the actor cannot see is not rendered at all, a read-only root is read-only for everyone, an SVG is downloaded rather than previewed (§33), a share may never exceed the sharer's own access, and a filename is validated before it reaches storage. /files mounts three roots covering each case.

Exercisable §25.2

Job outcomes, estimates and actions

The §25.6 runtime: `partial` as its own outcome rather than a success or a failure, an estimate refused when it would be guesswork, and cancel/retry gated on the job's own cancellable/retryable rather than on its state word. /jobs covers all six states.

Exercisable §25.6

Audit as compliance history

§25.5's distinction made real: `assertServerAudit` refuses anything the client could have produced, a missing sequence is reported rather than filled, correlated rows are grouped into the one action that caused them, and export is requested from the server rather than assembled here. /activity renders it below the activity list.

Exercisable §25.5

Chains, SLA, delegation and bulk decisions

The §25.4 runtime: SLA with an at-risk band before the breach, chain position, delegation recording whose authority was used, and a bulk plan that says what will *not* happen before the user confirms. /approvals renders four requests covering decidable, self-raised, step-up and delegated.

Exercisable §25.4

Widget data providers

The §22.5 registry with cache, cancellation, retry, partial data, dependency health and telemetry. /dashboard runs four real providers covering complete data, partial data with a named degraded upstream, a hard failure whose internal message never reaches the customer, and a provider nobody registered.

Exercisable §22.5

Company-scoped event bus

Validation before dispatch, company scoping at publish, wildcard subscriptions and failure isolation. /realtime publishes through the real FoundationEventBus, including events it must refuse.

Exercisable §31.1, §31.2, §46

Missed-event recovery

Revision ledger, gap detection and the resync plan. /realtime drops the stream, publishes while it is down, and shows that reconnecting does not clear `resyncRequired` until revisions have actually been compared.

Exercisable §31.3

PWA adapter

Install-prompt capture, periodic background sync, and the §29.6 offline cache policy. The Lab is itself an installable PWA: /manifest.webmanifest and /sw.js are generated from one PwaProductConfig, and /distribution registers that worker for real and reads back what the browser actually cached.

Exercisable §29.3

Tauri adapter

Keychain-backed secure storage, scoped file system, deep links, updater and system-browser auth, over a host-injected bridge. /distribution shows it both with and without that bridge.

Partly exercisable §29.4

There is no packaged Tauri build, so the bridge is supplied by the Lab rather than by a real host. What is genuinely exercised is the adapter's behaviour in both cases — including reporting every native capability unsupported when the bridge is absent, which is what a browser tab sees.

Electron adapter

The same contract over Chromium/Node, sharing the desktop implementation with Tauri.

Partly exercisable §29.5

No packaged Electron build; the host bridge is supplied by the Lab, as for Tauri.