Operators Guide

Module Dependencies & Lockfiles

Declare module dependencies and pin them to exact versions with lockfiles for reproducible builds.

Module Dependencies & Lockfiles

Reproducible Builds with Pinned Dependencies

A module can depend on other modules. These dependencies are recursively resolved and fetched by rescile before building the graph. To ensure reproducible builds, rescile supports a module.lock file which pins dependencies to exact commit hashes or checksums.

Declaring Dependencies

Dependencies are declared in the [dependencies] section of module.toml (see the module.toml Reference for full schema details). Three formats are supported:

[dependencies]
# Simple string: Git URL with optional tag (colon-separated)
base-security = "https://github.com/my-org/rescile-modules/base-security:v1.0.0"

# Detailed Git dependency with explicit tag
other-module = { git = "https://github.com/my-org/rescile-modules/other.git", tag = "v1.0.0" }

# Semver constraint matching available Git release tags
shared-lib = { git = "https://github.com/my-org/rescile-modules/shared-lib.git", version = "^1.2.0" }

# Release archive with a version template
packaged-module = { url = "https://github.com/my-org/rescile-modules/releases/download/{version}/module.zip", version = "v1.2.0" }

Dependency Keys, Aliases, and Slugs

Dependency keys in [dependencies.<key>] are parent-scoped aliases:

  • Valid keys use lowercase ASCII characters, numbers, and hyphens ([a-z0-9][a-z0-9-]*).
  • Slug Matching Warning: Rescile derives a normalized slug from the resolved module’s declared [module].name. When a dependency key differs from that generated slug, Rescile emits a warning:
    WARN: Dependency key 'my-sec' in '/path/to/module.toml' resolves to module 'base-security' whose generated slug is 'base-security'. This alias remains allowed, but may conflict with another dependency.
    
  • Custom aliases remain allowed to support parent-scoped aliasing and disambiguation, but matching the target module slug avoids unexpected collisions across the dependency tree.

Repository Identity and Semver Solving

  • Canonical Git Source: Git URLs with or without a .git suffix, or with trailing slashes, are normalized to the same canonical package identity (https://github.com/org/repo.git == https://github.com/org/repo). They are cloned once and share cache entries.
  • Semver Solving: When multiple modules depend on the same package via semver constraints (e.g. ^1.0 and >=1.2, <2.0), Rescile queries the remote tags and selects the highest common version satisfying all constraints across the entire graph. If constraints cannot be satisfied simultaneously, resolution fails fast with an explicit error.
  • Exact Ref Conflicts: If different modules require conflicting exact branches or tags (e.g. branch = "v1" vs branch = "v2"), resolution fails immediately rather than silently picking one.

The mod Subcommands

The rescile-ce mod subcommand provides utilities for managing dependencies, lockfiles, and starter asset synchronization.

rescile-ce mod add <NAME> <URL>

Adds a new dependency entry to data/module.toml, resolves the graph, and updates data/module.lock. Supports --tag, --branch, --commit, and --version pins. Pass --sync to automatically copy or merge the module’s starter assets and inputs immediately upon adding:

# Add and pin a dependency
rescile-ce mod add landing-zone https://github.com/rescile/landing-zone-module.git --tag v1.2.0

# Add and immediately pull starter CSVs and JSON inputs
rescile-ce mod add aws-provider https://github.com/rescile/aws-provider-module.git --sync

rescile-ce mod sync

Copies or merges starter asset and input files from locked dependency modules into the local data/ directory using the configured bootstrap_policy (default: merge).

This is the primary command to update or backfill starter CSV columns and reference data when dependency modules evolve, replacing the legacy top-level bootstrap command.

# Sync starter assets from all dependencies
rescile-ce mod sync

# Preview changes without modifying local files
rescile-ce mod sync --diff

# Restrict synchronization to one dependency
rescile-ce mod sync --module aws-provider

# Overwrite existing files
rescile-ce mod sync --force

rescile-ce mod lock

Resolves the full dependency tree based on the current module.toml and generates or heals the module.lock file. Run this after adding or changing a dependency declaration.

rescile-ce mod lock

Pass an optional module name to refresh a single dependency in place instead of regenerating the whole lockfile. All other entries keep their existing pins; the named dependency and its transitive dependencies are re-resolved:

rescile-ce mod lock base-security    # refresh one dependency in place

module.lock format

The lockfile is generated by rescile-ce mod lock; it is not intended to be hand-edited. It contains one [[module]] entry per resolved dependency:

version = 1

[[module]]
name = "base-security"
source = "https://github.com/my-org/rescile-modules/base-security.git"
identity = "https://github.com/my-org/rescile-modules/base-security.git"
requirements = ["branch=main"]
commit = "0d5a5c0e8c5b4b06b0e8a0e4d1f0e0d5c5b5a5a5"
dependencies = []
Field Description
name Declared dependency name from module.toml.
source Canonical source URL (.git suffix and trailing slashes are normalized away).
identity Canonical package identity; must match source. Used to reject duplicate identities.
requirements Selector metadata the pin was generated for (branch=..., tag=..., commit=..., short=..., or a semver requirement). Entries missing or contradicting this metadata are rejected with a mod lock hint because a lockfile written for one selector cannot safely serve a different one.
commit Pinned commit for Git sources. Mutually exclusive with checksum.
checksum Pinned archive checksum for archive sources (e.g. GitHub release assets). Mutually exclusive with commit.
dependencies Names of the dependency’s own dependencies, as resolved by this pin.

Hand-written lockfiles that omit identity/requirements are rejected with an error suggesting mod lock; the fields are documented above because the error previously did not explain them.

Lock drift

When a lock entry’s source no longer matches what module.toml requests, the entry is ignored on every run and the dependency is re-resolved dynamically. rescile-ce validate reports such entries as MODULE_LOCK_STALE workspace diagnostics, and validate --strict fails on them. The fix is always rescile-ce mod lock (optionally with a module name for an in-place refresh).

rescile-ce mod check

Compares the module.lock against remote repositories to report whether any updates are available — for example, new commits on a tracked branch or a newer semver tag matching your constraint.

rescile-ce mod check

rescile-ce mod update

Updates the module.lock file to the latest available versions according to the remote repositories. Pass an optional module name to update a single dependency rather than all of them.

rescile-ce mod update                  # update all
rescile-ce mod update base-security    # update one

rescile-ce mod fetch

Resolves the dependency tree and downloads all remote modules into the persistent local registry without starting the server. Useful for pre-seeding a CI environment or an air-gapped build cache.

rescile-ce mod fetch

Explicit --module overrides

When you pass a module explicitly with --module <path-or-url>, it always takes precedence over any entry with the same name in module.lock. This lets you temporarily override a pinned dependency with a local checkout or a different URL. A startup warning is shown when the lockfile entry is ignored.

Local replacement of a remote dependency: --module-override

A workspace declares its dependencies as remote git sources. For local verification against a clone you have adjusted, pass the clone with --module-override <path>: the local module is loaded and replaces the remote dependency instead of coexisting with it.

# data/module.toml declares: aws-provider = "https://github.com/rescile/aws-provider-module"
rescile-ce serve -d data/ --module-override ../aws-provider-module

Matching order:

  1. By [module].url — the override’s declared url matches the dependency’s source. The remote is never fetched (offline-safe, no wasted clone).
  2. By declared module name — when the override declares no url. The remote is resolved first, then replaced by the local module.

Every replacement logs a WARN line naming the local module and the replaced source. An override that matches no dependency still loads as a regular module. Local modules never appear in module.lock.

Without the flag, a local module and a remote dependency declaring the same module name are a hard error — the override preference is strictly opt-in, so accidental name collisions stay visible.

For guidance on authoring and publishing your own modules, see the Modules guide.