# vibe_erp — Project Progress > Snapshot of where the framework stands today, what's done, and what's next. > The detailed plan of record is > [`docs/superpowers/specs/2026-04-07-vibe-erp-implementation-plan.md`](docs/superpowers/specs/2026-04-07-vibe-erp-implementation-plan.md). > The architecture is locked in > [`docs/superpowers/specs/2026-04-07-vibe-erp-architecture-design.md`](docs/superpowers/specs/2026-04-07-vibe-erp-architecture-design.md). ## At a glance | | | |---|---| | **Latest version** | v0.8 (post-P1.6) | | **Latest commit** | `01c71a6 feat(i18n): P1.6 — ICU4J translator + per-plug-in locale chain` | | **Repo** | https://github.com/reporkey/vibe-erp | | **Modules** | 13 | | **Unit tests** | 118, all green | | **End-to-end smoke runs** | All cross-cutting services + all 3 PBCs verified against real Postgres; reference plug-in returns localised strings in 3 locales | | **Real PBCs implemented** | 3 of 10 (`pbc-identity`, `pbc-catalog`, `pbc-partners`) | | **Plug-ins serving HTTP** | 1 (reference printing-shop, 7 endpoints + own DB schema + own metadata + own i18n bundles) | | **Production-ready?** | **No.** Foundation is solid; many capabilities still deferred. | ## Current stage **Foundation complete; business surface area growing; i18n online.** All eight cross-cutting platform services that PBCs and plug-ins depend on are live (auth, plug-in lifecycle, plug-in HTTP, plug-in linter, plug-in DB schemas, event bus + outbox, metadata loader, ICU4J translator). Three real PBCs (identity, catalog, partners) validate the modular-monolith template against three different aggregate shapes. The reference printing-shop plug-in is the executable acceptance test: it owns its own DB schema, CRUDs its own domain through the api.v1 typed-SQL surface, registers its own HTTP endpoints, ships its own message bundles, returns localised strings in three locales, and would be rejected at install time if it tried to import any internal framework class. The next phase continues **building business surface area**: more PBCs (inventory, sales orders), the workflow engine (Flowable), the metadata-driven custom fields and forms layer (Tier 1 customization), and eventually the React SPA. ## Total scope (the v1.0 cut line) The framework reaches v1.0 when, on a fresh Postgres, an operator can `docker run` the image, log in, drop a customer plug-in JAR into `./plugins/`, restart, and walk a real workflow end-to-end without writing any code — and the **same** image, against a different Postgres pointed at a different company, also serves a different customer with a different plug-in. See the implementation plan for the full v1.0 acceptance bar. That target breaks down into roughly 30 work units across 8 phases. About **17 are done** as of today. Below is the full list with status. ### Phase 1 — Platform completion (foundation) | # | Unit | Status | |---|---|---| | ~~P1.1~~ | ~~Postgres RLS transaction hook~~ | **N/A** — removed by single-tenant refactor | | P1.2 | Plug-in linter (ASM bytecode scan) | ✅ DONE — `7af11f2` | | P1.3 | Plug-in lifecycle: HTTP endpoints, DefaultPluginContext, dispatcher | ✅ DONE — `20d7ddc` | | P1.4 | Plug-in Liquibase application + `PluginJdbc` | ✅ DONE — `7af11f2` | | P1.5 | Metadata store seeding | ✅ DONE — `1ead32d` | | P1.6 | ICU4J `Translator` implementation + locale resolution | ✅ DONE — `01c71a6` | | P1.7 | Event bus + transactional outbox | ✅ DONE — `c2f2314` | | P1.8 | JasperReports integration | 🔜 Pending | | P1.9 | File store (local + S3) | 🔜 Pending | | P1.10 | Job scheduler (Quartz) | 🔜 Pending | ### Phase 2 — Embedded workflow engine | # | Unit | Status | |---|---|---| | P2.1 | Embedded Flowable (BPMN 2.0) + `TaskHandler` wiring | 🔜 Pending | | P2.2 | BPMN designer (web) | 🔜 Pending — depends on R1 | | P2.3 | User-task form rendering | 🔜 Pending | ### Phase 3 — Metadata store: forms and rules (Tier 1 customization) | # | Unit | Status | |---|---|---| | P3.1 | JSON Schema form renderer (server) | 🔜 Pending | | P3.2 | Form renderer (web) | 🔜 Pending — depends on R1 | | P3.3 | Form designer (web) | 🔜 Pending — depends on R1 | | P3.4 | Custom field application (JSONB `ext` validation) | ⏳ Next priority candidate | | P3.5 | Rules engine (event-driven) | 🔜 Pending | | P3.6 | List view designer (web) | 🔜 Pending — depends on R1 | ### Phase 4 — Authentication and authorization | # | Unit | Status | |---|---|---| | P4.1 | Built-in JWT auth + Argon2id + bootstrap admin | ✅ DONE — `540d916` | | P4.2 | OIDC integration (Keycloak-compatible) | 🔜 Pending | | P4.3 | Permission checking against `metadata__role_permission` | 🔜 Pending | ### Phase 5 — Core PBCs | # | Unit | Status | |---|---|---| | P5.1 | `pbc-catalog` — items, units of measure | ✅ DONE — `69f3daa` | | P5.2 | `pbc-partners` — customers, suppliers, contacts, addresses | ✅ DONE — `3e40cae` | | P5.3 | `pbc-inventory` — stock items, lots, locations, movements | 🔜 Pending | | P5.4 | `pbc-warehousing` — receipts, picks, transfers, counts | 🔜 Pending | | P5.5 | `pbc-orders-sales` — quotes, sales orders, deliveries | 🔜 Pending | | P5.6 | `pbc-orders-purchase` — RFQs, POs, receipts | 🔜 Pending | | P5.7 | `pbc-production` — work orders, routings, operations | 🔜 Pending | | P5.8 | `pbc-quality` — inspection plans, results, holds | 🔜 Pending | | P5.9 | `pbc-finance` — GL, journal entries, AR/AP minimal | 🔜 Pending | ### Phase 6 — Web SPA (React + TS) | # | Unit | Status | |---|---|---| | R1 | Vite + React + TS bootstrap, login flow, OpenAPI client | 🔜 Pending | | R2 | Identity screens | 🔜 Pending | | R3 | Customize / metadata UIs | 🔜 Pending | | R4 | Per-PBC list/detail/create/edit screens | 🔜 Pending | ### Phase 7 — Reference printing-shop plug-in | # | Unit | Status | |---|---|---| | REF.0 | Hello-world plug-in (PF4J + Plugin lifecycle + endpoints + own DB) | ✅ DONE through v0.6 | | REF.1 | Real BPMN workflow handler (quote → job card) | 🔜 Pending — depends on P2.1 | | REF.2 | Plate / ink / press CRUD as customer-style domain | ✅ Partial — plate + ink CRUD live (`7af11f2`) | | REF.3 | Reference forms + automation rules in plug-in metadata | 🔜 Pending — depends on P3.3, P3.4 | ### Phase 8 — Hosted, AI agents, mobile (post-v1.0) | # | Unit | Status | |---|---|---| | H1 | Instance provisioning console (one process per customer) | 🔜 Post-v1.0 | | A1 | MCP server (REST endpoints exposed as AI-agent functions) | 🔜 v1.1 | | M1 | React Native skeleton | 🔜 v2 | ## What's live right now These are the cross-cutting platform services already wired into the running framework. Each was built with a real end-to-end smoke test against a Postgres container. | Service | Module | What it does | |---|---|---| | **Auth** (P4.1) | `platform-security`, `pbc-identity` | Username/password login → HMAC-SHA256 JWT (15min access + 7d refresh). Bootstrap admin printed to logs on first boot. Spring Security filter chain enforces auth on every endpoint except `/actuator/health`, `/api/v1/_meta/**`, `/api/v1/auth/login`, `/api/v1/auth/refresh`. Principal bridges into the audit listener so `created_by` columns carry real user UUIDs. | | **Plug-in HTTP** (P1.3) | `platform-plugins` | Plug-ins call `context.endpoints.register(method, path, handler)` to mount lambdas under `/api/v1/plugins//`. Path templates with `{var}` extraction via Spring's `AntPathMatcher`. Plug-in code never imports Spring MVC types. | | **Plug-in linter** (P1.2) | `platform-plugins` | At plug-in load time (before any plug-in code runs), ASM-walks every `.class` entry for references to `org.vibeerp.platform.*` or `org.vibeerp.pbc.*`. Forbidden references unload the plug-in with a per-class violation report. | | **Plug-in DB schemas** (P1.4) | `platform-plugins` | Each plug-in ships its own `META-INF/vibe-erp/db/changelog.xml`. The host's `PluginLiquibaseRunner` applies it against the shared host datasource at plug-in start. Plug-ins query their own tables via the api.v1 `PluginJdbc` typed-SQL surface — no Spring or Hibernate types ever leak. | | **Event bus + outbox** (P1.7) | `platform-events` | Synchronous in-process delivery PLUS a transactional outbox row in the same DB transaction. `Propagation.MANDATORY` so the bus refuses to publish outside an active transaction (no publish-and-rollback leaks). `OutboxPoller` flips PENDING → DISPATCHED every 5s. Wildcard `**` topic for the audit subscriber; topic-string and class-based subscribe. | | **Metadata loader** (P1.5) | `platform-metadata` | Walks the host classpath and each plug-in JAR for `META-INF/vibe-erp/metadata/*.yml`, upserts entities/permissions/menus into `metadata__*` tables tagged by source. Idempotent (delete-by-source then insert). User-edited metadata (`source='user'`) is never touched. Public `GET /api/v1/_meta/metadata` returns the full set for SPA + AI-agent + OpenAPI introspection. | | **i18n / Translator** (P1.6) | `platform-i18n` | ICU4J-backed `Translator` with named placeholders, plurals, gender, locale-aware number/date formatting. Per-plug-in instance scoped via a `(classLoader, baseName)` chain — plug-in's `META-INF/vibe-erp/i18n/messages_.properties` resolves before the host's `messages_.properties` for shared keys. `RequestLocaleProvider` reads `Accept-Language` from the active HTTP request via the servlet container, falling back to `vibeerp.i18n.defaultLocale` outside an HTTP context. JVM-default locale fallback explicitly disabled to prevent silent locale leaks. | | **PBC pattern** (P5.x recipe) | `pbc-identity`, `pbc-catalog`, `pbc-partners` | Three real PBCs prove the recipe across three aggregate shapes (single-entity user, two-entity catalog, parent-with-children partners+addresses+contacts): domain entity extending `AuditedJpaEntity` → Spring Data JPA repository → application service → REST controller under `/api/v1//` → cross-PBC facade in `api.v1.ext.` → adapter implementation. Architecture rule enforced by the Gradle build: PBCs never import each other, never import `platform-bootstrap`. | ## What the reference plug-in proves end-to-end The printing-shop reference plug-in is the framework's executable acceptance test. As of `1ead32d` it demonstrates **everything a real customer plug-in needs** except workflow handling: ``` Plug-in JAR drop → host bootstrap: 1. PF4J discovers the JAR and reads plugin.yml 2. PluginLinter ASM-walks every class — passes 3. PluginLiquibaseRunner applies META-INF/vibe-erp/db/changelog.xml → creates plugin_printingshop__plate, plugin_printingshop__ink_recipe 4. MetadataLoader.loadFromPluginJar reads META-INF/vibe-erp/metadata/printing-shop.yml → 2 entities, 5 permissions, 2 menus tagged source='plugin:printing-shop' 5. VibeErpPluginManager calls Plugin.start(context) with a real PluginContext (logger + endpoints + eventBus + jdbc all wired) 6. The plug-in's start() lambda registers 7 HTTP endpoints 7. Boot completes, app is serving traffic Live HTTP traffic: POST /api/v1/auth/login (admin) → 200, JWT POST /api/v1/plugins/printing-shop/plates (Bearer) → 201, plate created GET /api/v1/plugins/printing-shop/plates → list of plates GET /api/v1/plugins/printing-shop/plates/{id} → fetch by id POST /api/v1/plugins/printing-shop/inks → 201, ink created GET /api/v1/_meta/metadata → all 6 entities + 18 permissions GET /api/v1/identity/users (Bearer) → still works GET /api/v1/catalog/uoms (Bearer) → 15 seeded UoMs ``` This is **real**: a JAR file dropped into a directory, loaded by the framework, executing customer-specific business logic against its own database tables, exposed via REST. The plug-in code never imports a single internal framework class — and the linter would refuse to load it if it tried. ## What's not yet live (the deferred list) - **Workflow engine.** No Flowable yet. The api.v1 `TaskHandler` interface exists; the runtime that calls it doesn't. - **Custom fields.** The `ext jsonb` columns exist on every entity; the metadata describing them exists; the validator that enforces them on save does not (P3.4). - **Forms.** No JSON Schema form renderer (server or client). No form designer. - **Reports.** No JasperReports. - **File store.** No abstraction; no S3 backend. - **Job scheduler.** No Quartz. Periodic jobs don't have a home. - **OIDC.** Built-in JWT only. OIDC client (Keycloak-compatible) is P4.2. - **Permission checks.** `Principal` is bound; `metadata__permission` is seeded; the actual `@RequirePermission` enforcement layer is P4.3. - **More PBCs.** Identity, catalog and partners exist. Inventory, warehousing, orders, production, quality, finance are all pending. - **Web SPA.** No React app. The framework is API-only today. - **MCP server.** The architecture leaves room for it; the implementation is v1.1. - **Mobile.** v2. ## How to run what exists today ```bash # Bring up Postgres + the plug-in JAR (in background) docker compose up -d db ./gradlew :reference-customer:plugin-printing-shop:installToDev # Boot the framework against it ./gradlew :distribution:bootRun # In another shell: curl -s localhost:8080/api/v1/_meta/info curl -s localhost:8080/api/v1/_meta/metadata | jq # Read the bootstrap admin password from the boot logs, then: ACCESS=$(curl -s -H 'Content-Type: application/json' \ -X POST localhost:8080/api/v1/auth/login \ -d '{"username":"admin","password":""}' | jq -r .accessToken) curl -s -H "Authorization: Bearer $ACCESS" localhost:8080/api/v1/identity/users curl -s -H "Authorization: Bearer $ACCESS" localhost:8080/api/v1/catalog/uoms curl -s -H "Authorization: Bearer $ACCESS" localhost:8080/api/v1/plugins/printing-shop/plates ``` ## Module map ``` api/api-v1 PUBLIC CONTRACT — semver-governed platform/platform-bootstrap Spring Boot main, props, web filters platform/platform-persistence Audit base, JPA entities, PrincipalContext platform/platform-security JWT issuer/verifier, Spring Security config, password encoder platform/platform-events EventBus impl, transactional outbox, poller platform/platform-metadata MetadataLoader, MetadataController platform/platform-i18n IcuTranslator, RequestLocaleProvider (P1.6) platform/platform-plugins PF4J host, linter, Liquibase runner, endpoint dispatcher, PluginJdbc pbc/pbc-identity User entity end-to-end + auth + bootstrap admin pbc/pbc-catalog Item + Uom entities + cross-PBC CatalogApi facade pbc/pbc-partners Partner + Address + Contact entities + cross-PBC PartnersApi facade reference-customer/plugin-printing-shop Reference plug-in: own DB schema (plate, ink_recipe), own REST endpoints, own metadata YAML distribution Bootable Spring Boot fat-jar assembly ``` 13 Gradle subprojects. Architectural dependency rule (PBCs never import each other; plug-ins only see api.v1) is enforced by the root `build.gradle.kts` at configuration time. ## Where to look next - **Detailed roadmap:** [`docs/superpowers/specs/2026-04-07-vibe-erp-implementation-plan.md`](docs/superpowers/specs/2026-04-07-vibe-erp-implementation-plan.md) — every unit, every dependency, every acceptance test. - **Architecture rationale:** [`docs/superpowers/specs/2026-04-07-vibe-erp-architecture-design.md`](docs/superpowers/specs/2026-04-07-vibe-erp-architecture-design.md) — 14 sections, validated against 2026 ERP/EBC SOTA. - **Project guardrails:** [`CLAUDE.md`](CLAUDE.md) — the 11 rules every change is held to. - **Git history:** every chunk is one commit with a detailed message describing what landed, what was deferred, and any bug caught by the smoke test.