3.6.0¶
Release Date: May 20, 2026
✨ New Features¶
Terraform Modules¶
Composable Terraform Service Module Migrations ⚠️¶
Breaking Change —
modules/apino longer creates a web app. See Breaking Changes for migration steps.
The Forge Terraform service modules have been fully migrated from bespoke monolith patterns to the composable saif-resources module system. Services now gain consistent naming, networking, identity, and RBAC patterns with near-zero drift.
Composable modules introduced or refined:
- ✅
modules/environment— Resolves TFC outputs, networking, naming, and tags - ✅
modules/identity— UAMI + App Registration + Key Vault secret + baseline RBAC - ✅
modules/external-identity— Okta lookup, Key Vault secrets, RBAC (external-facing services) - ✅
modules/api— APIM-only; acceptsvar.backendsmap (compute-agnostic) - ✅
modules/webapp— Linux web app + staging slot (compute-only) - ✅
modules/cosmosdb,modules/storage,modules/ai-project,modules/bot— Data-layer composable modules
Services migrated:
saif-api-service— Phase 1 (environment + identity + external-identity); zero-destroy migration withmoved.tfblockssaif-event-service— composable environment context; APIM v1 → v2saif-event-subscriber-service— full environment + identity migration
Example:
module "webapp" {
source = "app.terraform.io/SAIFCorp/resources/saif//modules/webapp"
version = "~> 3.6.0"
context = module.environment.context
# ... additional required inputs
}
module "api" {
source = "app.terraform.io/SAIFCorp/resources/saif//modules/api"
version = "~> 3.6.0"
context = module.environment.context
backends = {
default = {
url = "https://${module.webapp.webapp.default_hostname}"
resource_id = module.webapp.webapp.id
}
}
api_name = var.api.name
api_type = var.api.type
open_api_spec = var.api.open_api_file
}
Phase 2: Composable Terraform — API and Webapp Modules¶
PR: #684
Module: modules/api, modules/webapp
Extends the composable system for saif-api-service migration — introduces a compute-agnostic api module and a dedicated webapp module for Linux web apps with staging slots. Replaces hundreds of lines of repetitive bespoke infrastructure code.
Phase 3: Cosmos DB, Storage, and Front Door to Composable Modules¶
PR: #694
Modules: saif-resources/modules/cosmosdb, saif-resources/modules/storage, modules/webapp-route
- Cosmos DB: Adds
data_reader_identitiesinput; fixesdefault_ttlnull handling; bumped to~> 4.7.0for data-reader role support - Storage: New composable wrapper with
shared_access_key_enabled = falseanddefault_to_oauth_authentication = true(v4 security defaults) - Front Door: New
webapp-routecomposable module replaces inline Front Door resources insaif-web-service
Composable saif-web-service Migration¶
PR: #665
Module: saif-web-service
Migrates saif-web-service to the composable saif-resources module pattern, replacing inline resource definitions with modular, reusable components.
Composable saif-resources Modules + staticsite-service Migration¶
PR: #664
Modules: saif-resources, saif-static-site-service
Introduces the foundation of composable saif-resources modules and migrates staticsite-service to the new pattern. Establishes the baseline for all subsequent phase migrations.
New saif-business-roles-corp and saif-business-roles-external Modules¶
PR: #668
Modules: modules/saif-business-roles-corp, modules/saif-business-roles-external
Introduces two new Forge Terraform modules for managing business roles, replacing the deprecated okta-business-roles pattern with purpose-built modules:
saif-business-roles-corp— Manages Entra ID security groups for corporate business roles viasaif-business-roles-corp/saifTFC registrysaif-business-roles-external— Manages Okta groups and group rules for external business roles
Example:
module "business_roles" {
source = "../../modules/saif-business-roles-corp"
business_roles = {
"platform-admins" = { display_name = "Platform Admins" }
}
is_production = true
}
💡 Migration note: Once you have migrated to
saif-business-roles-corp, any Okta-related variables (e.g.okta_org_name, Okta provider credentials) can be safely removed from the corp repository's TFC workspace variables — the corp module uses Entra ID exclusively and has no Okta dependency.
Project Templates¶
saif-business-roles dotnet new Template¶
PR: #675
Template: saif-business-roles
New Golden Path template for business roles repositories, enabling teams to scaffold a fully configured repository for managing group membership (Entra ID corporate or Okta external users).
Features:
- ✅ Prompts for Owner (team/ADO project) and Tenant (
corporexternal) - ✅ Generates conditional infrastructure —
infra/entra/for corp,infra/okta/for external - ✅ Includes TFC workspace bootstrap in
infra/bootstrap/ - ✅ Configures Azure DevOps pipeline referencing
pipeline-templatesatreleases/v3 - ✅ Registers the pipeline under
\business-rolesfolder in ADO - ✅ Sets up branch policies and auto-reviewers
Usage:
Docs Site Configuration in Project Templates¶
PR: #683
Templates: All project templates
Project templates now include the MkDocs docs site configuration out of the box, so new projects are immediately set up with a documentation site ready to publish.
CLI Tools¶
Cross-Platform MSAL Token Cache Support¶
PR: #678
Package: SAIF.DevTools.CLI
The CLI's MSAL token cache now works natively on all platforms:
- macOS: Token cache stored in Keychain under service
SAIF.Platform.CLI - Linux: Token cache stored in GNOME Keyring (
defaultcollection) - Windows: Existing DPAPI-encrypted file behaviour unchanged
Benefits:
- 🚀 Developers on macOS and Linux no longer lose cached tokens between sessions
- 🔒 OS-native secure storage on all supported platforms
- 🖥️ No new dependencies required (
Microsoft.Identity.Client.Extensions.Msalalready referenced)
🔧 Enhancements¶
- Kiota — Remove empty access token check (
refactor(kiota)#703): Removed the redundant empty access token check fromClientCredentialsTokenProvider, simplifying the token provider and eliminating a potential source of false negative token failures. - CLI — Remove pwsh dependency from tool checks (
refactor(cli)#679): The CLI tool-check logic no longer requirespwshexplicitly. Install documentation updated for consistency. - Front Door — Subdomain format and domain validation (#700): Updated Front Door subdomain format and domain validation logic for
staticsiteand custom subdomain services, ensuring correct DNS resolution across environments.
🐛 Bug Fixes¶
saif-event-subscriber-service(#702): Merged identityapp_settingsinto the function app configuration to prevent missing settings at runtime.- Templates — Package version alignment (#690): Aligned Aspire and
Microsoft.Extensionspackage versions withSAIF.Platformversions to prevent build conflicts in generated projects. - Templates —
saif-business-rolesbranch policies (#686): UpdatedbranchPoliciesto useBasicRequirementenum strings, fixing policy validation errors on new repositories. - CLI — Smithy server resource identifier (f93f86c): Corrected the resource identifier used when resolving the Smithy server, fixing CLI discovery failures.
- Azure —
AddScopedFeatureManagementdouble-registration (b07be024): ReplacedAddFeatureManagementwithAddScopedFeatureManagementinAddAzureDefaultsto prevent double-registration exceptions in scoped service scenarios. - Identity — Race condition in Terraform (#674): Added explicit
resource_group_nameandlocationto identity resources to prevent race conditions during parallel Terraform plan/apply. - API — Streaming and OpenAPI spec (#658): Bumped
saif-openapiand module versions to fix streaming response handling and OpenAPI specification generation.
⚠️ Breaking Changes¶
modules/api no longer creates a web app¶
Scope: Terraform — saif-resources/modules/api
The modules/api module has been refactored to be compute-agnostic — it now handles APIM registration and Front Door routing, but no longer creates a web app. If you were relying on modules/api to create a web app, you must now provision it separately using modules/webapp and pass backend details via the new backends input.
Before:
After:
module "webapp" {
source = "app.terraform.io/SAIFCorp/resources/saif//modules/webapp"
version = "~> 3.6.0"
context = module.environment.context
# ... additional required inputs
}
module "api" {
source = "app.terraform.io/SAIFCorp/resources/saif//modules/api"
version = "~> 3.6.0"
context = module.environment.context
backends = {
default = {
url = "https://${module.webapp.webapp.default_hostname}"
resource_id = module.webapp.webapp.id
}
}
api_name = var.api.name
api_type = var.api.type
open_api_spec = var.api.open_api_file
}
Impact: All callers of modules/api that expected a web app to be created must be updated. Use moved.tf blocks during migration to avoid resource recreation (destroy + create).
Required Actions:
- Add a
modules/webappblock to your Terraform configuration - Pass backend details via the
backendsinput in themodules/apiblock - Add
moved.tfblocks if migrating existing state to prevent zero-destroy issues
📋 Additional Notes¶
- Total commits: 44
- Files changed: ~200+
- Contributors: Emmitt Johnson, Jason Coria Corona Yue, Copilot
Support¶
- 📧 Teams Support Channel: Support