# SAIF.Platform

The base "service defaults" package every Forge .NET service depends on. It follows the [Aspire service defaults pattern](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/service-defaults#custom-service-defaults) — most of `Extensions.cs` is stock Aspire template output — with a documented Forge-specific delta on top.

## Getting started

```csharp
builder.AddServiceDefaults();

// ...

app.UseServiceDefaults();
```

## Stock Aspire behavior

The following are unmodified from the Aspire template and documented by Microsoft, not re-explained here:

- OTel base instrumentation for ASP.NET Core, `HttpClient`, and the .NET runtime.
- OTLP export, enabled when `OTEL_EXPORTER_OTLP_ENDPOINT` is set.
- `AddDefaultHealthChecks()` and its `self` liveness check.
- `AddServiceDiscovery()`.
- `AddStandardResilienceHandler()`, including its retry-on-POST behavior.
- `/health` and `/alive` endpoints, mapped by `MapDefaultEndpoints()`.

## Forge-specific delta

The rest of `Extensions.cs` is Forge's own addition on top of the template. This is the part worth reading before you rely on it.

### `ConfigureDistributedCache`

Reads `REDIS_CONNECTION_STRING` and falls back to `AddDistributedMemoryCache()` when it's unset. **That Redis branch is currently dead code** — nothing in the repo ever sets `REDIS_CONNECTION_STRING` (no Terraform module, no template, no pipeline), so 100% of Forge apps take the in-memory fallback today. Tracked for removal in [#1171](https://github.com/saif-corp/forge/issues/1171).

The practical consequence is that the distributed cache is **per-instance**. Under scale-out or slot deployments, each instance holds its own cache, which shows up as extra OBO token round trips in the `saif.authentication.token_cache.hits`/`misses` counters. This isn't a correctness bug, just a characteristic worth knowing.

The method itself isn't going away even once the Redis branch is removed — `SAIF.Platform.Authentication`'s `RequestAccessTokenService` depends on `IDistributedCache` for OBO access-token caching.

### `AddDefaultCors`

- **Development**: permissive — any method, any header, credentials allowed. Allowed origins are auto-discovered by recursively walking the `Services` configuration section.
- **Production**: no CORS policy at all.

This is a dev/prod behavior cliff, not a bug. If a service needs CORS in production, it has to configure its own policy explicitly.

### `ConfigureOpenTelemetry`

- `tracing.SetSampler(new AlwaysOnSampler())` — 100% trace retention. This is an ingest-cost/volume tradeoff, not a fixed requirement; it's worth revisiting per-service if trace volume becomes expensive.
- `AddSource("*")` / `AddMeter("*")` — wildcard collection of all sources and meters, opt-out rather than stock Aspire's explicit opt-in instrumentation list.

### `UseServiceDefaults(WebApplication)`

A Forge-only wrapper around `UseCors()` (in Development) plus `MapDefaultEndpoints()`. Stock Aspire only maps default endpoints; the CORS call is Forge's addition.

### `Diagnostics.ActivitySource`

Platform self-instrumentation — `AddServiceDefaults` and friends emit their own activities tagged with `platform.*` attributes (e.g. `platform.cache.provider`, `platform.cors.development_policy`), separate from the app-level tracing they configure.

## Related open work

- [#1171](https://github.com/saif-corp/forge/issues/1171) — remove the dead Redis path and its now-unused `Microsoft.Extensions.Caching.StackExchangeRedis` dependency. Not implemented by this PR; documented here as current behavior only.

## Related packages

<!-- These relative links will be rewritten by a future docs-publishing hook, similar to how src/terraform module READMEs are republished under docs/reference/terraform/. -->

- [`SAIF.Platform.Azure`](../SAIF.Platform.Azure/index.md)
- [`SAIF.Platform.AspNetCore`](../SAIF.Platform.AspNetCore/index.md)

---

[View source on GitHub](https://github.com/saif-corp/forge/blob/main/src/dotnet/SAIF.Platform/README.md)
