Module Dependency System
Overview
The module dependency system allows modules to declare relationships to other modules via depends_on in repository.yml. Dependencies are used for graph visualization, validation, cache invalidation, and release gating.
YAML Schema
Source: go/eac/core/contracts/types.go
Data Flow
repository.yml
│
▼
modules.LoadFromWorkspace()
│
▼
Registry (holds all ModuleContract)
│
├──► GetDependencyGraph() → map[string][]string (module → dependencies)
├──► GetReverseDependencyGraph() → map[string][]string (module → dependents)
└──► CalculateExecutionOrder() → ExecutionPlan (layers + flat order)
Key Files
| File | Purpose |
|---|---|
go/eac/core/contracts/types.go |
BaseContract.DependsOn - YAML parsing |
go/eac/core/contracts/modules/types.go |
ModuleContract.GetDependencies() - accessor |
go/eac/core/repository/dependencies.go |
Graph operations, execution order |
go/eac/commands/impl/validate/module-hierarchy.go |
Cycle detection |
go/eac/commands/impl/release/await-deps.go |
Release-time CI verification |
Execution Order Algorithm
File: go/eac/core/repository/dependencies.go
Uses Kahn's topological sort:
- Build in-degree map (count of unprocessed dependencies per module)
- Layer 0 = modules with in-degree 0 (no dependencies)
- Process layer, decrease in-degrees of dependents
- Repeat until all modules processed
- Detect cycles if layer is empty but modules remain
type ExecutionPlan struct {
Layers [][]string // [[eac-core], [eac-commands, r2r-cli], [ext-eac]]
ExecutionOrder []string // Flattened: [eac-core, eac-commands, r2r-cli, ext-eac]
LayerCount int
}
Cycle Detection
File: go/eac/commands/impl/validate/module-hierarchy.go
Uses DFS with recursion stack:
func validateNoCircularDependencies(reg *modules.Registry, report *moduleHierarchyReport) {
visited := make(map[string]bool)
recStack := make(map[string]bool) // Tracks current path
// DFS - if we hit a node already in recStack, we found a cycle
var detectCycle func(moniker string, path []string) bool
}
Dependency Uses
| Use Case | Description |
|---|---|
| Graph validation | Checked for cycles, missing references |
| Build order | Optional layer-sequential execution with --layered-build |
| Cache invalidation | Dependents rebuild when dependency changes |
| Release gating | Block release if dependency CI failed |
Build Behavior
Default: All modules run in parallel (up to MaxConcurrency)
--layered-build: Layer-sequential execution (Layer 0, then Layer 1, etc.)
--no-deps: Isolated build - only build specified module, no dependency resolution
CI Isolation
Each CI workflow builds its module in isolation - no cross-CI artifact downloads, no dependency resolution.
Build Command Flag
The --no-deps flag:
- Skips downloading artifacts from other CI workflows
- Skips adding transitive dependencies to execution plan
- Builds only the explicitly specified module(s)
build-module Action
The build-module action defaults to isolated mode (no-deps: true).
# Default: isolated build (no cross-CI dependencies)
- uses: ./.github/actions/build-module
with:
module: eac-commands
# Explicit: build with dependencies (for integration tests)
- uses: ./.github/actions/build-module
with:
module: r2r-installer
no-deps: 'false' # Download dependency artifacts from other CI runs
Why CI Isolation?
- Speed: Each CI runs independently, no waiting for other workflows
- Reliability: CI failures are isolated to their module
- Simplicity: No complex artifact download chains between workflows
- Correct gating: Release-time verification (await-deps) ensures dependencies passed
CI and Release Gating
CI workflows run in parallel for all changed modules. Release workflows verify dependencies before allowing release.
CI Dispatch
change-trigger.yaml
│
└──► Dispatch CI workflows in parallel
[eac-core, eac-commands, r2r-cli, ext-eac, docs, books, ...]
No layering, no waiting
Release Verification
release-ext-eac.yaml
│
└──► approve-release action
│
├──► release check-ci (verify own CI passed)
│
└──► await-dependency-ci action
│
└──► release await-deps ext-eac
│
├──► Get transitive deps: [eac-commands, eac-core]
│
├──► For each dependency:
│ ├──► Find last commit that changed it
│ ├──► Verify CI passed for that commit
│ └──► If in-progress: wait; If failed: block
│
└──► All passed → allow release
await-deps Command
File: go/eac/commands/impl/release/await-deps.go
release await-deps <module> [--timeout N] [--skip-static]
1. Load module registry
2. Get transitive dependencies of <module>
3. For each dependency:
a. Find last commit that changed dep's files (git log -1)
b. Check CI status for ci-<dep>.yaml at that commit
- Success: continue
- In-progress: wait (with timeout)
- Failed: exit 1 with error
- Not found: check if dep has CI workflow
4. All passed → exit 0
Output example:
Awaiting CI for ext-eac dependencies...
Dependencies: eac-commands, eac-core
Checking eac-commands
Last changed: abc1234 (feat: add new feature)
CI run: #12345 ✓ passed
Checking eac-core
Last changed: def5678 (fix: resolve issue)
CI run: #12340 ✓ passed
✓ All 2 dependency CI checks passed