Dependency Management
How the resources/schemas/dependencies.yaml graph drives UI pre-selection, the missing-deps banner, Compose depends_on wiring, and cycle detection.
Schema
The file is a map under a single services: key. Each service may declare a requires: list. Every list entry is a dependency rule:
services:
ui-backend:
requires:
- oneOf: [mysql, oracle]
mandatory: true
- service: hazelcast
mandatory: true
condition: service_started
- service: prometheus
mandatory: false
autoselect: true
- service: clickhouse
mandatory: false
disablechange: true
condition: service_healthy
| Flag | Effect |
|---|---|
|
Fixed dependency on a single other service. |
|
Exactly one of the listed services must be selected. Resolved from the current |
|
Without this dependency present in |
|
Informational only. The dep graph renders the edge; absence does not block the build. |
|
The Active Choices picker pre-checks the dependency when its parent is selected. Users can still uncheck it unless |
|
The checkbox for this service is rendered as read-only. Used for infrastructure that should never be toggled out ( |
|
Translated into |
How createEnvStep consumes the graph
The resolution pipeline inside createEnvStep (and mirrored in updateEnvStep):
-
Load
dependencies.yaml→utils.toBasic→ plain Map. -
Resolve every
oneOfagainstDATABASE_TYPE. An entry likeoneOf: [mysql, oracle]becomesservice: mysql(or whatever was picked). -
Walk the requires-tree starting from
SELECTED_SERVICES, collecting mandatory deps that are not yet selected. -
If any are missing, show the missing-deps banner in
FINAL_PREVIEW. Block build unlessALLOW_MISSING_DEPS=true. -
Emit
depends_on:into the rendered compose file for every resolved edge whose target is inSELECTED_SERVICES. -
Run cycle detection (DFS) across the effective graph; print a warning if a cycle is detected. The build continues — Docker Compose itself will refuse to start a circular
depends_on.
utils.findMissingMandatoryDeps(selectedServices, databaseType) is the public entry point used by the UI banner.
The missing-deps banner
When findMissingMandatoryDeps returns a non-empty map, FINAL_PREVIEW renders a red banner listing each requesting service and its missing parent:
⚠ ui-backend requires: hazelcast (not selected)
⚠ provision-api requires: mysql (not selected — pick one of mysql/oracle)
The user has two options:
-
Add the missing services to
SELECTED_SERVICES(preferred). -
Tick
ALLOW_MISSING_DEPSto acknowledge the gap and let the build proceed.
ALLOW_MISSING_DEPS only hides the banner; it does not manufacture phantom services. If the missing dep really is required at runtime, the stack will fail at compose up or health-check time.
UI reflection
-
qaUiScripts.depGraphScript()renders the live SVG graph derived fromdependencies.yaml+ current selection +DATABASE_TYPE. -
Clusters in the graph are styled per "family" (databases, monitoring, UI, configuration, emulators). Recent commits rearranged clusters for clarity:
392adc6splitft-device-network-service,ui-ai-agent, andpostgresinto dedicated clusters;a0eef12movedft-configs-uiinto the Configuration cluster. -
Nodes that are
disablechange: truerender with a lock glyph; nodes that aremandatory: truerender with a solid border.
Cycle detection
Cycle detection is DFS-based and informational — it warns but does not block. The justification: some compose-level cycles are resolved by condition: service_healthy plus real healthchecks, and blocking such envs would be more disruptive than letting Compose reject the deploy with a clearer error.
In practice, a new circular dependency is virtually always a bug. Treat the warning as a build failure unless you have a specific reason to proceed.
Example: adding a rule
Scenario: my-service needs MySQL (or Oracle), plus hazelcast healthy before it can start.
services:
my-service:
requires:
- oneOf: [mysql, oracle]
mandatory: true
- service: hazelcast
mandatory: true
condition: service_healthy
After saving the file, re-run Create-Environment to verify:
-
DATABASE_TYPE=mysql,SELECTED_SERVICESincludesmy-servicebut nothazelcast→ banner fires. -
Add
hazelcast→ banner clears; dep-graph shows an edge. -
Rendered compose under
MASTER_ENVS_DIR/<env>/docker-compose.ymlcontains:my-service: depends_on: hazelcast: condition: service_healthy
Related helpers
-
utils.findMissingMandatoryDeps(services, dbType)— UI banner source. -
utils.toBasic(yaml)— collapses the parsedLazyMapto a plain Map before any downstream processing. -
qaUiScripts.allowMissingDepsScript()— the opt-in checkbox. Only rendered when deps are missing (see commit2026-04-20-dep-graph-missing-deps-false-bannerfor the bugfix that scoped the banner properly).