ADR-062: Centralized ID Management Integration

<!-- MADR 3.0 Template - Markdown Any Decision Records -->

Status

Proposed

Date

2025-12-18

Deciders

Context and Problem Statement

When creating Application Dictionary elements (tables, columns, windows, processes, etc.) through iDempiere Hub (CLI, Chat API, or MCP Server), unique IDs must be assigned to prevent conflicts across distributed development teams. iDempiere's Centralized ID Management system provides a web service at developer.idempiere.com that coordinates ID allocation based on entity type registration.

Currently, iDempiere Hub generates AD elements without integrating with the centralized ID server, which means:

Each implementor should be able to configure their own centralized ID server for their custom entity types (e.g., XX_, YY_, ZZ_ prefixes).

Decision Drivers

Considered Options

  1. No Integration - Continue using manual/sequential IDs
  2. Client-Side Integration - Hub directly calls centralized ID server
  3. iDempiere REST API Proxy - Delegate ID allocation to iDempiere's existing mechanism
  4. Hybrid Approach - Support both centralized and local modes with configuration

Decision Outcome

Chosen option: "Hybrid Approach", because it provides maximum flexibility for different deployment scenarios while maintaining compatibility with iDempiere's standard centralized ID mechanism.

Confirmation

Pros and Cons of the Options

Option 1: No Integration

Continue current approach with manual/hardcoded IDs.

Option 2: Client-Side Integration

iDempiere Hub directly calls centralized ID server REST API.

Option 3: iDempiere REST API Proxy

Delegate ID allocation to iDempiere's built-in mechanism via REST API.

Option 4: Hybrid Approach (Chosen)

Support multiple modes: centralized (client-side HTTP), proxied (via iDempiere), or local (no server).

Architecture

Configuration

Smart Auto-Detection Strategy:

The system uses a 3-tier decision model:

  1. Entity Type Detection (Primary):

    • U (System), D (Dictionary) → Always use local mode (no centralized needed)
    • XX, YY, ZZ, etc. (Custom) → Require centralized mode or explicit override
  2. Configuration Check (Secondary):

    • If centralized credentials configured → Use centralized mode
    • If --local-ids flag present → Use local mode (development override)
    • If neither and custom entity type → Error with guidance
  3. Interface-Specific Behavior (Tertiary):

    • CLI: Can prompt user interactively if ambiguous
    • MCP/Chat API: Must have configuration pre-set, cannot prompt

Environment Variables:

# Optional: Force specific mode (overrides auto-detection)
CENTRALIZED_ID_MODE=auto|centralized|proxied|local
# - auto: Smart detection based on entity type (default)
# - centralized: Always use centralized server
# - proxied: Always proxy via iDempiere REST API
# - local: Always use local sequential IDs (development only)

# Centralized mode credentials
CENTRALIZED_ID_SERVER_URL=https://developer.idempiere.com/cgi-bin/get_ID
CENTRALIZED_ID_PROJECT=Adempiere
CENTRALIZED_ID_USER=your_github_username
CENTRALIZED_ID_PASSWORD=your_assigned_password

# Proxied mode (via iDempiere REST API)
IDEMPIERE_URL=http://localhost:8080
IDEMPIERE_TOKEN=your_jwt_token

# Local mode - no configuration needed

Command-Line Flag (CLI only):

# Force local mode for development/testing
idempiere-hub registry create-table XX_TestTable --local-ids

# This bypasses centralized ID requirement (warning displayed)

ID Allocation Flow

┌─────────────────────────────────────────────────────────────────────┐
│  iDempiere Hub (CLI / Chat API / MCP Server)                       │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │  Generator: createTable, createColumn, createWindow, etc.     │ │
│  └──────────────────────────┬────────────────────────────────────┘ │
│                             │                                       │
│  ┌──────────────────────────▼────────────────────────────────────┐ │
│  │  CentralizedIdService                                          │ │
│  │  ├─ Mode detection (centralized/proxied/local)                │ │
│  │  ├─ Entity type resolution (U=System, XX=Custom)              │ │
│  │  └─ ID allocation strategy selection                          │ │
│  └──────────────────┬────────────┬──────────────┬────────────────┘ │
└────────────────────┼────────────┼──────────────┼──────────────────┘
                     │            │              │
       ┌─────────────▼──┐  ┌──────▼──────┐  ┌───▼────────┐
       │ Centralized    │  │ iDempiere   │  │ Local      │
       │ HTTP Client    │  │ REST Proxy  │  │ Sequential │
       └────────┬───────┘  └──────┬──────┘  └────────────┘
                │                 │
    ┌───────────▼─────────────────▼───────────────┐
    │  developer.idempiere.com/cgi-bin/get_ID     │
    │  OR custom implementor server               │
    └─────────────────────────────────────────────┘

API Contract

public interface CentralizedIdService {

    /**
     * Allocate unique ID for AD table
     * @param tableName AD table name (e.g., &quot;AD_Column&quot;)
     * @param entityType Entity type (e.g., &quot;U&quot;, &quot;XX&quot;)
     * @param comment Description for audit trail
     * @return Allocated ID
     * @throws IdAllocationException if allocation fails
     */
    int allocateId(String tableName, String entityType, String comment)
        throws IdAllocationException;

    /**
     * Validate entity type against centralized registry
     * @param entityType Entity type code
     * @return true if registered to current user/project
     */
    boolean validateEntityType(String entityType);

    /**
     * Check if centralized ID mode is enabled
     */
    boolean isCentralizedMode();

    /**
     * Get current mode
     */
    IdAllocationMode getMode();
}

enum IdAllocationMode {
    LOCAL_SYSTEM,          // System entity types (U, D) - always local
    CENTRALIZED_REQUIRED,  // Custom entity types (XX, YY, ZZ) - needs server
    LOCAL_OVERRIDE         // User forced local via --local-ids flag
}

enum IdAllocationStrategy {
    CENTRALIZED,  // Direct HTTP to centralized server
    PROXIED,      // Via iDempiere REST API
    LOCAL         // Sequential IDs (for LOCAL_SYSTEM or LOCAL_OVERRIDE modes)
}

HTTP Request Format (Centralized Mode)

GET /cgi-bin/get_ID?PROJECT=Adempiere&amp;USER=username&amp;PASSWORD=password&amp;TABLE=AD_Column&amp;COMMENT=XX_CustomField HTTP/1.1
Host: developer.idempiere.com

Response:

1000000

Supported AD Tables

Per Centralized ID Management wiki, the following 41+ tables are supported:

Entity Type Validation & Smart Detection

// Example: Creating table with XX_ prefix
String tableName = &quot;XX_ProductRating&quot;;
String entityType = &quot;XX&quot;; // Custom implementor entity type

// 1. Auto-detect mode based on entity type
IdAllocationMode mode = centralizedIdService.detectMode(entityType);

switch (mode) {
    case LOCAL_SYSTEM:
        // System entity types (U, D) always use local IDs
        logger.info(&quot;Using local ID allocation for system entity type: &quot; + entityType);
        return localIdGenerator.nextId(&quot;AD_Table&quot;);

    case CENTRALIZED_REQUIRED:
        // Custom entity types (XX, YY, ZZ, etc.) require centralized server
        if (!centralizedIdService.isConfigured()) {
            throw new CentralizedIdRequiredException(
                &quot;Entity type &#39;&quot; + entityType + &quot;&#39; requires centralized ID allocation.\n&quot; +
                &quot;\n&quot; +
                &quot;Options:\n&quot; +
                &quot;1. Configure centralized ID server:\n&quot; +
                &quot;   export CENTRALIZED_ID_SERVER_URL=https://developer.idempiere.com/cgi-bin/get_ID\n&quot; +
                &quot;   export CENTRALIZED_ID_USER=your_github_username\n&quot; +
                &quot;   export CENTRALIZED_ID_PASSWORD=your_password\n&quot; +
                &quot;\n&quot; +
                &quot;2. Use local IDs for development (NOT for production):\n&quot; +
                &quot;   idempiere-hub registry create-table &quot; + tableName + &quot; --local-ids\n&quot; +
                &quot;\n&quot; +
                &quot;3. Get credentials at: https://groups.google.com/g/idempiere&quot;
            );
        }

        // Validate entity type is registered
        if (!centralizedIdService.validateEntityType(entityType)) {
            throw new EntityTypeNotRegisteredException(
                &quot;Entity type &#39;&quot; + entityType + &quot;&#39; not registered to user: &quot; +
                centralizedIdService.getCurrentUser() + &quot;\n&quot; +
                &quot;Register at: &quot; + centralizedIdService.getServerUrl().replace(&quot;/get_ID&quot;, &quot;&quot;)
            );
        }

        // Allocate from centralized server
        return centralizedIdService.allocateId(&quot;AD_Table&quot;, entityType,
            &quot;Custom Product Rating Table&quot;);

    case LOCAL_OVERRIDE:
        // User explicitly requested local mode via --local-ids flag
        logger.warn(&quot;⚠️  Using LOCAL ID allocation for custom entity type: &quot; + entityType);
        logger.warn(&quot;⚠️  This is for DEVELOPMENT ONLY - do not deploy to production!&quot;);
        return localIdGenerator.nextId(&quot;AD_Table&quot;);
}

Migration Script Generation

Generated migration scripts must include centrally-assigned IDs:

-- Generated by iDempiere Hub with Centralized ID allocation
-- Entity Type: XX
-- User: johndoe
-- Date: 2025-12-18

-- ID 1000000 allocated from developer.idempiere.com
INSERT INTO AD_Table (AD_Table_ID, Name, TableName, EntityType, ...)
VALUES (1000000, &#39;Product Rating&#39;, &#39;XX_ProductRating&#39;, &#39;XX&#39;, ...);

-- ID 1000001 allocated from developer.idempiere.com
INSERT INTO AD_Column (AD_Column_ID, AD_Table_ID, ColumnName, EntityType, ...)
VALUES (1000001, 1000000, &#39;XX_ProductRating_ID&#39;, &#39;XX&#39;, ...);

Usage Examples by Scenario

Scenario 1: System Developer (U entity type)

Use Case: Core iDempiere contributor adding a system table

# No centralized config needed - auto-detects U entity type
idempiere-hub registry create-table C_PaymentMethod --entity-type U

# Output:
# ✅ Using local ID allocation for system entity type: U
# Created AD_Table with ID: 200001 (local sequence)

MCP/Chat API:

User: &quot;Create a system table C_PaymentMethod&quot;
Claude: [Uses create-table tool, auto-detects entity type U, no config needed]

Scenario 2: Plugin Developer (XX entity type) - Configured

Use Case: Developing XX_ProductRating for production deployment

# Configure centralized ID server (one-time setup)
export CENTRALIZED_ID_SERVER_URL=https://developer.idempiere.com/cgi-bin/get_ID
export CENTRALIZED_ID_USER=johndoe
export CENTRALIZED_ID_PASSWORD=secret123
export CENTRALIZED_ID_PROJECT=Adempiere

# Create table - automatically uses centralized IDs
idempiere-hub registry create-table XX_ProductRating --entity-type XX

# Output:
# 🌐 Connecting to centralized ID server...
# ✅ Allocated AD_Table ID: 1000500 from developer.idempiere.com
# ✅ Allocated AD_Column IDs: 1000501-1000510
# 📝 Migration script: migrations/XX_ProductRating.sql

MCP/Chat API:

# Pre-configured via environment variables
User: &quot;Create a product rating table with XX entity type&quot;
Claude: [Uses create-table tool, detects XX entity type, uses centralized server]

Scenario 3: Plugin Developer (XX entity type) - NOT Configured

Use Case: Quick local testing without centralized server

# Try to create without config
idempiere-hub registry create-table XX_TestTable --entity-type XX

# Output:
# ❌ Error: Entity type &#39;XX&#39; requires centralized ID allocation.
#
# Options:
# 1. Configure centralized ID server:
#    export CENTRALIZED_ID_SERVER_URL=https://developer.idempiere.com/cgi-bin/get_ID
#    export CENTRALIZED_ID_USER=your_github_username
#    export CENTRALIZED_ID_PASSWORD=your_password
#
# 2. Use local IDs for development (NOT for production):
#    idempiere-hub registry create-table XX_TestTable --local-ids
#
# 3. Get credentials at: https://groups.google.com/g/idempiere

# Use local override for development
idempiere-hub registry create-table XX_TestTable --entity-type XX --local-ids

# Output:
# ⚠️  Using LOCAL ID allocation for custom entity type: XX
# ⚠️  This is for DEVELOPMENT ONLY - do not deploy to production!
# Created AD_Table with ID: 200002 (local sequence)

MCP/Chat API:

User: &quot;Create XX_TestTable&quot;
Claude: &quot;I cannot create this table because entity type XX requires centralized ID allocation,
but no centralized ID server is configured.

Would you like to:
1. Configure the centralized ID server (I can guide you)
2. Use local IDs for development testing only (not for production)&quot;

Scenario 4: Implementor with Custom Server (YY entity type)

Use Case: Company with their own centralized ID server

# Configure custom implementor server
export CENTRALIZED_ID_MODE=centralized
export CENTRALIZED_ID_SERVER_URL=https://ids.mycompany.com/cgi-bin/get_ID
export CENTRALIZED_ID_USER=employee1
export CENTRALIZED_ID_PASSWORD=companypass
export CENTRALIZED_ID_PROJECT=MyCompanyERP

# Create table
idempiere-hub registry create-table YY_CustomFeature --entity-type YY

# Output:
# 🌐 Connecting to custom ID server: ids.mycompany.com
# ✅ Allocated AD_Table ID: 2000100 from ids.mycompany.com

Scenario 5: CI/CD Pipeline (Validation Only)

Use Case: CI pipeline should validate without allocating real IDs

# Force local mode for CI testing
export CENTRALIZED_ID_MODE=local

# Run tests/validation
idempiere-hub registry create-table XX_TestTable --entity-type XX

# Output:
# ⚠️  CENTRALIZED_ID_MODE=local - using local IDs
# ⚠️  This build is NOT production-ready
# Created AD_Table with ID: 200003 (local sequence)

Scenario 6: MCP Server Long-Running Session

Use Case: MCP server running for hours, token might expire

# Start MCP server with centralized ID config
export CENTRALIZED_ID_SERVER_URL=https://developer.idempiere.com/cgi-bin/get_ID
export CENTRALIZED_ID_USER=johndoe
export CENTRALIZED_ID_PASSWORD=secret123

java -Dquarkus.profile=mcp -jar target/idempiere-hub-runner.jar server mcp

In Claude Desktop:

User (8am): &quot;Create table XX_Morning with entity type XX&quot;
Claude: ✅ Created with centralized ID 1000600

User (2pm): &quot;Create table XX_Afternoon with entity type XX&quot;
Claude: ✅ Created with centralized ID 1000650
# (No token expiry issues - credentials stored in env vars)

User (5pm): &quot;Create table C_SystemTable with entity type U&quot;
Claude: ✅ Created with local ID 200004
# (System entity type, no centralized server call needed)

Implementation Plan

Phase 1: Core Infrastructure (1-2 weeks)

Phase 2: Generator Integration (1 week)

Phase 3: Custom Server Support (1 week)

Phase 4: Testing & Documentation (1 week)

Error Handling

// Example error scenarios

// 1. Server unreachable
try {
    int id = centralizedIdService.allocateId(&quot;AD_Table&quot;, &quot;XX&quot;, &quot;Test&quot;);
} catch (IdAllocationException e) {
    if (e.getCause() instanceof ConnectException) {
        // Fallback to local mode or prompt user
        logger.warn(&quot;Centralized ID server unreachable, falling back to local mode&quot;);
        return localIdGenerator.nextId(&quot;AD_Table&quot;);
    }
}

// 2. Invalid credentials
catch (IdAllocationException e) {
    if (e.getMessage().contains(&quot;401&quot;) || e.getMessage().contains(&quot;403&quot;)) {
        throw new IllegalStateException(
            &quot;Invalid centralized ID credentials. &quot; +
            &quot;Check CENTRALIZED_ID_USER and CENTRALIZED_ID_PASSWORD environment variables.&quot;
        );
    }
}

// 3. Entity type not registered
catch (EntityTypeNotRegisteredException e) {
    throw new IllegalArgumentException(
        &quot;Entity type &#39;&quot; + entityType + &quot;&#39; not registered. &quot; +
        &quot;Register at: &quot; + serverUrl.replace(&quot;/get_ID&quot;, &quot;&quot;) +
        &quot;\nOr contact your system administrator.&quot;
    );
}

Security Considerations

  1. Credential Management

    • Store credentials in environment variables, not in code
    • Support credential files for CI/CD (.idempiere-credentials)
    • Warn when using local mode in production
  2. HTTPS Required

    • Enforce HTTPS for centralized server communication
    • Validate SSL certificates
    • Support custom CA certificates for private servers
  3. Audit Trail

    • Log all ID allocations with timestamp, user, table, comment
    • Include allocation source (centralized/proxied/local) in audit
    • Generate allocation report for migration review

Custom Implementor Server Setup

For organizations wanting their own centralized ID server:

1. Deploy CGI Scripts

# Clone idempiere-stuff repository
git clone https://github.com/idempiere/idempiere-stuff.git

# Deploy CGI scripts to web server
cd idempiere-stuff/org.idempiere.webstore/WEB-INF/cgi-bin
cp get_ID.cgi /var/www/cgi-bin/
chmod +x /var/www/cgi-bin/get_ID.cgi

2. Configure Database

-- Create ID allocation tracking table
CREATE TABLE id_allocation (
    id SERIAL PRIMARY KEY,
    project VARCHAR(50),
    username VARCHAR(50),
    tablename VARCHAR(50),
    allocated_id INTEGER,
    comment TEXT,
    created TIMESTAMP DEFAULT NOW()
);

3. Configure iDempiere Hub

# Point to custom server
export CENTRALIZED_ID_MODE=centralized
export CENTRALIZED_ID_SERVER_URL=https://ids.yourcompany.com/cgi-bin/get_ID
export CENTRALIZED_ID_PROJECT=YourProject
export CENTRALIZED_ID_USER=youruser
export CENTRALIZED_ID_PASSWORD=yourpassword

More Information

Practical Flow Guide

👉 See 062-practical-flow.md for detailed scenarios:

References

Future Enhancements

  1. ID Range Reservation: Reserve ID ranges for batch operations
  2. Offline Mode: Cache ID ranges for offline development
  3. Conflict Resolution: Detect and resolve ID conflicts in existing installations
  4. Entity Type Registry UI: Web interface for entity type registration
  5. Multi-Project Support: Single server managing multiple project namespaces

Future: CloudEmpiere Internal ID Management (ADR-063)

The current implementation uses HTTP client for community server communication. For CloudEmpiere internal plugins, we plan to integrate with a generic Hub Storage Adapter (ADR-063):

┌─────────────────────────────────────────────────────────────────┐
│  CentralizedIdService                                           │
│  ├─ Scope Detection: CE_* → internal, XX_* → check registry     │
│  └─ Uses: StorageAdapter OR HTTP Client                         │
└─────────────────────────────┬───────────────────────────────────┘
                              │
        ┌─────────────────────┴─────────────────────┐
        │                                           │
┌───────▼───────┐                         ┌─────────▼─────────┐
│ StorageAdapter│                         │ HTTP Client       │
│ (ADR-063)     │                         │ (Community)       │
│               │                         │                   │
│ CloudEmpiere  │                         │ developer.        │
│ internal IDs  │                         │ idempiere.com     │
└───────────────┘                         └───────────────────┘

Scope Decision Flow:

This enables:


Note: This ADR focuses on ID allocation mechanism. Actual table/element creation logic remains in existing services (TableToolLogic, GeneratorToolLogic). This is a cross-cutting concern that enhances all AD element generation tools.

Path: /docs/developers/architecture/idempiere-hub/062-centralized-id-management-integration