Your AI feature must run on a fresh tenant, or it doesn't run
- Calling AI from Business Central: the platform realities nobody warns you about
- A 502 in your PDF-to-LLM pipeline is the gateway, not the model
- Shipping a Copilot feature in Business Central that survives real users
- Your AI feature must run on a fresh tenant, or it doesn't run
Here is the test that matters, and the one most AI features quietly fail: install it on a fresh tenant — a clean environment, demo company, no developer setup of any kind — and see if it works. No manually inserted API key. No SQL nudge. No “first run this provisioning script.” If it needs any of that, it is not a product. It is a demo that happens to run on your machine.
The thesis is blunt: a fresh tenant is the only honest test of whether you built a product or
just a demo with good lighting. This matters more now that custom BC agents ship as real AL
extensions — once your AI logic is an .app a customer installs, it lives or dies on the clean
install, exactly like every other extension.
Every item below is something I have watched turn a working demo into a support ticket the day a real customer installed it.
- 1iterate User, not User Setup
- 2permissions opt-out
- 3no placeholder prompts
- 4a default model that resolves
- 5sanitize before the filter
1. Iterate the right table
A subtle one. AI setup that loops over User Setup to provision per-user state breaks on a clean tenant, because User Setup is frequently empty — it is an optional configuration table, not the list of users. Iterate User instead. The records you can rely on existing are the ones the platform creates, not the ones an admin might have filled in.
The general rule: provision against tables that are guaranteed populated, never against ones a customer may never have touched.
2. Permissions opt-out, not opt-in
If your feature is gated behind a permission or setting that defaults to off, the default outcome is “nobody uses it” — and you will read that as the feature failing, when really nobody could find the switch. Default to available and opt-out. Make the capability present from install; let users who do not want it turn it off. The most expensive bug in an AI feature is that it was technically perfect and silently disabled.
3. No placeholder prompts
A prompt template with a "..." or <INSERT CONTEXT HERE> left in it does not throw — it just
quietly produces garbage, because the model dutifully works with the literal placeholder. These
survive review precisely because they are syntactically fine. Every template has to be complete
and grounded before it ships; a placeholder is a runtime bug wearing the costume of a TODO.
4. A default model that actually exists
Do not hardcode a default model that may not be enabled on the customer’s tenant. “Works on my box” is doing a lot of lifting when the model you defaulted to is one your environment has and theirs does not. The default belongs in configuration, and whatever it is, it must resolve on a clean install — a real, available model, not an aspirational one from your dev setup.
5. Always sanitize before the sink
The one that turns into a data bug instead of an error. Any AI-produced value that reaches a BC
filter must be sanitized first — a stray &, |, or * becomes filter syntax and silently
returns the wrong rows:
// Wrong: an AI value with filter metacharacters corrupts the filter
SalesLine.SetFilter("No.", AiValue);
// Right: sanitize, then filter
SalesLine.SetFilter("No.", '%1', SanitizeForBCFilter(AiValue));
The model does not know your filter grammar. Treat its output as untrusted input, every time, with no exception for “it’s just a product code.”
How to actually run the test
The test is only honest if the environment is genuinely clean, which means automating it so you cannot accidentally smuggle your dev state in. The setup that keeps me honest:
- A throwaway sandbox per run. A fresh container or a new sandbox tenant, demo company, every time. If it carries over state between runs, it is not testing a clean install.
- Install only the shipping artifact. The exact
.appfiles a customer gets — nothing from your source tree, no side-loaded helpers, no symbols that only exist on your machine. - Touch nothing by hand. The moment you “just enable this one setting” to make it work, you have proven the opposite of what you set out to prove. If a setting needs to be on, the install has to turn it on.
If the feature works there, it works. If it needs one manual touch, that touch is a bug, and you have just found it before the customer did.
Provision idempotently, then verify
The setup that turns a feature on — keys, accounts, per-company configuration — has to be safe to run more than once and has to check its own work. Two habits make this reliable:
- Idempotent setup. Re-running provisioning must be a no-op when everything is already in place, never a duplicate or an error. Bootstrapping that only works exactly once is bootstrapping that breaks the first time an install is retried or resumed.
- A health check after install. Do not assume the setup fired — confirm it. Walk each company, assert the feature is actually enabled, and re-run the (idempotent) setup if it is not. The most common “the AI feature isn’t working” report is a setup step that silently did not run on one company; a post-install check catches it before the user does.
The mindset is the same one good deployment has always needed: converge to the desired state and verify you got there, rather than firing a one-shot script and hoping.
The discipline
There is one habit underneath all five: build and test as if you are the customer, not the author. Provision a clean environment, install only the shipping artifact, and use the feature with none of the knowledge, keys, or setup that live in your head. Everything that breaks is a thing a real customer would have hit on day one.
A feature that needs you present to work has a dependency on you. Products do not get to have that. This is the last part of the production-AI series — and the gate every other part has to pass before any of it counts. It starts back at the platform guardrails.