import { Callout } from "zudoku/ui/Callout";

# Dependency resolver

[`Resolve-BcDependencies`](../powershell-module/feeds#resolve-bcdependencies) is ALbuild's headline capability: a **real transitive dependency resolver** across package feeds that respects the *actual* Business Central build and the cross-dependencies of candidate packages.

<Callout type="info" title="Why it matters">
Unlike a greedy "latest that satisfies the minimum version" approach, ALbuild solves for a mutually-compatible, transitively-complete set whose every member's own dependencies, including Microsoft first-party apps, are satisfied both by the chosen set and by the target build. The result is captured in a `dependencies.lock.json` for reproducible builds.
</Callout>

---

## The problem it solves

Given the app(s) being built and the BC build they target, produce a mutually-compatible,
transitively-complete set of dependency packages. For on-premise targets it selects the **runtime
package whose platform version matches the target build**.

[`Get-BcContainerAppInfo`](../powershell-module/containers#get-bccontainerappinfo) feeds the
separate `-InstalledApps` parameter: the apps a build container already has, pinned as a satisfied
baseline so they are not fetched again.

<Callout type="caution" title="`app.json` is a floor, not a ceiling">
`application` and `platform` in `app.json` are your app's **minimum** supported versions. Using them
as the resolution ceiling is a subtle and expensive mistake: the resolver then rejects real
dependency apps that were built for the current BC and silently falls back to **symbol-only**
AppSource packages, which compile but cannot be installed.

The ceiling is therefore chosen separately, by
[`Resolve-BcDependencyCeiling`](../powershell-module/feeds#resolve-bcdependencyceiling), highest
precedence first:

1. **An explicit target version.** In the *Resolve Dependencies* task this defaults to
   `$(bcArtifactVersion)` from *Get BC Artifact*, so even a **container-less** build honours the BC
   version it targets.
2. **The platform app installed in the build container**, which is the exact build.
3. **`bcVersion`** pinned in `albuild.json`.

If none is available, the resolver falls back to the newest version each `app.json` is compatible
with.
</Callout>

---

## Feed providers

Feeds can be supplied three ways: passed via `-Feeds`, registered with [`Register-BcFeed`](../powershell-module/feeds#register-bcfeed), or, most conveniently, declared once in the project's `albuild.json` `feeds` array, which `Resolve-BcDependencies` loads automatically (see [project config](project-config#feeds)). Because the resolver detects runtime/indirect packages from metadata and finds packages by app id, a declared feed is usually just its URL.

Supported sources: **NuGet v3** (nuget.org, Azure DevOps, GitHub Packages), **Azure Universal Packages**, **GitHub Releases**, the **local workspace** (`.alpackages`), and the **Microsoft public feeds** (MSSymbols, MSApps, AppSourceSymbols). Each provider maps an AL app id + publisher + name to a feed package id according to the package **kind**:

| Kind | Package id scheme |
| --- | --- |
| symbols | `{publisher}.{name}.symbols.{id}` |
| apps | `{publisher}.{name}.{id}` |
| runtime | `{publisher}.{name}.runtime-{version}` |
| localized | `…{countryCode}` |

The empty GUID `00000000-0000-0000-0000-000000000000` denotes Platform/Application and is provided by the container, never downloaded. **Microsoft apps are pinned to the target build** and never downloaded from a third-party feed, recognised by a Microsoft publisher, a `Microsoft.` package-id prefix, or the empty GUID. This covers the first-party apps System Application (`63ca2fa4-…`), Base Application (`437dbf0e-…`) and Business Foundation (`f3552374-…`), as well as platform-shipped extensions an ISV app may depend on.

### Real-world NuGet conventions consumed

ISVs publish to NuGet with Microsoft's BC packaging conventions, which the provider reads directly:

- **Dependencies come from the package's NuGet metadata** (the nuspec `<dependency>` entries), not from the `.app`'s `NavxManifest.xml`. This is read over the network without downloading the `.app`, and is the only thing that works for runtime apps. `Microsoft.Application` / `Microsoft.Platform` dependencies carry the required BC version.
- **Direct app packages** (e.g. Continia, Simova) contain the `.app`/`.runtime.app` themselves.
- **Indirect "metapackages"** (e.g. DYCE, 365 business development) contain no `.app`; their only payload dependency is a per-app-version **runtime sub-package**, whose own NuGet versions are the **BC platform builds** it was compiled for. For an on-premise target the provider selects the build **closest to but not exceeding** the target platform (e.g. BC `27.1.41698.51164` → `27.1.41698.41776`) and downloads that sub-package, which also carries the localised runtime app.
- **The app id (trailing GUID) is the only stable key.** Publishers vary the publisher/name segments. The provider therefore (1) carries the exact nuspec package id as a hint, (2) rewrites a plain `{publisher}.{name}.{id}` hint to this feed's id scheme infix, and (3) falls back to the feed's **search service queried by app id**: picking the package whose id ends in that GUID. Results are memoised so the backtracking solver does not re-fetch them.

---

## Source precedence

Beyond feeds, a project can supply dependencies as committed local `.app` files (`localPackages`), direct-download URLs (`urlPackages`) and Azure DevOps Universal packages (`universalPackages`) — see [dependency sources](project-config#dependency-sources). When the same app is available from more than one source, the resolution order is:

1. **`localPackages` / `urlPackages` — authoritative.** A dependency you ship directly (a committed `.app` or an ISV's download link) is **pinned and always used**, even when a registered feed offers a newer version. This keeps an ISV app that has no public feed — or one you deliberately fix to a known build — from being replaced by a feed copy.
2. **NuGet feeds.**
3. **`universalPackages` — feed-subordinate.** A registered feed's version wins over a universal copy; the universal package stays only as a fallback for apps no feed provides.

### Name-based pinning (encrypted runtime apps)

A local/URL `.app` is normally read to get its app id and pinned by id. An **encrypted BC runtime package** cannot be read on the host, so instead its `{Publisher}_{Name}_{Version}.app` file name is parsed and the dependency is pinned by **Publisher + Name** (exact, case-insensitive). Such a package:

- is **never fetched from a feed** (the declared dependency counts as satisfied locally), and is **installed straight from `localPackages`** — its real id/version are read inside the container;
- **is** copied into `.alpackages` like every other local package, and **both compile engines resolve against it from there** — the host AL Tool as well as the container compiler. `alc` reads the runtime package's symbol reference itself, so a separate symbol `.app` is **not** required.

The name-based pinning therefore exists because *ALbuild's host-side reader* cannot open the package, not because the compiler cannot use it. When a build container is available, ALbuild reads the real app id through it (the container decrypts the manifest) and pins by id instead.

---

## Choosing the BC version: downgrade on conflict

There is a chicken-and-egg problem in a `Select = Latest` pipeline: you want the newest Business
Central build, but your ISV dependencies may not have shipped packages for it yet. Picking the
latest artifact and *then* discovering that dependencies do not resolve wastes a container and
fails the build.

[`Select-BcArtifactForDependencies`](../powershell-module/feeds#select-bcartifactfordependencies)
inverts that. It enumerates the latest artifact and up to `-MaxStepsBack` earlier minor versions
(crossing a major boundary to the previous major's newest minor when a minor reaches `x.0`) and,
newest first, **dry-runs the resolver** (`-SkipDownload`) for every project against that build. The
first version where *all* projects resolve is the one used.

- If that is older than the latest, the result is flagged `Downgraded` and `ConflictReason`
  explains why the latest was skipped, which the pipeline surfaces as a warning rather than
  swallowing it.
- Because it uses an explicit target build and a synthetic Microsoft baseline, **no container is
  needed**: the decision is made before the single container is created.
- An explicit `-Version` pins the choice and skips the enumeration entirely.

---

## Diagnosing a resolve without changing anything

[`Get-BcDependencyReconciliation`](../powershell-module/feeds#get-bcdependencyreconciliation) is
read-only: it installs nothing. For each dependency in `app.json` it reports whether the dependency
is already **satisfied by an app installed in the container**, otherwise whether a **configured feed
can provide** a compatible version, otherwise that it is **missing**. Microsoft first-party apps
ship with the artifact, so they normally show up as satisfied by the container.

Use it when a resolve fails and you need to know which side the gap is on, before touching feeds or
versions.

---

## The algorithm (backtracking constraint solve)

1. **Pin Microsoft apps** to the target build (discovered transitively, not just the three first-party apps); a target older than a Microsoft requirement is a precise, reported conflict.
2. **Pick candidate versions** (≥ minimum) across feeds for each unresolved dependency, filtered to those whose `platform`/`application` are compatible with the target build. For an indirect metapackage this also requires that its runtime sub-package has a build at or below the target.
3. **Assign and recurse**: assign a candidate, enqueue its transitive dependencies, and recurse; on conflict, **backtrack**.
4. **Write the result**: on success, write the deduplicated, topologically sorted set (dependencies before dependants) to `.alpackages` and a `dependencies.lock.json` for reproducible builds, recording, for each package, both the app version and the concrete download package/version actually fetched. On failure, emit a precise conflict report.

Version-range checks reuse [`Test-BcVersionInRange`](../powershell-module/core#test-bcversioninrange) / [`ConvertTo-BcVersion`](../powershell-module/core#convertto-bcversion) from the Core module.

---

## Lock file

The `dependencies.lock.json` records, for every package, both the app version and the concrete `downloadPackage` / `downloadVersion` actually fetched (these differ from the app package for indirect metapackages). Commit it to get **reproducible builds**: the same dependency set every time, regardless of what newer versions later appear on a feed.

---

## Related cmdlets

- [`Resolve-BcDependencies`](../powershell-module/feeds#resolve-bcdependencies): the resolver.
- [`Register-BcFeed`](../powershell-module/feeds#register-bcfeed) / [`Get-BcFeed`](../powershell-module/feeds#get-bcfeed), feed registration.
- [`Find-BcPackage`](../powershell-module/feeds#find-bcpackage): query a feed for candidates.
- [`Get-BcUniversalPackage`](../powershell-module/feeds#get-bcuniversalpackage): Azure DevOps Universal feed downloads.
- [`Resolve-BcExternalDependency`](../powershell-module/feeds#resolve-bcexternaldependency): local committed `.app` sources.
- [`Resolve-BcDependencyCeiling`](../powershell-module/feeds#resolve-bcdependencyceiling): pick the BC version to cap resolution against.
- [`Select-BcArtifactForDependencies`](../powershell-module/feeds#select-bcartifactfordependencies): dependency-aware artifact selection with downgrade-on-conflict.
- [`Get-BcDependencyReconciliation`](../powershell-module/feeds#get-bcdependencyreconciliation): read-only report of satisfied / available / missing dependencies.
