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

# Architecture & modules

ALbuild is a single PowerShell package, `businessdev.ALbuild`, composed internally of **use-case nested modules** that ship and version together as one package. The Azure DevOps extension, the VS Code extension, the CLI and the MCP server are thin shells on top of it.

```
┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ Azure DevOps   │ │ VS Code        │ │ albuild CLI    │ │ MCP server     │
│ tasks          │ │ extension      │ │ (cross-platf.) │ │ (AI agents)    │
└───────┬────────┘ └───────┬────────┘ └───────┬────────┘ └───────┬────────┘
        │                  │                  │                  │
        └──────────────────┴─────────┬────────┴──────────────────┘
                                     ▼
        ┌─────────────────────────────────────────────────────────┐
        │            businessdev.ALbuild  (PowerShell)            │
        │  Core · Containers · Apps · Feeds · RuntimePackages ·   │
        │      Marketplace · OnPrem · Environments · Pipeline     │
        └─────────────────────────────────────────────────────────┘
```

All four surfaces are **thin shells** over the same module. The [CLI](../cli/index) and the
[MCP server](../mcp-server/index) added the cross-platform and AI-agent entry points, but none of
them re-implement container, compile or coverage logic. The behaviour is identical wherever you
drive it from.

### The one deliberate exception: AL Probe

[`albuild probe`](../cli/probe) is **not** a shell over the PowerShell module. It is an AL
interpreter written in C# and hosted inside the CLI: a lexer, parser and tree-walking evaluator
that reads your `.al` source directly and evaluates expressions and procedure calls in-process.

That is the whole point of it. Every other surface answers "what does Business Central do?" by
asking Business Central, which costs a container and minutes. Probe answers a narrow slice of the
same question in well under a second, with no Docker and no server, so an agent can check a piece
of logic as often as it likes.

The cost of a second engine is the risk of the two disagreeing, so probe is built to refuse rather
than guess: anything it cannot model exactly (`Commit`, `Random`, unsupported `CalcFormula` kinds,
`Format` format strings) returns a refusal, not an approximation. A
[differential harness](../cli/probe#fidelity) runs the same AL through both engines and
turns any disagreement into a failing test. Probe is a **fast pre-check, never a replacement** for
running the real tests.

---

## The nested modules

| Area (nested module) | Purpose | Tier |
| --- | --- | --- |
| [`businessdev.ALbuild.Core`](../powershell-module/core) | Config, logging, telemetry, licensing, helpers, version math, project config, build order, version stamping | Free |
| [`businessdev.ALbuild.Containers`](../powershell-module/containers) | Artifacts, Docker container lifecycle, Traefik v3, certificates | Free |
| [`businessdev.ALbuild.Apps`](../powershell-module/apps) | Compile (`al` tool / container), publish/install/sign/test, XLIFF | Free (except AppSource validation) |
| [`businessdev.ALbuild.Feeds`](../powershell-module/feeds) | Feed providers + the transitive dependency resolver, NuGet packaging | Free / Licensed |
| [`businessdev.ALbuild.RuntimePackages`](../powershell-module/runtime-packages) | Batched / parallel / incremental runtime-package engine | Licensed |
| [`businessdev.ALbuild.Marketplace`](../powershell-module/marketplace) | AppSource / Partner Center ingestion | Licensed |
| [`businessdev.ALbuild.OnPrem`](../powershell-module/onprem) | On-prem publish/install/upgrade, PTE, Dev | Licensed |
| [`businessdev.ALbuild.Environments`](../powershell-module/environments) | Demo/dev environment provisioning | Licensed |
| [`businessdev.ALbuild.Pipeline`](../powershell-module/pipeline) | Local pipeline runner | Free |

Licensing tiers are enforced **at runtime** (via `Assert-ALbuildLicensed`), independent of packaging, every cmdlet ships in the one package, and the licensed ones check on entry. See [Licensing & tiers](licensing).

---

## Naming conventions

- Cmdlet nouns use the **`Bc`** prefix for Business Central operations (`New-BcContainer`, `Invoke-BcCompiler`) and **`ALbuild`** for ALbuild's own tooling, configuration, logging and licensing (`Get-ALbuildConfig`, `Write-ALbuildLog`, `Assert-ALbuildLicensed`).
- Every public cmdlet ships comment-based help; the [cmdlet reference](../powershell-module/index) pages are generated from it.

---

## Two kinds of configuration

ALbuild deliberately separates **per-repository** settings from **per-machine** settings:

| | `albuild.json` (project config) | `Get-/Set-ALbuildConfig` (machine config) |
| --- | --- | --- |
| **Scope** | Per repository / per app folder | Per machine / per user |
| **Committed to source control?** | Yes | No |
| **Controls** | Country, artifact type, BC version, selection, test runner, dependency feeds | Artifact/package cache folders, retry, telemetry, licensing |
| **Reference** | [Project configuration](project-config) | [`Get-ALbuildConfig`](../powershell-module/core#get-albuildconfig) / [`Set-ALbuildConfig`](../powershell-module/core#set-albuildconfig) |

### The machine configuration file (`config.json`)

The per-machine settings live in a JSON file the module reads automatically (so the CLI, the Azure DevOps tasks and the MCP server all honour it). You normally manage it with cmdlets rather than editing it by hand:

```powershell
Get-ALbuildConfig                                    # the effective configuration
Get-ALbuildConfig -Name BcArtifactCacheFolder        # a single setting
Set-ALbuildConfig -Settings @{ BcArtifactCacheFolder = 'D:\bcartifacts.cache' } -Persist
```

`-Persist` writes the file; without it the change lasts only for the current session.

These are **machine** settings, so they are stored machine-wide by default, in two layers:

| Layer | File | Precedence |
| --- | --- | --- |
| **Machine** (`-Scope Machine`, the default) | `C:\ProgramData\ALbuild\config.json` | Wins |
| **User** (`-Scope User`) | `%APPDATA%\ALbuild\config.json` | Below the machine file |

Writing the machine file requires an **elevated session** on Windows, which is deliberate: a
build agent's cache location should not be something one account can change out from under another.
Without elevation you get an explicit message naming the scope, and can fall back to
`-Scope User` for a developer box.

<Callout type="caution" title="Why the machine layer exists">
The per-user file used to be the only store, and it caused a silent split on build servers: the
agent account had one cache folder in its own profile while an administrator on the same machine
read the built-in default and saw a completely different cache. Nothing announced the divergence.
If you still have a per-user file from that era, remember it now ranks *below* the machine file.
</Callout>

`ALBUILD_CONFIG` still names one explicit file and then **replaces the layering entirely**. It is
meant for tests and for pinning a configuration in an isolated run, not as the normal way to
configure an agent:

```powershell
$env:ALBUILD_CONFIG = 'C:\ProgramData\ALbuild\config.json'   # replaces both layers
Set-ALbuildConfig -Settings @{ BcArtifactCacheFolder = 'D:\bcartifacts.cache' } -Persist
```

It is plain JSON, so you can also drop the file in place directly:

```json
{
  "BcArtifactCacheFolder": "D:\\bcartifacts.cache",
  "ArtifactCacheFolder": "D:\\ALbuild\\artifacts"
}
```

Settings and their defaults:

| Setting | Default | Purpose |
| --- | --- | --- |
| `BaseFolder` | `C:\alb` on Windows, `~/.local/share/ALbuild` elsewhere | Root for the cache folders below. Windows deliberately uses a **short** path at the system-drive root: paths inside a BC artifact reach ~245 characters on their own, and the Azure DevOps task runs under Windows PowerShell 5.1, which enforces the 260-character `MAX_PATH` limit hard. A deeper per-user base intermittently pushed artifact extraction over it. |
| `ArtifactCacheFolder` | `…\ALbuild\artifacts` | Host-side extracted BC artifacts (compile symbols). |
| `PackageCacheFolder` | `…\ALbuild\packages` | Downloaded NuGet / dependency packages. |
| `BcArtifactCacheFolder` | `C:\bcartifacts.cache` | Persistent cache **shared into every container at `C:\dl`**, so the BC artifact is downloaded once per host and reused by later containers. BcContainerHelper-compatible. |
| `ProcessRetryCount` / `ProcessRetryDelaySeconds` | `3` / `5` | Retry policy for external processes. |
| `TelemetryEnabled` / `TelemetryConnectionString` | `false` / _(empty)_ | Opt-in usage telemetry. |
| `LicensingBaseUrl` / `LicenseAppId` / `LicenseGraceDays` | _(service)_ / _(app id)_ / `14` | Licensing service for the paid tier (offline grace window in days). |

<Callout type="tip" title="Self-hosted agents: stop re-downloading the artifact">
Container creation otherwise downloads the full BC artifact (multiple GB) *inside every new container*. Set the artifact cache to a roomy, persistent drive **once** — as a machine environment variable:

```text
ALBUILD_BCARTIFACT_CACHE = D:\bcartifacts.cache
```

or in `config.json` (`BcArtifactCacheFolder`). The first container on the host fills it; every later container reuses it. Precedence: `New-BcContainer -ArtifactCacheFolder` → `ALBUILD_BCARTIFACT_CACHE` → the config value (`-NoArtifactCache` disables it). Microsoft-hosted agents are ephemeral, so there's nothing to reuse — leave the default.
</Callout>

<Callout type="info" title="Readable errors everywhere">
Every surface reduces failures to a single, clean message via `Format-BcErrorMessage` (the VS Code extension mirrors it as an `ALBUILD-ERROR:` summary), instead of dumping a raw PowerShell error. Full detail is always available in the underlying log / output channel.
</Callout>

---

## Platform support

- **Container operations** (`*-BcContainer`, test toolkit, in-container compile/test) require a **Windows host with a running Docker engine**. They fail fast with a clear message on other platforms, enforced by [`Test-BcPlatform`](../powershell-module/core#test-bcplatform) and [`Test-BcDocker`](../powershell-module/containers#test-bcdocker).
- **Container-less operations**: compilation via Microsoft's cross-platform `al` dotnet tool, dependency resolution, signing, Marketplace/SaaS REST and XLIFF, run on any platform with PowerShell 7.
- **[`albuild probe`](../cli/probe)** needs neither Docker nor PowerShell nor a Business Central installation. It is pure .NET inside the CLI and runs anywhere the CLI runs.
