Technical Specifications
Technology Stack
| Layer | Technology | Version |
|---|---|---|
| Runtime | Java | 25 |
| Framework | Spring Boot | 4.0.3 |
| Plugin system | PF4J (Plugin Framework for Java) | — |
| Security | Spring Security, JWT (HMAC-SHA256) | — |
| OAuth2 | Google OAuth2 via Spring Security OAuth2 Client | — |
| ORM | Spring Data JPA + Hibernate | — |
| Database (production) | PostgreSQL | 16+ |
| Database (tests) | H2 (PostgreSQL compatibility mode) | — |
| Cache | Caffeine | — |
| Build tool | Maven | 3.9+ |
| Code generation | Lombok | — |
| JSON | Jackson | — |
Module Dependencies
metaone-sdk (no platform dependencies — pure contracts)
▲
│
metaone-core (depends on: metaone-sdk, Spring Boot, PF4J, JPA)
▲
│
crm-extension (depends on: metaone-sdk, PF4J)
chatwoot-extension (depends on: metaone-sdk, PF4J)Extensions must never depend on
metaone-core. They depend only onmetaone-sdk.
System Requirements
Runtime
| Requirement | Minimum |
|---|---|
| Java | 25+ |
| Memory (host) | 512 MB heap (2 GB recommended with plugins loaded) |
| CPU | 2 cores minimum |
| Disk (plugins) | 100 MB per extension JAR |
Database
| Requirement | Value |
|---|---|
| PostgreSQL version | 16+ |
| Connections per workspace pool | 5 (configurable) |
| Max concurrent workspace pools | 50 (configurable) |
Build
| Requirement | Minimum |
|---|---|
| Java (build) | 25+ |
| Maven | 3.9+ |
| Docker | For Chatwoot integration tests |
Coding Conventions
Package Structure
vn.metaone.core/
├── config/ # Spring @Configuration classes
├── constant/ # Enums and constants
├── controller/ # REST controllers (interface + impl)
│ └── dto/ # Request/response records
├── entity/ # JPA entities
├── repository/ # Spring Data repositories
└── service/ # Business logic
└── lifecycle/ # Extension lifecycle servicesExtension packages follow the same pattern under their own root:
vn.metaone.crm/
vn.metaone.chatwoot/Key Patterns
Controller split: Every controller is an interface (with @RequestMapping) and a separate *Impl class. The interface is the API contract; the impl holds Spring beans.
Capability handlers must return CapabilityResult<T>: The InProcessCapabilityGateway casts handler return values to CapabilityResult. Returning Map<String, Object> directly causes a ClassCastException at runtime.
// ✅ Correct
@Capability(key = "search")
public CapabilityResult<List<ProductDto>> search(Map<String, Object> req, CapabilityContext ctx) {
return CapabilityResult.success(results);
}
// ❌ Wrong
public Map<String, Object> search(...) { ... }Manifest is source of truth: Capability handlers do not self-register. The platform routes via ext_capability_registry, which is populated from the manifest at install time. Always update both the handler annotations and the manifest.
Workspace scoping: All repository queries are keyed by workspaceId. Never return data across workspaces.
WorkspaceContext threading: WorkspaceContext uses ThreadLocal. It is set automatically for capability invocations and event handlers. If you spawn custom threads, propagate it manually.
Commit Message Format
Follow Conventional Commits:
feat: add conversation mapping for Chatwoot channels
fix: handle null websiteToken in widget config
docs: update chatwoot testing guide
chore: remove deprecated chat-extension from build
test: add unit tests for CRM event publishing
refactor: extract getOrCreateSystemChannel helperPull Request Rules
- One feature per PR
- Include tests for new capability handlers
- Update
CHANGELOG.mdfor all user-facing changes - Ensure
mvn -DskipTests packagepasses before opening PR
Deployment
Production Deployment
Build the platform and plugins:
bashmvn clean package -DskipTestsPrepare plugin directory:
bashmkdir -p /opt/metaone/plugins cp crm-extension/target/crm-extension-*.jar /opt/metaone/plugins/ cp chatwoot-extension/target/chatwoot-extension-*.jar /opt/metaone/plugins/Set required environment variables:
bashexport METAONE_JWT_SECRET=<min-32-char-secret> export SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/metaone export SPRING_DATASOURCE_USERNAME=metaone export SPRING_DATASOURCE_PASSWORD=<secret> export AUTHORIZED_REDIRECT_URIS=https://app.example.com/oauth2/callbackRun the host:
bashjava -jar metaone-core/target/metaone-core-*.jar \ --metaone.plugins-path=/opt/metaone/pluginsVerify health:
bashcurl http://localhost:8080/api/actuator/health
Docker
A docker-compose.dev.yml is provided for local development:
docker-compose -f docker-compose.dev.yml up -dStarts:
- PostgreSQL 16 (port 5432) — platform database
- PostgreSQL 13 + Redis (ports 5433, 6379) — Chatwoot dependencies
Database Initialization
On first startup with spring.jpa.hibernate.ddl-auto: update, Hibernate creates all platform tables automatically. For production, use Flyway or Liquibase for versioned migrations (see TODOS.md).
Plugin Jar Naming
Plugin JARs must be named {pluginId}-{version}.jar and placed in metaone.plugins-path. The PF4J manifest entries in the JAR (Plugin-Id, Plugin-Version, Plugin-Class) must match.
Known Limitations
| Area | Limitation |
|---|---|
| Schema migrations | Plugin databases use ddl-auto: update (not Flyway). Tracked in TODOS P2. |
| Compatibility checks | requiresPlatformVersion is parsed but not strictly enforced. Tracked in TODOS P2. |
Full mvn test | Fails on Spring context startup in some environments due to missing CapabilityException. Use mvn -pl metaone-core -Dtest=ExtensionInstallerServiceTest test instead. Tracked in TODOS P1. |
Naming: tenantId vs workspaceId | SDK event models use tenantId; host code uses workspaceId. Both refer to the same concept. Cleanup tracked in TODOS P2. |