# Cegid izibizi — activation prerequisites (S0-7)

**For:** `S4` — the first end-to-end push. **Scope decided 2026-08-10:** one internal company for
the pilot; the izibizi account is active with API access; the Data Protection certificate is
self-signed on the host.

Two external prerequisites and one operator task. Neither prerequisite depends on engineering, so
both can start in week 1 rather than week 7 — which is the whole point of surfacing them now.

> The PowerShell below has **not been executed anywhere**; it is written from
> `docs/cegid-integration.md`, `docs/deployment/iis-and-worker.md` and the DI wiring. Verify each
> step on the target host and correct this file from what actually happened.

---

## S4-B1 — Data Protection certificate and key ring

**The code is already done — it shipped in the July spine on 2026-07-17 (`5f679a5`), alongside
`CegidCompanyConnections`.** What is missing is only the two host artefacts and the two config
values. Do not re-implement anything.

What exists:

```csharp
// SibylaServiceCollectionExtensions:204
services.AddDataProtection()
        .SetApplicationName("GOTT.Sibyla.CegidSecrets.v1")   // shared, so API/Web/Worker interoperate
        .PersistKeysToFileSystem(new DirectoryInfo(options.KeyRingPath))
        .ProtectKeysWithCertificate(certificate);            // LocalMachine\My, by thumbprint
```

Secrets are protected per company and per purpose (`CreateProtector(..., purpose, companyEntityId)`),
the UI field is write-only, and the **same key ring also protects the Moloni client secret and
refresh token** — `DataProtectionMoloniSecretProtector` takes `IOptions<CegidOptions>` and reads
`Cegid:SecretProtection`. The section name is a wart, not a bug; there is one key ring for both
providers.

It fails closed in both directions. `CegidOptionsValidator` refuses startup with
*"Cegid:SecretProtection requires KeyRingPath and CertificateThumbprint when Cegid is enabled"*,
and if the thumbprint is set but no matching certificate **with a private key** is in
`LocalMachine\My`, `ConfigureCegidDataProtection` throws at startup naming the thumbprint. There is
one quiet path worth knowing: with Cegid disabled and the section blank, `AddDataProtection()` still
registers but falls back to per-identity default key storage — API, Web and Worker would then each
hold a different key ring and be unable to read each other's ciphertext. Nothing warns about that
until Cegid is enabled.

**So B1 is host provisioning, not development:** create the certificate, create the key-ring
directory, grant the three runtime identities, set two config values. Until it exists, **no company
connection can be saved** — the secret has nowhere safe to go. First domino, not a parallel task.

**This is encryption at rest, not TLS.** Nothing has to trust the certificate, so a self-signed one
is correct here. What matters is that the private key is exportable and backed up: lose it and
every stored secret is unrecoverable.

```powershell
# 1. certificate
$cert = New-SelfSignedCertificate `
  -Subject "CN=Sibyla Data Protection" `
  -CertStoreLocation Cert:\LocalMachine\My `
  -KeyExportPolicy Exportable -KeySpec KeyExchange `
  -KeyUsage KeyEncipherment,DataEncipherment `
  -NotAfter (Get-Date).AddYears(10)
$cert.Thumbprint     # -> Cegid__SecretProtection__CertificateThumbprint

# 2. export WITH the private key, and put the .pfx somewhere backed up and off-host
$pw = Read-Host -AsSecureString "PFX password"
Export-PfxCertificate -Cert $cert -FilePath D:\Backup\sibyla-dataprotection.pfx -Password $pw

# 3. key ring directory
New-Item -ItemType Directory -Force -Path D:\SibylaData\Keys\Cegid

# 4. grant ONLY the three runtime identities - adjust the names to the real ones
$ids = @("IIS AppPool\SibylaApi", "IIS AppPool\SibylaWeb", "NT SERVICE\SibylaWorker")
foreach ($id in $ids) { icacls D:\SibylaData\Keys\Cegid /grant "${id}:(OI)(CI)(M)" }

# 5. same three identities need read on the certificate's private key (CNG)
$key  = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
$path = "$env:ProgramData\Microsoft\Crypto\RSA\MachineKeys\$($key.Key.UniqueName)"
foreach ($id in $ids) { icacls $path /grant "${id}:(R)" }
```

Then set on all three hosts (see `docs/deployment/production.env.example.ps1`, which currently
carries `CHANGE-ME`):

```
Cegid__SecretProtection__KeyRingPath          = D:\SibylaData\Keys\Cegid
Cegid__SecretProtection__CertificateThumbprint = <thumbprint from step 1>
```

**Check first, provision second.** Some or all of this may already exist on the host —
`docs/deployment/izibizi-b1-verification.md` is a read-only check that answers it, and also
collects the real IIS app-pool and service identity names, which the `$ids` above only guesses.

**Done when:** the three services start clean, a company connection saves and reloads with its
secret intact, and the `.pfx` is in the off-host backup. **Owner: Miguel.** **Target:** week 1 —
it is minutes of work and it blocks everything else here.

---

## S4-B2 — izibizi credentials for the pilot company

**What it is.** Per company: the HTTPS API endpoint, the client id, and the client secret. The
account is active with API access, so this is a generation-and-handover task, not a procurement one.

**Never** put a client secret in `appsettings.json`, an environment variable, a log, or a
deployment ZIP. It is entered once in the Sibyla UI and stored as company-bound ciphertext. The
field is write-only: leaving it blank on a later edit preserves the current value.

**Done when:** the three values exist for the pilot company and have been entered in Sibyla.
**Owner: Luís.** **Depends on B1** — until the key ring exists the secret has nowhere safe to go, so
this cannot be completed first even though it can be prepared in parallel. **Target:** week 1–2.

---

## Operator task — connect and test (was S4-B3)

Not an external dependency. `to_subentity_id` is **discovered by the application**, not supplied by
anyone: *Discover and test fiscal years* requests an OAuth token, calls `GET /fiscal_years_list`,
stores the returned `id` (for example `pt999999990_1#y2024_1_`) as the mapping's
`to_subentity_id`, then validates each selected mapping with `PUT /entity_sub_switch`.

In **Empresas internas**, for the pilot company:

1. *Software de integração* → **Cegid**.
2. Save a **disabled** draft with the endpoint, client id and secret.
3. **Discover and test fiscal years**; keep the intended years; save the selection.
4. Test again — a draft may have no fiscal-year mappings, an enabled connection may not.
5. Enable.

A company without a tested, enabled connection is skipped by the poller and by the Cegid portion of
document integration. That is deliberate and is not a configuration error.

**Done when:** the connection is enabled and the fiscal-year mapping tests green.
**Depends on:** B1 and B2. **Target:** the day after B2.

---

## Deliberately not required

**The DPAPI Graph token cache is not an izibizi prerequisite.** It serves
`MsalDelegatedGraphTokenProvider` — the Microsoft Graph workbook projection, not Cegid. With
`ExcelCommit__MicrosoftGraph__Enabled=false` the container injects `DisabledFinancialWorkbookProjector`
(`IsEnabled => false`), and `IntegrateDocumentJobHandler` both skips the projection and treats a
document as fully integrated on the Cegid commit alone:

```csharp
// IntegrateDocumentJobHandler:148
if (workbookProjector.IsEnabled && cegidOptions.Value.ProjectPurchasesToExcel) { ... }

// IntegrateDocumentJobHandler:255  - is the document fully integrated?
return !workbookProjector.IsEnabled || !cegidOptions.Value.ProjectPurchasesToExcel || <ExcelCommits confirmed>;
```

v6 defers Excel export parity, so the workbook copy, the three schema fingerprints and the DPAPI
cache all move to Phase 2 with it. They return only if Excel projection is reinstated.

---

## Order of enabling, when S4 arrives

Deploy with `Cegid__Enabled=false` — that installs the schema and the operations surface without
contacting Cegid. Then enable Cegid with the Excel projection flags off, validate the purchase push
and the sales pull, and stop there. `Table2` / `Table13` / `Table4` projections and PDF ingestion
are separate later steps, and the Excel ones are Phase 2.

One decision still open and now on the S4 critical path: **draft versus closed on
`supplierInvoices/insert`** — deferred since July.
