Decisions¶
The code base of the portal records its design decisions as Architecture Decision Records (ADRs). This page condenses them. The status is the one in the source: accepted means the decision is in force, proposed means it is written down but not confirmed, superseded means a later record replaced it. Names of deciders are left out.
Overview¶
| No. | Decision | Status |
|---|---|---|
| 0001 | Azure Functions backend for cost insights | Superseded by 0002 |
| 0002 | ASP.NET Core for backends | Superseded by 0015 |
| 0003 | Backstage in the repository root | Accepted |
| 0004 | Automating standard changes in landing zones | Superseded by 0015 |
| 0005 | Project structure aligned with ports and adapters | Accepted |
| 0006 | Managed service offerings as custom entities in the software catalog | Accepted |
| 0007 | One shared code base for all offerings | Accepted |
| 0008 | Build once, deploy many times | Accepted, implemented with GitHub Actions |
| 0009 | Separate main.tf per customer per environment |
Accepted |
| 0010 | Connect to customer environments over private endpoints | Accepted |
| 0011 | Azure Data Factory for cost data ingestion | Accepted |
| 0012 | FOCUS schema with Parquet and Snappy for cost exports | Accepted |
| 0013 | Process all data in the customer environment | Accepted |
| 0014 | Theme-anchored colour scheme for cost groups | Accepted |
| 0015 | Unify the backend on TypeScript | Accepted |
| 0016 | Relationship-based access control for sensitive data | Proposed |
| 0017 | Prose linting with Vale | Accepted |
| 0018 | Configure cost exports from a portal background job | Accepted |
| 0019 | REST-backed hierarchy resolution instead of GraphQL | Accepted |
| 0020 | Custom skill loading for the commanding agent | Accepted |
| 0021 | AI agent evaluation strategy | Proposed |
| 0022 | AI agent development-time evaluation | Proposed |
| 0023 | AI agent live evaluation, tracing and feedback | Proposed |
| 0024 | Move CI/CD from Azure Pipelines to GitHub Actions | Accepted |
| 0025 | No GitHub Environment on the plan and build workflows | Accepted |
| 0026 | The Terraform client secret is a repository secret, resolved at plan time | Accepted |
| 0027 | Plan and apply share one Azure identity | Accepted |
| 0028 | The build workflow holds no Azure credential | Accepted |
| 0029 | Per-environment configuration lives in the environments file | Accepted |
| 0030 | AI agents run as an in-process loop instead of Foundry Agent Service | Accepted |
Product and code base¶
One code base (0007, 0003, 0005). All customers run the same code. Customer-specific code changes are not made; a request that does not fit becomes separate feature work through the solution lead. The code is organised along ports and adapters, with the Backstage app in the repository root.
Custom entity kinds (0006). The catalog models offerings, customers and workloads with custom kinds instead of bending the standard system model. See Catalog and entity model.
TypeScript backend (0015). The earlier C# backends and the Azure Functions approach (0001, 0002) and the PowerShell-based automation of standard changes (0004) were replaced by one TypeScript backend inside the portal.
REST over GraphQL (0019). The hierarchy of organisation, domain, team, workload and subscription is resolved by a shared service behind REST routes and MCP actions. The decision favours REST because it matches Backstage and gives AI-generated requests a simpler surface.
Data and security¶
Private endpoints (0010). The provider does not share its source code, does not run an agent pool in the customer network and does not keep customer data such as Terraform state. It therefore connects over private endpoints that it creates in its own network and the customer approves. The customer can revoke the connection. See Onboarding a customer.
Data stays in the customer environment (0013). Cost data is exported and processed in the customer's subscription, which keeps the provider out of the data processor role. The same principle drove the in-process agent loop (0030).
Access control (0016). The proposal extends the Backstage permission system with an ownership check, rather than adding a separate authorisation service or a relationship-based store. Overview scores stay visible to every signed-in user; details are visible to owners; access checks are audited. The code implements the permissions for firewall logs, gateway blocked requests, security detail and compliance detail, although the record is still marked proposed. The proposal also lists cost data as sensitive, but the code does not gate it. See Authentication and access.
Cost insights¶
Data Factory, FOCUS, Parquet (0011, 0012). Cost Management exports go to storage in FOCUS format with Parquet and Snappy, and Data Factory loads them into SQL.
Exports configured by the portal (0018). A background job in the portal creates the exports, so no separate pipeline is needed. See Cost insights setup.
Delivery¶
Build once, deploy many (0008, 0028). One image is built per run. The build job has no Azure credential.
Separate roots (0009). One Terraform root per customer and environment, so a change for one cannot affect another.
GitHub Actions with OIDC (0024). The pipelines moved to GitHub Actions. Plan and build run without a GitHub Environment (0025) so that the readable plan exists before approval. The Terraform client secret is a repository secret because the plan is applied unchanged (0026). One JSON file holds the configuration per environment (0029).
Open risk (0027). Plan and apply use the same Azure identity because the read-only identity for plan does not exist yet. The decision keeps the split reversible. See Deployment and environments.
AI agents¶
In-process agent loop (0030). The agents first ran in the Foundry Agent Service. The standard set-up provisions a database, a search service and storage per customer, which is costly and unused here. The basic set-up moves conversations into a Microsoft-hosted backend outside the customer environment. The accepted decision runs the loop in the portal backend and uses Foundry only as a model endpoint. Conversations are stored in the portal database. See AI agents.
Skill loading (0020). The commanding agent delegates to the spoke and integration agents by loading skills, because the earlier connected-agent model is not available in the new Foundry agent service.
Evaluation (0021, 0022, 0023). The proposals describe a split between development-time evaluation (Promptfoo) and live evaluation. All three are still proposed and written for the Foundry agent service. See Agent evaluation.
Records and code that disagree
The records 0021 to 0023 and the older deployment guides still describe the Foundry agent service. The older architecture model of the repository still shows GraphQL and a separate data platform database, while 0015 and 0019 describe TypeScript and REST. One record points to a file name that does not exist. When a record and the code disagree, the code and the accepted records win.