This project has been created as part of the 42 curriculum by dlesieur, serjimen, danfern3, vjan-nie, shashemi.
Track Binocle is a collaborative workspace built on top of our own self-hostable Backend-as-a-Service. Users sign up on a public site, land in osionos — a Notion-style block editor with real-time collaboration, chat and video rooms — and can connect their Gmail and Google Calendar. Every one of those apps is served by grobase, a generic backend that the team wrote in Go, Rust and TypeScript, and which contains no application-specific code.
- Description
- Instructions
- Team Information
- Project Management
- Technical Stack
- Database Schema
- Features List
- Modules
- Individual Contributions
- Resources
- Known limitations
- Further documentation
Almost every web project rebuilds the same backend before it can start on the product: authentication, user management, CRUD, access control, file upload, email, logs. Our goal was to turn that repeated work into infrastructure — one backend, any frontend, zero per-project server code — and to prove it by building a real product on it.
The project therefore has two halves:
- grobase (roughly 70% of the work) — a Backend-as-a-Service. An application is declared in a JSON contract (its database, isolation strategy, roles, permissions and API keys); a generic provisioner creates everything from that file. Routes carry no domain logic: a request names the table it targets, and access rules are stored as data, not code.
- The products that run on it — a marketing and authentication site, the osionos editor, and mail and calendar integrations.
| Product | Tech | What it is |
|---|---|---|
| opposite-osiris | Astro | Public site: landing page, sign-up / sign-in, legal pages. The entry point. |
| osionos | React + Vite | The block editor: pages, databases-as-blocks, real-time collaboration, chat, video rooms. The flagship. |
| React + Vite | Gmail integration over Google OAuth | |
| calendar | React + Vite | Google Calendar integration over Google OAuth |
These four products are supported by four platform services (not applications in their own right): the auth-gateway (Go, sessions), the osionos-bridge (turns a site session into an editor session), Kong (the single API gateway in front of grobase) and LiveKit (the WebRTC media server for video rooms).
- Contract-driven backend. A whole application — database, isolation, roles, keys, frontend
configuration — is one JSON file.
POST /v1/tenants/me/appscreates a new isolated application with its own database and scoped API key. - Per-request owner isolation. The Rust data plane scopes every query to its owner on every
request; permissions are rows (ABAC/RBAC), not
ifstatements. - One API over many databases. PostgreSQL, MySQL, MongoDB, Redis, SQLite, MSSQL, DynamoDB and even remote HTTP APIs are queried through the same envelope and the same API key.
- Collaborative editor. Block-based pages, a database block with table / board / calendar views, real-time co-editing, channels and direct messages, video rooms.
- Single-command deployment.
make allbuilds and starts the whole stack in Docker, serves every frontend over trusted local HTTPS, and health-checks it.
| Requirement | Detail | Source |
|---|---|---|
| Linux host | Tested on Debian 13; the CA-trust step uses sudo + update-ca-certificates |
infrastructure/makes/certs.mk |
| Docker Engine ≥ 25 + Compose v2 | Healthchecks use start_interval, which needs Docker 25. Tested with Docker 29.8. |
docker-compose.yml:10 |
| RAM ≥ 8 GB | Measured: the VM peaked at ~6.5 GB of 8 GB during make all |
measured, 2026-09-30 |
| Disk ≥ 30 GB free for Docker | Measured on the dev VM with the stack running (docker system df, 2026-10-01): images 13.5 GB, build cache 9.5 GB, volumes 1.4 GB |
measured |
| GNU Make, git, curl, openssl | Make drives everything; curl runs the health check; openssl generates the local secrets | infrastructure/makes/app.mk, scripts/gen-local-env.sh:68 |
| A GitHub SSH key | .gitmodules uses git@github.com: URLs, so the recursive clone needs SSH access to GitHub |
.gitmodules |
| Google Chrome | Current stable — the browser the project is evaluated on | |
certutil (optional) |
Lets make all import the local CA into Chrome's NSS store; otherwise import it by hand (see Access) |
infrastructure/makes/certs.mk:43 |
Nothing else is installed on the host — no Node, npm, Go or Cargo. Everything builds and runs in containers.
make all asks for one sudo password the first time: it copies the local CA into the system
trust store. This is intended (certs-trust-local in infrastructure/makes/certs.mk).
Ports bound on the host (from docker-compose.yml, all on 127.0.0.1 except inside a
QEMU/VirtualBox NAT VM, where infrastructure/scripts/detect-bind-addr.sh binds 0.0.0.0 so the
host can reach the guest): 443, 3001, 3002, 3003, 3007 (always loopback), 4000, 4100, 4200, 4322,
4323, 7880, 7881, 8444, 8787 and UDP 50000–50060. grobase adds Kong on 127.0.0.1:8000
(literal loopback in its orchestrators/compose/base/gateway.yml).
This is the path rehearsed on a clean machine. It checks out the release tag and does not
follow submodule branch tips (SKIP_SYNC=1, see sync-submodules-soft in
infrastructure/makes/pipeline.mk).
git clone --recursive git@github.com:Univers42/groot.git
cd groot
git checkout "$(git describe --tags --abbrev=0 origin/main)"
git submodule update --init --recursive
make all SKIP_SYNC=1The git checkout line checks out the latest release tag on main; git tag -l 'v*' lists them.
Requirements, the VM + host-browser case, the verify script (scripts/verify/fresh-bringup.sh) and
troubleshooting: wiki/runbooks/fresh-machine.md.
Measured time from a cold machine: 9–17 minutes.
No .env needs to be written by hand. With no vault key present, grobase generates its own
secrets into apps/grobase/.env, and env-local-ensure derives the root ./.env.local from it,
including random OSIONOS_* secrets and the demo account's DEMO_LOGIN_PASSWORD
(scripts/gen-local-env.sh). An older .env.local without it gets one minted on the next
make all, which says so by name.
make all runs, in order (infrastructure/makes/pipeline.mk): submodule sync → secrets →
local TLS certificates → trust the local CA → grobase backend → derive .env.local → restore
data if the engines are empty → set the demo account's password → SQL migrations → frontends → health check → URL list.
git checkout develop
make allWithout SKIP_SYNC=1, make all first fast-forwards every submodule to the tip of its tracked
branch (sync-submodules-soft), so you build the newest code rather than the pinned commits.
It skips dirty submodules and never blocks the pipeline.
The exception is any submodule that .gitmodules marks update = none, today apps/graph_render. Its
gitlink is the build input, so neither mode moves it:
git submodule update --recursiveskips it, which also keeps its SSH-only nestedSciGraphsout;- the sync skips it and everything under it;
frontends-uprunsmake submodules-pinned(scripts/pin-submodules.sh), which checks it out at the recorded commit if it is absent, and fails if it is anywhere else or has edits to tracked files.
What the check does not do:
- It does not move a checked-out copy. After a pointer bump, run the fix it prints:
git submodule update --checkout -- apps/graph_render. - It ignores nested submodules and untracked files.
- It reads
.gitmodules, so a local.git/configupdateoverride is not seen. - It cannot guard a raw
docker compose build. - It is not offline-safe: the first
make allafter the pin lands clones graph_render over HTTPS, and fails if the network is down. A refused connection fails at once. A black-holed one waits for the TCP connect timeout (~1.5 min per attempt, and git retries once) before it fails.
The osionos pointer's own branch = main override is out of scope here (tracked with the pin audit).
| Command | What it proves |
|---|---|
make healthcheck |
grobase auth, bridge, osionos, website (and HTTP→HTTPS redirect), auth gateway, drawnosaurus board list (infrastructure/makes/app.mk) |
make e2e |
Playwright smoke in a pinned container against the running stack: DW3 Whiteboard tab, DW8 two browsers on one board, DW9 the board inside osionos, DW10 Mail/Calendar (tests/e2e/README.md) |
make mail-up calendar-up |
Start the opt-in Mail and Calendar apps. Without them DW10 skips and prints the reason; it never passes silently. |
make e2e-clean |
Soft-delete the e2e-* boards the smoke leaves behind |
| Service | URL |
|---|---|
| opposite-osiris (start here) | https://localhost:4322 |
| osionos editor | https://localhost:3001 |
| Whiteboard (drawnosaurus) | https://localhost:3007 |
| mail (opt-in) | https://localhost:3002 |
| calendar (opt-in) | https://localhost:3003 |
| osionos-bridge | https://localhost:4000 |
| auth-gateway | https://localhost:8787/api/auth |
| grobase API (TLS edge) | https://localhost:8444 |
| grobase API gateway (Kong, plain HTTP, loopback only) | http://127.0.0.1:8000 |
| LiveKit | ws://127.0.0.1:7880 |
make showcase prints the list of what is actually running.
Every frontend is served with a certificate from the local CA
apps/grobase/certs/track-binocle-local-ca.pem. make all trusts it in the system store and, if
certutil is installed, in Chrome's NSS store (~/.pki/nssdb). If it is not trusted, Chrome shows
a warning on each port — and the Whiteboard tab in osionos stays blank, because the embedded
:3007 iframe is refused silently. Fix: import the CA in Chrome (Settings → Privacy and security →
Security → Manage certificates → Authorities → Import), or run make certs-trust.
- Open
https://localhost:4322, create an account, and continue to osionos. Or sign in with the seeded demo account:make demo-loginprints its email and password. The password is generated per machine and kept only in./.env.local(git-ignored); it is never committed. It applies to the local stack only, not to production. - In osionos, open View → Whiteboard, click + New Board, give it a title.
- Open the same board URL in a second window and draw: the strokes appear live in the other
window (the e2e smoke asserts within 5 s —
tests/e2e/specs/dw8-realtime.spec.ts).
Honest limitations of this demo: board editing is loopback-only and has no per-user
authorization yet — drawnosaurus is bound to 127.0.0.1:3007 for exactly that reason
(docker-compose.yml:27-32). We make no end-to-end-encryption claim: traffic is TLS to the
local proxy, nothing more.
- Real credentials live only in git-ignored
.env*files; the committed template is.env.example, which documents every key as required, recommended or optional. - Optional integrations turn themselves off cleanly when left blank:
LIVEKIT_API_KEY/LIVEKIT_API_SECRET— default to a local dev value; video rooms still work.GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRETinapps/mail/.env.localandapps/calendar/.env.local— without them, the mail and calendar OAuth flows are disabled.SONAR_TOK,FLY_API_TOKEN,DOCKER_PAT— CI and operations only.
Inside the team, secrets are shared through vault42, our zero-knowledge secret store, via the
42ctl CLI. A machine that holds a vault identity in ~/.config/42ctl/ pulls the whole .env
tree automatically during make all (or with make vault42-pull-all APPLY=1). A new identity is
created with 42ctl keys init and enrolled with an invitation from the vault's operator.
Details: DATA-MIGRATION.md.
| Command | What it does |
|---|---|
make all |
Full lifecycle: certs → backend → frontends → health check → URLs |
make healthcheck |
Probe backend, site, editor, bridge, auth gateway and whiteboard |
make showcase |
Print the URL list for what is actually running |
make pulls |
Update all submodules |
make -C apps/grobase editions |
List the available backend shapes |
make all GROBASE_EDITION=full |
Bring up everything, including extra engines and monitoring |
docker compose ps |
Service status |
docker compose logs -f <service> |
Follow one service's logs |
docker compose down |
Stop the frontends (data is kept) |
Backend editions. make all starts grobase in the devlean edition
(GROBASE_EDITION ?= devlean, infrastructure/makes/grobase.mk:48; listed in grobase's
orchestrators/makes/00-config.mk:68): the core engines (PostgreSQL, MySQL, MongoDB, Redis,
MinIO), the full application / control / data planes and realtime — without the heavy extra
engines and without the monitoring stack. GROBASE_EDITION=migrate adds every snapshot engine;
GROBASE_EDITION=full turns everything on. Some module demonstrations need a larger edition or a
feature flag — see Modules.
Roles as recorded in the team's working notes (wiki/project/03-team.md):
| Member | Login | Role(s) | Responsibilities |
|---|---|---|---|
| Dylan Lesieur | dlesieur |
Product Owner · Developer | Product vision, backlog and priorities; validates completed work |
| Daniel Fernández | danfern3 |
Tech Lead · Product Owner | Architecture, stack decisions, code quality, reviews |
| Sergio Jiménez | serjimen |
Project Manager · Developer | Frontend, secrets management, deployment |
| Vadim Jan | vjan-nie |
Project Manager · Developer | Planning, tracking, communication, unblocking |
shashemi |
Developer | Features and modules |
What the repository records:
- Planning: milestones (M1 hardening, M2 federation, M3 coherence, M4 observability,
M5 security, M11 external-app integration) in
archive/wiki-2026-09/todo/. - Tools: GitHub — pull requests on
Univers42/groot, Dependabot for dependency updates (.github/dependabot.yml), GitHub Actions for CI (.github/workflows/). - Branch model: work lands on short-lived
feat/,fix/,docs/,test/andchore/branches, merged intodevelopby pull request;developis merged intomainby pull request (git log --merges --format=%s | grep 'pull request'). - Code workflow: submodule-based monorepo — changes are committed inside a submodule first,
then the root records the new commit. Numbered verification gates
(
apps/grobase/scripts/verify/run-gate-battery.sh) are the team's definition of "done": grobase's CI runs named gates on pull requests and the full--enterprisebattery, with a nightly schedule (grobase.github/workflows/ci.yml).
| Technology | Used in | Why |
|---|---|---|
| Astro 6 + Sass | opposite-osiris | Mostly static marketing content: Astro ships near-zero JavaScript by default and pre-renders pages, which keeps the public site fast. |
| React 19 + Vite 6 + TypeScript | osionos, mail, calendar | A block editor is a heavily interactive UI; React's component model fits, and Vite gives fast builds. |
| Tailwind CSS 4, Radix UI, lucide-react | osionos | Utility-first styling framework plus accessible, unstyled primitives for menus and popovers. |
| Zustand | osionos | Small, explicit client state store. |
| Zod | osionos | Schema validation of user input on the client side. |
| livekit-client | osionos | WebRTC video rooms. |
| Playwright | osionos | End-to-end browser tests. |
| Plain CSS | mail, calendar | Each has a single hand-written src/styles.css; no framework. |
grobase is split into planes — groups of responsibilities that can be sized and replaced independently — each written in the language that suits it.
| Plane | Technology | Role | Why this language |
|---|---|---|---|
| Control plane | Go | Provisioning, tenancy, identity (POST /v1/keys/verify turns an API key into an identity) |
Long-running, concurrent I/O; a provisioner needs to be correct and readable, not fast. |
| Data plane | Rust | Executes every query, applies per-owner scoping and ABAC locally | The hot path: predictable latency with no GC pauses, and memory safety where a bug would be a cross-tenant data leak. |
| Realtime plane | Rust | Turns data changes into events; WebSocket subscriptions | Same reasons; pluggable event bus. |
| Application plane | TypeScript / NestJS | Query and storage routers, schema, session, permissions, email, GDPR services | The original backend, progressively replaced by the Go and Rust planes. |
| Auth | GoTrue + Go auth-gateway | Sign-up, sign-in, sessions, email OTP | Battle-tested auth server rather than a hand-rolled one. |
| Gateway | Kong | The only public entry to the backend; key auth and rate limiting | Keeps every internal service off the public network. |
| Media | LiveKit | WebRTC SFU for video rooms |
- PostgreSQL is the primary database: the control-plane registries (tenants, roles, policies, API keys) and all application data (see Database Schema). Why: row-level security (RLS), JSONB for block content and flexible properties, strong transactional guarantees, and a mature ecosystem. RLS is what makes per-owner isolation enforceable inside the database itself, not only in application code.
- MySQL, MongoDB, Redis, MinIO (object storage) run in the default edition; SQLite, MSSQL, DynamoDB and remote HTTP APIs are supported through the same data-plane interface. This is a deliberate feature of the platform (one API over many engines), not a need of osionos itself.
- Docker / Docker Compose — every component runs in a container; one
make all. - A local certificate authority + TLS proxy — all frontends served over trusted HTTPS locally.
- vault42 / 42ctl — our own zero-knowledge secret store and CLI (separate repositories).
- CI security scanning — on every pull request,
.github/workflows/mini-baas-security.ymlruns Semgrep (SAST),npm/pnpm audit, Snyk (when a token is set) and Trivy; Dependabot opens dependency updates.sonar-project.propertiesandrenovate.jsonare present, but no workflow in this repository runs them.
- Why build a BaaS instead of a single backend? Because the same backend pieces are rebuilt for every project. We moved everything that varies between projects — routes, schemas, access rules, configuration — from code into data, so a new application is a contract, not a server.
- Why three languages? Each plane has a different job. Go for boring, correct orchestration; Rust where performance and memory safety protect tenant isolation; TypeScript where it already existed. The data plane was migrated from TypeScript to Rust behind a per-request switch with a shadow mode (Rust computes and discards results for comparison), so the migration needed no downtime and could be rolled back without a deployment.
- Why per-request isolation? Scoping on every request (instead of per connection) means a pooled connection can never carry one user's permissions into another user's query.
The schema lives in two places:
- Application schema — idempotent SQL migrations in
models/(*-migration.sql), applied to grobase's PostgreSQL bymake allthroughscripts/apply-models-migrations.sh. That runner deliberately skips the files written for the auth gateway's own database (user.sql,gdpr-migration.sql,auth-security-migration.sql,rls-hardening-migration.sql,seeds.sql); its header explains why. - Platform schema — grobase's control-plane registries (tenants, mounts, roles, policies,
API keys), created by grobase's own migrations, plus per-application databases created at
runtime from each contract (e.g.
infra/config/contracts/website.schema.sqlin grobase).
erDiagram
osionos_workspaces ||--o{ osionos_workspace_members : "has"
osionos_workspaces ||--o{ osionos_pages : "contains"
osionos_pages ||--o{ osionos_pages : "parent of"
osionos_pages ||--o{ osionos_page_comments : "has"
osionos_pages ||--o{ osionos_page_favorites : "favourited in"
osionos_workspaces ||--o{ osionos_channels : "has"
osionos_channels ||--o{ osionos_channel_members : "has"
osionos_channels ||--o{ osionos_messages : "contains"
osionos_messages ||--o{ osionos_message_reactions : "has"
osionos_messages ||--o{ osionos_message_attachments : "has"
osionos_workspaces {
uuid id PK
uuid owner_id
text name
text slug UK
jsonb settings
timestamptz created_at
}
osionos_workspace_members {
uuid workspace_id PK,FK
uuid user_id PK
text role "owner | admin | editor | viewer"
text_array permissions
}
osionos_pages {
uuid id PK
uuid workspace_id FK
uuid parent_page_id FK
uuid owner_id
text title
text visibility "private | shared | public"
jsonb properties
jsonb content "the blocks"
boolean is_template
}
osionos_channels {
uuid id PK
uuid workspace_id FK
text kind "text | dm | voice | video"
text name
boolean is_private
jsonb abac
text dm_key UK
}
osionos_messages {
uuid id PK
uuid channel_id FK
uuid author_id
text content
jsonb attachments
timestamptz edited_at
timestamptz deleted_at
}
Other table groups, by migration file:
| Area | Tables | Migration |
|---|---|---|
| Users & sessions (auth gateway DB) | users, sessions, user_activities, user_tokens |
user.sql |
| Auth security & GDPR (auth gateway DB) | auth_audit_events, gdpr_requests, user_consents, newsletter_optins |
auth-security-migration.sql, gdpr-migration.sql |
| Site ↔ editor bridge | osionos_bridge_identities, osionos_bridge_audit_events |
osionos-bridge-migration.sql |
| Social & communities | osionos_communities, osionos_community_members, osionos_community_channels, osionos_connections, osionos_user_blocks, osionos_user_reports, osionos_feed_* |
osionos-communities-, -social-, -feed-engagement-migration.sql |
| Databases-as-blocks | osionos_object_databases, osionos_workspace_databases, osionos_app_connections |
osionos-object-databases-, -workspace-databases-, -app-connections-migration.sql |
| Publishing & sharing | osionos_published_pages, osionos_share_rules, osionos_page_links |
osionos-publish-, -share-rules-, -page-links-migration.sql |
| Tasks & notifications | osionos_tasks, osionos_notifications, osionos_push_subscriptions |
osionos-tasks-, -push-migration.sql |
| Mail & calendar | mail_accounts, mail_messages, calendar_accounts, calendar_sources, calendar_event_cache |
mail-migration.sql, calendar-migration.sql |
Access control is stored as data: roles, user_roles and resource_policies tables, evaluated
by the SQL function public.has_permission(...) and mirrored by the Rust data plane's own ABAC
evaluator. Every table generated by grobase receives an owner_id column automatically, which is
what makes one isolation rule apply to any table.
Two users tables. models/user.sql defines users.id as SERIAL (integer): it belongs to
the auth gateway's database. The osionos tables reference users by UUID, in grobase's
PostgreSQL, where make all never applies user.sql — doing so fails on the uuid/integer
foreign-key clash (scripts/apply-models-migrations.sh:27-38).
| Feature | Description |
|---|---|
| Sign-up / sign-in | Email and password (hashed and salted) on the public site; email OTP enabled in production with real SMTP |
| Site → editor handoff | After login, a one-time bridge session hands the user to osionos (/api/auth/bridge/consume) — no token in a query string |
| Legal pages | Privacy Policy, Terms of Service, Cookie Policy, Data Rights, linked from the site |
| Block editor | Pages built from blocks, nested pages, templates, covers, favourites, search |
| Database block | A block that is a database, with table / board / calendar views over any mounted data source |
| Real-time collaboration | Several users editing the same space live, over the protected collab:<spaceId> channel |
| Chat | Workspace channels and direct messages, reactions, mentions, attachments, read receipts |
| Video rooms | LiveKit-based video channels inside workspaces |
| Comments, sharing, publishing | Page comments, share rules, public page publishing |
| Communities & social feed | Communities, connections, feed with likes, comments and shares; blocking and reporting |
| Read Gmail inside the workspace via Google OAuth | |
| Calendar | Google Calendar events via Google OAuth |
| Contract-driven provisioning | One JSON contract → isolated database, roles, keys and frontend .env |
| Self-serve applications | POST /v1/tenants/me/apps creates a new isolated app with its own database and key |
| Multi-engine data API | One API and one key over PostgreSQL, MySQL, MongoDB, SQLite, MSSQL, DynamoDB, Redis, HTTP |
| Public API | Key-authenticated, rate-limited API behind Kong, documented with OpenAPI |
The subject requires 14 points (Major = 2, Minor = 1). A module that cannot be demonstrated live scores zero, so the confidence column is honest about where each one stands:
- A — works as-is in the default stack
- B — backend exists; needs a feature flag and a migration, no new code
- C — backend exists; needs a user-facing screen to be demonstrable
Only the A rows plus the two modules of choice — 10 points — are demonstrable in the default stack today, which is below the 14 required.
| # | Module | Category | Type | Pts | Conf | How it is implemented |
|---|---|---|---|---|---|---|
| 1 | Public API (key, rate limit, docs, ≥5 endpoints) | Web | Major | 2 | A | Kong gateway with key auth and rate limiting; OpenAPI specs in grobase infra/config/openapi/ |
| 2 | Backend as microservices | DevOps | Major | 2 | A | 15 compose planes (Go control, Rust data, Rust realtime, TS app, storage, auth…) talking over an internal network with HMAC-signed service calls |
| 3 | Real-time features via WebSockets | Web | Major | 2 | A | Rust realtime plane; changes published as events; protected channel namespaces (gate m175) |
| 4 | Real-time collaboration | Web | Minor | 1 | A | osionos live co-editing over collab:<spaceId> on the realtime plane |
| 5 | Advanced permissions | User management | Major | 2 | C | Roles and policies as rows, ABAC conditions; needs PERMISSION_CONDITIONS_ENABLED + API_KEY_ABAC_ENABLED, migration 063 |
| 6 | Organisation system | User management | Major | 2 | C | Organisations are on in production; only a management screen is missing |
| 7 | Monitoring with Prometheus + Grafana | DevOps | Major | 2 | C | Observability plane; needs TENANT_OBS_ENABLED and DATA_PLANE_TENANT_OBS |
| 8 | Health/status page, backups, disaster recovery | DevOps | Minor | 1 | C | TENANT_BACKUP_ENABLED, migration 042 |
| 9 | GDPR compliance | Data | Minor | 1 | B | Data export and hard erase: TENANT_EXPORT_ENABLED + HARD_ERASE_ENABLED; gdpr_requests table |
| 10 | Two-factor authentication | User management | Minor | 1 | B | TOTP (grobase one shape) or passkeys (PASSKEYS_ENABLED) |
| 11 | Remote authentication (OAuth 2.0) | User management | Minor | 1 | B | SSO_ENABLED, migration 053 |
| 12 | File upload and management | Web | Minor | 1 | C | Storage plane on MinIO, STORAGE_BUCKET_SCOPE_ENABLED |
| 13 | Module of choice: contract-driven application factory | Free choice | Major | 2 | A | See justification below |
| 14 | Module of choice: SSRF guard on the HTTP engine | Free choice | Minor | 1 | A | See justification below |
Point calculation
| Points | |
|---|---|
| Catalogue modules listed (rows 1–12) | 18 |
| Modules of choice (rows 13–14) | 3 |
| Total listed | 21 |
| Demonstrable today (confidence A: rows 1–4, 13–14) | 10 |
| Required | 14 |
The Multiple languages module is not claimed: osionos lists four languages in its settings,
but switching only records a stub action (i18n_change_stub in osionos
src/features/settings/SettingsCenter.tsx); no translation resources are loaded.
- Why this module. It is the central idea of the project: a backend with zero application-specific code, where an application is a declarative contract.
- Technical challenge. From one JSON file, a generic provisioner must create an isolated
database, apply its schema, create roles and policies, mint scoped API keys and emit the
frontend's
PUBLIC_*configuration — idempotently, on every boot. The self-serve endpointPOST /v1/tenants/me/appsdoes it on demand, with a freshCREATE DATABASEper application. - Value. A new application costs a contract instead of a new server. The public site and vault42 are both provisioned this way in production.
- Why it deserves a Major. It spans the Go control plane, the Rust data plane and the
database layer, and it comes with isolation proofs: gate
m177proves self-serve creation, and gatem176proves one application cannot read another's data (a foreign mount resolves to zero rows — a 404, never a cross read).
- Why this module. The data plane can mount a remote HTTP API as if it were a database — which also means "let a user make our server fetch any URL", the classic SSRF hole.
- Technical challenge. The guard blocks loopback, private (RFC 1918), link-local — including
the cloud metadata endpoint
169.254.169.254— CGNAT, IPv6 ULA and link-local, IPv4-mapped addresses and internal hostnames, then pins the connection to the validated public IP so a later DNS rebind cannot redirect it inward. - Value and weight. Small, self-contained and demonstrable in seconds (a mount pointing at
169.254.169.254is refused), and it shows security judgement on a feature we chose to build.
The work split is visible in the history of the root repository and of each submodule:
git shortlog -sn --all
git submodule foreach 'git shortlog -sn --all'From the project's history:
- Migrating the data plane from TypeScript to Rust with no downtime, using a per-request switch and a shadow mode that compared both implementations under real traffic.
- Keeping eight database adapters at parity, enforced by a conformance battery that runs every adapter against a real engine and checks it serves exactly what it advertises.
- Auditing our own authorisation path and shipping a reversible mitigation for a weakness we
found (see
wiki/security/03-known-weaknesses.md).
- Docker Compose — https://docs.docker.com/compose/
- PostgreSQL row-level security — https://www.postgresql.org/docs/current/ddl-rowsecurity.html
- Kong Gateway — https://docs.konghq.com/gateway/latest/
- Supabase Auth (GoTrue) — https://github.com/supabase/auth
- The Go Programming Language documentation — https://go.dev/doc/
- The Rust Programming Language — https://doc.rust-lang.org/book/
- NestJS — https://docs.nestjs.com/
- Astro — https://docs.astro.build/
- React — https://react.dev/ · Vite — https://vite.dev/ · Tailwind CSS — https://tailwindcss.com/docs
- LiveKit — https://docs.livekit.io/
- NIST SP 800-162, Guide to Attribute Based Access Control — https://csrc.nist.gov/pubs/sp/800/162/final
- OWASP SSRF Prevention Cheat Sheet — https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html
- OWASP Top 10 — https://owasp.org/www-project-top-ten/
- GDPR, full text — https://gdpr-info.eu/
This project was built with heavy AI assistance, and we state it plainly.
- Tools. Team members used AI assistants in different forms, according to their own work.
- Code. AI wrote and refactored code across the stack: grobase planes and adapters, the frontends, SQL migrations, the Makefile pipeline and the Docker setup.
- Testing and verification. AI wrote verification gates and test scripts, including the
Playwright smoke in
tests/e2e/, and ran them against the live stack. - Reviews. AI-assisted code review of pull requests and security review of the authorisation
path (
wiki/security/03-known-weaknesses.md). - Documentation. The wiki and this README were drafted and distilled with AI assistance from the project's own design documents and code, then checked against the repository.
- How we kept control. A human reviews and merges every pull request; nothing reaches
developormainby an agent alone. A change is accepted only when its verification gate passes. Every member must be able to explain the AI-assisted code in their area.
- No game. All gaming-dependent modules are out of scope.
- Expressiveness is narrow by design. grobase has no place for custom server logic; business rules must be expressed as policies, schema constraints or database triggers.
- Documented security weaknesses. Known and declared in
wiki/security/03-known-weaknesses.md. - Several features are behind flags that are off by default — see the confidence column in Modules.
- No interface translation. The language setting in osionos is a stub (see Modules).
- Whiteboard editing is loopback-only and has no per-user authorization yet (see Demo).
There is no LICENSE file at the repository root. grobase carries its own licence files inside
its submodule (apps/grobase/LICENSE, apps/grobase/LICENSING.md).
wiki/README.md— the documentation cataloguewiki/ONBOARDING.md— seven pages to understand the project, in orderwiki/DEFENSE.md— evaluation questions mapped to answerswiki/platform/— grobase: planes, data plane, contracts, isolation, engineswiki/security/— security model, proofs, known weaknesses, networkDATA-MIGRATION.md— fresh-machine and migration runbookapps/grobase/CLAUDE.md— grobase's own authoritative technical document (inside the submodule)