Skip to content

Dependency Resolution

Z-M-Huang edited this page Apr 27, 2026 · 4 revisions

Dependency Resolution

How core resolves the load order of extensions that declare dependencies on each other, and how it rejects configurations that cannot be satisfied.

contractVersion: 1.0.0


Scope

Dependency Resolution runs after Validation Pipeline passes for each extension and before init. A validated-but-unresolvable extension (depends on something missing) produces a dependency diagnostic, not a validation diagnostic.


Declarations

An extension's contract may declare:

classDiagram
    class Dependencies {
        dependsOn : List~Ref~
        conflictsWith : List~Ref~
        optionalCapabilities : List~Ref~
    }
    class Ref {
        kind : Category
        name : string
        capability : string
    }
    Dependencies --> Ref
Loading
Field Meaning
dependsOn The extension will not function without the referenced extension. Load order: dependency first.
conflictsWith The extension will not function if the referenced extension is also loaded.
optionalCapabilities The extension benefits from a referenced capability but runs without it.

References are by { kind, name } or { kind, capability }. Capability-based references match any extension in the kind that advertises the capability.


Resolution algorithm

flowchart TD
    Validated[validated set] --> Build[build dependency graph]
    Build --> Cycle{cycle<br/>detected?}
    Cycle -->|yes| FailCycle["reject: cycle"]
    Cycle -->|no| Conflict{conflict<br/>pair loaded?}
    Conflict -->|yes| FailConflict["reject: conflict"]
    Conflict -->|no| Missing{required<br/>dep missing?}
    Missing -->|yes| FailMissing["reject: missing"]
    Missing -->|no| Topo[topological sort]
    Topo --> Order["init order: deps first"]
Loading

Cycles fail the session with a clear diagnostic — the wiki does not permit silent topological-break heuristics.


Conflict handling

A conflictsWith pair that is mutually loaded fails one of the two. Which one loses is deterministic:

  1. Layer priority. A project-scope extension wins over a global one; global wins over bundled.
  2. Tiebreaker within a layer. The extension discovered earlier by Extension Discovery order wins.

The loser is rejected with a DependencyConflict diagnostic naming the winner.


Missing-dependency behavior

A dependsOn reference that resolves to zero loaded extensions rejects the dependent extension.

Severity Posture
The dependent is itself critical Hard-fail the session.
The dependent is optional Skip the dependent with a warning; continue.

Missing optionalCapabilities never fail anything — they are informational. An extension that cares whether a capability is present checks at runtime; the dependency graph tracks it only for diagnostics.


Load order

Topological sort (Kahn's algorithm) produces the init order. The sort is deterministic: when multiple extensions are eligible to init at the same step (zero unsatisfied dependencies), they are admitted in lexicographic order by extId. This guarantees that the same extension set always produces the same initOrder regardless of the order in which declarations are presented.

disposeOrder is always the exact reverse of initOrder. Dependents shut down before their dependencies in every context — normal session teardown, graceful reload, and error recovery.

Extensions without any dependsOn entries init in lexicographic extId order among the other zero-degree nodes at the front of the queue.

Within a category, load order influences nothing except init timing. The activation order for ordered categories (Hooks) follows the ordering manifest, not the dependency graph. See Hooks.


Reload and dependencies

A reload that changes the extension set re-runs dependency resolution. A reload that would introduce a conflict or break a dependency rejects the reload; the previous extension set stays active. See Extension Reloading.

A graceful reload re-orders deactivate in reverse of the current topological order (i.e. disposeOrder) so that dependents shut down before their dependencies.


Diagnostic shape

A failed resolution produces one of:

Code Meaning
DependencyCycle Two or more extensions depend on each other transitively.
DependencyMissing A dependsOn reference has no matching loaded extension.
DependencyConflict Two extensions declare mutual conflictsWith.

Each carries the involved extensions' identifiers and the cycle path or conflict target. Diagnostics land on the event bus and in the Audit Trail.


Session-manifest integration

The resolved dependency graph is recorded in the session manifest: for each loaded extension, the list of dependencies it required at load time. Resume verifies the same graph resolves against the current extension set; drift on a resumed dependency follows the same rules as drift on a state slot (see Extension State).


Security notes

  • Dependency declarations are trust input. A malicious project may declare a dependency on a well-known security hook and then conflict it away, effectively disabling the hook. Project trust is the guard — see Project Trust.
  • Optional-capability probing is not a channel for side effects. An extension that reads another extension's state as a "capability check" is a bug. Dependency resolution is structural only.

Related pages


Changelog

1.0.0 — initial

  • Dependency Resolution spec as documented above. dependsOn / conflictsWith / optionalCapabilities declarations; topological load ordering; structural-only resolution.

Introduction

Reading

Core runtime

Contracts

Category contracts

Context

Security

Runtime behavior

Operations

Providers (bundled)

Integrations

Reference extensions

Tools

UI

Session Stores

Loggers

Providers

Hooks

Context Providers

Commands

Case studies

Flows

Maintainers

Clone this wiki locally