Skip to content

3.6.0

Release Date: May 20, 2026


✨ New Features

Terraform Modules

Composable Terraform Service Module Migrations ⚠️

Breaking Changemodules/api no 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; accepts var.backends map (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 with moved.tf blocks
  • saif-event-service — composable environment context; APIM v1 → v2
  • saif-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_identities input; fixes default_ttl null handling; bumped to ~> 4.7.0 for data-reader role support
  • Storage: New composable wrapper with shared_access_key_enabled = false and default_to_oauth_authentication = true (v4 security defaults)
  • Front Door: New webapp-route composable module replaces inline Front Door resources in saif-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 via saif-business-roles-corp/saif TFC registry
  • saif-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 (corp or external)
  • ✅ Generates conditional infrastructure — infra/entra/ for corp, infra/okta/ for external
  • ✅ Includes TFC workspace bootstrap in infra/bootstrap/
  • ✅ Configures Azure DevOps pipeline referencing pipeline-templates at releases/v3
  • ✅ Registers the pipeline under \business-roles folder in ADO
  • ✅ Sets up branch policies and auto-reviewers

Usage:

saif new saif-business-roles

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 (default collection)
  • 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.Msal already referenced)

🔧 Enhancements

  • Kiota — Remove empty access token check (refactor(kiota) #703): Removed the redundant empty access token check from ClientCredentialsTokenProvider, 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 requires pwsh explicitly. Install documentation updated for consistency.
  • Front Door — Subdomain format and domain validation (#700): Updated Front Door subdomain format and domain validation logic for staticsite and custom subdomain services, ensuring correct DNS resolution across environments.

🐛 Bug Fixes

  • saif-event-subscriber-service (#702): Merged identity app_settings into the function app configuration to prevent missing settings at runtime.
  • Templates — Package version alignment (#690): Aligned Aspire and Microsoft.Extensions package versions with SAIF.Platform versions to prevent build conflicts in generated projects.
  • Templates — saif-business-roles branch policies (#686): Updated branchPolicies to use BasicRequirement enum 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 — AddScopedFeatureManagement double-registration (b07be024): Replaced AddFeatureManagement with AddScopedFeatureManagement in AddAzureDefaults to prevent double-registration exceptions in scoped service scenarios.
  • Identity — Race condition in Terraform (#674): Added explicit resource_group_name and location to identity resources to prevent race conditions during parallel Terraform plan/apply.
  • API — Streaming and OpenAPI spec (#658): Bumped saif-openapi and 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:

module "api" {
  source = "../../modules/api"
  # Previously created web app implicitly
}

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:

  1. Add a modules/webapp block to your Terraform configuration
  2. Pass backend details via the backends input in the modules/api block
  3. Add moved.tf blocks 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