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.
The architecture is locked in
docs/superpowers/specs/2026-04-07-vibe-erp-architecture-design.md.
At a glance
|
|
| Latest version |
v0.6 (post-P1.5) |
| Latest commit |
1ead32d feat(metadata): P1.5 — metadata store seeding |
| Repo |
https://github.com/reporkey/vibe-erp |
| Modules |
11 |
| Unit tests |
92, all green |
| End-to-end smoke runs |
All cross-cutting services verified against real Postgres |
| Real PBCs implemented |
2 of 10 (pbc-identity, pbc-catalog) |
| Plug-ins serving HTTP |
1 (reference printing-shop, 7 endpoints + own DB schema + own metadata) |
| Production-ready? |
No. Foundation is solid; many capabilities still deferred. |
Current stage
Foundation complete; first business surface in place. All seven cross-cutting platform services that PBCs and plug-ins depend on are now live (auth, plug-in lifecycle, plug-in HTTP, plug-in linter, plug-in DB schemas, event bus + outbox, metadata loader). Two real PBCs validate the modular-monolith template. The reference printing-shop plug-in has graduated from "hello world" to a real customer demonstration: it owns its own DB schema, CRUDs its own domain through the api.v1 typed-SQL surface, registers its own HTTP endpoints, and would be rejected at install time if it tried to import any internal framework class.
The next phase is building business surface area: more PBCs (partners, 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 15 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 |
⏳ Next priority |
| 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 |
⏳ Next priority candidate |
| 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/<plugin-id>/<path>. 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. |
|
PBC pattern (P5.x recipe) |
pbc-identity, pbc-catalog
|
Two real PBCs prove the recipe: domain entity extending AuditedJpaEntity → Spring Data JPA repository → application service → REST controller under /api/v1/<pbc>/<resource> → cross-PBC facade in api.v1.ext.<pbc> → 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.
-
i18n / Translator. Plug-ins that try to use
context.translator get an UnsupportedOperationException — the stub points at P1.6.
-
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. Only identity and catalog exist. Partners, 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
# 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":"<from-logs>"}' | 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-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
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
11 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