# 3.6.0

**Release Date:** May 20, 2026

---

## ✨ New Features

### Terraform Modules

#### Composable Terraform Service Module Migrations ⚠️

> **Breaking Change** — `modules/api` no longer creates a web app. See [Breaking Changes](#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:**

```hcl
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](https://github.com/saif-corp/forge/pull/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](https://github.com/saif-corp/forge/pull/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](https://github.com/saif-corp/forge/pull/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](https://github.com/saif-corp/forge/pull/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](https://github.com/saif-corp/forge/pull/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:**

```hcl
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](https://github.com/saif-corp/forge/pull/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:**

```bash
saif new saif-business-roles
```

---

#### Docs Site Configuration in Project Templates

**PR:** [#683](https://github.com/saif-corp/forge/pull/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](https://github.com/saif-corp/forge/pull/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](https://github.com/saif-corp/forge/pull/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](https://github.com/saif-corp/forge/pull/679)): The CLI tool-check logic no longer requires `pwsh` explicitly. Install documentation updated for consistency.
- **Front Door — Subdomain format and domain validation** ([#700](https://github.com/saif-corp/forge/pull/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](https://github.com/saif-corp/forge/pull/702)): Merged identity `app_settings` into the function app configuration to prevent missing settings at runtime.
- **Templates — Package version alignment** ([#690](https://github.com/saif-corp/forge/pull/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](https://github.com/saif-corp/forge/pull/686)): Updated `branchPolicies` to use `BasicRequirement` enum strings, fixing policy validation errors on new repositories.
- **CLI — Smithy server resource identifier** ([f93f86c](https://github.com/saif-corp/forge/commit/f93f86c031e5964d32222bb2abfcf5e1e4eb52bd)): Corrected the resource identifier used when resolving the Smithy server, fixing CLI discovery failures.
- **Azure — `AddScopedFeatureManagement` double-registration** ([b07be024](https://github.com/saif-corp/forge/commit/b07be0243255f6b477e7ef3d1f472de2666d585b)): Replaced `AddFeatureManagement` with `AddScopedFeatureManagement` in `AddAzureDefaults` to prevent double-registration exceptions in scoped service scenarios.
- **Identity — Race condition in Terraform** ([#674](https://github.com/saif-corp/forge/pull/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](https://github.com/saif-corp/forge/pull/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:**

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

**After:**

```hcl
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](https://teams.microsoft.com/l/channel/19%3Acb611810fb0b42b080cfff5590bdd51c%40thread.tacv2/Support?groupId=514d2dac-2d62-48ce-bf99-0fa0ce39469c&tenantId=a86cb8ed-369b-4df5-ace5-43811f6e08cf)

---
