Skip to main content

Technical Architecture: Bun + TanStack + Next.js Stack for a Multi-Institution Campus OS

Target architecture, not current state

This document specifies the architecture the platform is being built toward. It reads as though more of it exists than does: the compliance engine, the digital twin, agent orchestration, the real-time layer, and the credentials lifecycle are design, not implementation. For what actually runs today — and what is a scaffold — read What's Built Today first.

Context​

docs/ARCHITECTURE_STRATEGY.md lays out the product thesis: don't build "ERP + AI layer," build an institutional knowledge graph with always-on compliance, digital-twin simulation, agent-native operations, and verifiable credentials, with the 20 KIOT modules as the surface on top. That doc used Python/Django as the default backend because there was "no strong preference."

This document specifies how to actually build this on a Bun + TanStack + Next.js stack, open-source-first, with two hard requirements called out explicitly:

  1. Any number of institutions can be added — onboarding a new tenant must not require new code or schema changes.
  2. Each institution's data stays correctly isolated, and each institution's "brain" (the Pillar 4 agents) works correctly per-tenant — no cross-contamination, and the architecture must explain concretely how this holds as tenant count grows.

This doc is a companion to the strategy doc — it answers "what do we actually run in production," not "why this product wins."

Key research findings that shaped the stack choices​

  • Bun is genuinely production-ready for HTTP APIs in TypeScript (Bun.serve()+Hono benchmarked at ~400k req/sec, 3–4x Node with Express/Fastify) and Node-compatible for most libraries (Drizzle, Hono all work unmodified). An earlier draft of this doc used Temporal for the workflow engine, which forced a Node.js exception (Temporal's TS SDK worker crashes on Bun's native bindings, and Deno has the identical problem for the identical reason). That exception is gone: §5 below replaces Temporal with a workflow layer composed from Bun-verified, genuinely open-source primitives, so the entire fleet runs on Bun with no mixed-runtime seam.
  • TanStack Start hit 1.0 in 2026 (6M weekly downloads, up from 600k in April) — "just React," best-in-class end-to-end TypeScript type safety across routing/params, Vite-speed dev loop. Next.js remains stronger for RSC, SEO, and ecosystem/hiring depth. The research doesn't say pick one — it says they solve different problems, which maps cleanly onto this product having both an internal data-dense app and public-facing pages.
  • GraphRAG evidence from the strategy doc still applies — hybrid graph+vector retrieval materially outperforms vector-only on the multi-hop institutional questions this product needs to answer.
  • Multi-tenant AI memory research is explicit: Postgres + pgvector + RLS is "the best database infrastructure for teams starting or managing under a few hundred tenants," and the critical correctness detail is pre-filtering by tenant before the similarity/graph search runs, not filtering results after — post-filtering lets one tenant's data get scored against another's query before being discarded, which is a real leak surface.
  • License check, since the user asked for open-source-first: Mastra (AI agent framework) — Apache 2.0, genuinely open source. Apache AGE (graph) — Apache 2.0. ElectricSQL (sync engine) — Apache 2.0. Better Auth — MIT. DBOS Transact (TS) — MIT. pg-boss — MIT-family, open source, with CI-verified Bun support. XState — MIT, zero native dependencies. Hono, Drizzle — MIT. Veramo (verifiable credentials) — open source. Excluded on purpose: Memgraph (graph DB) is BSL 1.1, not OSI open source, ~$25k/yr commercial — disqualified given the explicit open-source preference, even though it benchmarks well. Temporal (Apache 2.0, genuinely open source) and Restate (BSL core / MIT SDKs) were both evaluated for the workflow layer and set aside — Temporal because its TS SDK worker doesn't run on Bun (or Deno) at all, Restate because it isn't OSI open source — in favor of the composed DBOS/pg-boss/XState layer in §5, which is fully open source and fully Bun-native.
  • Building a general-purpose durable execution engine from scratch was considered and deliberately rejected. That category of system — deterministic replay of arbitrary code, event-sourced history with strict ordering, non-determinism detection, workflow versioning — is a genuinely hard, multi-year distributed-systems problem; research explicitly notes "few implementations get it 100% right," even for dedicated teams. The actual requirement here (M4's approval chains, SLA timers, audit trail; Pillar 2's policy-evaluation triggers) is narrower: durable state machines plus durable job scheduling, not arbitrary-code durability. §5 is scoped to that narrower, achievable problem.

System Overview​

One Postgres database (with pgvector and apache_age extensions) is the system of record for every tenant, every module, and both the graph and vector indexes — this is what makes "any number of institutions" and "isolated institutional data" the same architectural answer instead of two separate problems.

┌─────────────────────────────┐
│ TanStack Start app │ internal, authenticated
│ (all 20 modules' UI, │ Campus OS: admin, faculty,
│ TanStack Router/Query/ │ HoD, IQAC, warden, placement
│ Table/DB) │
└───────────────┬───────────────┘
│ typed RPC (Hono client)
┌───────────────▼───────────────┐
│ Hono API (Bun.serve()) │ metadata-driven module
│ — the modular monolith │ engine + REST/RPC surface
└───────────────┬───────────────┘
┌────────────────────┼─────────────────────┐
│ │ │
┌──────────▼─────────┐ ┌────────▼────────┐ ┌──────────▼──────────┐
│ Drizzle ORM │ │ Mastra agent │ │ Workflow service │
│ → Postgres │ │ service (Bun) │ │ (Bun) — DBOS Transact │
│ (RLS, pgvector, │ │ — Pillar 4 agents │ │ checkpointing + XState│
│ Apache AGE graph) │ │ + A2A + MCP tools │ │ machines + pg-boss │
└────────────────────┘ └───────────────────┘ │ scheduling — Pillar 2 │
│ policies, M4 approvals,│
│ SLAs (see §5) │
└──────────────────────┘
┌────────────────────┐ ┌───────────────────┐ ┌──────────────────────┐
│ ElectricSQL │ │ NATS JetStream │ │ Veramo credentials │
│ (Postgres logical │ │ — bus GPS / IoT │ │ service (Bun) — W3C │
│ replication → shapes)│ │ event stream │ │ VC issuance, APAAR/ │
│ → TanStack DB clients│ │ │ │ ABC/DigiLocker API │
└────────────────────┘ └───────────────────┘ └──────────────────────┘

┌─────────────────────────────┐
│ Next.js app │ public, per-institution:
│ (public sites + VC │ landing page, credential
│ verification pages) │ verification, platform marketing
└─────────────────────────────┘

1. Runtime, monorepo, and package management​

  • Bun as the runtime for every service, with no exceptions: the Hono API, the Mastra agent service, the workflow service (§5), the Veramo credentials service, the TanStack Start app's server, and all test execution (bun test — Jest-compatible, no separate test runner needed).
  • Monorepo: Bun workspaces for dependency management + Turborepo for task orchestration/caching. Research is specific here — small monorepos are fine on Bun workspaces alone, but a 20-module system with multiple apps (TanStack Start app, Next.js public app, Hono API, Mastra agents, Veramo service) and many internal packages is exactly the "large monorepo with complex dependency graphs" case Turborepo is for.
  • Deployment: each service as its own container (Bun's official Docker base image for the Bun services), Postgres as a managed instance with pgvector and apache_age extensions enabled, NATS as a lightweight container, India-region cloud (unchanged from the strategy doc).

2. The metadata-driven module engine, in TypeScript​

This replaces the strategy doc's Python/Frappe-style reference with a concrete TS-native design:

  • A module_definitions table stores each of the 20 (and future) modules as metadata: entities, fields, relationships, permission rules, as JSONB.
  • Core, high-traffic, graph-critical entities (Person, Course, Room, Vehicle, Request) are first-class Drizzle-typed Postgres tables, for query performance and type safety.
  • Module-specific/long-tail fields live in JSONB extension columns keyed by the metadata definition, validated at runtime with Zod schemas generated from the same metadata (Drizzle + drizzle-zod bridges the two) — so a new module is a new metadata row plus Zod validation, not a migration.
  • Hono exposes this generically: one set of CRUD/query/workflow-trigger routes parameterized by module metadata, plus a typed RPC client (Hono's built-in RPC, rivaling tRPC) consumed directly by the TanStack Start app — this is what gives "end-to-end type safety across hundreds of entity types" without hand-writing per-module endpoints.

3. Multi-tenancy: how "any number of institutions" and "each institution's data stays correct" are the same mechanism​

  • Every table — including the Apache AGE graph's vertex/edge tables and pgvector's embedding tables — carries tenant_id. Postgres Row-Level Security policies enforce isolation inside the database itself, so an application bug cannot leak cross-tenant rows.
  • Every request is wrapped in a transaction that sets tenant context first: SET LOCAL app.current_tenant_id = <id> (the validated Drizzle + RLS pattern from research), before any query runs.
  • Critical correctness detail specific to AI/graph queries: tenant filtering happens as a pre-filter, before the vector similarity search or graph traversal runs — never as a post-filter on results. Post-filtering means tenant A's query still scores against tenant B's vectors/graph before the results are discarded, which is a real (if narrow) leak surface; pre-filtering makes it structurally impossible. This applies to every pgvector query and every AGE Cypher query in the codebase, enforced through one shared GraphQueryService/VectorQueryService abstraction that every module and every Mastra agent must go through — not ad hoc queries scattered through the codebase.
  • Onboarding a new institution = one row in a tenants table + a Better Auth organization record + a module-entitlement config row (which of the 20 modules are licensed) + which regulatory framework (NAAC/NBA/NIRF vs. others) applies. No schema change, no new deployment, no new code path. RLS policies and the shared query services apply automatically to the new tenant from the first write.
  • Graduation path if a specific institution ever needs physical isolation (rare, but real for some enterprise contracts): the same schema can be deployed into a dedicated Postgres instance for that one tenant — the shared-schema model is the default, not the only option.

4. Graph + vector layer (Pillar 1, GraphRAG)​

  • Apache AGE (Apache 2.0, a Postgres extension) is the graph layer — same database, same transaction boundary, same RLS/tenant_id model as everything else, so no separate graph service to operate at MVP. Queried via raw Cypher-in-SQL (AGE's cypher() function) through Drizzle's sql template tag, wrapped inside the shared GraphQueryService from §3.
  • pgvector in the same Postgres instance handles the vector-search side of hybrid retrieval.
  • Retrieval strategy (per the strategy doc's Pillar 1): vector search for simple lookups, AGE graph traversal for multi-hop cross-entity questions ("students with attendance risk AND fee dues AND hostel flags"), full agentic multi-step (Mastra) for the hardest cases.
  • Graduation path: if query complexity or scale ever outgrows AGE, ArcadeDB (Apache 2.0, true OSI open source, supports Cypher/SQL/Gremlin/GraphQL) is the migration target — not Memgraph, which is BSL-licensed and excluded per the open-source requirement. Because all graph access already goes through one GraphQueryService abstraction, this swap touches one service, not the whole codebase.

5. Workflow engine (M4) and the always-audit-ready compliance engine (Pillar 2): a purpose-built, Bun-native, open-source layer​

Rather than adopting Temporal (which forces a Node.js exception — see §"Key research findings") or attempting to clone Temporal's general-purpose deterministic-replay model from scratch (a genuinely hard, multi-year distributed-systems problem, deliberately ruled out above), this is a workflow layer scoped exactly to what M4 and Pillar 2 need — durable approval chains, SLA timers with escalation, and an audit trail — composed from three proven, open-source, Bun-verified primitives:

  • DBOS Transact (TypeScript, MIT) is the durability substrate. It's a library, not a separate orchestrator server: it checkpoints each workflow step's state transactionally into the same tenant-isolated Postgres (§3) used for everything else — same transaction boundary, same RLS, same tenant_id model. A crash mid-approval-chain resumes from the last checkpoint on restart, with no separate orchestrator process to deploy or reason about, and no native-binding compatibility risk (it's Postgres-plus-TypeScript, not a Rust core with N-API bindings like Temporal).
  • pg-boss handles durable scheduling — SLA timers, escalation reminders, and the scheduled policy-evaluation runs Pillar 2 needs — as retryable, cron-capable, dead-letter-backed jobs against Postgres (SKIP LOCKED-based). This isn't a speculative compatibility bet: pg-boss has explicit, CI-verified Bun support, exercised against a real Postgres on every commit.
  • XState (MIT, zero native dependencies) is the declarative shape of an approval chain — stages, transitions, roles, delegation rules as a state machine definition. This is what M4's no-code UI compiles to: KIOT IT (or any tenant's IT) defines stages and routing as configuration, which becomes an XState machine definition, which DBOS then executes durably and pg-boss schedules the time-based parts of.
  • Everything runs on Bun — this layer has no separate worker runtime requirement, so there is no mixed-runtime exception anywhere in the fleet.
  • Open source: this composed layer is the concrete open-source project to release — positioned honestly as a Postgres-native, Bun-first workflow engine for approval and compliance workflows, not a Temporal clone or a general-purpose durable execution engine.
  • Pillar 2's policies-as-code run as pg-boss-scheduled evaluations triggered by relevant writes (a new syllabus, a new placement record) via the Hono API, with every evaluation's evidence trail written back through the same Drizzle/Postgres layer that DBOS checkpoints into — one durability story, not two.

6. AI / agent layer (Pillar 4) — concretely, per-tenant​

  • Mastra (Apache 2.0, TypeScript-native, YC-backed, ships agents/workflows/tools/RAG/memory/evals/telemetry as typed primitives) is the agent framework, running as its own Bun service.
  • Each Pillar-4 role-agent (IQAC Agent, Placement Agent, Warden Agent, Curriculum Agent, ...) is one shared, stateless Mastra agent definition — not one running process per institution. Institution count does not multiply deployed services.
  • What makes an institution's "brain" its own: every agent invocation loads tenant-scoped context at request time — the tenant's tenant_id (for the pre-filtered graph/vector queries from §3), its enabled modules, its regulatory framework, and its own conversation/task memory partition (Mastra's memory primitive, scoped by tenant_id, stored in the same tenant-isolated Postgres). Two institutions asking their "IQAC Agent" the same question get answers grounded only in their own graph, because the retrieval layer underneath is tenant-pre-filtered — the agent code itself has no institution-specific branches.
  • Agents expose tools internally via MCP and delegate to each other via A2A (Linux Foundation v1.0 standard, production-proven at EY/Salesforce/JPMorgan) — e.g., the IQAC Agent asking the Curriculum Agent for outcome data crosses a role boundary, not a tenant boundary; A2A calls still carry and enforce the same tenant context.
  • Every agent action still routes through the same human-approval governance gate as the strategy doc specifies, with full trace logging — this doesn't change with the stack, it's enforced at the workflow layer (§5) regardless of which service initiated the action.

7. Frontend: where TanStack Start, TanStack DB, and Next.js each fit​

Both frameworks are used, for genuinely different jobs — not an unresolved choice:

  • TanStack Start (+ TanStack Router, TanStack Query, TanStack Table) is the internal, authenticated Campus OS — every one of the 20 modules' admin/faculty/HoD/IQAC/warden/placement UI. This is where TanStack's core strength (best-in-class end-to-end TypeScript type safety across routing, search params, and hundreds of entity types from the metadata engine) matters most, and TanStack Table is the right tool for the dense data-grid UI an ERP is full of.
  • TanStack DB (open source, differential-dataflow reactive client store, backend-agnostic) paired with ElectricSQL (Apache 2.0, Postgres logical replication → typed "shapes" streamed to clients) implements Pillar 2's live, always-current compliance dashboard and M18's Leadership Dashboard as genuinely real-time, offline-capable, instantly-reactive views — Postgres write → Electric shape stream → TanStack DB collection → UI update — without hand-rolled WebSocket plumbing, and fully open source end to end.
  • Next.js is used specifically for the public-facing surfaces: each institution's public landing/marketing page, and critically the public Verifiable Credential verification pages from Pillar 5 (e.g., an employer visiting a public URL to independently verify a graduate's degree) — where SEO, RSC, and fast static generation matter more than the internal app's needs, and where the platform's own marketing/investor site also lives.
  • Mobile (student + parent apps): Flutter, unchanged from the strategy doc — a separate discipline regardless of the web framework choice.

8. Auth and identity​

  • Better Auth (MIT, open source, runs inside the application rather than as a hosted service) replaces the strategy doc's earlier Clerk mention — a better fit given the open-source requirement and the multi-tenant model: Better Auth's built-in organization plugin generates exactly the organization/member/invitation schema and RBAC this product needs, mapping directly onto "one organization per institution."
  • Veramo (open source JS/TS framework for W3C Verifiable Credentials and DIDs, runs on Node/Bun/browser/React Native) implements Pillar 5's credential issuance and verification, isolated in its own service given its distinct key-management/security requirements (credential signing keys) — consistent with the strategy doc's risk note to keep this isolated from the main app.

9. Real-time / IoT (M13 bus tracking)​

Unchanged from the strategy doc: NATS JetStream (true open source, official Bun/Node-compatible JS client) for GPS telemetry ingestion, feeding hot state to WebSocket/TanStack DB-based live views. This is a separate concern from the ElectricSQL-based UI sync in §7 — NATS handles high-frequency device telemetry ingestion; ElectricSQL handles Postgres-backed application-state sync to the UI.

Risks specific to this stack​

  • The DBOS + pg-boss + XState workflow layer is a less battle-tested combination than Temporal for multi-month-duration workflows. Temporal has years of production hardening specifically for long-running, high-stakes orchestration; this composed layer is newer and intentionally narrower in scope. Mitigate by keeping its responsibilities deliberately narrow (approval chains, SLA timers, audit trail — not arbitrary business logic) and by load-testing a multi-month-duration synthetic workflow (a simulated admissions cycle, end to end) before relying on it for a real one.
  • This workflow layer is now also an open-source project the team maintains, not just an internal dependency choice — it needs the same scrutiny (documentation, versioning, a real test suite) as any other OSS release, since external users may come to depend on it.
  • TanStack Start is young (1.0 in 2026) — smaller ecosystem, fewer third-party integrations than Next.js, dependency versions worth pinning deliberately. The two-framework split in §7 contains this risk to the internal app, where the team controls all the dependencies directly.
  • Apache AGE's Cypher-in-SQL is a less common query pattern than plain Cypher (Neo4j/ArcadeDB) — worth a short internal guide/examples doc once the Foundation phase starts, and worth confirming the team is comfortable maintaining this abstraction.
  • Pre-filtering discipline (§3) is a rule, not a guarantee, unless enforced in one place — the GraphQueryService/VectorQueryService abstraction is load-bearing for tenant isolation; any code path that queries AGE or pgvector directly instead of through it reintroduces the leak risk the research specifically warned about. Worth an automated lint/review rule, not just a convention.

Verification​

  1. Stand up Postgres with pgvector + apache_age in the Foundation phase; load two tenants' worth of synthetic data for two modules; confirm a cross-tenant query attempt is blocked by RLS and that a pre-filtered graph/vector query never touches the other tenant's rows (test both, since they're different enforcement layers).
  2. Run one Mastra agent (e.g., a minimal IQAC Agent) against both synthetic tenants with the same prompt, confirm it returns tenant-specific, non-leaking answers using the shared agent code path from §6 — this is the concrete test of "each institutional brain works fine."
  3. Prototype one real M4 workflow (e.g., M3 Guest Logistics' invite → logistics → budget approval → execution → closure chain) as an XState machine, executed via DBOS, with one SLA timer via pg-boss — confirm it survives a simulated crash/restart mid-chain and resumes correctly, and confirm the entire workflow service runs on Bun with no Node.js dependency anywhere in the fleet.
  4. Build one ElectricSQL + TanStack DB live view (e.g., a compliance readiness tile) end-to-end before committing to it as the pattern for all of M18/Pillar 2's live dashboards.
  5. Before Phase 3 (where Pillar 2's continuous policy evaluation goes live), specifically load-test a multi-month-duration scenario (many scheduled evaluations over a simulated long period) to validate the workflow layer's durability claim under realistic conditions, not just a quick crash test.