Security change on December 1, 2026: TLS 1.2 will become the minimum standard for 365 business Print Agent. Details and system requirements
365 business development
Concepts

Call graph

ALbuild builds the call graph of your AL apps on every build: which method calls which, across your apps and into Microsoft's. From it, every build reports what it contains, what no test reaches, what is dead or calls obsolete code, and what a change affects, compared with the previous build. A short architecture file turns the graph into a map of the app: its parts, the flows it exists for and a tour for new developers. Agents query the same map instead of searching the code.

Built on the AL Tool

The graph comes from Microsoft's al graph command, part of the AL Tool from version 18.0.39 (Business Central 29). ALbuild picks the AL Tool exactly as for compiling, from the BC version you build against. For BC 28 and older the AL Tool has no al graph, so the step is skipped with a warning.

The PowerShell module does the work; the Azure DevOps tasks, the CLI and the MCP server are thin surfaces over it.


How the graph is built

All AL projects of the workspace are compiled together with the source of their dependencies (al graph extract-whole). That matters: compiled one by one, the 365 business Banking test app lost all 6,342 of its calls into the app.

  • Microsoft's apps (Base Application, System Application, Business Foundation, ...) come from the BC artifact. Their source is unpacked once per app and version and cached on the agent.
  • Other dependencies are compiled along when their package ships source. A dependency without source is listed in the report: calls into it are missing, and so are calls that merely pass one of its types, because the compiler cannot bind them.
  • The whole-corpus graph is large (Banking: 141,340 nodes, 573,151 calls), almost all of it Microsoft-internal. It is pruned to your apps and their boundary: everything in your apps, plus the Microsoft methods and events they touch (Banking: 13,911 nodes, 78,541 calls).

On a warm agent this costs about a minute for an app the size of Banking; the first build of a BC version also unpacks Microsoft's source once.

What every build reports

FigureMeaning
Objects, methods, triggers, callsThe size of your apps. Calls are split by kind: direct, event, interface, trigger.
Calls into dependenciesHow much your code relies on Microsoft's and other apps.
Event publishers / subscribers, interface implementationsThe extension points your apps offer and use.
Test methods[Test] procedures found in the source.
Not reached by testsProduct methods and triggers that no [Test] method reaches along the call graph.
Obsolete callsProduct code calling a method or object marked obsolete. Test code is not counted.
Dead methodsNon-public methods nothing calls.
Unassigned / described objectsOwn objects no part claims, and objects with a description. See the map of the app.

With a baseline every figure also shows its change against the previous build.

Static, and honest about it

"Not reached by tests" is static reachability, not measured coverage: events the platform raises (table triggers such as OnAfterModifyEvent) are not followed. Use it next to code coverage, which measures what actually ran.

Dead code without false alarms

A call graph alone would report far too much dead code. ALbuild counts a method as dead only when nothing reaches it in any of these ways:

  • A call in the graph.
  • An event subscription: [EventSubscriber] methods are entry points.
  • Interface dispatch. al graph resolves interface calls to one overload only, so every overload of an interface method counts. A codeunit that implements an interface from an app without source keeps all its non-local methods.
  • A call by name: a string literal with the method name, e.g. a notification action.
  • A call the compiler could not bind: the name is called somewhere in the source text.

On Banking these rules took the count from 301 to 36 dead methods, each one checked against the source.

AL checks

The report also lists, for information only:

  • own objects with inherent permissions or entitlements,
  • Microsoft objects with inherent permissions or entitlements that your code calls,
  • [NonDebuggable] product methods.

These lists never fail a build and have no gate.

Why cloud-to-OnPrem is not checked

A check for cloud code that reaches OnPrem-only members was measured and left out. On Banking, OnPrem members are reached only through Microsoft's own calls (53 nodes, none called directly), so the check would raise nothing but false alarms.

Impact: what a change affects

Every build compares its graph with a baseline: a pull request build with the last successful build of its target branch, any other build with the last successful build of its own branch. The baseline is the callgraph artifact of that build.

ListMeaning
Added / removedMethods and triggers that appeared or disappeared.
ModifiedMembers whose calls changed, or whose body text changed (per-member source hashes).
Affected callersEverything that transitively calls a changed member, or called a removed one.
Tests reaching the changeThe [Test] methods that reach a changed member: the tests to run first.

The map of the app

A graph of thousands of nodes does not explain an app. The map groups your objects into parts, describes them and shows how they connect.

The parts come from callgraph.architecture.json in the repository root. A person or an agent writes it locally and commits it. The pipeline never calls AI: it only reads the file.

  • Without the file, the parts come from the folders: one part per folder of each app's source (below an optional src/), split one level further when a folder holds many objects. Test apps get one part each. Every repository gets a readable map this way.
  • Descriptions: the /// summary above an object's declaration always describes it. The file only adds descriptions for objects without one, or overrides them.
  • A broken file does not break the report: the map falls back to the folders and says why.

The file

An excerpt from 365 business Banking, with shortened texts:

JSONCode
{ "$schema": "albuild/callgraph-architecture@1", "project": { "name": "365 business Banking", "summary": "Connects bank accounts to banks and payment service providers." }, "parts": [ { "id": "bank", "label": "Bank", "kind": "feature", "summary": "The company's own bank accounts: accounts, payments, direct debits, reconciliation.", "paths": ["app/src/Bank/**"] }, { "id": "bank.reconciliation", "label": "Reconciliation", "kind": "feature", "parent": "bank", "summary": "Turns retrieved bank transactions into payment reconciliation journal lines and applies them.", "paths": ["app/src/Bank/Reconciliation/**"] } ], "objects": { "Table bdev.BNK Banking User": "A user known to a banking provider. Usually just the 'DEFAULT' record for the whole company." }, "flows": [ { "id": "connect-bank-account", "title": "Connect a bank account (finAPI)", "steps": [ { "node": "PageExtension bdev.BNK Bank Account List", "title": "Connect action", "note": "The user starts Connect bank accounts." }, { "node": "Codeunit bdev.BNK Bank Account", "title": "Open the wizard", "note": "ConnectBankAccounts runs the connect wizard." }, { "node": "Page bdev.BNK Connect Provider", "title": "Generic wizard", "note": "The user picks a provider." } ] } ], "tour": [ { "node": "TableExtension bdev.BNK Bank Account", "title": "Start at the bank account", "note": "A bank account names two services: AIS() and PIS()." }, { "node": "banking.model", "title": "The model tables", "note": "Provider-neutral temporary tables the contracts exchange." } ] }
KeyMeaning
projectName and summary of the app.
partsid, label, summary; parent nests a part in another. kind is one of app, feature, service, integration, data, ui, setup, library, test, external (default feature).
parts[].pathsGlobs on the source path: ** any folders, * within one segment, ? one character, {a,b} alternatives. A pattern without a wildcard also matches as a folder.
parts[].objectsPatterns on Type Name or Type Id, same globs, e.g. Codeunit bdev.BNK API*.
objectsDescriptions by Type Name, for objects without a /// summary or to override it.
flowsWhat the app exists for, step by step. Each step names a node (an object or a part id), a title and a note.
tourThe stops shown on the report's first open, in the same form as flow steps.

An object belongs to the deepest part that claims it. Between parts of the same depth, an objects pattern beats a paths pattern, and the more specific pattern wins.

Banking in figures

The Banking file has 51 parts, 5 flows and an 8-stop tour. All 935 objects are assigned; 628 are described, 130 of them in the file and the rest by ///.

Write and check it

TerminalCode
albuild graph describe --worksheets .albuild/callgraph/worksheets albuild graph describe --check

graph describe writes a starting file without AI: the folder-based parts, empty flows and an empty tour. With --worksheets it also writes one Markdown worksheet per part: its objects, their members and what they use. Write the summaries, flows and tour from them, by hand or with an agent.

graph describe --check validates the file against the graph and exits 1 on problems (--no-fail reports only):

  • unknown parts and nodes, with a suggestion,
  • patterns that match nothing,
  • flow steps that do not call each other.

In PowerShell: New-BcCallGraphArchitecture and Test-BcCallGraphArchitecture.

In the pipeline

YAMLCode
- task: GetBcArtifact@0 # sets $(bcArtifactUrl) - task: CompileApp@0 - task: AnalyzeCallGraph@0 inputs: artifactUrl: '$(bcArtifactUrl)'

AnalyzeCallGraph@0 is BuildCallGraph@0 followed by ReportCallGraph@0. Both pick up callgraph.architecture.json from the workspace root; there is no input for it. Use the two separately to build and report in different steps or jobs. Every build then gets:

  • a Call graph tab: the interactive report. It follows the Azure DevOps theme, light or dark.
    • Map: the parts as cards with their calls between them. Open a part down to its objects and methods, with breadcrumbs and search; select a card for its summary, what it calls and what calls it. A lens decides the only colour on the map: test reach, structure (the kind of part), changes since the baseline, or dead code. Solid lines are calls out of the selection, dashed lines calls into it. The arrow keys follow the calls.
    • Pages: everything a page, report, query or xmlport reaches. Test doubles are left out.
    • Flows: each flow step by step. The guide (the tour) opens on first use.
    • Impact, Findings (not reached by tests, dead code and obsolete calls in one tab), AL checks, and Apps with any architecture problems.
  • the figures, the impact and the top findings in the build summary.
  • the callgraph artifact: the graph, the analysis and the report, and the next build's baseline.

Building or reporting the call graph never fails a build. Only a gate you set does:

InputFails the build when
maxNotReachedPercentmore than this percentage of product methods is not reached by tests
maxDeadMethodsmore dead methods are found
failOnObsoleteCallsproduct code calls obsolete code

The central ci.yml template runs AnalyzeCallGraph@0 after the compile (parameter callGraph, on by default) and sets no gate.

Locally

TerminalCode
albuild graph build --workspace . --country de --bc-version 29.0 albuild graph report --open albuild graph query --to "obj:My Codeunit" --depth 1 albuild graph impact --baseline ../main/.albuild/callgraph

The graph is written to .albuild/callgraph in the workspace. graph report --open opens the same interactive report the build tab shows.

Lookups for agents

An agent that needs to know where something is can ask the graph instead of searching the code. The lookups are read-only and need neither the AL Tool nor a container.

TerminalCode
albuild graph overview albuild graph find ebics order albuild graph show Codeunit bdev.BNK API Client albuild graph path --from "Page bdev.BNK Bank Access" --to "Codeunit bdev.BNK API Client"
LookupAnswers
overviewThe parts, what each is for, and the strongest connections between them.
findParts, objects and methods whose name or description contains all the words.
showOne part, object or method: description, part, members, what it uses and what uses it.
pathThe shortest call chain between two objects or parts. On Banking the example returns Bank Access → Banking User → API Client.

The MCP server offers the same as graph-overview, graph-find, graph-show and graph-path, plus graph-describe, graph-check and graph-hint. In PowerShell it is Get-BcCallGraphInfo.

albuild graph hint (Set-BcCallGraphAgentHint) writes a marked section into CLAUDE.md that points agents at these lookups. The rest of the file is left alone. --file picks another file such as AGENTS.md; --remove takes the section out again.

Last modified on