# S4-B1 — verify on the host, change nothing

**Purpose.** Find out whether the Data Protection certificate and key ring are already provisioned
on the target host. The *code* shipped on 2026-07-17 (`5f679a5`); this answers whether the two host
artefacts and two config values exist. It also collects the **real runtime identity names**, which
`docs/deployment/izibizi-activation.md` currently only guesses at.

**This is read-only.** Nothing below creates a certificate, grants an ACL, edits configuration,
starts or stops a service, or writes to the database. If something is missing, **report it — do not
provision it in the same session.** Provisioning is a separate, owned task.

> None of this has been run anywhere. If a command errors because a path or module differs on this
> host, record the error and move on — a failed command is a finding, not a blocker.

---

## Fill these in first

```powershell
# Adjust to this host. If you do not know a path, leave it and note that in the report.
$DeployPaths = @('C:\inetpub\wwwroot\SibylaApi','C:\inetpub\wwwroot\SibylaWeb','D:\Sibyla\Worker')
$ExpectedKeyRing = 'D:\SibylaData\Keys\Cegid'   # from cegid-integration.md; may differ here
```

## 1 — Configuration, on every host that runs API, Web or Worker

```powershell
'--- machine environment ---'
'Cegid__Enabled','Cegid__SecretProtection__KeyRingPath','Cegid__SecretProtection__CertificateThumbprint',
'ExcelCommit__MicrosoftGraph__Enabled' | ForEach-Object {
  '{0} = {1}' -f $_, ([Environment]::GetEnvironmentVariable($_,'Machine'))
}

'--- appsettings ---'
foreach ($p in $DeployPaths) {
  Get-ChildItem $p -Filter 'appsettings*.json' -ErrorAction SilentlyContinue | ForEach-Object {
    "== $($_.FullName)"
    (Get-Content $_.FullName -Raw | ConvertFrom-Json).Cegid | ConvertTo-Json -Depth 4
  }
}
```

Report the two `SecretProtection` values verbatim, and whether `Cegid:Enabled` is `true` or `false`.
Do **not** paste any client secret — there should not be one in these files, and if there is, that
itself is the finding.

## 2 — Certificate

```powershell
Get-ChildItem Cert:\LocalMachine\My |
  Select-Object Thumbprint, Subject, NotAfter, HasPrivateKey |
  Format-Table -AutoSize
```

Is there a certificate whose thumbprint matches the configured one, and does it have
`HasPrivateKey = True`? A matching thumbprint **without** a private key is the failure mode that
makes `ConfigureCegidDataProtection` throw at startup.

## 3 — Key ring

```powershell
$kr = [Environment]::GetEnvironmentVariable('Cegid__SecretProtection__KeyRingPath','Machine')
if (-not $kr) { $kr = $ExpectedKeyRing }
"key ring path: $kr"
Test-Path $kr
Get-ChildItem $kr -Filter 'key-*.xml' -ErrorAction SilentlyContinue |
  Select-Object Name, Length, LastWriteTime
(Get-Acl $kr).Access | Select-Object IdentityReference, FileSystemRights | Format-Table -AutoSize
```

An existing directory with **zero** `key-*.xml` files means nothing has been encrypted yet — the
provisioning may be half done. Files present mean the ring is live and **must be backed up before
anything else happens to it**.

## 4 — Who actually runs the three processes

This is needed regardless of the outcome: the activation runbook guesses the identity names, and
the grants must target the real ones.

```powershell
Import-Module WebAdministration -ErrorAction SilentlyContinue
Get-ChildItem IIS:\AppPools -ErrorAction SilentlyContinue |
  Select-Object Name, @{n='Identity';e={$_.processModel.identityType}},
                      @{n='User';e={$_.processModel.userName}} | Format-Table -AutoSize

Get-CimInstance Win32_Service |
  Where-Object { $_.Name -like '*Sibyla*' -or $_.DisplayName -like '*Sibyla*' } |
  Select-Object Name, State, StartName | Format-Table -AutoSize
```

## 5 — Private-key ACLs, only if a matching certificate exists

```powershell
$tp = [Environment]::GetEnvironmentVariable('Cegid__SecretProtection__CertificateThumbprint','Machine')
if ($tp) {
  $cert = Get-Item "Cert:\LocalMachine\My\$($tp -replace '\s','')" -ErrorAction SilentlyContinue
  if ($cert -and $cert.HasPrivateKey) {
    $k = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
    $path = "$env:ProgramData\Microsoft\Crypto\RSA\MachineKeys\$($k.Key.UniqueName)"
    "private key file: $path"
    (Get-Acl $path).Access | Select-Object IdentityReference, FileSystemRights | Format-Table -AutoSize
  } else { 'no certificate with a private key for the configured thumbprint' }
} else { 'no thumbprint configured' }
```

## 6 — Whether any secret has actually been stored

Read-only SQL against the Sibyla database. `SELECT` only.

```sql
SELECT "Provider", "CompanyEntityId", "Enabled",
       ("ClientSecret" IS NOT NULL AND "ClientSecret" <> '') AS has_secret,
       length("ClientSecret") AS secret_len
FROM "CegidCompanyConnections";
```

If the table does not exist under that name, list what does:

```sql
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public' AND table_name ILIKE '%onnection%';
```

**Never print the ciphertext itself** — its length is enough. A row with a stored secret is the
strongest possible evidence that B1 was provisioned and worked, because the secret could not have
been written without a usable key ring.

---

## Report back as this table

| # | Check | Result |
|---|---|---|
| 1 | `Cegid:Enabled` | |
| 1 | `KeyRingPath` configured | |
| 1 | `CertificateThumbprint` configured | |
| 2 | Certificate present with private key | |
| 3 | Key-ring directory exists | |
| 3 | `key-*.xml` files present (how many) | |
| 4 | API / Web / Worker identity names | |
| 5 | Private-key ACL grants those identities | |
| 6 | Company connection rows, and any with a stored secret | |

Plus anything that errored, verbatim.

## What the answers mean

**Rows in `CegidCompanyConnections` with a stored secret, plus key files present** — B1 is done.
Close it, confirm the `.pfx` backup exists, and the only remaining prerequisite is B2.

**Certificate and key ring present, no key files, no rows** — provisioned but never exercised.
B1 is done pending a save-and-reload test once B2 lands.

**Thumbprint configured but the certificate is missing or has no private key** — worse than not
provisioned: the three services will throw at startup as soon as `Cegid:Enabled` becomes `true`.
Report this loudly.

**Nothing configured** — B1 is genuinely outstanding. Follow
`docs/deployment/izibizi-activation.md`, using the identity names from check 4 rather than the
guessed ones.

**Key files present but no rows, and the key-ring path recently changed** — stop and report before
anything is enabled. Ciphertext written under an old ring is unreadable under a new one.
