# Work package 6: production composition

## Production ownership

`CredentialProviderCompositionRoot` owns persisted provider selection, account
binding, AzureAuth installation discovery, process execution, materialization,
and readiness.

The composition root is a snapshot. After `identity configure`,
`identity reconfigure`, or `identity unconfigure` changes persistence, callers
create a fresh production root to observe the new state. Identity mutation does
not construct the current production root first, so `reconfigure` and
`unconfigure` can repair malformed records.

Missing provider configuration is `ProviderNotConfigured`; production does not
synthesize Direct MSAL or any deterministic credential provider. Direct MSAL
remains explicitly unavailable when selected. Deterministic providers exist
only through `CreateExplicitTestScaffold`.

## AzureAuth 0.9.5 discovery

The supported release is AzureAuth `0.9.5`, source commit
`21258ff3a2cbb01d6891243114a55abe9ae3587e`.

The provider config stores the selected version, not an executable path.
Windows production derives:

`%LOCALAPPDATA%\Programs\AzureAuth\0.9.5\azureauth.exe`

Native Windows obtains `LocalApplicationData` from
`Environment.GetFolderPath`. WSL invokes the fixed Windows PowerShell only to
obtain `LocalApplicationData` and the executable file version using normal
framework APIs, then maps the absolute `C:` path under `/mnt/c`. Discovery
requires an ordinary file and version `0.9.5`.

Native Linux uses the official `azureauth` Debian package payload at
`/usr/lib/azureauth/azureauth`. Discovery reads the adjacent managed
`azureauth.dll` assembly identity and requires version `0.9.5.0`; it does not
launch `azureauth info`, which creates telemetry device-ID state. An explicit
absolute `AZUREAUTH_CREDPROVIDER_AZUREAUTH_PATH` supports isolated acceptance
and deployment bundles without PATH lookup.

Discovery does not inspect hashes, Authenticode, ACLs, owners, SIDs, reparse
points, ancestors, System32 trust, or stable artifact identities. The supported
model trusts the OS and official same-user installation. Missing, wrong-version,
unsupported-host, and unavailable discovery results have actionable codes.

## Launch

Windows and WSL interactive acquisition use the mapped absolute executable and
its containing directory as the working directory:

```text
azureauth.exe aad
  --client 872cd9fa-d31f-45e0-9eab-6e460a02d1f1
  --tenant <bound tenant>
  --scope 499b84ac-1321-427f-aa17-267ca6975798/.default
  [--domain <best-effort account domain>]
  --output token
```

`--domain` is derived only when the optional bound account has a usable suffix
after `@`. It is a cached-account preference, not account enforcement.

The process inherits the normal host integration environment. The product does
not set `OEAUTH_MSAL_DISABLE_CACHE`, construct `WSLENV` allow/deny lists, or
attest PATH. The product omits `--mode` and relies on pinned AzureAuth `0.9.5`'s
Windows default: WAM broker, then web authentication. The broker first tries the
OS account and its broker-backed cache, which permits unattended reuse when
Windows already has a suitable account. A prompt broker failure falls through
to web; an unanswered WAM dialog consumes AzureAuth's global timeout and does
not fall through.

Native Linux interactive browser acquisition adds explicit `--mode web` and
removes inherited `AZUREAUTH_MODE`, `AZUREAUTH_NO_USER`, and
`Corext_NonInteractive` values from the child. Native Linux `SilentOnly` also
uses `--mode web` but sets `AZUREAUTH_NO_USER=1`; AzureAuth retains its initial
cached-token attempt while suppressing interactive flows. A cache miss returns
`AzureAuthSilentTokenUnavailable`. This cache is owned by AzureAuth and is not
the Azure CLI cache; `az login` does not populate it.

Explicit native Linux device-code acquisition uses `--mode devicecode`, clears
the same inherited interaction controls, captures token stdout privately, and
tees only bounded stderr instructions to the CLI human prompt stream. It
requires `InteractionAllowed`, `UserAllowed`, and that prompt stream; all other
device-code request shapes reject before launch.

When Linux keyring persistence is unavailable on a headless host, pinned
AzureAuth 0.9.5 falls back to its documented unprotected cache with owner-only
directory and file modes. The native Linux headless support decision explicitly
accepts that provider-owned behavior under the same-user threat model because
silent Git and Python reuse otherwise cannot exist without adding a replacement
credential store. Product-owned derived credentials remain non-persistent and
have no plaintext fallback.

Windows AzureAuth `0.9.5` has no equivalent cache-only CLI mode, so Windows and
WSL `SilentOnly` still return `SilentAcquisitionUnavailable` without launching.
Interactive and silent readiness remain independent.

## Host interaction routing

Interaction is bounded by host protocol capabilities and explicit
non-interactive environment signals:

| Host request                                                | Identity flow | Policy           | Acquisition mode     |
| ----------------------------------------------------------- | ------------- | ---------------- | -------------------- |
| NuGet `IsNonInteractive=true`                               | Browser       | `Never`          | `SilentOnly`         |
| NuGet interactive and `CanShowDialog=true`                  | Browser       | `HostToolAllows` | `InteractionAllowed` |
| NuGet interactive and `CanShowDialog=false`                 | Device code   | `HostToolAllows` | `InteractionAllowed` |
| Git credential helper                                       | Browser       | `Never`          | `SilentOnly`         |
| Python keyring default local request                        | Browser       | `UserAllowed`    | `InteractionAllowed` |
| Python keyring with an explicit non-interactive environment | Browser       | `Never`          | `SilentOnly`         |

NuGet's non-interactive flag overrides dialog capability. The product does not
attach the CLI human prompt stream to NuGet plugin execution, so that NuGet
request shape remains unavailable before process launch. Explicit native Linux
`login --device-code` attaches the CLI stderr stream and tees AzureAuth's bounded
device-code instructions in real time while keeping token stdout private.
Python keyring follows the ecosystem's interactive-backend behavior by default.
It becomes silent-only when `ARTIFACTS_KEYRING_NONINTERACTIVE_MODE` is `true`
(case-insensitive), matching the upstream artifacts-keyring contract, or when
the existing `AZUREAUTH_NO_USER` signal has any non-empty value. Unset, empty,
`false`, `1`, and malformed dedicated-keyring values remain interactive.
`PIP_NO_INPUT` and `TWINE_NON_INTERACTIVE` alone do not suppress requests:
keyring's shared shim protocol exposes no trustworthy caller interaction bit,
and the corresponding CLI options are not reliably exported. Forced
non-interactive pip or Twine keyring calls must therefore set
`ARTIFACTS_KEYRING_NONINTERACTIVE_MODE=true`. The product does not inspect
parent process arguments, infer policy from a terminal, or select device code
for keyring requests.

## Persistence

Provider and binding records use bounded plain UTF-8 JSON in the normal
XDG/HOME or Windows LocalApplicationData root. Writes use an in-process mutex,
ordinary cross-process lock, content-hash conflict revision, and same-directory
atomic move. Product-created paths receive owner-only Unix modes.

There are no Base64 envelopes, filesystem ownership/ancestor proofs, `statx`,
directory durability interfaces, `fsync` protocol, or ABA generation
guarantees. Malformed records and cooperative concurrent-write conflicts remain
explicit.

## Token handling

AzureAuth stdout is bounded raw token text. Transport validation rejects empty,
multiline, whitespace-containing, control-containing, or oversized output. JWT
signature, audience, tenant, and time-claim consistency are not inferred from
unsigned local parsing. Only `exp` may be read as untrusted functional metadata;
SPS `validTo` remains authoritative for exchanged session credentials.

## Synchronous boundary

The public synchronous adapter blocks straightforwardly on the cooperative
async acquisition service. Timeouts and cancellation are enforced at actual
process and HTTP I/O. It does not create detached `Task.Run` workers to simulate
containment of a hostile provider.

## Readiness and stable states

| Code                              | Meaning                                               |
| --------------------------------- | ----------------------------------------------------- |
| `ProviderNotConfigured`           | Run `identity configure` to record identity context.  |
| `DirectMsalNotImplemented`        | Run `identity reconfigure` to use the supported path. |
| `AzureAuthInstallationMissing`    | Install AzureAuth 0.9.5 for the current platform.     |
| `AzureAuthVersionMismatch`        | Install/select supported version 0.9.5.               |
| `AzureAuthBindingRequired`        | Run `identity configure` with the intended tenant.    |
| `AzureAuthBindingMalformed`       | Run `identity reconfigure` or `identity unconfigure`. |
| `SilentAcquisitionUnavailable`    | Windows/WSL AzureAuth has no silent-only mode.        |
| `AzureAuthSilentTokenUnavailable` | Native Linux cache lookup requires interaction.       |

Status and doctor report provider selection, installation, binding, and separate
interactive/silent readiness without printing tokens or process output. Login,
logout, and doctor qualify their no-plaintext statement as
`product-plaintext-fallback` so it is not misread as a claim about AzureAuth's
provider-owned cache.
