ADR-008 vs ADR-053: Comparison and Integration Strategy
Date: 2025-12-12 Context: Analyzing overlap between existing AD Registry (ADR-008) and proposed Build-Time Discovery (ADR-053)
Executive Summary
Key Finding: ADR-008's RegistryToolLogic is more powerful than ADR-053's REST API approach and should be the foundation for build-time metadata generation.
Recommendation: Integrate ADR-008 and ADR-053 by using RegistryToolLogic as the data source for build-time metadata generation, instead of querying REST API.
Side-by-Side Comparison
| Aspect | ADR-008 (Registry) | ADR-053 (Build-Time Discovery) | Winner |
|---|---|---|---|
| Status | ✅ Implemented (v1.23.0) | ⏸️ Proposed | ADR-008 |
| Data Source | Direct JDBC → AD tables | REST API → /models/{tableName}/yaml or $metadata |
ADR-008 |
| Metadata Scope | Tables, Columns, Windows, Tabs, Processes, References, Elements, Patterns | Tables, Columns (from OData schema) | ADR-008 |
| Relationships | Full traversal (Window→Tab→Field→Column) | Limited (OData navigation properties) | ADR-008 |
| Patterns | Document, master-data, transaction-line | Not included | ADR-008 |
| Translations | Multi-language via _trl tables |
Not supported | ADR-008 |
| Runtime Queries | Yes (direct JDBC) | No (embedded metadata) | ADR-053 |
| Offline Capability | No (requires DB connection) | Yes (metadata in JAR) | ADR-053 |
| Performance | Fast (direct DB), runtime cost | Zero runtime cost (pre-generated) | ADR-053 |
| Freshness | Always current | Stale after build | ADR-008 |
| Build Dependency | None | Requires iDempiere/DB at build time | ADR-008 |
| Security | Direct AD access (bypasses REST) | Respects REST API security | ADR-053 |
| Implementation | RegistryToolLogic class | Maven plugin + metadata generation | ADR-008 |
Capabilities Comparison
ADR-008 RegistryToolLogic Methods
// Statistics
ToolResult getStatistics()
// Tables
ToolResult listTables(String pattern, boolean customOnly, Integer limit, String language)
ToolResult describeTable(String tableName, String language)
ToolResult listColumns(String tableName) // ✅ Already used in ChatToolProvider!
// Windows
ToolResult listWindows(String pattern, Integer limit)
// Processes
ToolResult listProcesses(String pattern, Integer limit)
// References
ToolResult listReferences(String pattern, Integer limit)
// Search
ToolResult search(String query) // Cross-entity SQL search
ToolResult semanticSearch(String query, String sourceType, Integer limit) // RAG-powered!
ToolResult smartSearch(String query, Integer limit) // Intelligent strategy selection
// RAG Status
boolean isRagAvailable()
ToolResult getRagStatus()
Additional Features:
- Translation support - Returns localized names/descriptions via
_trltables - Relationship mapping - Window/Tab usage, parent/child links
- Pattern recognition - Identifies document vs master-data vs transaction tables
- Context enrichment - Adds hints and related tables
- Access level decoding - Human-readable access levels
- Reference type mapping - DisplayType to human-readable names
ADR-053 Proposed Capabilities
// Maven Plugin (proposed)
ModelDiscoveryMojo.execute()
- Fetch table list from REST API
- Download YAML/EDMX schemas
- Generate model-metadata.json
- Embed in JAR
// Runtime (proposed)
void loadModelMetadata() // Load embedded JSON
String getColumnsForTable(String tableName) // Extract from metadata
Features:
- Build-time generation - Metadata embedded in JAR
- Offline capability - No runtime DB/API required
- Type safety - Generated from OpenAPI schemas
- Standards-based - OData EDMX format
Evidence of ADR-008's Power
Proof: Already Used in ChatToolProvider Fix
The searchRecords() hallucination bug was fixed using ADR-008:
// ChatToolProvider.java (src/main/java/org/idempiere/cli/chatapi/tool/impl/ChatToolProvider.java:71)
@Inject
org.idempiere.cli.ai.shared.RegistryToolLogic registryLogic;
@Tool("Get the column/field names and metadata for an iDempiere table. " +
"Use this BEFORE searchRecords to discover actual column names. " +
"Returns column names, types, and descriptions to prevent hallucinations.")
public String getTableColumns(
@P("Table or model name (e.g., 'M_Product')") String tableName) {
log.info("Tool: getTableColumns - table=" + tableName);
org.idempiere.cli.ai.shared.ToolResult result = registryLogic.listColumns(tableName);
return result.toJson();
}
This proves:
- ADR-008 already provides exactly what ADR-053 needs
- The integration is trivial (already working!)
- No REST API needed
What RegistryToolLogic.listColumns() Returns
SELECT c.ad_column_id, c.columnname, c.name, c.description,
r.name as reference_name, c.fieldlength, c.ismandatory, c.iskey
FROM ad_column c
JOIN ad_table t ON c.ad_table_id = t.ad_table_id
LEFT JOIN ad_reference r ON c.ad_reference_id = r.ad_reference_id
WHERE t.tablename = ?
AND c.isactive='Y'
ORDER BY c.seqno, c.columnname
Result:
{
"success": true,
"message": "Found 45 column(s) for M_Product",
"data": {
"table": "M_Product",
"count": 45,
"columns": [
{
"id": 3668,
"columnName": "M_Product_ID",
"name": "Product",
"description": "Product, Service, Item",
"reference": "ID",
"length": 10,
"mandatory": true,
"key": true
},
{
"id": 3670,
"columnName": "M_Product_Category_ID",
"name": "Product Category",
"description": "Category of a Product",
"reference": "Table Direct",
"length": 10,
"mandatory": true,
"key": false
}
// ... 43 more columns
]
}
}
This is EXACTLY what LLM needs to prevent hallucinations!
ADR-008's Advantages Over REST API
1. Comprehensive Metadata
ADR-008 (Direct JDBC):
describeTable("M_Product", "de_DE")
// Returns:
// - Table metadata (ID, name, description, help, flags)
// - ALL columns (45 fields with types, lengths, descriptions)
// - Default window (ID, name, description)
// - ALL windows/tabs using this table (with parent/child relationships)
// - Column count, access level, entity type
// - Localized German translations
// - Hints and related tables (via ADContextService)
ADR-053 (REST API):
GET /models/M_Product/yaml
// Returns:
// - Table schema (columns with types)
// - Limited metadata (no window info, no relationships, no translations)
2. Pattern Recognition
ADR-008 knows:
- Document tables (DocStatus, DocAction, Processed)
- Master data (Value, Name, Description)
- Transaction lines (Parent_ID, Line, Qty)
- Audit columns (Created, Updated, CreatedBy, UpdatedBy)
- Multi-tenancy (AD_Client_ID, AD_Org_ID)
ADR-053: No pattern awareness
3. Relationship Traversal
ADR-008 provides:
{
"table": "M_Product",
"windowsAndTabs": [
{
"windowId": 140,
"windowName": "Product",
"tabId": 180,
"tabName": "Product",
"tabLevel": 0,
"isReadOnly": false
},
{
"windowId": 143,
"windowName": "Product Info",
"tabId": 495,
"tabName": "Product",
"tabLevel": 0,
"linkColumn": "M_Product_ID",
"parentTabName": "Business Partner"
}
],
"usedInWindowCount": 2
}
ADR-053: Not available
4. Semantic Search (RAG Integration)
ADR-008 has:
semanticSearch("tables related to sales", null, 10)
// Uses vector embeddings to find C_Order, C_OrderLine, M_InOut, etc.
// Even without exact keyword matches!
ADR-053: No semantic capability
Integration Strategy: Best of Both Worlds
Proposed Unified Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Maven Build Phase │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ModelDiscoveryMojo (custom Maven plugin) │
│ ├─ Connect to iDempiere database (JDBC) │
│ │ Uses: ${idempiere.db.url} from config │
│ │ │
│ ├─ Instantiate RegistryToolLogic │
│ │ (reuse existing ADR-008 infrastructure!) │
│ │ │
│ ├─ Extract comprehensive metadata: │
│ │ └─ registryLogic.listTables(null, false, null) │
│ │ For each table: │
│ │ ├─ registryLogic.describeTable(tableName) │
│ │ ├─ registryLogic.listColumns(tableName) │
│ │ └─ Get windows/tabs, patterns, relationships │
│ │ │
│ ├─ Generate enhanced model-metadata.json │
│ │ Structure: │
│ │ { │
│ │ "generatedAt": "2025-12-12T10:00:00Z", │
│ │ "idempiereVersion": "13.0.0", │
│ │ "tables": { │
│ │ "M_Product": { │
│ │ "id": 208, │
│ │ "name": "Product", │
│ │ "description": "Product, Service, Item", │
│ │ "accessLevel": "Client+Organization", │
│ │ "pattern": "master-data", │
│ │ "defaultWindowId": 140, │
│ │ "columns": { │
│ │ "M_Product_ID": { │
│ │ "id": 3668, │
│ │ "type": "ID", │
│ │ "referenceId": 13, │
│ │ "mandatory": true, │
│ │ "key": true │
│ │ }, │
│ │ "M_Product_Category_ID": { │
│ │ "id": 3670, │
│ │ "name": "Product Category", │
│ │ "type": "Table Direct", │
│ │ "referenceId": 19, │
│ │ "mandatory": true, │
│ │ "foreignTable": "M_Product_Category" │
│ │ } │
│ │ }, │
│ │ "windows": [ │
│ │ {"id": 140, "name": "Product", "type": "Maintain"} │
│ │ ] │
│ │ } │
│ │ }, │
│ │ "patterns": { │
│ │ "document": ["C_Order", "C_Invoice", ...], │
│ │ "master-data": ["M_Product", "C_BPartner", ...] │
│ │ } │
│ │ } │
│ │ │
│ └─ Write to target/classes/model-metadata.json │
│ │
└─────────────────────────────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────┐
│ Runtime - ChatToolProvider │
├─────────────────────────────────────────────────────────────────┤
│ │
│ @Inject │
│ RegistryToolLogic registryLogic; // For runtime queries │
│ │
│ private Map<String, TableMetadata> embeddedMetadata; │
│ │
│ @PostConstruct │
│ void loadModelMetadata() { │
│ InputStream is = getClass() │
│ .getResourceAsStream("/model-metadata.json"); │
│ this.embeddedMetadata = parseMetadata(is); │
│ log.info("Loaded metadata for " + embeddedMetadata.size() + │
│ " tables"); │
│ } │
│ │
│ @Tool("Search records with OData filter. " + │
│ "Available columns for M_Product: " + │
│ "M_Product_ID, Value, Name, M_Product_Category_ID, ...") │
│ String searchRecords(String tableName, String filter) { │
│ // LLM gets column names directly in tool description! │
│ // Embedded metadata used for validation │
│ return apiFactory.models().modelsTableNameGet(...); │
│ } │
│ │
│ @Tool("Get columns for a table (runtime fallback)") │
│ String getTableColumns(String tableName) { │
│ // First check embedded metadata │
│ if (embeddedMetadata.containsKey(tableName)) { │
│ return embeddedMetadata.get(tableName).toJson(); │
│ } │
│ // Fallback to runtime query (handles new tables) │
│ return registryLogic.listColumns(tableName).toJson(); │
│ } │
│ │
└─────────────────────────────────────────────────────────────────┘
Implementation Steps
Phase 1: Maven Plugin (Build-Time)
- Create
ModelDiscoveryMojoin new Maven plugin module - Inject
RegistryToolLogicvia CDI or direct instantiation - Extract metadata for all tables using ADR-008 methods
- Generate comprehensive
model-metadata.json - Copy to
target/classes/for JAR embedding
Phase 2: Runtime Integration (ChatToolProvider)
- Load embedded metadata at
@PostConstruct - Inject column names into
@Tooldescriptions dynamically - Keep
RegistryToolLogicinjection for runtime fallback - Implement hybrid approach: embedded first, runtime fallback
Phase 3: Advanced Features
- Generate per-table
@Toolmethods from metadata - Create OData filter builders with column validation
- Add pattern-based tool suggestions
- Implement metadata refresh command
Benefits of Unified Approach
✅ Combines Strengths
| Feature | Source |
|---|---|
| Comprehensive metadata | ADR-008 (RegistryToolLogic) |
| Offline capability | ADR-053 (embedded JSON) |
| Relationship traversal | ADR-008 |
| Zero runtime queries | ADR-053 |
| Pattern recognition | ADR-008 |
| Type safety | ADR-053 |
| Semantic search | ADR-008 (RAG) |
| Standards-based | ADR-053 (OData) |
✅ Eliminates Weaknesses
| Weakness | How Unified Approach Fixes It |
|---|---|
| ADR-008: Requires runtime DB | Embed metadata at build time |
| ADR-053: REST API overhead | Use direct JDBC via RegistryToolLogic |
| ADR-053: Limited metadata | Use ADR-008's comprehensive queries |
| ADR-008: Runtime cost | Pre-generate and embed |
| ADR-053: Build dependency | Use existing DB config (already needed) |
✅ Reuses Proven Infrastructure
- RegistryToolLogic - Already in production (v1.23.0)
- Already integrated - ChatToolProvider uses it now!
- Battle-tested - JDBC queries are production-ready
- No new code - Just orchestrate existing methods
Migration Path
Current State (After searchRecords Fix)
// Runtime queries for every table lookup
@Tool("Get table columns...")
public String getTableColumns(String tableName) {
return registryLogic.listColumns(tableName).toJson();
}
Step 1: Add Build-Time Generation
<!-- pom.xml -->
<plugin>
<artifactId>model-discovery-maven-plugin</artifactId>
<executions>
<execution>
<phase>generate-resources</phase>
<goals><goal>discover</goal></goals>
</execution>
</executions>
</plugin>
Step 2: Load Embedded Metadata
@PostConstruct
void init() {
loadEmbeddedMetadata(); // From JAR
// Keep registryLogic for fallback
}
Step 3: Hybrid Queries
@Tool("Get table columns...")
public String getTableColumns(String tableName) {
// Try embedded first (fast, offline)
if (metadata.containsKey(tableName)) {
return metadata.get(tableName).toJson();
}
// Fallback to runtime (handles new tables)
return registryLogic.listColumns(tableName).toJson();
}
Step 4: Dynamic Tool Descriptions
// Generate tool description with actual column names
String toolDesc = "Search " + tableName + " records. " +
"Available columns: " +
String.join(", ", metadata.get(tableName).getColumnNames());
Recommendation
Update ADR-053 to specify:
- Data Source: Use RegistryToolLogic (ADR-008) instead of REST API
- Build Plugin: Call RegistryToolLogic methods directly via JDBC
- Metadata Format: Enhanced JSON including patterns, relationships, windows
- Runtime Strategy: Hybrid (embedded first, runtime fallback)
- Integration Point: ChatToolProvider already uses RegistryToolLogic
Benefits:
- ✅ More comprehensive metadata
- ✅ Simpler implementation (reuse existing code)
- ✅ No REST API dependency at build time
- ✅ Includes patterns, relationships, translations
- ✅ Already proven to work (current fix uses it!)
Action Items:
- Update ADR-053 with integration strategy
- Create ModelDiscoveryMojo plugin
- Generate metadata at build time
- Update ChatToolProvider to load embedded metadata
- Document hybrid approach in ADR-053
Conclusion
ADR-008 is not overlapping with ADR-053 — it's the FOUNDATION for ADR-053!
Instead of querying REST API at build time, we should:
- Use RegistryToolLogic to extract comprehensive AD metadata
- Generate enhanced model-metadata.json at Maven build time
- Embed in JAR for offline capability
- Load at runtime to inject into LLM tool descriptions
- Keep runtime fallback for schema evolution
This unified approach gives us:
- All of ADR-053's goals (offline, no runtime queries, embedded metadata)
- All of ADR-008's power (relationships, patterns, translations, semantic search)
- Simpler implementation (reuse existing code)
- Already partially implemented (ChatToolProvider uses RegistryToolLogic)
Next Step: Update ADR-053 to document this integration strategy.