Skip to content

Instantly share code, notes, and snippets.

@ChakshuGautam
Last active February 26, 2026 06:47
Show Gist options
  • Select an option

  • Save ChakshuGautam/6cacb1b6ac5d2db14fac1c5789680f27 to your computer and use it in GitHub Desktop.

Select an option

Save ChakshuGautam/6cacb1b6ac5d2db14fac1c5789680f27 to your computer and use it in GitHub Desktop.

egov-workflow-v2: Hardcoded STATE_LEVEL_TENANT_ID Prevents Multi-Tenant Support

Problem

egov-workflow-v2 has a hardcoded state.level.tenant.id configuration property (set via STATE_LEVEL_TENANT_ID env var) that is used for MDMS master data lookups at startup and during escalation. This means the workflow service can only serve one state tenant root — any workflow definitions or MDMS data stored under a different root tenant are invisible.

Affected Code

WorkflowConfig.java:94-95 — Config injection:

@Value("${state.level.tenant.id}")
private String stateLevelTenantId;

MDMSService.java:90 — Escalation MDMS call:

public Object mDMSCall(RequestInfo requestInfo){
    MdmsCriteriaReq mdmsCriteriaReq = getMDMSRequest(requestInfo, workflowConfig.getStateLevelTenantId());
    // ↑ Always queries hardcoded tenant (e.g., "pg"), ignoring the request's actual tenant
}

MDMSService.java:101-102 — Startup bean init:

public Object getBusinessServiceMDMS(){
    MDC.put(WorkflowConstants.TENANTID_MDC_STRING, workflowConfig.getStateLevelTenantId());
    MdmsCriteriaReq mdmsCriteriaReq = getBusinessServiceMDMSRequest(new RequestInfo(), workflowConfig.getStateLevelTenantId());
    // ↑ Builds stateLevelMapping only from the hardcoded tenant at startup
}

BusinessServiceRepository.java:190,195 — Filter function (dead code in V2, but alive in V1):

if(isStatelevel){
    if(tenantId.equalsIgnoreCase(config.getStateLevelTenantId())){
        // ↑ Only includes workflow defs whose tenantId matches hardcoded value
        filteredBusinessService.add(businessService);
    }
}

Impact

When deploying DIGIT for multiple states or creating new tenant hierarchies:

  1. Workflow definitions from non-default roots are invisible — If PGR workflow is created under statea but STATE_LEVEL_TENANT_ID=pg, the stateLevelMapping bean won't include it.

  2. Escalation only works for the hardcoded tenantEscalationService calls mDMSCall() which always fetches auto-escalation config from the hardcoded tenant.

  3. Cannot onboard new states without restarting the service — Even if you bootstrap a new tenant root with all the correct MDMS data, the workflow service will never see it because the mapping is built once at startup from the hardcoded tenant.

How Other Services Handle This

The same codebase already has the right pattern. MultiStateInstanceUtil.getStateLevelTenant(tenantId) dynamically extracts the root from any tenant ID:

// From MultiStateInstanceUtil (shared library, already imported in MDMSService)
public String getStateLevelTenant(String tenantId) {
    String[] tenantArray = tenantId.split("\\.");
    return tenantArray[0]; // "pg.citya" → "pg", "statea.cityb" → "statea"
}

PGR, for example, already uses this pattern everywhere:

// MDMSUtils.java in pgr-services
MdmsCriteriaReq mdmsCriteriaReq = getMDMSRequest(
    requestInfo, 
    multiStateInstanceUtil.getStateLevelTenant(tenantId)  // ← Dynamic
);

Proposed Fix

  1. MDMSService.mDMSCall() — Accept tenantId parameter, derive root dynamically:

    public Object mDMSCall(RequestInfo requestInfo, String tenantId) {
        String stateTenant = centralInstanceUtil.getStateLevelTenant(tenantId);
        MdmsCriteriaReq req = getMDMSRequest(requestInfo, stateTenant);
        ...
    }
  2. MDMSService.getBusinessServiceMDMS() — At minimum, keep the hardcoded default for startup but also expose a tenant-aware variant for runtime use.

  3. BusinessServiceRepository.filterBusinessServices() — Derive state level from the business service's own tenant ID rather than comparing against the hardcoded config:

    String derivedStateTenant = centralInstanceUtil.getStateLevelTenant(tenantId);
    if(tenantId.equalsIgnoreCase(derivedStateTenant)){
        // This is a state-level definition
    }
  4. Keep state.level.tenant.id as optional fallback — for escalation batch jobs that don't have a request-scoped tenant context, the config value serves as a default.

Reproduction

# 1. Bootstrap a new tenant root
curl -X POST .../tenant_bootstrap -d '{"target_tenant": "statea", "source_tenant": "pg"}'

# 2. Create workflow definition under "statea"
curl -X POST .../egov-workflow-v2/egov-wf/businessservice/_create -d '{"tenantId": "statea", ...}'

# 3. Try to use it — fails because workflow service only knows about "pg"
curl -X POST .../pgr-services/v2/request/_create -d '{"tenantId": "statea.city1", ...}'
# Result: workflow not found

Environment

  • DIGIT Core: master branch @ cfcfe60
  • Docker Compose deployment with STATE_LEVEL_TENANT_ID=pg
  • All Java services (idgen, user, HRMS, PGR, workflow) receive this env var
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment