# Apollo — Discovery, Architecture, and Phased Plan

> *Naming note (2026-09-02, see `naming-glossary.md`): "Sibyla" written here before 2026-09-02 means the FDR skill build or the module Apollo built from it, which is now **Argus**; the retired applications and database are **Sibyla Legacy**; "Apollo" means this platform, which became **Sibyla** in Phase 3; "Hermes" before 2026-09-02 means the Hermes Agent runtime or GOTT's instance of it, which is now the agent **Apollo** (Phase 4).*

**Last updated:** 2026-08-27 (single living document, edited in place) · **Status:** Phase 0 deep discovery underway (go-ahead 2026-08-27) · **Target:** `D:\fileStorage\repos\GOTT.Apollo` on the POC server (IIS + local PostgreSQL 18) · **No implementation code has been written.**

**[FACT — environment, verified 2026-08-27 on the POC server]** Source locations on this machine: Invoice Skill Build `D:\fileStorage\repos\invoice-skill-build` · IdentityServer `D:\fileStorage\repos\GOTT.IdentityServer` · Medusa `D:\fileStorage\repos\Medusa - CRM Task Manager` · Calypso `D:\fileStorage\repos\GOTT.Calypso`. Tooling: .NET SDK 10.0.301, PostgreSQL 18.4 (service `postgresql-x64-18`, running), glab authenticated to gitlab.com; repo remote `https://gitlab.com/gottsolutions.dev/sibyla/gott-apollo.git`. Latest UI prototype drop: **`Interface/Share/Share 260827`** (supersedes the 260826 noted in §1.2; D11's latest-at-build-time rule applies). **[FACT — owner, 2026-08-27]** The house Nextcloud instance is at **`https://nextcloud.gottsolutions.net/`** — first live storage target for tenant #1 and for Nextcloud provider development/testing (§2.5).

Markers: **[FACT]** verified in sources · **[INFERENCE]** architectural reading · **[RECOMMENDATION]** · **[ASSUMPTION]** · **[DECIDED]** ruled by you on 2026-08-26.

**Companion plan (separate initiative, not part of Phases 0–3 below):** [`docs/ai-projects-harness-plan-260831.md`](ai-projects-harness-plan-260831.md) — the Administrator-only **AI Projects / Development Harness** inside Apollo (project-first navigation → session → chat → Hermes · Codex · Claude, running against real repositories). Planned 2026-08-31, fast-prototyping mode, no implementation code written. It carries its own decision register (D-AI-1…8) and touches this plan's ground in three places: it centralizes authorization into ASP.NET Core policies (closing a real gap — `/companies`, `/storage`, `/erp` are today reachable by any authenticated user, their role check being only a rendered message), it adds a `SELECT`-only `NOBYPASSRLS` reader role so the operational Hermes agent's database access is pinned to the selected licence, and it holds Apollo read-only against `gott_apollo` until the FDR cutover — operational writes continue through the `invoice-skill-build` JSON layer and the sync (§3).

---

## 0. Decisions in force

| # | Decision |
|---|---|
| **D1** | **[DECIDED]** Apollo is a clean build from the named references: the Invoice Skill Build (Sibyla authority), the Medusa and Calypso documentation projects, the UI prototype under `Interface/Share`, and GOTT.IdentityServer. No other codebase is considered. |
| **D2** | **[DECIDED]** The table-structure authority is the **Invoice Skill Build prototype** (FDR schema, current state). |
| **D3** | **[DECIDED]** Apollo clients/scopes may be registered in GOTT.IdentityServer (configuration/data only; no code changes to the IdP). |
| **D4** | **[DECIDED]** Frontend is **Blazor** (.NET 10), with a design-token + component layer for prototype transfer. |
| **D5** | **[DECIDED]** One shared PostgreSQL database; OwnerId on all tenant-owned rows; EF global query filters; **row-level security as fail-closed backstop**. |
| **D6** | **[DECIDED]** Tenant storage credentials in an **envelope-encrypted store in PostgreSQL** (ASP.NET Data Protection, dedicated key ring); only opaque references in config tables; pluggable to Vault/KeyVault later. |
| **D7** | **[DECIDED]** New **GitLab** project; remote CI manual/milestone-gated; local-first during prototyping. |
| **D8** | **[DECIDED]** Cloud storage: **Nextcloud fully live first**; **OneDrive and Google Drive later**. The provider-selection UI lists all planned providers from the start (non-live ones visible but disabled). |
| **D9** | **[DECIDED]** **ERP integration is in scope from the beginning, configured per company.** Selectable and configurable in the UI from the start: **Cegid Izibizi** and **Cegid Primavera**. Others (**Moloni**, **TOC Online**, …) listed as future providers, visible but disabled. **Reaffirmed 2026-08-27:** the prototype's ERP select (Primavera/Sage/SAP/Dynamics, noted in `docs/discovery/07`) is ignored — D9's provider list rules. **Ruled 2026-08-27: the Primavera target is the on-prem v10 Web API** (not cloud ROSE) — the ERPCFG abstraction keeps room for a ROSE variant later, but development, connection tests, and the first Primavera push flow target v10; the v10 network-exposure pattern is **resolved (owner, 2026-08-27): the Apollo server has a VPN to the Primavera on-prem server** — v10 Web API calls go over that VPN; no public exposure of the customer's v10 endpoint. ERPCFG's v10 base URL is therefore a VPN-reachable address, and connection tests in Phase 1 can run against the real v10 instance. (For future customers, VPN-per-customer is the established pattern; `docs/discovery/08`.) |
| **D10** | **[DECIDED, AMENDED 2026-09-02]** **Source data keeps evolving daily until live deployment.** Migration is therefore a **repeatable, delta-aware synchronization** from the FDR, re-run at every milestone, with a final cutover sync at go-live — not a one-shot import. The full corpus is loaded from the start and kept current. **Amendment (owner ruling 2026-09-02, §3):** the POC's financial documents, their edits and the physical files (PDFs) are **not** carried into Apollo; the sync is narrowed to master data and whatever the owner has not named as document-like. **Re-amended (owner ruling 2026-09-05, §3): the handover carries the full corpus after all — every document wave including bank — so the cutover is again a final freeze-and-sync with reconciliation sign-off; and (ruling of the same day) the physical PDF files of the archive are imported into the tenant's Nextcloud storage and registered against their document rows.** |
| **D11** | **[DECIDED]** **UI prototype policy:** the prototype under `Interface/Share` may change daily. Each new Apollo screen is built from the **latest Share version at the moment it is built**; once a screen exists in Apollo it is **frozen — revisited only when you say so**. No automatic per-drop reconciliation of built screens. |
| **D12** | **[DECIDED]** **Hosting:** self-hosted on an **AWS Windows instance — IIS + local PostgreSQL, no containers.** **Claude CLI runs connected to the Claude subscription** (not per-call API billing). |
| **D13** | **[DECIDED]** **Final gold for Sibyla in this phase: Ingestion + Processing + Quality + Audit + Integration.** Other Sibyla areas (reconciliation, receivables/payments views, P&L, bank checks, …) follow in later phases. |
| **D14** | **[DECIDED]** **Licence model:** a licence covers **all** companies of the owner by default — no licence↔company selection table. The licence specifies the **maximum number of companies**, set by the **superuser** (platform operator) when issuing/editing the licence; the tenant **admin can create companies only up to that count** (enforced server-side, not just in UI). **Active modules are defined in the licence** (LICMOD). Modules not active in the licence remain **visible** to users but render **only a module presentation page** (marketing/overview) — no data, no routes, no APIs. |
| **D16** | ~~**[DECIDED 2026-08-27]** (queue D-19) **Production endpoint: `apollo.gottsolutions.net`** hosts Apollo.Web. Client registrations at the IdP use this hostname for production redirect URIs.~~ **Superseded 2026-09-02 by naming ruling N2:** the platform is `sibyla.gottsolutions.net` (intake API `api.sibyla.gottsolutions.net`), taken after the legacy sites move to `legacy.gottsolutions.net` / `api.legacy.gottsolutions.net` (Phase L, owner-run). IdP redirect URIs follow the sibyla hostname. **Added 2026-09-03:** the platform's documentation site is `docs.sibyla.gottsolutions.net`, an IIS static site over this repository's `docs\` folder (`D:\fileStorage\repos\GOTT.Apollo\docs`), the counterpart of Sibyla Legacy's `docs.legacy.gottsolutions.net`; it is the third site of the production deployment. |
| **D17** | **[DECIDED 2026-08-27]** (queue D-18) **Development uses the production IdP `https://login.gottsolutions.net/`** — no local IdP instance. Its database is **`gott_identity` in the local PostgreSQL on this server** (user/credentials available in the server environment), so the Apollo client registrations (D3, data-only) can be executed directly here. Dev redirect URIs for localhost are registered alongside production ones. |
| **D18** | **[DECIDED 2026-08-27]** (queue D-68) **Platform keys are `uuid`** — OWNMST and all platform-table keys use uuid (supersedes deliverable 06's bigint sketches; 09/10's uuid signatures stand). |
| **D19** | **[DECIDED 2026-08-27]** (queue D-63) **Data Protection key ring is NOT backed up with the database** — separate protection, keys, and backup location (amends §2.9; threat assessment G5). |
| **D20** | **[DECIDED 2026-08-27]** (queue DN-10/D-10) **Pre-cutover, Apollo is read-only on sync-owned columns** — corrections continue at the FDR and arrive via sync; enforced by guard triggers. *Scope follows the D10 amendment of 2026-09-02: document columns are no longer sync-owned, because documents are not synced.* |
| **D15** | **[DECIDED]** **No Supabase.** The Invoice Skill Build interface is reported to now connect to Supabase; that is prototype infrastructure only. Apollo stays as defined: **local PostgreSQL on the server** (D5/D12) — no Supabase dependency in schema, APIs, or hosting. Phase 0 verifies what the prototype's Supabase connection implies for sources: whether `Editor/Data/*.json` remains the canonical data layer for the continuous sync (§3) or whether a Supabase instance has become an additional/primary source to read from, and whether the prototype's data-access shape usefully informs Apollo's API contracts. |

Still open (defaults apply unless you say otherwise): assumptions **A1–A3** in §4.

**D15 verification closed (2026-08-27)** — see `docs/discovery/03-supabase-d15-verification.md`. **[FACT]** The prototype's Supabase connection (added 2026-08-27, "Stage 04") is a one-way downstream mirror: `Editor/Data/*.json` → workbook → `registry-data.json` → a single JSONB row-store (`sib_*` tables); user input lands in side tables with no write-back upstream; Share drops reach it only via a local Python bridge (port 8766) with silent fallback to bundled JSON; no credentials committed. **`Editor/Data/*.json` remains the sole canonical layer for the continuous sync (§3); Apollo ignores the Supabase instance entirely**, taking from the prototype only contract-level ideas (sync-run provenance as a resource, queued-and-audited change submissions, workbook-shaped read payloads, and the promoted filter columns as the starting query surface).

---

## 1. Discovery snapshot (what the sources contain)

### 1.1 Sibyla authority — `Itoorer Master - Invoice Skill Build`

**[FACT]** The authoritative proof of concept: a 36-sheet registry (`Invoice_Registry.xlsx`), the canonical JSON data layer under `Editor/Data/` (data.json, entities.json, bnkmov.json, docrqe.json, …), ~200 pipeline scripts under `Scripts/`, governed `Specs/` (18 Engagement Rules, Policies, `Data Schema/schema.md` + ERD, Change Log, Roadmap), the packaged `invoice-registry` skill, and company document archives (`Gott/` with ~1,250 DOCLOG-referenced files, `Itoorer/`, `Toorist/`), all mapped by a generated `INDEX.md` (2026-08-25).

**[FACT — corrected 2026-08-27 against `Specs/Data Schema/schema.md` (Stage 13 R29); see `docs/discovery/01-fdr-schema-inventory.md`]** Table inventory (6-letter codes; to be preserved) — **36 tables, 567 fields**: ENTMST, ENTALS, ENTITM, ENTBNK, ENTBLC, ENTPAY · ITMMST, ITMALS, ITMCLS · DOCLOG, DOCTYP, DOCFLG, DOCEFL, DOCRQE, **DOCFAI** (layer-1 failure table, previously missing from this list) · FDCHDR, FDCDTL · RCVCTR, RCVDTL, RECREV · PAYCTR, PAYDTL · BNKACC, BNKMOV, BNKREC, BNKMAT, BNKCHK · SNCACC, MNGACC, COCACC, EXCRAT · quality/control (AUDCHK, DQLCHK, DPRCHK, WRKFLW, OFDGAP). Corrections vs the earlier list: **QUACHK is retired** (renamed/rescoped to DQLCHK at Stage 13 R02; sheet removed — its 71 ledger codes exist only historically), and **PLMKO/PLMKEY are columns of MNGACC**, not a table.

**[FACT]** The FDR's governed rule set (Engagement Rules, Policies, Procedures under `Specs/`) is live and recently extended (August 2026). Rules Apollo's schema and worker must honor from day one include: the **counterparty resolution precedence ladder** (own-account exclusion first, then IBAN, name, document identity, learned repetition, amount last and provisional; an amount is not an identity; a resolution may not learn from a guess; an honest blank beats a plausible name); **line completeness** — every document entry line carries Entity + ItemCode + EICode, and an unresolvable pair is reported, never guessed; **hammered lines** — human-set exception lines are true for themselves and evidence for nothing (no learning surface may read them, no inference may overwrite them); and the **Master Data Consistency Policy** — copies repaired silently, declared exceptions reported and never repaired, printed source text never touched. The item-class taxonomy (ITMCLS) binds at its **current** state in the FDR tree.

**[FACT]** The packaged `.skill` lags the current `Specs/` tree. Apollo packages its worker skills **from the live Specs tree** at a pinned date, never from a stale package.

**[INFERENCE]** The FDR is company-scoped and single-tenant (Gott/Itoorer/Toorist inside one registry). The OWNMST/licence layer is genuinely new and belongs to the Apollo platform, not to any source.

### 1.2 UI prototype — `Interface/Share`

**[FACT]** README declares "Share 260825" current, but **`Share 260826` exists and is newer** (files written 2026-08-26); latest-by-evidence is Share 260826. It is a framework-free HTML/JS/CSS SPA (index.html 45 KB, app.js 304 KB, styles.css 36 KB, `data/registry-data.json`, Sibyla icon asset) with **16 views**: overview, documents, classification, entities, items, receivables, payments, reconciliation, bankChecks, pnl, quality, audit, workflow, processingQueue, integration, admin. The gold scope (D13) maps chiefly onto: documents + processingQueue + classification (Ingestion/Processing), quality, audit, integration, plus the shell/overview and admin surfaces the platform needs.

### 1.3 Identity provider — `D:\fileStorage\repos\GOTT.IdentityServer` (POC server; formerly `C:\WIP\GOTT.IdentityServer`)

**[FACT]** Self-hosted .NET 10 **OpenIddict** + ASP.NET Core Identity + EF Core + PostgreSQL. Flows: **authorization code** (+ refresh tokens via `offline_access`) and **client credentials**. Registered scopes: `email`, `profile`, `roles`, `offline_access`. Client registration/secret/scope management services with admin UI; SMTP password recovery; production authority `https://login.gottsolutions.net`. Phase 0 completes the deep-dive (claims content, token lifetimes, logout/front-channel behavior, PKCE enforcement, integration conventions) and prepares the exact Apollo client registrations.

### 1.4 Medusa and Calypso

**[FACT]** Both are documentation/design projects (brainstorming → design → planning → showcase), no code. Calypso includes a Sibyla–Calypso integration plan (2026-08-13) and a Portfolio Spine decision (2026-08-18/19). **[INFERENCE]** They constrain Apollo only through the module seam: module registration, shared ENTMST-style master data, and shared platform services (auth, tenancy, documents, notifications, background jobs). Phase 0 reads both in full to validate the seam design.

---

## 2. Architecture (per decisions)

### 2.1 Solution shape — modular monolith + document worker

```
C:\FreshLook\Apollo
├── docs/                                  # this plan, ADRs, discovery deliverables
├── src/
│   ├── Apollo.Platform.Domain/            # OWNMST, LICMST(+LICMOD), COMMST, USRMST/USRCMP/USRROL,
│   │                                      # shared ENTMST-family master data, AUDLOG, notifications
│   ├── Apollo.Platform.Infrastructure/    # EF Core + tenancy enforcement + RLS session management,
│   │                                      # PostgreSQL job queue, storage abstraction + providers,
│   │                                      # encrypted credential store, outbox, health checks
│   ├── Apollo.Platform.Contracts/         # versioned contracts shared with workers
│   ├── Apollo.Modules.Sibyla.Domain/      # FDR tables & business rules (DOCLOG, FDCHDR/FDCDTL, …)
│   ├── Apollo.Modules.Sibyla.Infrastructure/   # incl. ERP integration providers (Cegid Izibizi,
│   │                                           # Cegid Primavera; Moloni/TOC Online later)
│   ├── Apollo.Modules.Sibyla.Contracts/   # versioned extraction schemas, queue messages
│   ├── Apollo.Web/                        # Blazor host: OIDC, tenant context, module registration,
│   │                                      # design-system components, PT/EN localization
│   └── Apollo.Worker.Documents/           # separately deployable (Windows Service);
│                                          # queue consumer; Claude CLI runner
├── tests/
│   ├── Apollo.Tests.TenantIsolation/      # cross-tenant attack suite — release-blocking
│   ├── Apollo.Tests.Platform/  Apollo.Tests.Sibyla/  Apollo.Tests.Browser/ (Playwright)
├── db/                                    # RLS policies, migration verification
└── local/                                 # PowerShell run scripts, local PostgreSQL setup, seeds
```

Modules plug in through `IApolloModule` (routes, navigation, entitlement key, EF model contribution, permission catalogue, **and a presentation page**). Per D14, every registered module is visible in navigation; when the tenant's licence does not activate it, only its presentation page renders — module routes, APIs, and data are entitlement-gated server-side. Medusa/Calypso/Themis therefore exist from the start as catalogue rows + presentation pages + entitlement checks, proving the seam without exposing any functionality.

### 2.2 Tenancy and access

New platform tables in the house 6-letter style: **OWNMST** (owner/tenant) · **LICMST + LICMOD** (licence: plan, status, start/expiry, **MaxCompanies** and other usage/user limits, billing refs, suspension history; LICMOD = the modules active under the licence). Per D14, licence administration is a **superuser** (platform-operator) capability outside any tenant; the tenant admin's company-creation right is checked server-side against the active licence's MaxCompanies at insert time (constraint + service check, race-safe) · **COMMST** (company, exactly one owner) · **USRMST / USRCMP / USRROL** (user↔IdP `sub`; all-companies flag or selected-company grants; platform/module-scoped roles — no role-per-company-combination explosion) · **STGCFG** (tenant storage connection) · **ERPCFG** (per-company ERP connection, §2.6) · **AUDLOG**.

Isolation defense-in-depth **[DECIDED D5]**: OwnerId (+CompanyId where scoped) NOT NULL and embedded in unique constraints; immutable per-request `ITenantContext` resolved server-side from token `sub` → USRMST (browser-supplied tenant/company IDs are validated against assignments, never trusted); EF global query filters; **PostgreSQL RLS** with a non-bypass application role and `SET LOCAL app.owner_id` per transaction, so a forgotten filter fails closed; tenant-scoped job payloads, storage paths, cache keys, log scopes, temp directories; cross-tenant attack tests from the first migration, release-blocking.

### 2.3 Identity integration

Apollo.Web = confidential authorization-code + PKCE client of GOTT.IdentityServer; refresh via `offline_access`; worker/service calls via client credentials. Authentication only from the IdP; **all** tenant/licence/company/module authorization computed in Apollo per request from its own tables. No Apollo-specific claims minted into IdP tokens initially (`sub` is the join key) — avoids IdP changes and stale-claim invalidation. Client registration through the IdP's existing admin/config **[DECIDED D3]**.

### 2.4 Frontend **[DECIDED D4, D11]**

Blazor interactive server on .NET 10, PT/EN localized. Structure for prototype transfer: design tokens (CSS custom properties) extracted from the prototype's `styles.css`; a component inventory mapping each prototype pattern (shell/nav, cards, grids, filters, status chips, review panes, dialogs) to exactly one Razor component; screens composed only from those components. **Working rule per D11:** each new screen is built against the latest Share version at build time and recorded with that version stamp; built screens are then frozen and revisited only on your instruction — at which point the token/component layer keeps the rework localized. Impeccable / web-design-guidelines audits run against built screens each phase.

### 2.5 Tenant cloud storage **[DECIDED D6, D8]**

`IDocumentObjectStore` abstraction + provider registry keyed by STGCFG (owner-level). **Nextcloud (WebDAV + app credentials/OAuth2) implemented first; OneDrive (Microsoft Graph) and Google Drive next.** The storage-configuration UI lists all planned providers from the start, non-live ones visible but disabled. Provider-neutral locator `apollo://{owner}/{company|_intake}/{category}/{year}/{docId}/{artifact}` mapped to deterministic, sanitized provider paths (owner / company / category / business year / receipt date / stable ID / original name / artifact type); originals immutable where supported; artifacts separate from originals. Credentials envelope-encrypted in PostgreSQL via Data Protection with a dedicated key ring; STGCFG holds only the opaque reference plus non-sensitive settings, connection status, last validation, enabled flag. Ingestion is durable-local-first: accept → checksum → registration → queued cloud transfer → queued processing; outages delay transfer but never lose documents; failed transfers remain visible and retryable per tenant. The full failure-mode matrix (expired auth, quota, renamed/missing folders, permission changes, partial uploads, duplicates, provider change/migration, throttling, staging cleanup, retention) is specified in Phase 0.

### 2.6 ERP integration **[DECIDED D9]**

Per-company, from the beginning, behind one provider abstraction (`IErpIntegrationProvider`): **ERPCFG** rows are company-scoped (FK → COMMST, therefore tenant-scoped through the company), carrying provider selection, connection settings, credential reference (same encrypted store as storage), enabled flows, status, and last validation. **Cegid Izibizi and Cegid Primavera are selectable and configurable in the UI from the start**; Moloni, TOC Online and others appear as future providers, visible but disabled. Design principles: a fiscal document is **one canonical record with N external representations** — pushes are idempotent (an integration ledger records commits/operations per provider so re-runs never duplicate), auditable, and queued through the same job engine as document processing; a company may have different providers per flow in future without schema change. Phase 0 produces the Izibizi/Primavera API comparison (auth model, document endpoints, idempotency options, sandbox availability) and the integration state machine; the walking skeleton includes configuration + connection test, with the first live push flow landing in the gold build (Phase 2).

### 2.7 Ingestion, queue, worker

Channel adapters (UI upload first; WhatsApp, BUZZ and email later, delivered by Apollo, the gateway agent, through the channel-intake API) feed one ingestion pipeline with idempotency keys — the same external event never creates duplicate work. Captured per document: owner, company-if-known, channel, filename, MIME, size, checksum, timestamps, sender metadata, correlation + idempotency IDs, storage provider + object ID, transfer status, processing status, retry info, history. Unassignable documents stay tenant-scoped in an `_intake` area and route to classification/human review.

Queue = PostgreSQL job table: `FOR UPDATE SKIP LOCKED` claims, lease + heartbeat for restart recovery, **deterministic FIFO within (Owner, Company), round-robin across owners** so one tenant's backlog or poison document never starves others, bounded retries with backoff, dead-letter → manual review, per-tenant concurrency caps for back-pressure. The worker (Windows Service on the same or a separate instance) establishes tenant context from the job row (trusted), fetches content only through the storage abstraction, runs **Claude CLI under the Claude subscription** with skills packaged from the current FDR `Specs/` tree, validates output against explicit versioned schemas (Claude output = untrusted input), populates the FDR tables, stores processing evidence and agent output for audit, and cleans per-job sandbox directories (per-tenant temp roots; no shared caches; no cross-tenant content in prompts, logs, or outputs).

### 2.8 Security baseline

Server-side authorization everywhere; file validation + size/type limits + malware-scan integration point; prompt-injection defenses (document content is data, never instructions; extraction contracts allow no imperative fields; hostile-document test corpus before non-golden channels open); OAuth state/callback protection on provider connections; TLS in transit; encrypted credential store with rotation/revocation runbooks; audit logs for admin actions, access changes, and automated document decisions; retention policies; log redaction; storage/ERP authorization monitoring; failed-upload recovery procedures. Tenant-isolation, credential-exposure, and cross-tenant document-access findings are release-blocking.

### 2.9 Hosting and environments **[DECIDED D12]**

**Production:** one AWS Windows Server instance — IIS hosts `Apollo.Web` (ASP.NET Core hosted in IIS via the ASP.NET Core Module); `Apollo.Worker.Documents` runs as a **Windows Service** on the instance; **PostgreSQL installed locally** on the same instance (no containers anywhere); GOTT.IdentityServer remains at `https://login.gottsolutions.net`. Claude CLI is installed on the instance and authenticated against the **Claude subscription**. Data Protection key ring stored on the instance with restricted ACLs and backed up with the database. Serilog structured logs to local rolling files; health checks exposed for IIS/uptime monitoring; scheduled PostgreSQL backups (base + WAL) to durable storage.

**Local development:** Windows machine mirroring production shape — local PostgreSQL install (no Docker), IIS Express/Kestrel for the web host, the worker as a console/service, PowerShell scripts under `local/` for restore/build/migrate/seed/test/run so the same validation later runs in CI. No secrets committed; user-secrets/environment configuration per host.

---

## 3. Data migration — continuous sync until go-live **[DECIDED D10]**

The FDR corpus evolves daily and remains the operating system of record until Apollo goes live. Migration is therefore built as a **repeatable, delta-aware synchronization pipeline**, not an import: source = the FDR's canonical data layer — `Editor/Data/*.json` cross-checked against `Invoice_Registry.xlsx` (spreadsheet-skill assisted), with Phase 0 confirming whether the prototype's new Supabase connection changes where the canonical layer lives (D15); target = tenant #1's Apollo tables; each run is idempotent, ordered (master data → documents → bank → controls/flags), and ends with a **reconciliation report** (row counts, checksums, referential integrity, tombstone rules — no ENTITM row may reference a retired entity or item, dangling codes reported never guessed). The full corpus loads at the start and the sync re-runs at every milestone; archive files are registered against the tenant's storage (Local/dev store first, re-homed to Nextcloud when connected). **Cutover plan:** at go-live, a final freeze-and-sync run migrates the last deltas, the reconciliation report is signed off, and the FDR pipeline stops being the system of record. A second synthetic tenant (with deliberately overlapping real-world entities, e.g. GOTT appearing as a supplier) is maintained purely to power the cross-tenant attack suite.

**2026-09-02 — owner ruling: no carry-over of the POC's financial documents (D10 amended).** Sibyla was a proof of concept. Its financial documents, the edits made to them and the physical files (the `*.pdf` under the archive) are **not** migrated into Apollo; the "documents → bank → controls/flags" legs of the sync order and the "archive files are registered against the tenant's storage" clause above are withdrawn from the plan. What stays: master data (wave 1) and anything the owner has not named as document-like; anything derived from documents — the document tables themselves (wave 2: fdchdr/fdcdtl, doclog, docfai), document-scoped controls, flags, gaps and settlements (waves 4 and 5), the intercompany ledger (wave 7) and the ERP integration ledger (wave 8) — is out of the carry-over unless the owner rules it back in as a separate decision. The cutover therefore no longer has a "final freeze-and-sync of the last document deltas"; go-live starts Apollo's document history at zero. This ruling narrows the *plan*; retiring the corresponding `Apollo.Sync` waves and their tests is code work with its own owner decision, not done by this entry. The operational rule that the live `gott_sibyla` database and `C:\SibylaApps` are not dropped or mutated until the freeze is unchanged.

**2026-09-05 — owner ruling: the handover imports all the documents, including bank (D10 re-amended).** The owner's words: "lets include importing all the documents in the handover, including bank." The 2026-09-02 narrowing is reversed for the data: the sync order is again master data → documents → bank → controls/flags → ledgers, all waves stay in `Sibyla.Sync` (none is retired), and the cutover is again a **final freeze-and-sync of the last deltas with reconciliation sign-off**, after which Apollo is the system of record with the FDR's full document history. What the 2026-09-02 ruling still excludes, because the owner did not name it back in: the physical PDF files under the POC archive and the POC's edit history are not transferred as files; the document rows arrive through the sync as before. The operational restriction on `gott_sibyla_legacy` and `C:\SibylaApps` is unchanged.

---

## 4. Assumptions still standing (say if wrong)

- **A1** Tenant #1 = NP Group with companies incl. GOTT, itoorer, Toorist; the FDR corpus belongs to it.
- **A2** UI bilingual PT/EN from the start.
- **A3** ERP flows in the gold build: purchases/sales push per company as configured; exact flow-by-provider matrix to be fixed in Phase 0 from the Izibizi/Primavera analysis.

---

## 5. Phased plan

### Phase 0 — Deep discovery & architecture dossier *(in progress since 2026-08-27; analysis only)*

**Progress (2026-08-27, session 1)** — delivered under `docs/discovery/`:
- `01-fdr-schema-inventory.md` — full field-level inventory: 36 tables / 567 fields, layer map, structural observations (fresh-per-render pseudo-keys incl. BNKREC; CodeName-ridden joins where EntityCode exists — incl. the PAYCTR/RCVCTR column *named* EntityCode that holds a CodeName; 1NF violations in comma-joined fields; type sentinels like `"NO RATE"`; BNKMOV sign polymorphism per account type; denormalized render copies vs deliberate snapshot contracts), 16 open questions.
- `02-editor-data-inventory.md` — 73 JSON files / ~28k records inventoried. Sync keystones: `permanent_code_ledger.json` (~13.7k codes across 25 tables) is the identity registry to import first; **no modification metadata exists anywhere** → delta sync = full-read + per-row content hash keyed on permanent codes (~25 MB, feasible); QUACHK data lives only in the workbook; four tombstone mechanisms; entity references are CodeName strings to resolve via ENTALS; RCVDTL/PAYDTL JSONs are empty placeholders.
- `03-supabase-d15-verification.md` — D15 verified and closed (see §0).
- `04-identityserver-deep-dive.md` — flows (code+PKCE per-client, refresh, client credentials), OpenIddict **default** lifetimes (access 1 h, id 20 min, refresh 14 d rolling), `sub` = Identity user GUID; **D3 confirmed** (clients via admin UI/seed config; custom `apollo-api` scope creatable via admin UI — data-only); exact `apollo-web`/`apollo-worker` registrations specified. Notables: no introspection/revocation endpoints (Apollo's per-request table authz is the kill switch, as planned); front-channel logout stored but never invoked; exact-match redirect URIs; IdP hygiene findings to report to its owner (dev certs active in production; SMTP password committed in `appsettings.Production.json`).

- `05-sibyla-workflow-summary.md` — all 19 Engagement Rules + Master Data Consistency Policy catalogued in full (precedence ladder ranks 0–5, hammered lines, 11 BNKMAT match types, AccountPeriod ladder R1–R4, evidence tiers E1–E4, DOCRQE/RECREV rules A–H, SKILL.md-only rules incl. the permanent-code ledger and Reference-Only state); pipeline mapped in a 20-row stage→tables→rules→Apollo-component table. Central design implication: the FDR's most-repeated defect class is **duplicated rule ownership** → each rule gets exactly one owning service in Apollo; every learning store gets a provenance-gated write path (never learn from guesses, hammered rows, or rank-5 amount matches); queues are exception surfaces, non-gating. 12 open questions.
- `06-source-to-target-mapping.md` — all 36 tables mapped (DDL sketch + field map + sync notes each), plus new first-class tables: `code_ledger`/`code_counter` (identity registry; Apollo becomes issuing authority at cutover, closing §6 risk 3), `sync_run`, `entbnk_iban`, 19 declaration/override/tombstone tables, `legacy_workbook_archive`, 12 replacement views. Headline transforms: BNKREC persisted with `(bnkmov, fdchdr)` natural key (kills BRCodes and the comma-joined BankReference/MatchType); CodeName joins unified onto entmst FKs (alias resolution once at ingest); BNKMOV signed-amount convention (`cash_delta`/`direction` computed at ingest); `"NO RATE"` sentinels → NULL + status; render copies → views, snapshot contracts preserved. **9 [DECISION-NEEDED] items with defaults (DN-1…DN-9) await the owner's call.**
- `07-ui-inventory-design-tokens.md` — built from **Share 260827** (stamped): 16 views confirmed, but **`workflow` view is retired** (redirects to `audit`); admin hosts 11 registry-driven sub-views; 27 Razor components proposed (one prototype pattern → one component); ~80 design tokens extracted with consolidation observations. New in 260827: DPRCHK Processing Review Queue, review-decision + AI-input submit panels, `/api/refresh`. **Open conflict for the owner: the prototype's ERP select lists Primavera/Sage/SAP/Dynamics — D9 rules Cegid Izibizi + Primavera (+Moloni/TOC Online disabled).** 11 open questions.
- `08-erp-provider-analysis.md` — **iziBizi has a public documented API** (Cloudware JSON:API platform, OAuth2 auth-code, purchase-document push, self-service credentials; base URLs/sandbox/limits unknown until signup). **Primavera = two provider variants in ERPCFG:** on-prem v10 self-hosted Web API (network-exposure problem per customer — largest risk) vs cloud ROSE (OAuth2 client-credentials, developer test subscriptions — best sandbox). TOConline shares the Cloudware API family with iziBizi; Moloni public+sandbox. **No provider offers server-side idempotency — the ERPLDG integration ledger guarantees it client-side** (persisted mid-push draft IDs; find-before-create after ambiguous outcomes). Phase 1 = config + connection tests; Phase 2 first live push = iziBizi purchases, then ROSE.

**Progress (2026-08-27, session 1 continued)** — dossier completed:
- `09-storage-architecture.md` — Nextcloud via **app passwords / Login Flow v2** (not its scope-less OAuth2); `WebDav.Client` + raw HttpClient; provider comparison (Graph, Google Drive — fileId-based identity, `drive.file` scope avoids the CASA audit); `IDocumentObjectStore` contract; STGOBJ locator→objectId manifest; full 10-row failure-mode matrix (auth revocation parks jobs after ≤3 attempts — naive retries get the server IP throttled by Nextcloud's brute-force protector). House instance: `https://nextcloud.gottsolutions.net/`.
- `10-queue-worker-design.md` — fairness by exact least-recently-served owner rotation via JOBSCH scheduler table (concrete SQL; naive ORDER BY starves tenants); lanes (claude/io/erp/admin); 9-class error taxonomy (ClaudeUnavailable = first-class lane-pause, refunded attempts; business rejections route to review, never retry); Claude CLI in per-tenant sandbox under a Windows Job Object, immutable `{date}-{hash}` skill packages, dual output validation, full evidence capture.
- `11-continuous-sync-design.md` — 8 atomic run phases (code_ledger first), SHA-256 raw-row delta with separate `transform_version`, verification gates G0–G10 (counts and amount checksums exact, blocking), cutover runbook C1–C8, deterministic synthetic tenant #2 with deliberate cross-tenant collisions. DN-10…DN-17 queued (default: Apollo read-only on sync-owned columns pre-cutover).
- `12-testing-and-cicd-strategy.md` — template-database cloning + Respawn; all integration tests under a `NOBYPASSRLS` role with a filter-stripped RLS probe and a pg_policies meta-test; 9-class cross-tenant attack suite with endpoint-coverage meta-test; **free-tier CI: no `.gitlab-ci.yml` during prototyping; at milestones, web/tag-triggered manual jobs on the POC server registered as a Windows project runner (0 shared minutes)**; `local\*.ps1` scripts as the single validation entry point; deploy via folder-swap script, promoted to a manual job at Phase 3.
- `13-threat-assessment.md` — STRIDE per 11 trust boundaries; 63 threats, 21 gaps. High gaps: RLS GUC leakage across pooled connections (needs interceptor + zero-rows probe); **SSRF via tenant-supplied storage/ERP URLs** (not previously in the plan); worker/CLI egress + least-privilege; **key ring must NOT be backed up with the DB** (contradicts §2.9 — queued as D-63); learning-store poisoning gate; sender→tenant binding for future channels. RB1–RB9 testable release-blocking criteria.
- `14-risks-acceptance-criteria.md` — the Phase 0 exit checklist: 32-risk register, **70-item owner decision queue** (incl. DN-1…17, D-68 owner-key-type bigint-vs-uuid to rule before migration #1), 62 acceptance criteria across phases, external-contact list. §5 addendum records the owner rulings of 2026-08-27: D9 governs the ERP list; **Primavera = on-prem v10 (not ROSE), reached over the existing VPN from the Apollo server** (R-10 resolved for tenant #1; Phase 1 connection test against real v10); Nextcloud instance confirmed.
- `15-architecture-diagrams.md` — the 8 required diagrams + sync pipeline, as Mermaid; the diagramming pass surfaced 6 structural tensions, folded into 14 §5.1.

**Phase 0 dossier is complete (15 deliverables). Exit criterion: owner review of the dossier + answers to the decision queue in `14-risks-acceptance-criteria.md` (items D-01…D-70; defaults apply only on explicit acceptance).**
Produce the 20 required deliverables under `C:\FreshLook\Apollo\docs\discovery\`, scoped by the decisions: full field-level inventory of `schema.md` + `Editor/Data/*.json` + `Invoice_Registry.xlsx`; complete Sibyla workflow summary (classification, extraction, validation, review, quality, audit, archiving); **source-to-target PostgreSQL mapping built from the FDR** (preserving business meaning while correcting structural problems); IdentityServer deep-dive + exact client registrations; storage architecture with Nextcloud-first provider comparison; **ERP provider analysis (Cegid Izibizi vs Cegid Primavera: auth, endpoints, idempotency, sandboxes) + integration state machine**; queue state machine + retry/failure design; continuous-sync migration design with verification gates; UI component/screen inventory from the latest Share + design-token extraction; local-first testing strategy (Windows, no containers); cost-conscious CI/CD strategy; threat assessment; risks; per-phase acceptance criteria. Diagrams: owner/licence/company, user access, module boundaries, ingestion, storage, ERP integration, worker, deployment. **Exit: your review/approval of the dossier.**

### Phase 1 — Walking skeleton *(first vertical slice, local-first)*

**Progress (2026-08-27):** solution scaffolded (commit `f6fe79d`); RLS + isolation suite 9/9 green (`7fb4762`); IdP clients registered data-only and verified live; **P1-01 sign-in verified end-to-end by the owner** — authorization code + PKCE against `login.gottsolutions.net`, tenant panel showing NP Group with GOTT/itoorer/Toorist under RLS (`89906e0`). Storage + ERP configuration UIs with live connection tests shipped (`7dfcbec`; Nextcloud instance is local to this server; **Primavera v10 Web API licence still pending installation — its connection test fails by design until Cegid installs it**). **Upload → queue → worker → Claude CLI slice verified end-to-end 2026-08-27**: DOCINT intake + JOBQUE/JOBSCH queue (least-recently-served owner rotation, lease/fencing, jittered backoff, dead-letter; `apollo_worker` NOBYPASSRLS role with explicit worker_access policies on queue/intake tables only), worker ran Claude CLI headless over a real FDR invoice (Telles de Abreu 2024-12) and persisted a contract-validated `apollo.extract.v0` result. Note: the npm `claude` shim is a .cmd — the worker resolves and launches the native `claude.exe` directly (cmd.exe mangles multi-line args). Isolation suite stays green. **2026-08-28: iziBizi connection test green against the real Cegid Cloudware endpoint** (client_credentials per the official docs; the discovery's authorization-code assumption was corrected in 08 — the initial failure was a stored-credential mismatch, secrets are now trimmed on store, and `local/tools/DiagErpTest` replays the token call with decrypted stored credentials for future disputes). /erp and /storage gained per-row editing. Review/correction flow and race-safe MaxCompanies company admin shipped (`2a627e4`). **First FDR sync wave is green and idempotent (`159f01f`)**: Sibyla module schema wave 1 (code_ledger/code_counter/sync_run + 12 master-data tables, uuid keys, forced RLS, separate migrations history) and `Apollo.Sync` implementing design 11's P1–P4 with G0/G1/G2/G3 gates — first run loaded 13,767 ledger codes + 4,661 rows with zero quarantine; second run 100% unchanged. Live-data corrections found by the sync: `entbnk.company` is nullable owner-wide (28 blank-company rows vs the mapping's NOT NULL), `Country` is a full name not ISO2. **Documents wave green and idempotent (`5be3bea`)**: 1,237 fdchdr (I 476 + R 245 + bank-generated 516, disjoint unions verified) + 1,218 fdcdtl + 1,571 doclog + docfai/doctyp/docefl/code_migration/retired_duplicate_document, with the G4 amount-checksum gate exact to the cent (net 1,368,286.81 / vat 41,077.36). The sync's quarantine caught four real mapping-vs-data corrections (doctyp natural key incl. OriginClass; doclog vocabulary exceeds the doctyp catalogue — 725 rows preserved as text with NULL FK; code_migration keyed (old, entry); Period derived for I/R). **Bank wave (`3a26e4c`) and controls/flags/queues wave (`4510e67`) green — THE FULL FDR CORPUS IS NOW SYNCED AND IDEMPOTENT**: 39 tables, ~36,500 rows + 13,767 ledger codes, G4 checksums exact to the cent (fdchdr net 1,368,286.81 / vat 41,077.36; bnkmov −16,809.41), G1 ledger membership green over 20 code families, isolation suite green over the full schema, second run 100% unchanged. bnkrec is persisted on the (movement, document) pairing identity; cash_delta/direction implement the sign convention in one place; review queues carry the protected human memory verbatim. Remaining sync items (smaller): the 19 declaration/override tables (§3.3), archive-file registration, replacement views (§5), gate G10 (Excel witness) and guard triggers on protected columns. **Milestone P1-15's core is met — the corpus loads with verification gates; the remaining pieces harden it.**

**Progress (2026-08-28, overnight session):** the sync is now COMPLETE for the JSON layer — wave 5 added the 17 declaration/override tables (§3.3) with raw references always kept (`8dda59d`); wave 6 the 13 replacement views, security_invoker so RLS applies (`123e888`); the G8 archive gate verified **1,244/1,246 archived files sha256-exact** and surfaced **2 files genuinely missing at source** (Itoorer BICS 202606/202607 — reported to the owner, non-blocking); G5 confirms **zero entitm pairs referencing retired entities/items** and **1,218/1,218 lines carry ItemCode+EICode** (`ededfa1`). **The module seam is live (D14, `4c332b3`)**: module catalogue with sibyla active and medusa/calypso as visible-unlicensed presentation pages; first Sibyla read-only screens over the synced corpus (/sibyla/documents, /entities, /bank, /reviews — the last showing the 5 open blocking flags), all entitlement-gated server-side and reading under RLS transactions. **Deferred with rationale:** guard triggers on protected columns wait for Apollo-native write paths (Phase 2 — nothing writes those columns yet except the BYPASSRLS sync); **G10 is COMPLETE (`812a6e4`)**: 4-layer Excel witness (counts all 40 sheets · cent-exact sums · seeded spot checks, 9,308 field comparisons, 0 mismatches · gating exit code), with the owner's option-B ruling encoded (FDCDTL = JSON + O/P/F mirror lines, accepted until the source ships them) and the render-transformed column class excluded from field equality (compared structurally via FKs). DN-17 runs at the default 25 rows/sheet (`--sample` configurable); **DN-7 closed by owner ruling (2026-08-28): QUACHK genuinely retired and replaced by DQLCHK — no history extraction; the 71 burned codes in `code_ledger` are the permanent remnant (recovery path: FDR commit `ce9b834`).** **DN-1 is resolved by source evolution (2026-08-28): the 47 workbook-orphan fdchdr rows were backfilled into the JSON layer by the FDR pipeline — the G10 witness check (`local/tools/workbook-witness/workbook_witness.py`) shows workbook 1,237 = JSON 1,237, zero divergence; it re-runs at every milestone.**

**Progress (2026-08-28, afternoon):** design system landed (`5c50d99`) — the Share 260827 tokens as `wwwroot/css/tokens.css` (deliverable 07's consolidations applied) with the app shell (topbar + entitlement-aware sidenav) and every screen restyled; the three remaining gold screens shipped (`fdb7c5f`): /sibyla/quality (checklists + open-flag counts + effective relevancy), /sibyla/processing (DOCRQE queue with proposal view), /sibyla/classification (the ENTITM authority). **Nextcloud transfer pipeline live (`8783d56`)**: idempotent WebDAV Put, storage-transfer jobs on the io lane, worker decrypting credentials via the shared key ring, type-aware fenced completion, LocalOnly-never-lost semantics, cloud chip on /documents — first real transfer awaits the owner's app password. **Worker hardened (`4d3904b`)**: JOBLNE lane pause on Claude unavailability, lease heartbeat, sandbox sweep, 2 slots.

**Progress (2026-08-28, evening — Share-parity screens):** the Sibyla sidenav now mirrors the Share 260827 view set and order, and every view the prototype has is reachable in Apollo, built from that drop (stamped; D11 freeze applies): **Workflow** — Overview, Documents, Uploads, Processing, Quality, **Audit** (`/sibyla/audit`: Findings/History modes; Company/Year/Period/Source/Status filters; All/Warning/Reconciliation/Flag/Exception type toggle with counts; summary tiles; findings assembled exactly as the prototype's `auditRows()` — BNKCHK bound explicitly off State with the case-statement descriptions, AUDCHK-routed DOCRQE with the mirrored-DOCFLG de-duplication, RECREV → AUD-MOVEMENT-UNMATCHED; History = DOCLOG + terminal occurrences), **Integration** (`/sibyla/integration`: Documents/Entities tabs, ERP select fed by the licence's configured connections per D9 rather than the prototype's illustrative list, Company/Period, Lines/Ready/Errors/Local value tiles, the prototype's per-line checks with the Review drill-down; **Integrate is present but disabled until the ERPLDG ledger push lands in Phase 2** — no simulated status flips); **Management** — Entities, **Items** (`/sibyla/items`: Entity/Country/Search over ENTITM links; Code/Item/Class-Sub/PLMKEY/PLMKO/SNCACC/CC), Classification, **Payments / Receivables** (`/sibyla/payments`, `/sibyla/receivables`: one component, Company/Year/Period/Status with the prototype's *Due / Outstanding* default; hold-flag lock shown), **Reconciliation** (`/sibyla/reconciliation`: Company/Period/Status/Priority; ID/Created/DocID/Source/Source ID/Type/Status/Priority/Match with the issue-vs-AI-proposal detail panel), **Bank Checks** (`/sibyla/bank-checks`: Company/Bank/Type/Period; the 12 prototype columns — ID shows the (account·period) coordinates since BCCode was dropped at ingest), **P&L** (`/sibyla/pnl`: Company/Year; PLMKO × PLMKEY × AccountPeriod pivot in local currency with the prototype's calculated rows — Net Revenue / Direct Margin / Gross Margin / Net Margin at their exact insertion points — and collapsible Payroll/Services/Taxes groups), plus Bank movements. All screens are read-only over the synced corpus under RLS transactions, honour the global Company scope, and use the Share's own labels/filters; PT/EN chrome strings for them were seeded from the prototype's `PT_TRANSLATIONS` map (accents restored) into `L`. Edits/creates remain deferred to the Apollo-native write paths (Phase 2) — the prototype's session-only edits are not imitated. **Administration parity** in the same session: the 11 `adminViews` sub-views under `/sibyla/admin/*` (User Access from USRMST/roles/grants; Entity Roles; Item Classes; SNC Accounts with the PT column names; Cost Centers; Bank Accounts; Entity Accounts with IBAN lists; **Period Close** with the prototype's readiness snapshot — documents, open DQLCHK blockers, open review decisions, integration errors, bank warnings — status read from `scope_closure`, Open/Close/Reopen disabled until the declaration write path exists; Quality Checklist shows the governed DQLCHK rather than the prototype's 4 hardcoded rows; Integration Checklist; Configuration Center as read-only platform defaults). **Render smoke suite added (`tests/Apollo.Tests.Browser`, 27 green)**: every Sibyla screen (26 routes) is prerendered through the real pipeline — a cookie forged with the app's own Data Protection ring → TenantResolver → RLS transactions → Blazor prerender — against the local corpus, asserting 200 + the screen's table/panel + no loading placeholder or module gate; plus the anonymous-challenge probe. It is now part of `local\test.ps1`, so a query or render regression on any screen is caught without an IdP round-trip.

**2026-08-31 — Phase 1 gap closed: the ERP integration ledger (ERPLDG/ERPLDA).** Discovery 08 §6 required these to migrate in **Phase 1** — schema and state-machine shape only, no live pushes — *"so the cross-tenant attack suite covers them from the first migration (D5)"*. They had been missed; `IErpProvider` still carries only `TestConnectionAsync`. Now shipped in the Sibyla module (the canonical anchor is `doclog`; the platform keeps owning the ERPCFG *connection*, the module owns *what gets pushed*), with cross-context FKs to `erpcfg`/`commst` and forced RLS. **`erpldg`** is the idempotency anchor — the deterministic key `{Provider}:{Company}:{Flow}:{DoclogId}` plus `UNIQUE (erp_config_id, flow, doclog_id)`, so a second attempt-chain for one document cannot exist, and `push_state` (jsonb) holds the provider draft ids written before each subsequent call, so a killed worker re-enters through find-existing rather than blind re-create — the reason being that **no provider in scope offers server-side idempotency**. **`erplda`** is the per-attempt log (redacted, size-capped request/response snapshots). The nine states of 08 §4.1 are modelled, including the two rules that are easy to get wrong: `Rejected` is never auto-retried (retrying unchanged input is noise; a human fixes the data and starts a *new* chain) and `Committed` is never reopened by provider-side voiding — that is detected by reconciliation instead. **Four cross-tenant probes added** (`ErpLedgerFailClosedTests`): no GUC reads nothing, each owner sees only its own chain, another owner's ledger stays invisible even when addressed by its own id, and the policy's WITH CHECK refuses re-stamping a row into another owner. Isolation suite 11 → 15 green. No push code ships: `IErpProvider` stays connection-test-only until Phase 2.

**2026-08-31 — owner ruling: follow the invoice-skill-build interface.** Decision 2 closed in the prototype's favour: `/sibyla/quality` is now the Share's **occurrence** view, not the catalogue. Findings/History modes split on the prototype's TERMINAL_REVIEW_STATUSES (closed · decided · resolved · applied · superseded · recorded · rejected · waived); occurrences are DQLCHK-routed DOCRQE items plus DOCFLG instances **not mirrored in the queue** (the queue item wins — it carries the decision); the subject of a finding is the **document**, so capture date, company and entity are resolved through DOCLOG/FDCHDR rather than taken from the detector; the relevancy rule is the prototype's `isRelevantQualityIssue` (terminal is never relevant; effective relevancy decides when present, else impact/blocking). Columns, the eight filters, Group by (issue list / by document / by issue class), the type tabs with counts and the summary tiles all follow the drop. The governed catalogue stays where the prototype puts it — Administration › Quality Checklist. **Measured on the current corpus: Findings 0, History 343 (Missing 201 · Needs Input 107 · Inconsistent 35)** — the FDR has worked its DQLCHK queue to completion, so the default tab is legitimately empty and the open backlog lives in AUDCHK, i.e. on /sibyla/audit. Decision 3 (write paths) stays closed for now: the prototype's edits are session-only, so Apollo keeps its edit affordances disabled until the Phase 2 native write paths land.

**Progress (2026-08-31) — FDR Stage 14 absorbed (source commits `c9347cf` 2026-08-28 + `12084f6` 2026-08-31, "Close Stage 14 multi-company registry rollout").** The source moved under us in three structural ways, all now carried:

1. **The tenant grew to seven companies** — Silêncios Atraentes (PT518198499) and Viga Planetária (PT519031563) joined in Round 9. Seeded (`SILA`/`VIGA`, within NP Group's cap of 10) and mapped in `SyncOptions.CompanyMap`. **Without this the sync blocks by design** — an unresolvable company quarantines every row that names it.
2. **Classification moved to a per-company grain, and two of Apollo's unique keys were invalidated by it.** ENTITM's grain became the `(company, entity, item)` trio (408 rows; 39 pairs legitimately repeat across companies) and MNGACC's chart became per-company (76 rows: Gott's 34 accounts + the 7-account default × 6 companies; the same PLMKO appears 7×). Apollo's `UNIQUE(owner, entmst, itmmst)` and `UNIQUE(owner, plmko)` would both have been violated on the next run. Migration `SibylaStage14MultiCompany` adds the company dimension to both, re-keys them, and backfills pre-Stage-14 rows to Gott — the FDR's own rule ("the home of all pre-Stage-14 history"). **ITMMST's classification columns are tombstoned at source**, so `itmcls_id` became nullable there and an unclassified item is now a note, not a quarantine (this is the FDR's own open tail: IC00402–IC00406). ITMCLS stays owner-wide but its single `mngacc_id` FK can no longer name one chart row, so the declared PLMKO is persisted as a value and the chart is resolved per `(company, plmko)` at read time. Every screen that read classification through `itmmst → itmcls → mngacc` now reads it from the trio: Overview, Items, Classification, P&L, Integration, Period Close.
3. **Intercompany pairs.** New `intercompany_pair` table (the ICPairID ledger, forced RLS) plus `ic_pair_id`/`counter_entry_code` on fdchdr, and **gate G9**: every pair must be two crossed legs (one R, one I) agreeing on amount and currency so the owner-level consolidation nets to zero. 2/2 pairs eliminate.

Also absorbed: **the 516 FDCDTL mirror lines are now published data** (`fdcdtl_generated_mirror.json`, Round 10) — Apollo loads them instead of the G10 witness synthesising them, which **closes the owner's option-B ruling of 2026-08-28 on its own terms** ("accepted until the source ships them"); fdcdtl is 1,755 and line completeness is 0 without ItemCode (was 7). The PAYCTR status vocabulary widened (`Waiting (Statement)` Round 10, `Paid (Cash)` Round 14), so the Payments/Receivables *Due / Outstanding* filter treats any `Paid …` as settled and `Waiting …` as still outstanding — the FDR's point being that a declared pull is not a chase item but is not settled either.

**The tenancy collision was ruled, not merged.** Stage 14 gave the FDR its own `OWNMST`/`OWNCPN`/`USRMST`/`USRCMP` — the same names as Apollo's platform tables. They were **not** imported (**superseded for `OWNCPN` by the 2026-09-01 ruling below — it is now mirrored as corpus; the other three still are not**): the policy is explicit that they "record and attribute, they do not enforce" and "must not be read as a security boundary", while Apollo's platform tenancy *is* that boundary (D14 + forced RLS). Instead **gate G11** verifies the correspondence — every live OWNCPN company must have an Apollo company to land in (blocking), and drift the other way is reported. Currently 7/7 map; 7 live USRCMP rows noted as a declared record.

**Result: the full corpus re-syncs green and idempotent** — zero quarantine, zero gate failures; G4 cent-exact (fdchdr net 1,397,190.13 / vat 41,481.41; bnkmov −16,809.41), G8 1,264/1,266 archived files verified (the two known-missing Itoorer BICS files), G9 2/2, G11 7/7; second run 100% unchanged. Isolation suite 11 green, render smoke 27 green.

**Two source-side findings for the owner (Apollo follows the JSON layer, canonical per D15 — the workbook is a witness only; both were written in the same pipeline pass, so this is not staleness):**
- ~~**PAYCTR Status, 4 of 495 rows**~~ — **RESOLVED at source, verified 2026-09-01.** The witness re-run reports `count PAYCTR: 495 -> OK` with no status divergence; the render now carries the Round 10/14 rules the JSON layer had.
- ~~**ENTITM, 2 rows**~~ — **RESOLVED at source, verified 2026-09-01.** Counts reconcile; `EI000394`/`EI000395` are no longer an unpublished pair.

**2026-09-02 — Owner rulings on the intake flow: the licence is certain, the company is optional and comes from the document; Apollo's default profile is NP Group's.**
The owner described the flow as it was on the legacy system and ruled it: through registration,
upload, queue and processing a document has a licence (from the user or the channel binding) and
may or may not have a company; the Claude CLI's analysis returns the company among its feedback,
again optional; when the analysis has problems a person edits the fields. Built the same day:
extraction contract **`sibyla.extract.v1`** (the id the naming shift left open, now cut) adds the
optional `recipient_name` and `recipient_tax_id`, the party the document is addressed to; the
prompt asks for them; the worker, after storing a valid result, matches the recipient's tax id
against the licence's active companies (`CompanyMatcher`: whitespace, punctuation and case
ignored, a two-letter country prefix tolerated on either side) and assigns the company on exactly
one match, re-running the per-company duplicate check the document never entered while it had no
company; none or several matches leave it in triage; every outcome is an audit row
(`document.company.inferred` / `document.company.unresolved`) with the reason. A document that
already has a company is not touched. The Uploads page states the rule. Rows processed under
`apollo.extract.v0` keep that id in their evidence. Tests: `CompanyMatcherTests` and the recipient
case in `ExtractionContractTests`; Platform and Browser green. The second ruling — Apollo, the
Hermes default profile, is the agent instance of the NP Group licence — matches the seeded client
binding (`sibyla-channel-intake` → tenant #1); the per-licence profile table of the harness plan
does not exist yet, so nothing else is recorded in data. Not built: the automatic hand-off from a
processed intake row into the Argus document tables, which no ruling covers yet.

**2026-09-02 — The channel-intake API (`src/Sibyla.Api`), Apollo's plugin, and the deployment reframed to two hosts.**
Built to revision 2 of `docs/apollo-channel-intake-api-spec.md`, which is round 1's 27
adjudications applied as written; the re-review D-CI-10 requires has not happened and the review
record says so. What exists. **Contract** `sibyla.channel-intake.v1` in
`Sibyla.Platform.Contracts/ChannelIntake` (sealed records, unknown members refused, the closed
code set with its status table). **Data**: `chnbnd` (client and sender bindings), `chnreg`,
`chncan`, migration `ChannelIntake` on Main and Preview with `UNIQUE (owner_id, id)` on `commst`
and composite owner+company foreign keys, `tenant_isolation` on all three, and the API role's
policies (`channel_binding_lookup` permissive, `channel_binding_client_scope` restrictive) and
grants in `db/channel-intake-api-role.sql`, applied by the migration when the role exists and by
`setup-db.ps1` otherwise. **Service**: `ChannelBindingResolver` (raw connection, transaction-local
GUCs), `ChannelIntakeService` (register with replay and fingerprint, get with lazy expiry, upload
through the one ingestion path), `ApiTenantContext` (owner from the credential, company from the
sender binding, actor = client id), `IngestionService.AcceptUploadCoreAsync` (the web path wraps
it unchanged; Platform 96/96 proves that), `ChannelBindingAdminService` and the Uploads page's
"trust this sender" and "release into a company" actions. **Host**: JWT bearer pinned to the IdP
with audience `apollo-channel-intake`, problem+json for every refusal including 401/403,
correlation id required and echoed, per-client rate limits, `/health` and `/health/ready`.
**Plugin** `local/hermes/apollo-channel-intake` (36 unit tests) and `install-plugin.ps1`.
**Scripts**: `setup-db.ps1` provisions `apollo_api`, `seed-channel-bindings.ps1`, `run-api.ps1`,
`RegisterIdpClients --channel-intake`. **Tests**: `Sibyla.Tests.Api` service tests 26/26 with the
golden fingerprint; 17 contract tests and 9 isolation tests written. **What did not run, and
why**: the `apollo_api` role needs a database superuser and none is reachable from this session
(the `gott` account cannot create roles; no pgpass, no trust auth), so the contract tests and
eight isolation tests fail on connection until the owner runs `local/setup-db.ps1 -Target Main`
as a superuser and then `local/setup-db.ps1` once more (the role file applies after the tables
exist). Argus 137, Browser 121, Platform 96 and the other 44 isolation tests are green.
**Deployment reframed**: two hosts and one service; the deployment prompt's superseded block
names the API site (`api.sibyla.gottsolutions.net`, `apollo_api`, environment, limits, health,
staging ACL, prerequisites in order), the tooling spec's section 12 gives Slice 2 the second
`Iis.Apply`/`Tls.Apply` pair and its preconditions, and the intake spec's cutover order gained
step 0 (the role). Not run against the IdP: `RegisterIdpClients --channel-intake` (cutover
step 2, owner's go). Open for the owner: the re-review, the superuser step, the initial sender
list, and the four open points of the spec's section 11.
*Same day, later:* the owner ran the superuser step ("Main target ready"); the role, its two
policies and the append-only grants verified in `pg_policies` and `information_schema`; the
API suite is 45/45 and TenantIsolation 52/52 after two test expectations were corrected (the
processing-status enum value, and a client keeps seeing its own bindings under another owner's
GUC, which is the design, not a leak). Evidence: `tests/Sibyla.Tests.Api/evidence/CHECKPOINT-AB-green.txt`
with a framed fingerprint over contracts, services, migration, tests and plugin. Still open: the
re-review, the IdP client (cutover step 2), the sender list, the spec's section 11.
*Cutover step 2 done on the owner's word:* `RegisterIdpClients --channel-intake` registered
the client `sibyla-channel-intake` in `gott_identity`; the operator's copy of the secret is in
`local/secrets/idp-clients.json`, the runtime copy is still to be put in the Hermes `.env`
(every re-run of the tool rotates it). A mint-and-inspect against `login.gottsolutions.net`
found two things the tests could not: the issuer carries a trailing slash (the host now pins
both forms) and the scope name already existed from Sibyla Legacy with resource `sibyla-api`,
so the tool now adds the API's audience `apollo-channel-intake` to that scope without removing
the legacy resource; a real token carries both audiences until cutover step 8. Api suite
45/45 after the change.
*Cutover step 3 done on the owner's ruling ("the same sender list as the legacy intake
channel"):* the enabled channels of active legacy profiles were read once from `gott_sibyla`
and seeded as tenant #1's sender bindings through `seed-channel-bindings.ps1`: 8 senders, all
passing the new rules, none skipped, all routed to the licence-wide `_intake` area because the
legacy registry carried no company. The bindings file is gitignored. Next: cutover step 4, the
plugin install and the Hermes configuration, with the secret placed in `.env` by hand.
*Cutover step 4 done on the owner's word:* plugin installed under `%LOCALAPPDATA%\hermes\plugins`,
`config.yaml` switched (backup kept beside it: `apollo-channel-intake` enabled, the
`apollo_channel_intake` toolset on buzz, cli and whatsapp and in the known toolsets, the legacy
plugin left disabled until step 8), the five `APOLLO_CHANNEL_INTAKE_*` keys in `.env`, the
plugin doctor green with three tools, the cron `apollo-email-gateway` created paused on the same
mail runner as the legacy one. Found on the way: the Hermes gateway has been down since
2026-09-01 and the legacy mail cron no longer existed; the gateway was not started (owner's
call; recovery is the `Hermes_Gateway` scheduled task). The tools become functional after
Phase L and the API deployment; until then they answer with the API's refusals.

**2026-09-02 — Deployment tooling Slice 1 V3 merged as `Sibyla.Deployment`, recovered after the naming shift.**
The branch `ops/apollo-deployment-slice1-v3` (landed at `89a8dfb` against `aed729c`, reviewer
verdict ACCEPT recorded in its evidence) was stranded by the naming shift. Recovery, in three
commits on the branch and one merge: rebased onto `cdaaf2c`; the approved hunks applied to the
renamed hosts (`src/Sibyla.Web/Program.cs`, `src/Sibyla.Worker.Documents/Program.cs`, the
service-name resolver with default `Sibyla.Worker.Documents`, its test in `Sibyla.Tests.Argus`);
two forced deltas inside the module's fingerprint (`RepoPaths` finds `Sibyla.slnx`; default
hostnames `sibyla.gottsolutions.net` per N2); then, on the owner's word, the module renamed to
`Sibyla.Deployment` with `*-SibylaProduction` / `Invoke-SibylaDeployment` /
`Invoke-SibylaReleaseGate` entry points, `SIBYLA-S1-*` error codes and the hosts' secrets variable
`SIBYLA_SECRETS_FILE`. Every earlier evidence transcript is kept verbatim; `RECOVERY-green.txt` and
`RENAME-green.txt` record the two later runs with their fingerprints; the spec's section 11 tells
it. Kept by ruling: `GottSibylaWorker`, the `apollo_*` roles, the IdP clients, `D:\ApolloData`.
Proof on `main` after the merge: deployment tooling 585/585 (xUnit plus PowerShell 5.1 probes),
Argus 137/137, Browser 121/121. What the module still lacks is Slice 2: execution adapters,
rollback at execution time, journal, health checks that run, and the two open owner decisions
the spec records (credential leaves in the Install plan; Configure on the activated release
path). Nothing deploys until then; every entry point refuses without `-WhatIf`.

**2026-09-03 — Plan addition: the platform's documentation site `docs.sibyla.gottsolutions.net` (owner's instruction).**
The name freed by Phase L is reserved for a static IIS site over this repository's `docs\`
folder, mirroring the legacy `Sibyla.Docs` site as it runs today (anonymous, directory
browsing on, MIME maps for `.md` as `text/markdown; charset=utf-8`, `.svg`, `.json`; app pool
No Managed Code on `ApplicationPoolIdentity` with read access to the folder; `index.html` as
the default document when one is added). Shape: IIS site name `Sibyla.Docs` once the legacy
site is renamed `Sibyla.Legacy.Docs` (IIS names are not user-facing), physical path
`D:\fileStorage\repos\GOTT.Apollo\docs` served straight from the working copy so a commit is a
publish, bindings `http *:80` and `https *:443` for `docs.sibyla.gottsolutions.net` with a
win-acme renewal like the Phase L ones (`--source manual`, self-hosted http-01, store
`WebHosting`, IIS installation). DNS: the record already exists (it served the legacy site).
It joins `Sibyla.Deployment` Slice 2 as a third `Iis.Apply` / `Tls.Apply` pair with one health
check (`GET /` → 200) and no service, no database, no environment; until Slice 2 lands it can
be created by hand with the same steps as Phase L. Concern stated for the owner: the folder
holds the plan, specs, discovery dossiers and decision logs, and the legacy site's shape
publishes them anonymously to the internet; an IP allow-list or IdP-protected front is the
alternative if that is not intended. Recorded in the deployment prompt, the Slice 1 V3 spec's
Slice 2 section and the naming document.

**2026-09-03 — Sibyla is deployed to production on the POC server: `sibyla.gottsolutions.net`, `api.sibyla.gottsolutions.net`, `docs.sibyla.gottsolutions.net` and the worker service; the legacy apps still run (Phase 5 waits for the owner).**
Run on the owner's instruction ("Phase L is done, proceed with the whole plan sequentially") through
the deployment prompt's phases 0–4; the full record is `docs/apollo-deployment-run-260903.md`.
Baseline and backups at `D:\Backups\SibylaDeployment\20260903-001541` (IIS backup
`SibylaDeploy-20260903-001541`, robocopy copies of the three legacy folders). Phase 1 green:
build, 462 tests (isolation 52, platform 107, argus 137, browser 121, api 45), both databases
migrated, sync idempotent. Phase 2 produced the hand-path scripts `local\publish-release.ps1`,
`deploy-release.ps1`, `rollback-release.ps1` and `provision-production.ps1` (commit `3fa3159`);
release `20260903-002407-3fa3159` lives under `C:\Apps\Sibyla\{web,api,worker}\<release>`, never
the working tree. Phase 3: the legacy IIS sites and pools were renamed `Sibyla.Legacy.Web/Api/Docs`
(names only) so the platform sites took `Sibyla.Web` (id 10), `Sibyla.Api` (11) and `Sibyla.Docs`
(12, over this repository's `docs\`); No-Managed-Code pools on ApplicationPoolIdentity; Let's
Encrypt certificates as win-acme renewals `[Platform] …` (due 2026-10-28); each host receives
only `SIBYLA_SECRETS_FILE` (applicationHost location sections) pointing at
`C:\ProgramData\Sibyla\secrets\{web,api,worker}.json` — `apollo_app`, `apollo_api`, `apollo_worker`
respectively, the OIDC secret, and the key ring `D:\ApolloData\dp-keys` (the development key
copied in, since the stored tenant credentials are protected by it); the API site carries the
52 500 000-byte limit and the 120 s timeout; the docs site is anonymous with directory browsing
and the `.md` MIME map; the `apollo-web` IdP client's production URIs now name
`sibyla.gottsolutions.net`. The worker is the Windows service `Sibyla.Worker.Documents` under the
new local account `SibylaWorker` (harness plan roadmap item 3: a dedicated run-as identity) with a
pinned copy of the Claude CLI at `C:\Apps\Sibyla\tools\claude\` and `CLAUDE_CONFIG_DIR` at
`D:\ApolloData\worker-claude`, **not yet logged in** — the Claude lane pauses until the owner
authenticates as that account. Phase 4: the web host answers and challenges to the IdP with the
right redirect URI; API `/health` and `/health/ready` 200; the intake plugin's status tool
round-trips (token minted, `404 channel_intake_not_found` with the correlation id) — intake
cutover step 1 is done; the worker polls on `apollo_worker`; the web host reads on `apollo_app`;
docs serves markdown; the legacy hosts, the IdP, buzz and nextcloud answer as before.
**Not verified and left to the owner: the sign-in itself and the Argus screens in production**
(no user credential in the session); therefore **Phase 5 did not run** (prompt rule 5 and the
business check), the retire/restart commands are in the record. Found on the way, not touched:
`dashboard.gottsolutions.net` answers 502 because the Hermes dashboard on port 9119 is not
running. Two mistakes reverted within a minute and fixed in the scripts: the first deploy
repointed the legacy site that still bore the name `Sibyla.Web` (guard added), and site settings
written with the provider's `-Location` landed in the release `web.config` files (appcmd now).
*Same day, 00:48 UTC — Phase 5 done on the owner's word* ("Sign-in verified, screens render;
proceed with Phase 5"): `Sibyla.Legacy.Web` (site and pool) stopped, `GottSibylaWorker` stopped
and Disabled, one at a time, with the prompt's checks between them — the platform hosts, the
legacy API and docs, the IdP, buzz, nextcloud and the Hermes gateway unchanged; folders and
backups kept, nothing deleted. **Sibyla is now the operational application.** Still running by
design: `Sibyla.Legacy.Api` (intake cutover step 8), `Sibyla.Legacy.Docs`, `gott_sibyla`.
*Same day, 01:35 UTC — intake cutover step 5 done on the owner's word:* the Hermes persistent
addendum rewritten as `sibyla-apolo-document-intake/2.0` (Sibyla's own API, the Apollo plugin's
three tools, SHA-256 per candidate, unknown sender quarantined not refused,
`possibleDuplicateOfIntakeId` as a flag for a person, expiry, the API's size limit), `SOUL.md`
and the five named skills updated, the token probe rewritten for `APOLLO_CHANNEL_INTAKE_*`
(green against production), the gateway restarted cleanly; record in the intake spec's step 5.
The owner's Claude login for the worker (`SibylaWorker`) was done the same night; the test
upload was deferred. *01:42 UTC — step 6 done:* cron `apollo-email-gateway` unpaused and green
after fixing the legacy Graph watcher's mark-as-read on the pre-move id (it is mailbox triage
only; automatic registration of emailed documents waits for the Apollo-owned watcher, F7).
*Step 7 (observation) postponed by the owner;* protocol in the intake spec. Step 8 follows 7.
*02:00 UTC — F7 built on the owner's word ("do all you can sequentially"):* the Apollo-owned
email watcher `local/hermes/apollo-email-intake/` (installed as the Hermes cron script
`apollo_email_intake.py`, job `apollo-email-intake` created **paused**, id `25c3ac4615bf`):
reads `Validar`, holds messages whose `Authentication-Results` explicitly failed (the F7 DMARC
residual), downloads the supported attachments into an approved upload root, registers once
through the plugin's own client on the RFC Message-ID with hashed candidate keys, uploads,
records the outcome in a ledger and as an Outlook category, deletes the cached bytes; 12 unit
tests green, dry run against the live mailbox green (one candidate in the last 120 days).
Spec section 7.1. Unpausing it is the email leg of step 7, the owner's call.
*02:20 UTC — Sibyla.Deployment Slice 2 specification, draft 1* (`docs/apollo-deployment-slice2-spec.md`):
the executing invoker with eight adapters, execution-time rollback, the journal and resume, the
nine health checks, three sites and one service, and six owner decisions (D-S2-1…6) with
recommendations drawn from the hand path of the same night, which the spec records as the
oracle. Spec first: no Slice 2 code until it is reviewed and the decisions are ruled.
*02:25 UTC — channel-intake re-review, round 2 (D-CI-10), done:* two independent read-only
reviewers on the same split as round 1; **S: ACCEPT** (1 Medium, 9 Low, every F-claim verified),
**C: REVISE** (2 High in the night's new watcher, 5 Medium, 8 Low). All 24 consolidated findings
adjudicated in `docs/apollo-channel-intake-api-spec-review.md`; 17 fixed the same night: the
POST body bounded at 256 KiB, `sub` required, the duplicate flag only on completed candidates,
an advisory lock under the daily quota, a suspended licence refused, an expired registration
re-opened by its identical replay (the API's real gap: it could never be re-registered), a row
lock on the upload, a per-request log line; the watcher pages the folder and survives a poison
message, saves its ledger per message, marks mail without a document, gains `--retry`; the
plugin matches the server's WhatsApp rule and handles `Retry-After` and unreadable bodies; the
spec text corrected (statuses, F6 note, Appendix A, the unpause protocol for step 7). Deferred
with reasons: the transaction-during-upload refactor, the `docint` composite key, the trust
action's disabled-binding case, the legacy credential until step 8, the §8 test debt. Proof:
build clean, 462 tests green (isolation 52, platform 107, argus 137, api 45, browser 121), plugin
39/39, watcher 16/16. Plugin and watcher re-installed into Hermes, gateway restarted; the API
host redeployed as release `20260903-021830-7191ca3` through the hand path (previous release
kept, `/health` and `/health/ready` 200, plugin round-trip green). Checkpoint A of D-CI-10 is
closed; the final review with the evidence record remains.
*02:40 UTC — owner ruling:* "accept all six Slice 2 recommendations" — D-S2-1…6 are decisions
(recorded in the spec's section 1); the independent spec review (two reviewers: protocol
fidelity to Slice 1, operations and safety against the live host) was launched on the owner's
word; its outcome and the adjudication go into the spec's review record.
*03:00 UTC — Slice 2 spec review round 1: both REVISE; revision 2 written.* Reviewer P (protocol,
25 findings, 2 Critical): no Slice 2 handler-table or phase-order oracle, adapters in Core would
break the S1 byte scan, the capability gate and V1/V2 pipeline unstated, failed-intent
compensation, resume ids, the cross-process mutex, disposable-target proof. Reviewer O
(operations, 26 findings, 1 Critical): a precondition needing a database credential the module may
not hold, **live ACLs broader than the spec (inherited `Users` CreateFiles on release, docs and
data folders)**, journal durability and resume against a changed host, win-acme rebinding versus
a fixed thumbprint, SCM environment snapshot, and a dozen adapter-semantics points. All 51
adjudicated in `docs/apollo-deployment-slice2-spec-review.md`; revision 2 applies every one:
execution only through the one-shot, two new assemblies (`Execution` dual-TFM, `Adapters.Windows`
net48-only, spike done on this host), the 36-key V2 handler table and the three-host phase table,
`CapabilityPreflight` mode, V2 pipeline, failed-intent and idempotent compensation, plan digest and
live-state comparison on resume, `/health/schema` instead of a database credential, optional
thumbprints, ten health checks, disposable probe targets. **Host fixed the same night** (run
record 7b): inheritance cut and exact ACLs on the install root, docs and data folders; the docs
site's anonymous identity moved from `IUSR` to the pool identity. Round 2 of the spec review
launched on revision 2.
*03:25 UTC — round 2: both REVISE again, sharper; revision 3 written.* P found "Core untouched"
contradicted by V2 types in Core, the health and key counts disagreeing across sections, the
probe seam unreachable from PowerShell, codes without producers; O verified the hardened host
(every target exact, four stray explicit ACEs) and found that revision 2's scoped safety stop
would leave the web and API pools stopped after a "successful" rollback, preconditions running
after Install had mutated, a mid-run win-acme renewal defeating the TLS readback and rollback,
and the key-ring grant half-written in the script. Revision 3: V2 lives in `Execution` (Core
changes in one place), the 38-key table with adapter/kind/result/arguments, the exact phase
table (N = 14, M = 9, docs never recycled, pools recycled after every `Iis.Apply`/`Rollback`,
service stopped after the copies), eleven named health checks, one-code-one-producer table, the
probe profile selected only by `SIBYLA_DEPLOYMENT_PROFILE`, renewal-safe TLS rules, the
`Started`-without-terminal resume rule. Host: the four stray ACEs removed, the script completes
the key-ring shape and selects releases by regex (run record 7c). Round 3 launched.
*03:55 UTC — round 3: both REVISE; revision 4 written.* O found the service-side twin of the
pool defect (an unconditional safety stop leaves the worker stopped after a precondition
refusal), `Tls.Rollback` ordered before `Iis.Rollback`, resume order and non-mutating `Started`
lines undefined, the 404 first-run exemption undecidable, the win-acme window misstated, eleven
sub-folders with tolerated stray ACEs. P found the probe profile incompatible with the S1 scan
and "Core untouched", the registry still grouped, an eighth HTTP intent feeding no check,
no-op `Skipped`s breaking dependencies, no V2 sanitizer type. Revision 4: no profile at all
(adapter-level probes, Core changed in exactly two enumerated places), a 38-row registry with
ordered argument names, no-ops as `Succeeded(NoChange)`, Install never touches a process and
the worker stops right before `Scm.Apply`, conditional safety stops, `Tls.Rollback` last, seven
HTTP intents plus the schema GET, `FailureCodesV2` with categories, the ChecksOnly plan row,
resume walking the compiled lists. Host: explicit ACEs stripped recursively below every target
(0 left); `rollback-release.ps1` tightened. Round 4 launched.
*04:30 UTC — round 4: both REVISE, now with only one High (a grep oracle contradicting the Core
literal) and mediums that are executability gaps; revision 5 written.* Core changes in exactly
one place (the const rename), the probe name confined to a V2 value object in Execution,
typed refusals for readbacks/health/mutex with their own codes, elevation per adapter,
release-agnostic plans with per-phase compile and digests, guarded recycles that start a stopped
pool, bindings never written by `Iis.*`, resume deferring to each adapter's own rule, the
harness never restoring IIS configuration. Round 5 launched.
*05:05 UTC — round 5: both REVISE, no High; revision 6 written.* O walked the crashing-release
rollback end to end and found it correct, then one Medium that would roll back a *healthy*
release on a quiet host (an in-process site loads its dll on the first request, so the pool
readback must follow the root GET) and nine Lows; P found five Mediums of specification
precision (one code per health failure, fixed-entry disposition on success, readback typing,
cross-phase plan carriage, one namespace) and twelve Lows. All applied: twelve health checks
(a twelfth for the API pool), HTTP before pool readbacks, `GuardIntentId`, per-adapter post-state
digests, resume of a completed run, per-value SCM comparison, atomic text files, the harness diff
by hash only. Owner note recorded: the legacy pools' environment variables in
`applicationHost.config` (and `C:\inetpub\history`) carry the `gott_sibyla` connection string
with its password — Sibyla Legacy's, for Phase D. Round 6 launched.
*05:30 UTC — round 6: **O ACCEPT** (host exact, §5.2 walk clean, six Low notes); P REVISE with two
one-line Mediums (the Process rows needed a pool/service name argument; the outcome under
`-NoRollbackOnHealth` was undefined) and ten Lows. Revision 7 applies all eighteen items. Round 7:
P on revision 7, O confirming the deltas.
*06:05 UTC — round 7: **P ACCEPT** (ten Low notes); O's delta check REVISE on a regression I
introduced in revision 7: the "lossless-or-fail" receipt rendering rule, against a render set
without `_` or `*`, would have failed the first Configure snapshot on every production run and
rolled Install back deterministically. Revision 8: lossless `\uXXXX` escaping with a per-field
cap and a B2 test over every section-2 literal; the 300 s timeout as a cancellation deadline
that never compensates concurrently with a live `Write` call; conditional fixed entries
re-evaluated on a resume after `-NoRollbackOnHealth`; pool names guarded like site names; the
remaining Lows of both reviewers. Round 8: both confirm the deltas.
*06:35 UTC — round 8: **O ACCEPT** (four Low notes); P REVISE on one Medium of the same class as
the revision-7 regression (a per-field cap would fail a host-folder snapshot as releases
accumulate) plus six Lows. Revision 9: the cap is per scalar leaf with sets rendered per element
and a B2 test over a 50-release snapshot, the backslash escaped so the rendering is injective
with a stated decoder, ownership resolving every application on the pool against the input site
name, typed `LocationAssertion` expectations and a `mediatype` value object, distinctness per set,
drain time and cancellation-token allowances for workload-less writes, one tree hash per host as
the artifact receipt. Round 9: both confirm the deltas.
*06:55 UTC — round 9: **both ACCEPT.** The Slice 2 specification is accepted; revision 10 is the
editorial pass applying the six Low notes both reviewers left for A2 (no further round). Nine
rounds in one night, every finding adjudicated in `docs/apollo-deployment-slice2-spec-review.md`.
Sub-slice A2 (V2 contracts and planning types, the switches and value objects, the V2 preflight
and error boundary, the S1 oracle amendments, `/health/schema` in the API, the publish and deploy
script changes) may start under the evidence rules; its RED oracles are typed before code and the
next review is checkpoint A2+B2.*
*07:05 UTC — A2 handed to a fresh session on the owner's word:* the brief is
`docs/apollo-deployment-slice2-a2-prompt-260903.md` (scope, RED-first procedure, branch
`ops/sibyla-deployment-slice2-a2`, evidence files, hard constraints: no host touch, no `-Execute`,
the spec as the only authority, no merge before the A2+B2 checkpoint review).
*08:10 UTC — sub-slice A2 delivered on `ops/sibyla-deployment-slice2-a2` (worktree
`apollo-deployment-slice2-a2`, unmerged).* RED first: the oracles were typed from the spec text
(the section 0.2 amendments of `validators.json`, `entrypoints.json` — with the D-S2-6 truth table —
and `forbidden.json`; new `forbidden.v2.json`, `sanitizer.v2.json`, `registry.v2.json` with the 38
descriptors of 5.1, and `validators.v2.json` for the section 4 value objects), then the tests, then a
throwing scaffold so they compile; `evidence/A2-red.txt` (fingerprint `eae84452…`, 164 files) shows
529 of 1036 failing, the Slice 1 suite failing in exactly the 96 places the amendments predict, and
the 18 first-run passes all structural. GREEN (`evidence/A2-green.txt`, fingerprint `a4935d62…`,
180 files): deployment suite 1036/1036 (585 S1 + 451 A2), `Sibyla.Tests.Api` 46/46, `local\test.ps1`
TenantIsolation 52, Api 46, Platform 107, Argus 137, Browser 121. Delivered: the
`Sibyla.Deployment.Execution` assembly (V2 contracts and planning types, the value objects and
rules, `CapabilityPreflightV2` with the three modes, `HandlerTableV2` with the 38 descriptors,
`FailureCodesV2` = 49 S2 + 27 S1 codes, `DeploymentFailureV2`/`ErrorBoundaryV2`, the plan-only
invoker); Core changed in exactly one line (`SafeScmName` = `Sibyla.Worker.Documents`); `-Execute` on
the six entry points with the section 4 parameters on the one-shot; `GET /health/schema` on the
API (role + applied migration ids) with migration `SchemaHealthGrant` (SELECT on the migrations
history for `apollo_api`, applied on Main and Preview); `publish-release.ps1` writes
`requiredMigrations`, `deploy-release.ps1` copies them into `MANIFEST.json`. A2 state: every
`-Execute` path is refused after the preflight (`SIBYLA-S2-EXECUTION-REFUSED`) because the
executing invoker does not exist until B2; nothing touched a host. The production API still runs the
pre-A2 release: the hand path (`publish-release.ps1` / `deploy-release.ps1 -Hosts api`) deploys
`/health/schema` at a time the owner chooses, before D2. Next: B2, then the checkpoint review A2+B2.
*13:25 UTC — sub-slice B2 delivered on `ops/sibyla-deployment-slice2-b2` (worktree
`apollo-deployment-slice2-b2`, branched from the A2 branch, unmerged).* RED first: the three
oracles typed from the spec (`execution-order.json` by an author who had not read A2's
implementation of the same rows, `execution-semantics.json`, `health.v2.json`), the harness over
`FakeHostV2` and the fake adapters, the B2 tests, the eight truth-table rows with their B2
outcomes, and a scaffold so they compile; `evidence/B2-red.txt` (fingerprint `318ef4da…`, 201
files) shows 184 of 1210 failing: 177 B2 behaviour tests on the scaffold plus the 7 A2 tests the B2
outcomes replace, every first-run pass structural. GREEN (`evidence/B2-green.txt`, fingerprint
`e488195e…`, 206 files): deployment suite 1208/1208 (585 S1 + the A2 and B2 tests; two fewer than
RED because a three-row A2 theory became one B2 fact), `Sibyla.Tests.Api` 46/46, `local\test.ps1`
Api 46, Platform 107, TenantIsolation 52, Argus 137, Browser 121. Delivered: the executing and
read-only invokers (5.3: Started fsynced before the adapter call, the cancellation deadline and
drain on a dedicated thread, receipt type and rendering checks, the key codes), the per-phase
compiler with lineage and digests (5.2, 5.7), the journal and its reader (6), the receipt codec,
the health map (7), the engine (preview, run, ChecksOnly, the compensation walks, the resume that
recompiles from the journal, checks the five digests and walks the lists; the RollbackIncomplete
retry re-evaluates a fixed entry only when the rollback of its guard is retried) and the
orchestrator wiring; the eight test-side corrections and the five engine readings are listed in
the evidence notes. B2 state: the production runtime wires no adapter (C2), so `-Execute -WhatIf`
previews and every executing path fails closed at the first adapter call
(`SIBYLA-S2-ADAPTER-FAILED`), after the preflight and after the journal-root check; nothing
touched a host. A runaway `perl` left by the RED step's in-place edits filled C: during GREEN
(55.9 GB into a deleted temporary file); it was killed, the volume recovered, and the suites were
rerun from a clean build (recorded in the evidence). Next: the independent checkpoint review
A2+B2, then C2.
*16:00 UTC — checkpoint review A2+B2 held by the two reviewers of the spec rounds, read-only in
their own checkouts of `cc72ca4` (`evidence/CHECKPOINT-A2B2-review.md`).* Both ACCEPT at the bar:
reviewer P Medium 3 / Low 3 (the GREEN fingerprint did not reproduce from a commit — line endings
of eleven perl-edited files, explained and reproduced; the A2 RED/GREEN commits are
reconstructions of the fingerprinted states; the harness's NoChange relaxation; the decoder's
truncated escape; ten oracle keys the harness never read; the manifest text), reviewer O
Medium 4 / Low 2 (the `SchemaHealthGrant` migration grants to `apollo_api`, renamed by Phase D;
a run that compensated nothing but failed a compensation reported `Failed`; a failed pool recycle
left the outcome `RolledBack`; the per-call worker thread would lose a `System.Threading.Mutex`;
the decoder; a digest-line journal failure surfaced as INTERNAL). The revision `2e60fd7` answers
all but O1 (owner decision for D2: verify the live grant; `setup-db.ps1` covers a fresh database):
fingerprint `0853de1a…2e071` over 206 files from a clean export, deployment suite 1213/1213,
four oracle cases and one invoker rule added, every new test replayed against the pre-revision
code and failing there. Re-reviews: both ACCEPT; reviewer O found one new Medium in the O3 fix
(the broadened retry rule would have re-run the safety stop on a retry resume and left the
service stopped under a `RolledBack` trailer, demonstrated with a scratch oracle case) and one
Low; reviewer P two Lows. The second revision `e1f43cd` + `49a8618` (fingerprint `97fbc290…796d`,
1215/1215, the two safety-stop cases replayed against the first revision) closes them: only the
recycle re-runs on its own failure, a failed safety stop healed by the service's
`Process.Rollback` in the same walk is no `CompensationFailed`; the D2 row now carries the
`sibyla_api` privilege check as a precondition and the A2 row's grant sentence is corrected.
Closing confirmations received from both reviewers: Critical 0, High 0, Medium 0, Low 0 on
`49a8618` (reviewer O reproduced the fingerprint and re-ran his scratch cases against the clean
export, including a safety stop that is not healed later, which still yields
`RollbackIncomplete` and is healed by the retry through `Process.Rollback` alone). The checkpoint
A2+B2 is closed (`evidence/CHECKPOINT-A2B2-review.md`); O1 is the owner's decision carried to D2.
Next: C2 (the eight adapters on the `ZZ.Sibyla.Probe.*` objects), whose brief must carry both
reviewers' C2 notes from the record; both branches stay unmerged until the owner says otherwise.

*18:34 UTC — sub-slice C2 delivered on `ops/sibyla-deployment-slice2-c2`, unmerged pending the
C2+D2 checkpoint.* RED `evidence/C2-red.txt`: fingerprint `5cc72fcf…6370db` over 210 files; the
eight family tests failed intentionally against the throwing scaffold and the 1215-test existing
suite remained green. GREEN implementation `d8d57aa`, `evidence/C2-green.txt`: fingerprint
`b2abfcbb…109068` over 222 files from a clean archive; build 0 warnings/errors; deployment
1230/1230; API 46/46, Platform 107/107, TenantIsolation 52/52, Argus 137/137, Browser 121/121.
The packaged module composed FileSystem, ACL, Process, IIS, SCM, TLS, HTTP and Mutex adapters plus
the Windows operator provider under PowerShell 5.1. The elevated adapter-only probe passed 54/54
against only `ZZ.Sibyla.Probe.*` resources outside the win-acme window. IIS configuration,
HTTP.sys and WebHosting hashes excluding probe elements matched before/after; the backup and all
probe objects were absent after cleanup. C2 carries the A2+B2 review notes: typed elevation
refusal before adapter calls, calling-thread mutex ownership, truthful/exact rollback receipts,
lossless state including absent SCM values, the service rollback as wrong-image healer, bounded
polling, safe Observed/operator rendering and the compiled Process compensation-order assumption.
The one oracle amendment permits the Windows assembly to reference Execution because it implements
Execution's adapter interfaces. No production entry point ran and no production object or secret
was touched. Next: D2's recorded `sibyla_api` history-table privilege precondition and the
read-only production `-Execute -ChecksOnly` run; then the C2+D2 checkpoint review. Production
mutation remains prohibited until owner-scheduled E2.

*18:48 UTC — C2 revision from D2 preflight.* Before invoking the production entry point, read-only
discovery found ChecksOnly passed the API install-root directory as `LiveManifest`, which the Http
adapter would try to read as a file. RED `3612882` + `evidence/C2-d2-path-red.txt` reproduced the
single failure (9/10 existing focused cases green). GREEN `b638e1d` binds the earlier live API
`IisSnapshotV2` and reads `MANIFEST.json` below its exact physical path; it does not guess from the
install root or trust `CURRENT.txt`. Clean-export fingerprint `45bba5d1…f41cd20c` over 222 files;
deployment 1231/1231; focused 24/24; elevated disposable probe 55/55 with unchanged non-probe
hashes and full cleanup. D2 production execution remained paused throughout the correction.

*19:08 UTC — second C2 revision from D2 preflight.* Process pool readback matched only the
expected module filename, so a worker serving a stale or wrong physical path could false-pass
checks 6 and 12. RED `706722d` + `evidence/C2-d2-pool-path-red.txt` failed exactly at the new
wrong-live-path case after 46 prior adapter cases passed. GREEN `2a04901` requires the loaded
module's full path to equal the captured `IisSnapshotV2.PhysicalPath` plus the module name (or,
for release-mode validation, to contain the complete release ID path segment). Clean-export
fingerprint `f091883c…27d8a3` over 222 files; build 0 warnings/errors; focused 39/39; deployment
1231/1231; elevated disposable probe 57/57. The three non-probe hashes remained exact and the
independent cleanup count was zero. D2 production execution remained paused throughout.

*21:08 UTC — third C2 revision from D2's fail-closed first read.* The mandatory `sibyla_api`
history-table privilege query passed. The first permitted production `-Execute -ChecksOnly`
attempt then stopped at its first operation, `Iis.GetSnapshot`, with `SIBYLA-S2-REDACTION`; no
write intent or journal was reached. The real IIS tuple's redundant string digest exceeded the
512-character receipt-leaf cap. RED `c6eca16` + `evidence/C2-d2-iis-receipt-red.txt` reproduced
that exact failure against a disposable probe site after 18 prior adapter cases passed. GREEN
`1ae1e25` records `iis|site|SHA-256(length-framed tuple)` while retaining every raw tuple field in
the snapshot for lossless rollback. Clean-export fingerprint `e2442941…f14f3f` over 222 files;
build 0 warnings/errors; focused 105/105; deployment 1231/1231; elevated disposable probe 58/58.
The three protected hashes remained exact and independent cleanup was zero. D2 awaits a retry
from this candidate.

*21:17 UTC — fourth C2 revision from D2's schema read.* The D2 retry advanced through the prior
reads and stopped at `Precondition.Schema` with `SIBYLA-S2-REDACTION`; again no write intent or
journal was reached. The live 19-migration JSON is 712 characters. Free-text rendering capped it
at 512, but 49 replacement characters expanded its lossless receipt leaf to 757. RED `a8e0951` +
`evidence/C2-d2-schema-receipt-red.txt` reproduced this with a disposable 19-migration endpoint,
failing exactly at the new receipt-leaf case after 34 prior cases passed. GREEN `3b0dc03` compares
the full 4 KiB-bounded body, then budgets only sanitized `Observed` text by final escaped-leaf
cost. Clean-export fingerprint `ad5419ec…cf21f5a` over 222 files; build 0 warnings/errors; focused
105/105; deployment 1231/1231; elevated disposable probe 59/59. Protected hashes remained exact
and cleanup was zero. D2 awaits a retry from this candidate.

*21:19 UTC — sub-slice D2 passed on `ops/sibyla-deployment-slice2-d2`, unmerged pending the
C2+D2 checkpoint.* The mandatory schema-history privilege query returned true for `sibyla_api`,
`public."__EFMigrationsHistory"`, `SELECT`. The final Windows PowerShell 5.1 production
`Invoke-SibylaDeployment -Execute -ChecksOnly` run returned `ChecksOnlyResultV2`, outcome
`Executed`: 15/15 operations succeeded, every plan operation was `MutationKind.Read`, and all
12/12 section-7 checks passed. `applicationHost.config`, the production site/pool/service metadata
hash, and the zero-journal set were unchanged before/after. `evidence/D2-run.md` records the exact
sanitized results plus the two fail-closed reads that drove the final C2 corrections. No
production mutation or secret-content read occurred. Next: the independent C2+D2 checkpoint
review; keep C2/D2 unmerged and E2 prohibited until that review accepts.

*2026-09-04 — the independent C2+D2 checkpoint rejected `723e93f` / `fce287f`; C2 correction
delivered at `d42db25`, still unmerged.* Both reviewers independently found ambiguous file-tree
hashing and an inexact full-execution worker path; the spec reviewer also applied the mandatory
behavioral RED hard stop. RED `6d08d2a` plus `evidence/C2-checkpoint-rejection-red.txt` put the
actual correction oracles ahead of code: 0/9 focused and the elevated disposable probe failed on
the constructed tree-hash collision. GREEN answers all C2 findings with framed file/directory
identity, transaction-owned failed names, exact SCM-derived worker path, typed over-limit schema
rejection, compact ACL digests, TLS store/absence rules, effective command-line overlap detection,
the exact probe-name exemption and current Sibyla module wording. Clean-export fingerprint
`2b9d70a7…1f2922` over 223 files; build 0 warnings/errors; focused 114/114; deployment 1240/1240;
API 46/46, Platform 107/107, TenantIsolation 52/52, Argus 137/137, Browser 121/121; elevated
PowerShell 5.1 disposable adapters 67/67. Protected IIS, HTTP.sys and WebHosting hashes remained
exact and cleanup was zero. Next: correct D2 provenance/privilege/journal-set controls, rerun the
read-only D2 exception against this candidate, then independent re-review. E2 stays prohibited.

*02:13 UTC - corrected D2 rerun passed on 2026-09-04; independent re-review pending.* RED
`3e6e844` plus `evidence/D2-controls-red.txt` failed 4/4 evidence-control tests. GREEN `3a934cb`
plus `evidence/D2-controls-green.txt` passed 4/4 and the full deployment suite 1244/1244. The
tracked helpers bind the exact `gott_sibyla` / `sibyla_api` /
`public."__EFMigrationsHistory"` / `SELECT` query and freshness to a clean exact candidate commit,
source fingerprint, C2 ancestry, runner hash, and five module artifact hashes. Candidate
`e4368b3`, fingerprint `e4fa5ae7...aa3966` over 226 files, returned
`ChecksOnlyResultV2` / `Executed`: 15/15 read operations succeeded and 12/12 checks passed.
applicationHost.config, production site/pool/service metadata, and the actual framed journal set
were unchanged before/after. Sanitized results are in `evidence/D2-r2-result.json` and
`evidence/D2-r2-privilege.json`. No production mutation or secret-content read occurred. Next:
independent C2+D2 re-review. Keep both branches unmerged and E2 prohibited until acceptance.

*2026-09-04 — the re-review rejected the correction again (P High 1 / Medium 2, O Medium 2); C2
re-corrected at `5121db6`, still unmerged.* The High was procedural: the RED probe of `6d08d2a` had
stopped at its first failing case, so seven of the eight new adapter-level cases were never shown
failing before the code. The spec reviewer accepted an isolated forensic replay instead of a history
rewrite: two detached worktrees at the exact commits with an orchestration-only overlay (one named
case per elevated run, family routing, a live `/schema-over-limit.json`), rebuilt non-incrementally —
8/8 fail for their named reason on RED, 8/8 pass on GREEN (`evidence/C2-checkpoint-replay.txt`,
overlay diffs committed beside it). The two Mediums: `.failed-<txid>` now uses the lowercase `D` Guid
form the spec names (oracle retyped in `b9663cb`, fix `5121db6`, fingerprint `93195288…f1a3f6`), and
the GREEN record's nonexistent full SHA was corrected in place with a dated addendum. O's parse/throw
suspicion on the over-limit schema case is disproved on the live endpoint and recorded; no HTTP change.
Re-validation: build clean; focused 9/9; deployment 1240/1240; API 46/46, Platform 107/107,
TenantIsolation 52/52, Argus 137/137, Browser 121/121; elevated disposable adapters 67/67; protected
hashes exact; zero residue. Noted outside scope: `IntakeDuplicateControlTests` behaves as order-dependent
(fails when its class runs alone, on main too; green in the full run; root cause not investigated). Next: rebind D2 to this
candidate with a fresh privilege proof, then the independent re-review. E2 stays prohibited.

*08:55 UTC, 2026-09-04 — D2 rebound to the re-corrected C2; independent re-review next.* The
corrected C2 (`a2def6b`) was merged into D2 as candidate `a13c46e` (fingerprint `62e6a460…bd17dc`
over 226 files, equal from the clean export and the bound worktree; deployment 1244/1244, focused
13/13). A fresh interactive privilege proof (0.03 minutes old at start) and the tracked runner
returned `ChecksOnlyResultV2` / `Executed`: 15/15 read operations, 12/12 checks, applicationHost,
production metadata and the framed journal set unchanged (`evidence/D2-run-r3.md`, `D2-r3-*.json`
with SHA-256). Lesson recorded: on this machine `git archive` applies CRLF, so a worktree only equals
the clean-export fingerprint when checked out with the default conversion; the D2 worktree was
re-checked-out (no content change, `git status` clean) before binding. Both branches stay unmerged
and E2 prohibited until both reviewers ACCEPT.

*09:20 UTC, 2026-09-04 — checkpoint C2+D2 CLOSED: both independent reviewers ACCEPT.* Spec
reviewer Critical 0 / High 0 / Medium 1 / Low 2, operations reviewer 0 / 0 / 0 / 3, on C2 `a2def6b` /
D2 `4ca647b`; every previous finding CLOSED; the schema adjudication verified from source by both
(`evidence/CHECKPOINT-C2D2-review.md`). The Medium was a record defect: the round-2 D2 control
called the worktree fingerprint `e4fa5ae7…` "clean" — the clean-export value of `3a934cb`/`e4368b3`
is `910d3918…1f72`; corrected by dated addenda (the 02:13 UTC entry above should be read with that
correction). Record Lows closed by addenda; two operations Lows carried to E2 (harness hashes on
failing runs; section-8 redaction on the schema body). Merging C2 → D2 → `main` follows; E2 stays
prohibited until the owner names its time.

**2026-09-04 — owner ruling: the test suites run against the Preview database.** Cause: every
suite opened `gott_sibyla` through 22 copies of one helper, and since the worker went live on
2026-09-03 `Sibyla.Worker.Documents` has claimed every owner's jobs there — including rows a test
had just inserted, which it dead-lettered mid-test (`IntakeDuplicateControlTests` failed whenever
its class ran alone). The lane pause is global, so the tests could not fence themselves off. Ruling:
"point the suites at the Preview database gott_sibyla_preview." Done on
`ops/test-database-preview`: one shared helper `tests/Sibyla.Tests.Shared/TestDatabase` reads
`local/secrets/sibyla-preview-db.json` and refuses any role outside `sibyla_preview_*` or any other
database; `PreviewConnectionGuard` lets a host that declares `Sibyla:TestHarness` run a controller
environment on Preview credentials and only on those (RED `9bbd7f3` did not compile; guard class
23/23); the Preview target gains `sibyla_preview_api` and `sibyla_preview_ai_reader`
(`DatabaseTarget.ps1`, `setup-db.ps1`, `db/channel-intake-api-role-preview.sql`) so the Api and
TenantIsolation suites keep their fail-closed probes; `PreviewParityTests` reads Main only through
`OpenMainMigratorForParityOnly`. Against Preview: Argus 137/137, Platform 117/117, Browser
121/121; the formerly flaky class passed three isolated runs. After the superuser step
`local/setup-db.ps1 -Target Preview` (10:10 UTC; it also fixed a latent defect — the script passed
SQL as a native argument, and Windows PowerShell 5.1 stripped the quotes around
`"__EFMigrationsHistory"`; SQL now travels over stdin): Api 46/46, TenantIsolation 52/52; the
preview API role carries exactly the D-CI-7 policies and grants, the preview AI reader sees 107
tables and not `credst`, and no role can connect across the Main/Preview boundary. Worktrees of
coding agents still carry no `local/secrets`; a test worktree needs the junction the deployment
worktrees use.

**2026-09-04, 11:09–11:47 UTC — intake cutover step 7 started (owner's word); three findings,
three fixes, one API release.** (1) The gateway's document note carries no trusted envelope, so
Apolo failed closed on the first WhatsApp PDF; `apollo-channel-intake` 1.1.0 now appends the
envelope (message id, sender, timestamp, candidate keys) to every chat-borne document turn through
the Hermes `pre_gateway_dispatch` hook. (2) The sender arrived by LID and the trusted bindings are
E.164, so the registration was quarantined; the envelope now also carries the gateway's own
LID↔phone aliases and the skill/addendum tell Apolo to register under the E.164 alias with the LID
as `sender_original`. (3) The upload was answered `405` by IIS's WebDAV module; `src/Sibyla.Api/
web.config` removes the module and handler as the legacy API did, and API release
`20260904-114642-d3e32c7` was deployed by the hand path at 11:47 UTC on the owner's "Deploy now"
(`docs/apollo-deployment-run-260903.md` §7e; rollback target `20260903-171530-97ac43a`). Details
in `docs/apollo-channel-intake-api-spec.md` step 7. The observation resumes with a resend.

**2026-09-04, 11:50–11:53 UTC — step 7 legs 1 and 2; owner ruling on duplicate control.** Leg 1
passed end to end in 21 s (trusted WhatsApp → `completed` → Processed → transferred → company
inferred). Leg 2, the replay, found P2-01 not met: a company-less resend was extracted once before
the worker's post-inference re-check held it, because the per-(owner, company) lookup cannot see a
first copy that has meanwhile received its company. **Ruling (discovery 09 §9 row 6 amended): an
intake with no company is looked up across the whole licence and held before any processing; the
human ruling is unchanged; assignment to a company other than the original's releases it.** Fixed
RED-first (`IntakeDuplicateControlTests`, `IngestionService.ReleaseHeldProcessing`), together with
the latent double-extraction on "not a duplicate" for worker-flagged documents. Ships with the next
web/worker release.

**2026-09-04, 12:24–12:39 UTC — step 7 legs 3 and 4; step 7 complete; email intake cron live.**
Leg 3 (route A): an unbound sender's document was quarantined, not refused, held with no job until
the owner named a company and released it. Leg 4: a trusted email reached Validar through the triage
cron, the watcher's dry run listed it, the hand run registered it (`completed`, sender verified),
and the document was held as a possible duplicate after extraction (the ruled gap). The owner
resumed `apollo-email-intake` at 12:39 UTC: emailed documents now register automatically. Next on
this track: the web/worker release carrying the duplicate-control ruling, then step 8 (retire the
legacy API, plugin and IdP client) at the owner's call.

**2026-09-04, 12:44–12:48 UTC — release `20260904-124259-bb6a121` on all three hosts; intake
cutover step 8 done; the channel-intake cutover is complete (owner's word "Do all 3").** The
duplicate-control ruling is in production on web, API and worker (run record §7f; 19 migrations,
none new). Step 8: `Sibyla.Legacy.Api` stopped, the legacy Hermes plugin removed, the IdP client
`sibyla-apolo` deleted and `sibyla-api` dropped from the intake scope (run record §7g). Documents
now enter Sibyla only through its own API — WhatsApp and Mattermost via Apolo's plugin, email via
the live `apollo-email-intake` watcher, or the Uploads page. Still legacy on the box: the Docs site,
`GottSibylaWorker` (Disabled), `gott_sibyla_legacy`, the paused `sibyla-email-graph-gateway` cron.

**2026-09-04, 13:12 UTC — owner's word: the paused legacy cron `sibyla-email-graph-gateway` deleted
and the `GottSibylaWorker` service uninstalled** (run record §7h; definitions backed up; binaries
under `C:\SibylaApps` kept). What remains of Sibyla Legacy on the box: two stopped sites, the Docs
site, the folders, the database `gott_sibyla_legacy` and the service account.

**2026-09-04, 13:47 UTC — review finding 24 closed; the cutover evidence record written.** `tests/Sibyla.Tests.Api/WireEdgeTests.cs` (9 cases: chunked PUT 411, declared 50 MiB+1 → 413, body over 256 KiB → 400, `candidate_not_found`, `idempotency_key_invalid`, the register limiter 429 per client, the concurrent-registration race at service level in separate DI scopes, four golden pairs under `golden/wire-*.json` with ids, timestamps and random keys normalised); Api suite 56/56 on the Preview database. `tests/Sibyla.Tests.Api/evidence/CUTOVER-260904.md` collects the day: the step 7 legs, findings 1–4 with their fixes and releases, the step 8 retirement, the test state and the production state, as the input to the D-CI-10 final review, run next by two independent read-only reviewers.

**2026-09-04, 13:50–14:45 UTC — channel-intake D-CI-10 final review: both independent reviewers ACCEPT on `9aff2a1`; nine findings, seven fixed, two recorded.** Reviewer S (spec conformance, all 51 prior rows re-verified, golden pairs against §4) and reviewer O (operations and security; twelve read-only production checks all matched the evidence record) worked without coordination. Medium: the envelope hook let a sender-typed marker suppress the genuine envelope (plugin 1.1.1 neutralises it and always appends; `received_at_source` added; 60 plugin tests), and `ReleaseQuarantineAsync` did not apply the licence-wide rule to company-less releases (fixed, `IntakeDuplicateControlTests` 17/17). Low: `trustedMetadata` `null` members now refused, the request log names the registration id, the spec gained a revision 3 note and corrected lines, the evidence record an addendum (counts; the untriggered legacy task `Sibyla Documental Acceptance Runner`; round-2 row 5 still deferred). Api 56/56, TenantIsolation 58/58 on Preview. **Deployed 14:28–14:31 UTC on the owner's word:** release `20260904-142821-ecf8ca0` on all three hosts (health 200; rollback target `20260904-124259-bb6a121`), plugin 1.1.1 installed with the skill sentence, gateway restarted cleanly (WhatsApp back 14:31:00Z), doctor OK. Run record §7j.

**2026-09-04, 15:08 UTC — Uploads page: ruling buttons hidden by the evidence branch; evidence moved to a modal (owner's request, implemented by a delegated Opus agent).** The owner could not rule on the two held AWS copies because the result cell's if/else-if chain rendered the evidence table instead of the status actions for any document processed once; the evidence table also had no styles. Fix merged `fa5d2df`: independent `@switch` for the status actions, "Evidência (N)" button opening the wide modal panel, `.sib-evidence` styles; Browser 122/122 with a new red-then-green render test. Web-only release `20260904-150742-fa5d2df` deployed 15:08:39Z (run record §7k); api and worker unchanged on `20260904-142821-ecf8ca0`.

**2026-09-04, 15:14–15:20 UTC — Slice 2 E2 preparation GREEN and merged (`666bc9a`).** After the win-acme window, the session-scheduled task ran the elevated disposable probes on the prep branch head `4088400` (clean-export fingerprint `047f0ef3…` over 229 files): full run 69/69 (the 67 of the closed checkpoint plus the two D-S2-7 cases) with `.hashes.json` Unchanged=true; `-ReplayCase no-such-case` refused with exit 65 "unknown replay case" and the error file carrying the protected hashes; the two D-S2-7 cases replayed in isolation, both PASS; protected hashes identical before and after all four runs; zero `ZZ.*` residue and zero `Sibyla-C2-Probe-*` backups. Gates 18/18, deployment suite 1253/1253 on the branch and again on main after the merge. Evidence `local/deploy/tests/Sibyla.Deployment.Tests/evidence/E2-prep-green.txt` (companion of `E2-prep-red.txt`). The prep worktree is removed; the branch stays as the evidence reference. **Next: the owner's rulings D-E2-1..5 in `docs/apollo-deployment-slice2-e2-prompt-260904.md`** — note that D-E2-1 was written when all hosts ran `bb6a121`; production now runs `20260904-142821-ecf8ca0` (api, worker) and `20260904-150742-fa5d2df` (web).

**2026-09-04, ~16:00 UTC — owner ruled on the Uploads page: the two held copies of the AWS invoice (WhatsApp replay `01f43f9b…`, email `3fe08e55…`) are confirmed duplicates of the processed original `0f3e1f94…`** ("É duplicado" on both; both rows now `Duplicate` with the link kept, nothing deleted). Nothing from the 2026-09-04 observation remains open.

**2026-09-04, 15:35 UTC — owner ruling: E2 decisions D-E2-1..5 accepted as recommended** (brief `docs/apollo-deployment-slice2-e2-prompt-260904.md`, "Owner rulings"): parallel release from the main head at session start for all three hosts; one two-hour slot outside 09:00–15:00 UTC with the owner present, date and time still to be named; ACL-deny fault on one file of the parallel web tree; roll forward; `-ExpectedCommit` mandatory and equal to the gate commit. E2 starts when the owner names the slot.

**2026-09-05, 13:30 UTC — three owner rulings for Phase 2 Processing and the handover.** (1) **Carry-over (D10 re-amended, §3):** the handover imports all the documents, including bank; the cutover is again a final freeze-and-sync with reconciliation sign-off. (2) **Extraction contract v1 = the Invoice Skill Build's section 5 field list**, unchanged: lines with item candidates, the stated service period, the origin class, the bill-to and issuer fiscal identities for the company gate, and bank extract movements; the skill tree is packaged at a pinned commit so the worker hands it to the Claude CLI and the evidence hash covers the tree (closes the "extraction breadth not claimed" note of P2-05/P2-16). (3) **Sample set and measure:** 30 invoices, 5 credit notes and 5 bank extracts from the FDR corpus with the FDR's stored values as the answer key; field-level exact match on header and lines at a threshold of 95 percent before the contract opens to gateway traffic. Order of work after Slice 2 E2 closes: v1 extraction with the packaged skill → line and bank-movement resolution under the P2-05 rules → persistence and the lines section of the correction form. The channel-intake API needs no change: the file already arrives through it and the structured result never leaves the server.

**2026-09-05, 13:33 UTC — owner ruling: the physical files go to Nextcloud at the handover.** The archive PDFs are imported into tenant #1's Nextcloud storage and registered against their document rows (D10 row and §3 updated); the existing storage-transfer path is reused; the reconciliation report gains a per-file hash and count. Only the POC's edit history remains out of the carry-over.

**2026-09-05, 14:02 UTC — release `20260905-135846-bb8b376` on all three hosts (owner's word "lets deploy").** Carries the Hermes harness (`9039589`: `HermesHarness`, `aiprof` licence profiles with the NP Group → `default` seed, migration `20260905010737_HermesHarnessProfiles`), the bilingual UI and AI project administration (`1250d7c`) and the day's plan rulings. `local\test.ps1` 519/519 on `main`; `migrate.ps1 -Target All` found Main and Preview already level. Health 200 on every endpoint, `/health/schema` lists 20 migrations ending in the harness one; rollback targets web `20260904-150742-fa5d2df`, api and worker `20260904-142821-ecf8ca0`. Production still cannot execute a harness turn: `web.json` has no `AiHarness` section and the app-pool identity holds no CLI credentials (harness plan §8, unchanged); the harness UI is live and runs stay on the `run-web.ps1` prototype path until the identity work and slice 11. Run record §7l.

**2026-09-05, 14:39–14:58 UTC — Hermes harness configured for production (owner's word "lets configure the harness for production").** The `Sibyla.Web` pool keeps its identity; the Hermes CLI it spawns now has a dedicated home `D:\ApolloData\web-hermes` (gateway model block, `SOUL.md`, no gateway secrets or state), the owner's own fresh ChatGPT/Codex device-code login stored there, traverse-only reach into the operator profile plus read on the Hermes source/venv and the uv CPython, `AiHarness:Hermes` in `web.json` (executable, 10 min, `apollo_ai_reader` connection: LOGIN, NOBYPASSRLS, SELECT only), `HERMES_HOME` on the site and `loadUserProfile` on the pool (both now in `provision-production.ps1`). Smoke turn with the harness's exact environment: `ROWS=1` for `ownmst` under the owner-pinned reader in 24 s, native session id on stderr. Codex and Claude under IIS and per-licence profiles (slice 11) remain open. Run record §7m.

**2026-09-06, 01:20–01:50 UTC — first production Hermes turn; AI Projects becomes a navigation group.** The owner's turn ran end to end under the pool identity (native session captured, exit 0) but Hermes' terminal resolved bash to the WSL launcher and the answer arrived with OEM-decoded mojibake; fixed with `HERMES_GIT_BASH_PATH` plus read on the Hermes Git tree for the pool, UTF-8 stream decoding in `CliProcessRunner`, and `PYTHONIOENCODING`/`PYTHONUTF8` on the site (run record §7n). Owner's word on the navigation: "AI Projects should be a menu group like administration and not a link to another page with another menu" — `MainLayout` now renders the projects and their recent sessions as a `<details>` group with stored open state on every platform page; the bare `/ai` link is gone; Browser 128/128 with a render test on `/licences`.
**2026-09-05 15:54 → 2026-09-06 01:13 UTC — Slice 2 E2, the first module-driven production execution, PASSED on `ops/sibyla-deployment-slice2-e2` (owner at the console for every executing step; nothing launched by the session).** Gate `d6c4f79` (the main head at session start, D-E2-1) in a detached clone, two clean releases. E2-1 WhatIf PASS (Planned, 161). E2-2 PASS at the fourth attempt (Executed, 12/12, tx `21bbff76`); the three stopped attempts each left the host at its snapshot and surfaced a module defect invisible to the disposable C2 root and to D2's read-only plan: (1) ACL compensation readbacks bound to empty grants, (2) the install-root descent refusing its own sub-targets, (3) the secrets leaves being files with one explicit `Read` — `AclRights.Read` added, spec §3 amended. E2-3 inject (tx `1255f3a0`) ended RollbackIncomplete by the modelled `install-first-write-rollback-fails` path (the fault's held handle broke `PublishTree(web)`'s post-state digest; recorded as a deviation from the typed mechanism) and surfaced (4) the file-system rollback demanding the apply receipt of a failed forward; after the fix the resume retried only that compensation and ended RolledBack. E2-4 roll forward PASS (Executed, 12/12, tx `1d8d606d`): **all three hosts run `20260905-155351-d6c4f79`, PREVIOUS = `20260905-155303-d6c4f79`**; the pointer files now carry bare release ids, so `local\rollback-release.ps1` needs `-ToPath`. Every finding was typed RED before its fix (four RED commits, four GREEN); gates, elevated disposable probes (74/74, protected hashes unchanged, zero residue) and the deployment suite (1265/1265) green at the end. Five IIS backups `SibylaE2-*` kept, never restored. The other session's hand-path deploy of 13:58 (`20260905-135846-bb8b376`) became the pre-E2 baseline. Records: `local/deploy/tests/Sibyla.Deployment.Tests/evidence/E2-*.{txt,md}`, spec §9 row E2 paragraph. Next: the final Slice 2 checkpoint (two independent read-only reviewers over the whole record), merge only if both ACCEPT, then the hand-over from the hand-path scripts to the module.

**2026-09-07, ~11:05 UTC — owner ruling D-EX-5: the register duplicate is detected at the company gate.** The morning's BICS upload (851301, 175.16 EUR) was already in the register as `I26010021`; the intake became Processed beside it because the only duplicate control compares upload checksums with upload checksums and the bytes differ from the archive copy. Ruled: the checksum control stays as it is (licence-wide while company-less, per company once assigned); a fiscal-key lookup against `fdchdr` (company, issuer fiscal number, document number) is added in the company gate, holding a match as `PossibleDuplicate` of the register entry for a person to rule, "not a duplicate" recorded as a verified genuine repeat; the same lookup guards the persistence slice before `EnterAsync`. Recorded in the extraction spec (D-EX-5, the gate paragraph, persistence item 1) for the G3 hold transitions and the persistence slice. Nothing deployed changed.

**2026-09-07, 09:37–10:30 UTC — owner's BICS upload: the gate assigned GOTT in 13 s, the Uploads page did not show it.** The document went Queued → Processed with `company_id` = GOTT at 09:38:04Z (recipient `PT513362061`, one-sided gate of the deployed release), but Refresh kept the first render: `IngestionService.ListAsync` re-queried with tracking on the circuit's scoped context and EF handed back the tracked instances with their old values. Fixed with `AsNoTracking()` plus a regression test that is red without it. The two-sided gate of the extraction contract (G2) is on `main` but not deployed; G3 remains open. Run record §7q.

**2026-09-06, ~02:00 UTC — Slice 2 CLOSED: final checkpoint ACCEPT by both independent reviewers (spec 0/0/2/4, operations 0/0/1/4; `evidence/CHECKPOINT-E2-review.md`), merged `baa1f32`, deployment suite 1265/1265 on main.** The corrections the reviewers asked for were appended before the merge (branch base bb8b376, runner reads the schema receipt and backs up IIS on Resume, spec row E2 names an Install-phase fault, RED 4 path, D-E2-3 reconciliation). **Hand-over (spec §10, run record §7o):** the module is the deployment method from now on; `publish-release.ps1` stays the publisher, `deploy-release.ps1` is retired, `rollback-release.ps1` remains the emergency exit with `-ToPath`. Carried to the next deployment slice: the Executed trailer's `standing` list (S-L1), the compensation readback expecting the spec grants rather than the snapshot (S-L3), a live demonstration of the D-E2-3 foreign-ACE readback path with the watcher armed after the publish receipt, the `.staging` failed copies (owner's call), and the freshness of the privilege proof (15 min; one proof per slot would need a ruling).

**2026-09-06, 19:15-19:16 UTC - release 20260906-111227-2c8e5f2 on all three hosts, the first routine module deployment after E2 (owner at the console).** Prepared at 11:12 UTC by the E2 runner pattern (gate clone at 2c8e5f2, one clean release, candidate worktree with fingerprint c382417c equal to the clean export); the 15:00 UTC slot passed unused and the owner ran the launcher line at 19:15 UTC. Transaction 596256ab: Executed, 161 plan entries, 12/12 health checks, 67 seconds, nothing failed or compensated; PREVIOUS = 20260905-155351-d6c4f79; IIS backup SibylaE2-20260906-191526 kept. Run record section 7p. Content: the Hermes harness spinner fix and the web home tool-progress setting (2c8e5f2). Nothing else moved on main today; the extraction contract spec review (rounds 1-6, revision 7 in progress) lives on ops/argus-extraction-v1-spec.

**2026-09-06, 11:30-20:05 UTC - Argus extraction contract v1 specification ACCEPTED at revision 8 by two independent reviewers after eight rounds; merged to main (c63ff91).** Branch ops/argus-extraction-v1-spec: docs/apollo-argus-extraction-v1-spec.md (the contract sibyla.extract.v2, the skill package at a558523, scoring against the 40 answer keys, the worker/web/bench/deployment mechanics, the RED oracle list) and docs/apollo-argus-extraction-v1-spec-review.md (all 16 reports verbatim, every finding dispositioned). Verdicts by round (contract / feasibility): r1 0/5/12/10 + 0/10/12/9; r2 0/1/5/8 + 0/1/9/4; r3 0/2/3/5 + 0/2/8/6; r4 0/2/3/6 + 0/1/6/6; r5 0/1/6/3 + 0/1/4/5; r6 0/4/3/5 + ACCEPT 0/0/2/5; r7 0/1/1/4 + ACCEPT 0/0/2/3; r8 ACCEPT 0/0/1/3 + ACCEPT 0/0/0/4 on head 601edf8. The contract reviewer swept all 35 fiscal keys against their PDFs (rounds 6-8): every cell is derivable from document plus spec; the feasibility reviewer built the csproj lines in a throwaway project and verified every CLI flag on the pinned 2.1.259. Seven residual Lows and one Medium (the V-9 VAT clause for the branch-(2) shape) are carried into the build slice as a revision-9 amendment. Open for the owner: 26 rulings Q-EX-0..25 (section 1.2 of the spec, each with the recommendation the spec is written to). Next: rulings, then the RED oracles of section 7 (answer keys into tests/Sibyla.Tests.Argus/golden/extract-v2/, contract validator, scorer, the bench) before any code.

**2026-09-07, 11:25-11:55 UTC - R-EX-2 settled on the pinned CLI: the deny rules were not binding.** Three sittings under the owner's bench login (evidence/extract-v2/probes-20260907-114951/RECORD.md with the two earlier sittings and two diagnosis runs). Sitting 1 answered only that stream-json needs --verbose on 2.1.259 (the CLI refuses before starting without it; StreamJsonNeedsVerbose = true, the production json list unchanged - without it the R-EX-3 sittings would have failed on their first call). Sitting 2, logged in, read a file that permissions.json denied: Read(//D:/path/**) - the POSIX absolute form this code wrote - matches nothing against a Windows drive path and fails open. The diagnosis (tools/skill-package/Invoke-DenyRuleMatrix.ps1) put ten candidate forms in two shapes into one settings file under the full production flag set: every unprefixed form binds, both // forms fail, the canary read. Fixed in PermissionsFile.Rule and the probe script; the test asserts the evidenced form and drops its Provisional name; sitting 3 re-ran the three probes clean (the planted file refused by rule, the sibling release refused twice over, no planted CLAUDE.md, package reads working). Also settled: --settings applies under --safe-mode (proved, so the flag stays), the package reads work, the whole section 4.3 flag set parses. Host finding for R-EX-3: Read with a pages parameter fails here (no poppler), so multi-page PDFs are read whole. Suite green.

**2026-09-07, 10:45-11:20 UTC - D-EX-5 register branch built RED-first and GREEN on ops/argus-extraction-v1-red (G3's next step, in parallel with the owner's steps 1-4).** RED (5a1ee6e): 18 oracles - the fiscal key by contract id, the gate's register lookup (a match holds the intake as PossibleDuplicate OF the entry with the reference and the audit detail; a held row keeps its hold_reason; the receivable's counterparty is the recipient; N-2 before the lookup; a tombstoned entry is no match), the two rulings in the register's declaration vocabulary (confirm = Duplicate with a "Duplicate - archived (clause 1)" duplicate_classification row on the entry; reject = a "Verified genuine repeat" row naming the key and the person, then the hold restored or Processed), the page's register-duplicate block with the ruling buttons; one negative guard green by construction. GREEN: ExtractionResult.ReadRegisterKey, CompanyGate's lookup (fdchdr joined to entmst under the owner's transaction, the worker's existing SELECT grant), migration 20260907105936_ExtractionV2RegisterReference (docint.register_entry_id, register_entry_code) applied to Preview only - Main is the owner's (the guide's step 1 now applies both v2 migrations), RuleOnDuplicateAsync's register branch, the Uploads page. Suite: Platform 350/350, Browser 134/134, Argus 193/193, Api 56/56; the one red test is the Main/Preview parity until the owner migrates Main; 0 regressions. Readings for the reviewers in RED-green.txt (the counterparty by flow; duplicate_classification as the fiscal vocabulary; the public matcher seam in the worker; no label rule on v1 document numbers; the persistence guard stays with the persistence slice). Next, per the owner's order: the owner's steps 1-4, then housekeeping, one checkpoint over G1-G3 plus this branch, one release.

**2026-09-07, 09:40-09:55 UTC - extraction slice G3 (web and schema) GREEN on ops/argus-extraction-v1-red; the GREEN phase is complete; STOP.** Built: migration 20260907094356_ExtractionV2HoldReason (docint.hold_reason, applied to the PREVIEW database only - Main is the owner's per spec section 4.6 step 1 and the hand-off's hard rule), DocumentIntake.HoldReason mapped, the v2-aware review page (the v2 block by contract id with header, lines, movements read-only and the raw JSON; the held status with its reason; the no-completion-path message for a held statement; a superseded correction with its own stamp; the Re-extract action; localisation keys in both languages), the hold transitions (a held fiscal correction goes to Processed stamped with the source contract id - the apollo.extract.v0 literal is gone; a held statement is refused by a message naming it; a not-a-duplicate ruling restores the hold), ReExtractAsync with the numbered key and its audit, JobCompletion clearing hold_reason on success. local\test.ps1 on the G3 tree: the last 13 RED tests green - 242 of 242 cumulative, 0 regressions; Platform 332/332, Browser 133/133, Argus 193/193, Api 56/56; the ONE red test is PreviewParityTests.PreviewHasExactlyTheSameMigrations until the owner runs local\migrate.ps1 -Target All. A one-cast defect in a RED oracle was fixed and recorded. Second clean-export fingerprint recorded in RED-green.txt. Next, the owner's: migrate Main; the R-EX-2 probes (a), (a'), (b) on the pinned CLI; the R-EX-3 bench sittings (fiscal, statements, then the reserve once its keys are exported); the transcript purge (Q-EX-22); then the independent checkpoint review by two fresh reviewers over the whole record, and only then the release of web + api + worker as one release id through the module.

**2026-09-07, 09:20-09:40 UTC - extraction slice G2 (worker side) GREEN on ops/argus-extraction-v1-red; STOP.** Built: ExtractionRunner (sandbox copy, the one fixed prompt whose hash reproduces the golden trailer, per-job permissions.json, evidence under evidence/<jobId>/ never overwritten, the CLI envelope, validation, the evidence element with JobId and the v2 members), ClaudeCliProcess under the section 4.3 flag set (BuildArguments, EnumerateSiblingReleases, PermissionsFile with the provisional Read(//D:/path/**) form in one helper), JobCompletion with the Hold outcome, TimeoutLaneMonitor, CompanyGate (two-sided, the both-match audit detail), ExtractionResult.ReadIdentities, WorkerStartupChecks run in QueueWorker.StartAsync, EvidenceSecretScanner, the attempt table (900 then 1,350, then Hold), the bench entry point (tools/extraction-bench, dry-run verified on the real inputs: 40 documents, 40 file hashes, package hash = golden). local\test.ps1: 59 more RED tests green (all G2 classes; 229 of 242 cumulative), 13 still red all G3 (the hold_reason column, ReExtractAsync, the page markers, the v0 stamp literal), 0 regressions. Two pre-existing v1 evidence tests were re-fed v2 answers because the worker now validates under v2 (recorded in RED-green.txt). Next: G3 (web and schema).

**2026-09-07, 02:30-02:55 UTC - extraction slice G1 (contract side) GREEN on ops/argus-extraction-v1-red; STOP.** Built: ExtractionContractV2 (V-1..V-19, N-1..N-7, the canonical form byte-for-byte to the six goldens and the 50-file rejection corpus), StampDutySplit and the section 2.6 derivations (ExtractionDerivations, CompanyMatcher.Gate and SameTaxId), the scorer S-1..S-11 in tools/extraction-bench, the skill package (tools/skill-package: Build-SkillPackage.ps1, manifest.json, EXTRACT.md; the committed package under src/Sibyla.Worker.Documents/skill/ from a558523, 231,396 bytes, ten reflowed lines, four name masks; SkillPackage.cs; the csproj lines; the post-publish check). local\test.ps1 on the G1 tree: 170 RED tests turned green, all inside the five G1 classes; 72 still red, all G2/G3 classes (one skill-package test waits on ExtractionRunner.PromptHash); 0 regressions; Argus 193/193. Record: tests/Sibyla.Tests.Argus/evidence/extract-v2/RED-green.txt (G1 section, with ten readings for the reviewers: the validator rule order and the V-15/V-16/V-17 readings, the scorer's gate tally over gated fields only, the mask patterns kept in tools/skill-package/masks.json outside the package). Next: G2 (worker side).

**2026-09-07, 01:30-02:25 UTC - extraction slice RED phase delivered on branch ops/argus-extraction-v1-red (R0-R3; STOP at the RED head 62cd08b).** R0: spec revision 9 = the eight round-8 residuals applied exactly as the closing verdict states them, dispositions in the review record (88674df). R1: the 40 answer keys copied unchanged into tests/Sibyla.Tests.Argus/golden/extract-v2/ with INDEX.md and _sql, answer-key.meta.json (the sync run id left null - the key build never captured it and the session may not query gott_sibyla), answer-key.flags.json from a bounded hand check of the 40 PDFs with page references (five subagent reports filed as evidence), answer-key.corrections.csv with 23 rows, reserve-set.csv with doclog codes (c604f60). R2: a scaffold declaring the surface and throwing everywhere (the A2 precedent), then one commit per section-7 row: six goldens with canonical forms, a 50-file rejection corpus, and fourteen test classes (fac7902..62cd08b). R3: local\test.ps1 on the RED head fails 242 = Platform 186 + Browser 5 + Argus 51, every failure a new v2 test failing on the scaffold; 34 passes = 25 pre-existing tests kept plus 9 GREEN-by-design data guards; slice fingerprint def4cd1f over 529 files on a clean export; record tests/Sibyla.Tests.Argus/evidence/extract-v2/RED-red.txt. Findings for R-EX-2 (none edits the spec): the Telles NIPC IS printed (p1 footer band, image layer) so that pre-declared correction is withdrawn; the movement corrections are 16, not 18 (the two anticipated column-sliced Revolut rows do not exist); the Windows Read-rule syntax is asserted in one Provisional test pending probe (a'); the reserve keys still need the owner's export. Next: G1 (contract, Split, scorer, package build), G2 (worker), G3 (web + migration), then the sittings and the checkpoint review.

**2026-09-07, 01:25 UTC - the owner ruled the 26 extraction questions: "Accept all 26 recommendations" (Q-EX-0..25 as recommended in the accepted revision 8; transcribed as D-EX-4 in the spec).** Next is the RED phase of the extraction slice: a fresh session on branch ops/argus-extraction-v1-red per docs/apollo-argus-extraction-v1-red-prompt-260907.md - the revision-9 amendment for the eight carried residuals first, then the answer keys, flags, corrections and reserve set into tests/Sibyla.Tests.Argus/golden/extract-v2/, then every section-7 oracle typed RED with the failing run and the clean-export fingerprint recorded before any code; GREEN in three sub-slices (contract + scorer + package; worker; web + migration), then the hostile run and the bench sittings with the owner, then the independent checkpoint review.

**2026-09-10, 13:35–14:25 UTC — public hostnames moved to `sibyla.pt` (owner instruction; record `docs/sibyla-pt-hostname-cutover-260910.md`).** `app.sibyla.pt`, `api.sibyla.pt`, `docs.sibyla.pt`, `login.sibyla.pt` (the IdP issuer is now `https://login.sibyla.pt/`), `nextcloud.sibyla.pt`, `dashboard.sibyla.pt`, `buzz.sibyla.pt`, `postiz.sibyla.pt` replace their `gottsolutions.net` names. Eight new Let's Encrypt certificates via win-acme (`[sibyla.pt] <host>`); the eight old names moved to a new IIS site `Redirect.gottsolutions.net` (301, path and query kept) with one renewal of their own; the old renewals cancelled. Relying parties repointed in the same window (web/api secrets, Hermes `.env` + `config.yaml` + plugin, Postiz, Buzz, Nextcloud; containers recreated, Hermes gateway and dashboard restarted); IdP clients carry only the new URIs. Verified: headless sign-in through `login.sibyla.pt` into `app.sibyla.pt`, client-credentials token accepted by `api.sibyla.pt`, every companion answering on its new name, 301s from every old name. Repository: defaults, deployment scripts and the E2 probe (new hostnames and thumbprints), plugin, smoke scripts, glossary; module suite 1266/1266. IIS site/pool names unchanged; Buzz clients must be repointed by their users.

*Post-checkpoint owner ruling, 2026-09-03 — O1 (`SchemaHealthGrant` and `apollo_api`) closed.* The
naming-shift rule applies: `sibyla_api` is the only current platform API role; `apollo_api` is the
historical name and remains only inside immutable applied migrations and historical evidence.
`SchemaHealthGrant` was authored and applied before Phase D; Main's grant followed the PostgreSQL
role rename by OID. On a fresh database the old guarded blocks skip and `local/setup-db.ps1`
applies the canonical `db/channel-intake-api-role.sql` for `sibyla_api`, as Phase D specifies. Do
not rewrite the applied migration and do not add a narrow follow-up migration. D2 still records
`has_table_privilege('sibyla_api', 'public."__EFMigrationsHistory"', 'SELECT') = true` before
`-Execute -ChecksOnly` as defense in depth. This ruling changes no reviewed A2+B2 source or verdict.

*17:15 UTC — accepted A2+B2 merged and A2's API deployed by the hand path.* Merge `97ac43a` was
pushed to `origin/main`; API-only release `20260903-171530-97ac43a` replaced
`20260903-021830-7191ca3` and keeps it as the rollback target. `/health`, `/health/ready` and the new
`/health/schema` are 200; the schema response names `sibyla_api` and exactly the 19 required
migration ids in the clean release manifest. Published/installed `web.config` hashes match; IIS
site and pool are Started; no error-level API event followed activation. No other host or legacy
object was touched. The direct `has_table_privilege` proof remains for D2. Next: C2 on disposable
`ZZ.Sibyla.Probe.*` resources only, then D2 `-Execute -ChecksOnly` and the C2+D2 checkpoint.

**2026-09-02 — Naming shift, Phase L: the legacy sites moved to `legacy.gottsolutions.net` / `api.legacy.gottsolutions.net`; `sibyla.*` is free for the platform.**
Run from an agent session at the owner's instruction (checklist and run record in
`docs/apollo-naming-shift-prompt-260902.md`). DNS already pointed both names here; two
certificates were issued with win-acme as new renewals; the legacy IIS sites bind only the
`legacy.*` names; the `sibyla-web` IdP client accepts the legacy redirect URIs and no longer
lists the sibyla ones; the legacy plugin's `.env` and its secret helper point at
`api.legacy…`; the status tool round-tripped through the new host with a token from the IdP.
`sibyla.gottsolutions.net` and `api.sibyla.gottsolutions.net` no longer answer — the platform's
deployment (`Sibyla.Deployment` Slice 2, two sites) takes them next. `Sibyla.Docs` moved as
well on the owner's instruction (`docs.legacy.gottsolutions.net`). The Hermes gateway was not restarted and the mail crons stay paused. Remaining
owner steps: Phase D at decommissioning, the optional N3 rename, the IdP display names.

**2026-09-03 — Naming shift, Phase D run on the owner's word, ahead of the legacy decommissioning: `gott_apollo` is `gott_sibyla`, the roles are `sibyla_*`, the legacy database is `gott_sibyla_legacy`.**
Ruling N1 had placed this last, after `gott_sibyla` was gone; the owner asked for it with the
legacy API still live, so the legacy database moved aside. Checklist, dry-run script and run
record: `docs/apollo-naming-shift-phase-d-260903.md`, `localphase-dInvoke-PhaseD.ps1` and the
Phase D record in `docs/apollo-naming-shift-prompt-260902.md`. The owner ran the renames in an
elevated console (superuser password prompted there); the agent session verified the catalog
(policies, ownership and CONNECT privileges followed the OIDs; passwords kept under SCRAM), the
substituted carriers (platform secrets, repository secrets renamed to `sibyla-db.json` /
`sibyla-preview-db.json`, the legacy pools' and worker's connection variable), restarted the
pools and the service, and checked production and the legacy API. The repository follows on
`ops/naming-phase-d` (code, tests, scripts, SQL, README, glossary; migrations keep their text).
`apollo_ai_reader`, `gott_identity`, the IdP clients, `D:ApolloData` and the data-protection name
are unchanged. `localmigrate.ps1` reaches both renamed targets with nothing pending; `local	est.ps1` after the
rename: Api 45, Argus 137, TenantIsolation 52, Browser 121, Platform 106 of 107 with one race against
the live worker on the shared database (`IntakeDuplicateControlTests.AssigningReRunsTheDuplicateCheck…`,
green when rerun alone; not a naming failure).

**2026-09-02 — Naming shift, Phase 4: the agent is Apollo. The shift's code and document phases are complete.**
GOTT's gateway agent, the Hermes Agent instance that reads email, WhatsApp and Telegram, files
what it receives into Sibyla and answers with licence data under the read-only role, is Apollo;
the runtime it runs on keeps the name Hermes (ruling N8), so the harness selector still says
Hermes and the agent says Apollo where it is addressed: the seeded AI project `apollo`, the
plugin `apollo-channel-intake`, the cron `apollo-email-gateway` when it is created. The few
sentences that had used "Hermes" for the instance, or listed it as a channel beside WhatsApp and
email, now say Apollo and name the medium; the intake spec's cutover order names the new cron;
the intake comment on `DocumentIntake.Channel` says the channel names the medium, never the
carrier. Nothing user-visible changed and no test changed. The glossary
(`docs/naming-glossary.md`) is final: Argus the module, Sibyla the platform, Sibyla Legacy the
retired stack, Apollo the agent. What remains is the owner's: Phase L (legacy sites to
`legacy.gottsolutions.net` / `api.legacy.gottsolutions.net`, then the platform on `sibyla.*`;
checklist in the naming document), Phase D (databases and roles at decommissioning), the optional
folder and GitLab rename (N3), and the IdP client display names. One ruling to take when the
next extraction contract version is cut: its id (`sibyla.extract.v1` would be the natural name).

**2026-09-02 — Naming shift, Phase 3: the platform is Sibyla.**
The platform built here as Apollo is Sibyla in labels, routes and keys, and architecture. What a
user sees: the brand and brandmark read Sibyla, every page title ends "— Sibyla", the intake and
manual-entry copy says Sibyla with the Portuguese gender corrected, and every refusal and
provenance sentence written from today says Sibyla. What the code says: `Sibyla.Platform.*`,
`Sibyla.Web`, `Sibyla.Sync`, `Sibyla.Worker.Documents`, `Sibyla.Modules.Argus.*`,
`tests/Sibyla.Tests.*`, `Sibyla.slnx`, `SibylaDbContext` (the name Phase 1 freed),
`SibylaModuleDescriptor`, `AddSibylaPlatform`, the cookies `sibyla.auth`, `sibyla.owner`,
`sibyla.scope`, the setting `ConnectionStrings:SibylaDb`, the env `SIBYLA_DB`, `Sibyla:PathBase`.
What the database says: one stored path followed, the AI project's preview target, by migration
`SibylaPlatformNames` on Main and Preview; databases, roles, the code-ledger issuer `apollo`, the
gap-detector tag, the extraction contract `apollo.extract.v0`, the Nextcloud prefix and the
data-protection purposes keep their names because rows and files already carry them (N1, N7,
Phase D). Rulings applied: N2 (hostnames `sibyla.gottsolutions.net` / `api.sibyla…` after the
legacy move, D16 superseded, Phase L checklist written for the owner), N3 (folder and GitLab
stay), N4 (intake spec rewritten: contract `sibyla.channel-intake.v1`, `src/Sibyla.Api`, scope
`sibyla.channel-intake.write`, client `sibyla-channel-intake`; plugin stays `apollo-channel-intake`),
N5 (cookie and key renamed), N6 and N7 (history kept). The deployment prompt carries a superseded
block instead of a rewrite. Neither context has pending model changes. Suites: TenantIsolation
43, Argus 136, Browser 121, Platform 96. Apollo occurrences in code went from 5,811 to the
protected identifiers only. Next: Phase 4, the agent becomes Apollo; N8 is ruled (runtime stays
Hermes); nothing waits on the owner. Then Phase L (owner) before production, Phase D at
decommissioning.

**2026-09-02 — Naming shift, Phase 2: the retired stack is "Sibyla Legacy".**
The bare word "Sibyla" no longer means two things anywhere in the repository or the memory. The
retired applications, database, sites, plugin and cron are written "Sibyla Legacy" or by their
concrete identifier; the module is written Argus in every current-state document (the harness
plan, the deployment prompt, discovery 08, 12, 14 and 15) and in the two code comments that still
called it Sibyla; where the word meant the FDR it now says so. Dated documents are not rewritten:
this plan, the harness plan, the deployment prompt, the intake spec and the discovery documents
carry a one-line naming note under their title that fixes what the word meant before today. The
platform's future name is already in use in one place and stays: the AI project `sibyla`, the
development of this repository. The owner's quoted words stay verbatim. The gate in the naming
document lists every survivor. No code behaviour changed; the web host builds. Next: Phase 3, the
platform becomes Sibyla; it needs decisions N2–N7 (hostnames, repo and GitLab names, the intake
contract id, cookie and connection-string key, the migrations history table, stored provenance).

**2026-09-02 — Naming shift, Phase 1: the module is Argus.**
The financial-documents module, called Sibyla since the first wave, is now Argus in labels,
routes, key and architecture, in that order, with the suites green after each layer. What a user
sees: the module is "Argus" in the sidebar, the module picker, the page titles and the inactive
notice; every screen lives under `/argus/*`, and an old `/sibyla/*` link answers with a permanent
redirect that keeps the sub-path and the query string, for one release. What the code says:
`Apollo.Modules.Argus.*`, `ArgusDbContext`, `AddArgusModule`, `Components/Pages/Argus`,
`tests/Apollo.Tests.Argus`. What the database says: the module key is stored data, so the
platform migration `ArgusModuleKey` rewrote `sibyla` to `argus` in LICMOD entitlements and
USRROL module-scoped roles on Main and Preview; the seed follows. Not renamed, by rule: the 48
applied module migrations and their ids, the history table `__EFMigrationsHistory_Sibyla` (N6),
the FDR's own column `SibylaValue`, the Share and workbook names, the legacy references (Phase 2).
Neither context has pending model changes. Tests: `OldModuleRoutesRedirectPermanently` (3 cases),
`ArgusModuleKeyTests` (the migration's Up and Down on a synthetic owner inside a rolled-back
transaction; tenant #1 carries `argus` and no `sibyla`). Suites: TenantIsolation 43, Argus 136,
Browser 121, Platform 96. One lesson recorded for the next phases: a migration test must never
run a global Down outside a transaction, because it rewrites every owner, tenant #1 included; the
first run of this test did exactly that and the Browser suite failed on the gate until the
migration's own Up SQL was re-applied. Sibyla occurrences in code went from 4,329 to 140, all
classified in the naming document's gate. Next: Phase 2, the legacy stack qualified as "Sibyla
Legacy" in documents, comments and memory; it needs no owner decision.

**2026-09-02 — Naming shift, Phase 0: glossary, inventory and the decision list.**
Owner ruling of 2026-09-02: the module now called Sibyla becomes **Argus**; the retired stack
becomes **Sibyla Legacy**; this platform becomes **Sibyla**; GOTT's gateway agent, today the
Hermes Agent instance that handles email, WhatsApp and Telegram and answers with licence data,
becomes **Apollo**. The names chain, so the work is four phases in a fixed order, each freeing a
name the next one needs; the prompt, the phase map and the owner decisions N1–N8 are in
`docs/apollo-naming-shift-prompt-260902.md`, the definitions in `docs/naming-glossary.md`.
Phase 0 renamed nothing. It counted: 4,739 Sibyla, 6,986 Apollo and 207 Hermes occurrences,
classified as label, route or key, architecture, infrastructure, history, legacy, external or
runtime, with the phase that owns each class. Three findings shape what follows. The module key
`sibyla` is stored data (licence entitlements, module-scoped roles, every tenant's ActiveModules),
so Phase 1 carries a platform migration and a redirect from `/sibyla/*`, not a search-and-replace.
The target names collide with live identifiers (`gott_sibyla`, `sibyla.gottsolutions.net`,
`api.sibyla.gottsolutions.net`), so databases, roles, hostnames, the repo folder and the GitLab
project stay until the legacy freeze and move only by the owner's hand. And the AI Projects seed
already anticipated the ruling: project `apollo` is the Hermes-harness gateway agent, project
`sibyla` is the development of this repository. Recommendations on N2, N4, N5, N6 and N8 are
recorded under the inventory. Nothing waits on the owner for Phase 1; N1–N7 are needed before
Phase 3.

**2026-09-03 — UI-5: Management and Administration — master data, associations, the governed catalogues and Period Close, Build 060200 @ 56126f8.**
The last slice of the screen review plan. Three writers, one ruling each, and the Management /
Administration screens on top of them. Every writer refuses on an owner the FDR still syncs, with
the sentence, and every write is proven on a synthetic owner.

*Three writers.*
- **`MasterDataService`** — ENTMST and ITMMST rows Apollo issues or edits: business fields only
  (name, fiscal number, country, currency, role, account-period rule, frequency, class, cost
  centre); codes minted through the ledger (`EC`, `IC`), never typed; identity is the code (§23.1:
  a second row with the same CodeName is refused); **a synthetic FiscalNo is never invented**
  (§4: an entity without one is a question, not a row). ITMMST is not a ledgered family at the
  FDR, so its counter starts at 1 and must be seeded above the mirrored dictionary at cutover —
  recorded.
- **`AssociationService`** — the relationship writer. A (company, entity, item) pair is a minted
  ruling (`EI`) with the class's PLMKEY / PLMKO and the item's cost centre unless a person says
  otherwise; the same trio is **never ruled twice** (refused with the existing code); a pair is
  **retired, not deleted** (the lines it ruled keep their EICode under P2-05's composite FK) and a
  retired trio **revives as the same ruling**, never a second code. An entity account (ENTBNK)
  takes a pay method and an IBAN **declared with provenance** (`learned_evidence` names the person
  and the date, satisfying P2-12's CHECK); one IBAN names one counterparty.
- **`CatalogueService`** — the declarations writer, driven by one spec per catalogue
  (`Catalogues`, Domain/MasterData) that also drives the form: Cost Centers, Item Classes (PLMKO
  from the management account, else the PLMKEY's first two characters), SNC Accounts, Management
  Accounts, Bank Accounts, Document Types, Bank Matching, Exchange Rates, Entity Payments, Item
  Aliases; Entity Aliases go through the learning store, ENTALS's one writer. Uniqueness is the
  table's (class/subclass, account per year, type/origin, match type, rate per period and pair);
  codes are minted where the FDR ledgers the family. **Period Close** is a scope_closure
  declaration — Open / Closed with the person and the reason, company-scoped by the row's scope
  key — and readiness is shown, never enforced: closing with blockers is the owner's recorded call.

*Screens.*
- **Entities** — View dropdown *Master* (Create / Edit / Items / Accounts) and *Balance* (ENTBLC,
  read-only, by entity, period and side). The entity editor is the Master Data Edit form; Items
  and Accounts open the **Association form** (parent context → create row from existing master
  records → linked-record table with Retire → raw rows behind Details).
- **Items** — Create / Edit through the item editor; *Entities* opens the association from the
  item's side. The classification shown stays the trios', per company.
- **Classification** — the View dropdown from UI-1 now carries Create / Edit per catalogue: a pair
  through the association form (a pair is created from an entity, and the page says so), an item
  through the item editor, a class through the catalogue form; summary cards above the filters.
- **P&L** — the **Intercompany** filter (Include / Exclude / Only, default Exclude) shown only
  when ICPairID / CounterEntryCode markers vary, as the prototype does.
- **Administration** — the Configuration Center bundle grows to Document Types, Bank Matching,
  Exchange Rates, Entity Payments, Entity Aliases and Item Aliases; every catalogue view has
  **Create** in the heading and **Edit** per row through one `CatalogueEditForm`; **Period Close**'s
  Open / Close / Reopen are live declarations with a reason, no longer disabled placeholders.

*Proof.* `MasterDataTests` (5): an entity is minted never typed and a synthetic fiscal number is
never invented; a pair is a minted ruling, retired not deleted, revived not duplicated; an IBAN
is declared with provenance and names one counterparty; catalogues carry minted codes (a class's
PLMKO derived, a duplicate rate refused) and a period state is a declared row; the sync era
refuses every writer. `ManagementAndAdministrationCarryTheirWriteActions` prerenders Entities,
Items, three catalogue views and Period Close and asserts the live actions and the absence of
the old "lands in Phase 2" placeholders. Sibyla 136, Browser 118. Six later duplicate
localisation keys removed (the routine check now runs after every label batch).

*Not in this slice.* Retiring an entity or item (Status / RetiredDate exist; the FDR's
retirement procedure names a successor and a reason — a small follow-up); removing an IBAN;
an ENTPAY row's observed fields (MatchedPairs, ObservedEarlyDays …) which are the pipeline's
and stay read-only; Entity Roles (values on ENTMST, no table) and the checklists (governed
catalogues, edited by declaration at the FDR) stay read-only by design; the ITMMST counter
seeding at cutover.

**The screen review plan is complete.** Six slices, UI-0 to UI-5, all stamped Build 060200 @
56126f8, every screen on the Form Standard, every write through a Phase 2 engine or one of the
new writers, every refusal in the engine's words. Open with the owner, unchanged: the Share drop
versus the build workbook as the contract; Inventory; Project Control; a native-owner
demonstration versus cutover C6 as the acceptance evidence for the review forms; the external
use of the Argus name.

**2026-09-03 — UI-4: the Reconciliation section — Statements, Payables / Receivables over the event ledger, Match & Review, Build 060200 @ 56126f8.**
Fifth slice of the screen review plan. The section that UI-1 cut into the navigation gets its
forms, and the two write paths the plan named — both under the sync-era refusal, both proven on
a synthetic owner.

*Two services, one ruling each.*
- **`SettlementEventService`** — the settlement ledger's one writer. The FDR's ruling (§27.5,
  "PAYDTL is authoritative": every control line is backed by a ledger line, PaidAmount is a SUM,
  never a typed number) is the design: a person **records an event** (PAYDTL / RCVDTL, Apollo-native,
  bank-backed when it names a BNKMOV), and the control (PAYCTR / RCVCTR) is **re-derived** from the
  ledger — PaidAmount, DueAmount, PayDate, PaySource, PaymentMethod (observed-first), Status and
  CoverageState — or created with a minted PAY / RCV code when the document had none. The status
  ladder is `build_payctr.py`'s, the transcribed rungs: *No Payment Due*, *Paid* (settled and
  bank-backed), *Paid (Cash)* (the document is the witness), *Paid (Unconfirmed)* (settled
  numerically, no bank confirmation), *Overdue*, *Open*; coverage is `coverage_state`'s
  transcribed branches. What a person keeps on the control is a **hold** (a control_hold row,
  held-by and released-by recorded) and a **note** — nothing else is typed. Not transcribed, and
  refused or named on the row: cross-currency events (need `convert_amount()` at the document's
  AccountPeriod), *Waiting (Statement)* (needs statement coverage), *Paid (Off-Bank)* and
  declared pulls (need ENTPAY), document-settlement declarations.
- **`ManualMatchService`** — a person's match of a movement to a document is **not a second BNKREC
  writer**: it is a RECREV decision. The movement's open review item is reused, or a *Manual Match*
  item is opened (ItemKey `BNKMOV|{code}|manual-match`, re-raised with the FDR's `|rN` suffix),
  then decided *Accept* with `EntryCode=` and applied by `ReviewDecisionEngine`, which writes the
  protected match and the BNKREC row (P2-11). So a manual match carries the same memory
  (Observations), the same constraint (a rejected pairing is never re-proposed), and refuses on a
  protected or hammered movement and on a synced one.

*Screens.*
- **Statements** — filters on the visibility rule, four indicators, and a **Statement View** form:
  the check's state as the finding (the audit's own case sentence), its Notes as the
  recommendation, raw BNKCHK / BNKACC fields behind Details. Read-only: a statement check is the
  pipeline's, decided in Audit or at source.
- **Payables / Receivables** — Review (open work) / History (settled) modes with counts; Company,
  Year, Period, Status (default All, exact match — the prototype's stale *Due / Outstanding*
  default reported back), Method (payables) and Coverage filters; indicators Documents, Lines
  (events), Overdue, Local Amount. **Edit** opens the control editor: essentials, the *Control
  view* with every derived field read-only and labelled so, Hold and PayNote as the editable
  fields, the PAYDTL / RCVDTL ledger under it, and **Add line** — the event form (payer, bank
  account, date, amount defaulting to the due amount, the BNKMOV it names, method, notes).
- **Match & Review** — the View dropdown between the two catalogues. *Match* (BNKREC): ID, Period,
  Date, Bank, Bank amount, Entry, Entity, Document amount, Match, State; Company, Year, Period,
  Bank, Match, State filters; Documents, Lines, Unmatched, Bank Amount; **Edit** opens the Match
  form — the movement, then the match fields with a Look up on EntryCode that fills the document's
  type, entity, date, amount and currency and computes the difference; a protected movement says
  so and takes no save. *Review* (RECREV): Open / Review / Closed with counts, the new columns
  (Bank, Entity, Issue, Type, Relevancy, Priority, Amount); **View** opens UI-2's Review form for
  the RECREV item with the flagged issue first, the recommendation, the five decisions and an
  input whose placeholder says what an Accept names (`EntryCode=`).

*Proof.* `SettlementEventTests` (5): the control is derived, never typed (Open → Paid
(Unconfirmed), PaySource, PayDate, native rows); a bank-backed event makes Paid and cash has its
own word, Overdue when the money is late; refusals are sentences (cross-currency, zero, an
unknown movement, no person, the FDR-owned tenant); a hold is declared and released with the
control_hold row; a manual match is a protected ruling through the engine (item, decision,
BNKREC, protected movement, and a second match refused). `ReconciliationScreensCarryTheirTemplates`
prerenders the four screens over the corpus. Sibyla 131, Browser 112.

*Not in this slice.* The untranscribed rungs above; an ENTPAY / statement-coverage read for
Waiting; editing an event (an event is a fact; a correction is another event — recorded as the
rule); a Match view row for movements that have no BNKREC row yet (the FDR writes one per
movement, so today the two coincide); Entity detail links.

**2026-09-03 — UI-3: Documents (View, Attach, Manual Entry) and Integration (Review / Ready / History over the interpreter), Build 060200 @ 56126f8.**
Fourth slice of the screen review plan, on top of P2-05b / P2-18b. Two screens; every form is the
Form Standard's; every readiness verdict is the engine's.

*Documents.*
- **Document View** (`DocumentViewForm`) — the norm's field set as Essentials (ID, DocID, Date,
  Due, Pay, Period, Company, Entity, Item, Class / Sub, Currency, LocalAmount), the extracted lines
  as the task section **with the captured Quantity / UnitPrice / VATRate as columns, a blank shown
  as a blank**, the related Quality / Audit / Match & Review links (Match & Review disabled when no
  BNKREC names the document), Attach as an action, and every raw FDCHDR field, the DOCLOG records,
  the bank links and the control row behind Details. The document page `/sibyla/documents/{code}`
  *is* this form now (interactive, closing returns to the list); the list's row actions are View
  and Attach, and the ID stays a deep link.
- **Attach** (`DocumentAttachForm`) — attachment state as the FDR's archive records it (DOCLOG
  status, path, hash, date) plus Apollo's own intake copies matched by filename (transfer and
  processing state, remote path); *Upload or replace* goes to Uploads. **Open attachment renders
  disabled with its reason**: Apollo has no file endpoint yet, and a link that pretends to open a
  file is worse than none.
- **Manual Entry** (`ManualEntryForm`, Create) — DocID, DocDate, DueDate, Entity from ENTMST, Item
  from ITMMST, Net, VAT, Total (derived, read-only), Currency, Flow, an optional intake attachment,
  and the captured line facts behind an *optional* section, stored only when given. Submit is
  `DocumentEntryService.EnterAsync` (P2-05): a refused entry shows the service's sentence (on
  tenant #1, the sync-era one); a pair without a ruling shows the reported DOCRQE items under
  *Reported, never guessed*; an entered document announces its EntryCode and the list reloads. The
  Manual Entry button is no longer a disabled placeholder.

*Integration.* The page is rebuilt on `ErpPushEngine`:
- **`PreflightManyAsync`** — one evaluation per document over shared data (declarations, items,
  exclusions, per-company requirements and ITGLOG flow history loaded once); `PreflightAsync` and
  the batch call the same evaluator, and `ABatchPreflightIsTheSingleEvaluationByConstruction`
  proves they agree, code for code. **`PreflightSuppliersAsync`** judges a supplier by the
  Entity-Supplier ITGCHK rows over its ENTMST row and the derived ISO country
  (`ASupplierIsJudgedByTheEntitySupplierRequirements`); `ErpTarget` carries the connection's
  company for that. `ErpAdminService.ParseTargetShape` reads a connection's item-code map and
  enabled flows without its credentials, so a readiness screen never touches a secret.
- **Screen** — Review Header With Tags: Review / Ready / History modes with counts, the Documents /
  Entities toggle, ERP (the licence's connections, D9) · Company · Year · Period filters, four
  indicators. Rows: ID, DocID, Date, Period, Entity, **Object**, Local, Status, Actions (View ·
  Integrate · Retry). Status is derived from the ledger first (what happened), then from readiness
  (what would happen), never authored; with no connection every row says *No target*.
- **Integration Review form** — essentials → **Readiness** (the preflight's own sentences, code by
  code) or **Outcome** (the ERPLDG state and error) → warnings → *Retry failed line* on a terminal
  chain (`RestartAsync`, a recorded human decision, with the reason it asks for) → Details with
  the Flow (ITGFLW), the Checklist (every ITGCHK row of the object with Holds / Blocks / Warns and
  the interpreter's detail), the Checks, the Run (attempts, external id, committed at) and the
  **payload preview** — the lines with quantity, unit price, VAT rate and the derivation sentence
  that says which are captured and which are mapped. No hard-coded recommendation: the prototype's
  fixed sentences were reported back as a finding, and the form shows the engine's finding or
  says none is available. **Integrate selected stays disabled with its reason** until the live
  push is authorised.
- **The ID column as action trigger** — adopted here only, as the trial the norm says it is
  (`SibIdAction`): the identifier with a neutral chip opens a page-level pop-up (outside the
  table's scroll container, so never clipped) with the same actions as the Actions column, which
  stays.

*Proof.* `DocumentsAndIntegrationCarryTheirForms` prerenders the list (Manual Entry live), the
document page for a corpus document read from the database (View form, essentials), and the
Integration screen (review header, ID triggers, the Object column) — the batch preflight over the
owner's payables runs inside that prerender. Sibyla 126, Browser 108, Platform 96, all green.

*Two housekeeping findings.* The localisation dictionary uses index-initialiser syntax, so a
duplicated key silently overwrites instead of throwing — five later duplicates (Notes, Flag,
Action, Detail, Description) were removed and a duplicate check is part of the slice's routine
now. The Integration prerender evaluates one blocking-gate query per document; it is fast enough
today (the whole Browser suite runs in 13 s) and is the first thing to batch if the queue grows.

*Not in this slice.* A file endpoint for Open attachment; an Entity / Item detail page (the
related links render disabled until UI-5); the supplier flow itself (the Entities tab judges
readiness for a flow that does not exist yet, and says so through IC000006 on every purchase);
Integrate.

**2026-09-03 — P2-05b and P2-18b: captured line facts, and readiness as data — the rate list, the exclusions, the ITGCHK interpreter.**
The two follow-ups the 2026-09-02 review raised, done together because UI-3 must not show a VAT
quotient the FDR ruled wrong. Both are transcriptions of Stage 16 Rounds 5–13, each with the FDR's
own examples as tests; nothing here computes what the FDR declares.

*P2-05b — FDCDTL Quantity, UnitPrice, VATRate.* Three nullable columns with **no default** on
`fdcdtl` (`quantity`, `unit_price` at 18,4; `vat_rate` at 5,2 with `ck_fdcdtl_vat_rate_percent`).
The rulings hold at every door: the sync reads them as numbers and stores a blank as a blank (line
transform `l2:` folded into the set hash, so all 1,861 lines were rewritten once); the entry service
stores them as given — a zero quantity is a fact, a null is "not printed", a rate outside 0–100 is
refused as not a percentage — and never fills in `(1, NetAmount)` or `VAT / Net`; the unit price is
documented net of VAT (the receipt that prints a gross one is the Round 9 lesson). Measured after
the re-sync on tenant #1: quantity and unit price captured on 24 lines, VAT rate on 33, of 1,861 —
sparse **because captured**, and `DeclarationSyncTests` asserts it stays sparse (a 100%-populated
column would be a default hiding as data, §22.6).

*P2-18b — readiness as data.* Two declared lists are now synced as declaration tables, and the
push engine's preflight is an interpreter over the FDR's requirements instead of a second copy of
the rules:
- **`vat_rate_decl`** — `Specs/vat_rates.json` flattened to one row per country × rate × From
  (93 rows, 22 countries; every row dated; 0.0 declared everywhere as a real rate). The sync reads
  it from the Specs folder (`SyncOptions.SpecsDir`), because it is a governed document, not data.
- **`integration_exclusion`** — `integration_exclusions.json`, 14 rows, all *Out of Scope
  (declared)* for `Gott|iziBizi|PurchaseDocument`, each resolved to its header. A declared record
  is refused at preflight in the FDR's words (`declared-out-of-scope`, with the reason, who and
  when) and stops being asked about.
- **`VatRates`** (Infrastructure/Integration) — `snap_vat_rate` / `line_vat_rate` / `vat_rates_on` /
  `iso_alpha2` transcribed: a rate is **captured or identified**, never divided. The pool is the
  counterparty's country on the document's date (FiscalNo prefix, else the named country; `EU` and
  `XI` are scheme registrations, not countries), falling back to the union of every declared rate,
  which snaps less often, never wrongly. Six bases — captured, snapped, ambiguous, incoherent,
  unresolved, undeclared — and only the first two are a rate; the others carry the raw quotient
  under their own name so no caller mistakes it for an answer, with tolerance one cent plus a hair.
- **`LinePayload`** — `line_payload` / `collapse_zero_lines`: the payload prefers the captured
  (quantity, unit price) and falls back to `(1, NetAmount)` only where nothing was printed, **both
  labelled on the line** (`ErpPushLine.Derivation`); zero-net components fold into the nearest
  preceding priced line with the fold named in the description (`Collapsed`); a wholly zero
  document is not a bundle and is refused with the reason (`zero-value`) — "a document worth nothing
  is not sent", owner ruling 2026-09-01.
- **`IntegrationRequirementEvaluator`** — the one interpreter of ITGCHK predicates, the FDR's
  closed grammar (`not_blank`, `matches`, `not_matches`, `in_set`, `not_in_set`, `min_lines`,
  `lines_not_blank`, `fex_resolves`, `flow_ran`, `derived_not_blank`, `lines_priceable`). An
  unknown predicate is a refusal ("a requirement nobody can evaluate must not read as satisfied");
  a predicate reaching into BNKREC, RECREV or BNKMOV is refused ("reconciliation is not a
  precondition"); a failing Blocker is a problem, a failing non-blocker a **warning** on the result,
  reported and never dropped. The preflight evaluates every ITGCHK row for the document's company,
  flow object and target system, with `derived country_iso_alpha_2`, FEX resolution and the flows
  ITGLOG records as run.
- **`ErpPushEngine.PreflightAsync`** keeps Apollo's connection-level checks (flow enabled, item
  mapped on this connection, blocking flags, non-EUR without FEX, synthetic supplier) and drops the
  `Math.Round(VatAmount / NetAmount × 100)` quotient for good; `ErpPushDocument` carries the
  supplier's ISO alpha-2 country. The interpreter and the payload never read a reconciliation
  table — asserted on the source text by `VatRateTests.PreflightSourceNeverReadsReconciliationTables`.

*What this means on the corpus.* A preflight of any Gott purchase against an iziBizi target will
fail on IC000006 — "the supplier flow has run" — because ITGLOG holds no attempt; that is the
FDR's own measurement (434 of 443 blocked records wait only on the supplier flow, which has never
run), and nothing in Apollo calls preflight on the corpus until UI-3's Integration screen does.
That is the honest state, not a defect: the supplier flow
does not exist in Apollo yet (it is the Entity-Supplier push the plan's UI-3/Integration slice will
name), and the live push is not authorised. The ITGCHK rows' TargetSystem `iziBizi` is matched to
Apollo's provider code `CEGID_IZIBIZI` by letters only.

*Proof.* `VatRateTests` (13, pure): the FDR's coffee (19.50 / 4.48 → 23%, not 22.97%), water
(0.85 / 0.11 → 13%), the tiny ambiguous line, the incoherent line, a foreign rate, an undeclared
country, Romania across its 2025-08-01 change, captured-wins, the union pool, ISO alpha-2 from
prefix / name / never a scheme, the labelled default, the MEO bundle collapse, the zero document,
the closed grammar and the reconciliation refusal, the source-text guard. `ErpPushEngineTests`
(+5): captured vs identified with the default labelled; ambiguous and unresolved refused with the
FDR's sentences; bundle folding and the zero-value refusal through a real entry; a declared
exclusion refusing in the FDR's words; requirements as data — two pass, an unknown predicate
refuses, a reconciliation reference refuses, the supplier flow blocks, a Warning-impact failure
lands in Warnings. `LineCompletenessTests` (+1): zero stored as zero, blank as blank, 150% refused.
`DeclarationSyncTests` (3, corpus): the list mirrored row by row and dated, the exclusions resolved,
the captured facts sparse and in range. Migration `SibylaLineCaptureAndDeclarations` applied to Main
and Preview; both re-synced green (runs cf525979 / 878f3864). Full solution green.

*Not in this slice.* The company's own country as the domestic fallback before the union (the FDR
resolves it from the company's ENTMST row by CodeName; Apollo's companies have no country yet —
recorded as a data question for tenancy); `check_vat_rates.py`'s stale-list proposals into the
review queue; the supplier flow itself; sending anything.

**2026-09-03 — UI-2: the Review forms — Processing, Quality and Audit wired to the decision engine, Build 060200 @ 56126f8.**
Third slice of the screen review plan, and the first time an Apollo screen writes through a Phase 2
engine. The three review pages now carry the full *Review Header With Tags*, and their View action
opens the Form Standard's **Review form** instead of an inline expander.

*One form, one contract.* `ReviewForm` (Pages/Sibyla) takes a `ReviewItem` — what a page row is
backed by, and what the form may do with it:
- **DOCRQE / RECREV item** (`DR…` / `RR…`): the five decisions of the Review Procedures (Accept,
  Accept with Changes, Reject, Need More Information, Defer) and the user input → `DecideAsync`;
  when accepted, `ApplyAsync` runs and the outcome names what was applied or why not. The
  engine never parses prose: `TABLE.Field=value` is applied, anything else is recorded — the field
  says so.
- **DOCFLG instance** (`FL…`): Resolve (with evidence), Dismiss or Waive (with reason) →
  `FlagLifecycleService`.
- **Read-only records** (a BNKCHK statement check, a DOCLOG history entry, a terminal item): the
  form shows the reason in words and offers no answer. A statement check is decided in
  Reconciliation › Statements; a history entry is a record; a terminal item is frozen — "a new
  question is a new item", the engine's own rule.
- **Every outcome is the engine's sentence, unchanged**, in the form's notice (refusal) or the page's
  notice (success), and the page reloads its rows. On tenant #1 the FDR-authored items are refused
  with the sync-era sentence, as the plan requires; nothing is simulated.

*The engine grew one method.* `ReviewDecisionEngine.DecideManyAsync(ids, …)`: one answer for a
group of items in **one transaction, all or nothing** — the first refusal names its item and nothing
is written ("a group is decided as a group or not at all"). `DecideAsync` is now the single-item
case of it. Proven by `ReviewDecisionTests.AGroupIsDecidedAsAGroupOrNotAtAll`: a group holding an
already-decided item is refused and the open items stay Open; the same group without it is decided,
each item carrying the decision, the input and the person.

*Per page.*
- **Quality** — filters through `SibFilterBar` with the visibility rule (Section / Origin / Flow
  secondary, behind *More filters* under 1380 px), Search labelled Search; tag toggles are the
  **DQLCHK catalogue** with count pills (visible at zero), row-only types appended, **default All**
  (the build changed it from Relevant); the form leads with the finding, the recommendation in the
  prototype's order (AIProposal → checklist ResolutionAction → ProposedValue), Attach for
  `DOC-EVIDENCE-MISSING` (to Uploads until UI-3's Attach form).
- **Processing** — Review mode: DPRCHK catalogue tags, Status default Open, the prototype's
  once-applied current-period default; the form carries Priority, Blocking, Current → Proposed,
  Target and the prior decision in Details. Uploaded mode: **Group by Entry / Document** (a document
  row shows its line count), a **Type** filter, View as a link to the document (the Document View
  form is UI-3).
- **Audit** — **Group by** (Document / Issue / Context / Entity / Item, **default Issue**),
  **Context** (the source table said as a context: Document Review Queue, Document Flags,
  Reconciliation Review, Bank Checks, Document Log), **Relevancy** (default Important on Findings,
  All on History), Status secondary; the grouped table shows Context · group · type chips ·
  a five-line preview of the finding · Lines, and its View opens **one form for the group** whose
  answer reaches every decidable line (`DecideManyAsync`) and says how many of the lines that is;
  the single form **leads with the recommendation** and keeps the finding in Details (norm, Audit
  section). Entity and item now come from the document the finding names.

*Proof.* `ReviewPagesCarryTheReviewHeaderWithTags` prerenders the three pages over the live corpus
and asserts the header marker, tag pills, filter bar, tiles, secondary filters where the page has
them, and that the inline expander is gone. Browser suite green; Sibyla review tests green
including the new one. The Submit path itself is the engine call the P2-11 tests already prove on a
synthetic owner (decide, apply, refuse when FDR-authored, refuse when not Open).

*Two Razor lessons, recorded so nobody pays for them twice.* (1) An implicit expression in a
component attribute that contains a lambda or a comma (`Options="@Options(_rows, r => r.X)"`) is
cut short by the Razor parser and reads as a malformed tag several lines away — always wrap it:
`Options="@(Options(_rows, r => r.X))"`. (2) A RenderFragment parameter named `Meta` collides with
the HTML void tag `<meta>` and its closing tag is rejected — the filter bar's fragment is now
`Trailing`.

*Not in this slice.* The `CaptureBlockingGate` count on a document's form (needs the DOCLOG id on
the row; with the Document View form in UI-3); Entity and Item links (no detail pages exist — they
render disabled, as the norm says); Escape-key handling for the modal; the interface build's
"Submit answer for AI processing" semantics beyond recording the input (the FDR's `/api/review-answer`
has no Apollo counterpart yet; the input is the human field the engine keeps).

**2026-09-03 — UI-1: the navigation re-cut and the header templates, Build 060200 @ 56126f8.**
Second slice of the screen review plan. The left navigation now **is** the interface build's
Navigation sheet, and every list page carries one of its two header templates through
`SibPageHeader`. Read-only throughout; no engine is wired yet (UI-2).

*Navigation.* Four sections, in the sheet's order, section names in English as the prototype keeps
them: **Workflow** — Overview, Documents, Uploads, Processing, Quality, Audit, Integration;
**Reconciliation** (new, before Management, open by default) — Overview, Statements, Payables,
Receivables, Match & Review; **Management** — Entities, Items, Classification, P&L;
**Administration** — five of the six bundles (Accounts, Governance, Banks, Checklists, Configuration
Center), then the platform pages. Deliberate absences, each recorded where it is decided:
- **Inventory** — no schema exists (INVHDR/INVDTL/INVCHK are "Waiting" in the build's Queues); a nav
  entry to modelled numbers would be a claim. Owner question in the screen review plan.
- **Project Control** — the prototype reads its own workbook's ROADMAPCTL/WORKPLANCTL/QUEUECTL;
  Apollo has no such source. Same question.
- **Uploads** stays: it is Apollo's intake and has no prototype counterpart.
- **Bank** left Management: Bank Statements are the document-type toggle inside Documents (Screen
  Specs), so the Documents link is active on both pages.
- `/sibyla/reviews` — the pre-Share orphan — is deleted, and the module descriptor's presentation
  list names the same routes as the layout (it had still pointed Audit at the orphan and
  Integration at `/erp`).

*Administration bundles.* Each bundle is one nav entry opening on its first view; the page heading
carries the bundle's **View dropdown** (`SibViewSelect`, the different-catalogues case of norm lines
145–147) and navigates between views, so every existing `/sibyla/admin/{view}` route stays valid and
the smoke map unchanged. Three views were missing and are added as governed read-only tables:
**Management Accounts** (MNGACC), **Processing Checklist** (DPRCHK) and **Audit Checklist**
(AUDCHK), the latter two with the same columns and the user-relevancy marking as the Quality
Checklist. Period Close, its own page, shows the Governance bundle's header and dropdown. Integration
Checklist stays reachable by route but out of any bundle — as in the prototype — until UI-3 replaces
it with ITGCHK readiness.

*Headers.* Processing, Quality and Audit use the **Review Header With Tags** (mode toggle top-right
via `SibModeToggle`; tag counters follow in UI-2); Documents / Bank Statements carry the document-type
toggle; Classification's three tabs became a **View dropdown** (they are different catalogues);
Statements (was *Bank Checks*), Payables (was *Payments*), Receivables and Match & Review (was
*Reconciliation*) carry the Reconciliation eyebrow and the sheet's names; Entities, Items, P&L,
Integration and Uploads use the **Operational Header**. The Search control on Quality and Processing
is labelled *Search*, no longer *Detail* (norm line 125).

*Reconciliation › Overview* (`/sibyla/reconciliation/overview`) is new: the Workflow Overview's
layout applied to settlement — four flow cards (Statements, Payables, Receivables, Match & Review),
Settlement Control filters (Company / Year / Period, defaults as the prototype: scoped company,
current year, all periods) and six indicators (Statements, Payables, Receivables, Matches, Open,
Closed). Periods are the statement's, the document's AccountPeriod for controls, the movement's for
matches and reviews; a review that names no movement has no company and counts only under *All* —
never guessed. The Open/Closed split uses the RECREV terminal set of P2-06.

*Proof.* Browser suite green after the change; `NavigationFollowsTheInterfaceBuild` asserts the
sections and options in the sheet's order (bundles by route, since labels render in PT) and the
absence of Inventory, Project Control, Bank Checks and the orphan; the smoke map gains the overview
and the three admin views; the section test now expects Reconciliation open by default.

*Not in this slice.* Filters on the list pages are still each page's own selects (the visibility rule
and *More filters* arrive with UI-2 page by page); the Match view and the View dropdown on Match &
Review (UI-4); the Entities Master / Balance dropdown (UI-5); a hamburger drawer for the sidenav on
small screens (the norm's answer; the horizontal strip remains the honest interim).

**2026-09-02 (late) — UI-0: the shared component layer, stamped Build 060200 @ 56126f8.**
The first slice of the screen review plan below. Apollo had no component layer — every page was inline
markup over `tokens.css`, with no modal, drawer, grid, filter-bar or tile component. This slice builds
the layer the Form Standard needs, re-bases the typography on the build's Branding sheet, and proves
both with tests. **No existing screen changed its markup**; they all inherit the typography (body
14 px / 1.42, table header 11 px semibold, rows 13 px regular, buttons 13 px medium, tiles 20/11) and
the calculated P&L rows drop their bold weight, as the prototype's `.calculated-row` did — the one
visible change on frozen screens, and the norm's ("do not bold ordinary line content").

*The components* (`src/Apollo.Web/Components/Shared`, imported everywhere through `_Imports.razor`):
- **`SibForm`** — one component for the seven form types (`SibFormType`: View, Edit, Create, Review,
  Attach, Associate, Details). The markup order **is** the standard: header (type eyebrow, business
  title, subtitle, status chip, Close) → Essentials (≤8 non-empty cards, or custom content) → Task →
  Related → action row (Details toggle first, `.primary` ordered last) → **the Details drawer after
  the actions**, `hidden` until toggled (open by default only for a Details form). Overlay click and
  Close both raise `OnClose`. `Mockup` labels the eyebrow *· Mockup* (norm "Exceptions and overrides").
- **`SibContextCards`** (the prototype's `summaryCards` / `reviewBusinessContext`: empty dropped, Max
  8) · **`SibDetailGrid`** (Details content: compact multi-column, empty hidden, values ≥48 chars or
  `Wide` take a full row) · **`SibReviewPanel`** (finding → AI recommendation with the prototype's
  fallback sentences; `HideFinding` for Audit; relabelled for Integration's Readiness / Outcome) ·
  **`SibRelatedLinks`** (Document / Entity / Item …; a null target renders **disabled and grey**, never
  dropped — norm line 110).
- **`SibFilterBar`** + **`SibFilterSelect`** + **`SibFilterSearch`** — the visibility rule (norm line
  123, prototype `setFilterVisibility`): a select with fewer than two values does not render **and
  resets its value to All** so it can never filter invisibly; `Forced` for the governed Company / Year
  / Period; `Secondary` hides below 1380 px behind *More filters*; Search is labelled Search at 280 px.
- **`SibTagToggles`** (label + count pill as separate spans so translation keeps the badge; zero-count
  catalogue tags stay) · **`SibModeToggle`** (modes of the same data) vs **`SibViewSelect`** (different
  catalogues, top-right of the heading) — the distinction of norm lines 145–147 · **`SibTiles`** (one
  visual line, scrolls before it wraps; wraps only ≤720 px) · **`SibPager`** · **`SibPageHeader`**
  (the two header templates, `data-header="operational|review"`) · **`SibNotice`** — the post-write
  pattern: an engine returns `null` on success or **its own sentence**, which the notice shows unchanged.
- **CSS** in `tokens.css`: the Branding scale as tokens (`--sib-fs-page-title` … `--sib-fs-pager`,
  `--sib-fw-medium`, `--sib-accent`), the component classes (mirroring the prototype's roles:
  `.modal-panel`→`.sib-modal-panel`, `.detail-header`→`.sib-form-header`, `summaryCards`→`.sib-cards`,
  `reviewDecisionPanel`→`.sib-review-*`, `.review-detail-drawer`→`.sib-drawer`, `.type-toggle-*`→
  `.sib-tag-*`, `.bundle-view-select`→`.sib-view-select`), and the mobile block: at ≤720 px wide or
  ≤640 px high a form is a full-viewport panel with sticky header, sticky action row, one column,
  tables scrolling inside.

*Proof.* A **component gallery** at `/sibyla/components` (platform operators only; linked under
Administration for superusers) renders every component on fixed sample values — it says so on the
page, and its Submit says "nothing is written here". Two tests:
- `RenderSmokeTests.ComponentGalleryRendersTheFormStandardInOrder` prerenders the gallery through the
  real pipeline and asserts, by markup markers (the page renders in PT, so labels are not used):
  header < essentials < task < related < actions < drawer; the drawer carries `hidden`; inside the
  task: review panel < recommendation field < textarea; the primary action after the action row opens
  and before the drawer; a one-value filter is absent; a tag carries a count; an unavailable related
  link is `aria-disabled`; the ninth essential and an empty detail field are dropped.
- `DesignTokenTests` reads `tokens.css` and asserts the Branding sheet: 11 colours, 13 typography
  sizes, 2 radii, the font stack, that ordinary and calculated rows are regular weight, and that the
  mobile block exists with `100dvh` and sticky header / actions. **Measured, not eyeballed.**
- The module-only user is denied `/sibyla/components` (added to the denial theory).
- Full Browser suite: **99 / 99 green** after the typography change (30 prerendered routes unchanged).

*Not in this slice.* No page was moved onto the layer (that is UI-1 for headers/filters and UI-2 for
the first forms); the ID-column action menu (UI-3, as the trial it is); a "Summary" collapse of the
indicators on very small screens (the norm allows it, nothing needs it yet); keyboard trap / Escape
handling in the modal (added with the first real form in UI-2, where it can be tested with a
circuit). One finding for the interface build: the prototype's mobile block keys on `.modal-panel`
descendants by class name per screen; Apollo keys on the form's own sections, so a new form type gets
the mobile behaviour for free.

**2026-09-02 (night) — Screen review plan: the D11 freeze is lifted, and the screens are revisited against the interface build at Stage 06 Round 02.**
**[DECIDED — owner, 2026-09-02]** *"Screens have changed, plan the review based on the changes."* That is the
instruction D11 waits for: the built screens are unfrozen for one revisit. **Assumption stated, not
ruled:** since no Share drop was cut, the contract for this revisit is the working prototype
`Interface/sibyla-registry-web` **at FDR commit `56126f8`** together with the build workbook at stage
`06.02.00` (*Stage 06 Round 02 — ID Action Menu Trial*) and `references/interface-norms.md` at the same
commit. Screens built in this revisit are stamped **Build 060200 @ 56126f8** instead of a Share name; D11
otherwise stands (built → frozen → revisited on instruction). If the owner prefers a Share drop as the
only contract, the stamp changes and nothing else does.

*What the revisit is measured against.* The prototype moved from 7,379 to 11,214 lines of `app.js`
between Share 260827 and now: 68 functions changed, 146 new, 2 removed. Three things changed in kind, not
degree, and they order the work:
1. **A Form Standard exists.** Seven form types (View, Edit, Create, Review, Attach, Associate,
   Details/Info) with one structure — header (action type, title, status, close) → *Essentials* (4–8
   fields: what, who, when, status, amount) → *Task section* → *Related context* (Document / Entity /
   Item / Quality / Audit / Reconciliation / Integration links, **disabled when unavailable**) → action
   row (primary last, destructive secondary) → **Details drawer after the actions, never below the
   form**. Review forms lead with *finding → AI recommendation → user input → Submit answer*, then
   *Details / Document / Discard*. Association forms (ENTITM, ENTBNK) are their own family: parent
   context → create/search row → linked-record table with row actions → raw rows behind Details. On
   ≤720 px wide or ≤640 px high every modal is a full-screen panel with sticky header and sticky
   action row, one column, Essentials and Finding/Recommendation/Input open, the rest collapsed.
2. **Navigation is re-cut.** Workflow gains Inventory; a **Reconciliation** section (Overview,
   Statements, Payables, Receivables, Match & Review) sits before Management, which shrinks to
   Entities, Items, Classification, P&L; Administration collapses from eleven entries to six bundles
   with a top-right *View* dropdown (Accounts, Governance, Banks, Checklists, Project Control,
   Configuration Center). Two header templates are named: *Operational Header* (title, optional View
   dropdown or Review/History toggle, filters, indicators, table) and *Review Header With Tags*
   (mode toggle, filters, tag counters, one-line indicators, table). Filters hide when they have fewer
   than two values (Company excepted); secondary filters go behind *More filters*; Search is labelled
   Search; every tag toggle carries a count pill; typography is re-based (body 14 px / 1.42; page title
   30, view title 22, section 16, table header 11 / rows 13; ordinary rows never bold).
3. **The screens now name the engines Apollo already built.** Processing/Quality/Audit review forms
   submit an answer (P2-11's `ReviewDecisionEngine`), Audit submits one answer to a group, Integration
   has Review/Ready/History with *Integrate selected* and *Retry failed line* (P2-18's `ErpPushEngine`),
   Entities/Items open association forms (P2-05's ENTITM minting), Payables/Receivables edit a control
   and add PAYDTL/RCVDTL events, Match & Review edits BNKREC and decides RECREV.

*What Apollo has to build on.* The inventory is unflattering and exact: **there is no shared component
layer** — every page is inline markup over `tokens.css`; no modal, no drawer, no grid, no filter-bar,
no tile component (the tokens for modal/drawer shadows and z-index exist, unused). Pages hold in-memory
lists and re-filter; only Documents and Bank page at the database. **No page calls any Sibyla
infrastructure service** except the quality checklist read; the engines from P2-05 … P2-19 are all
UI-less. The plumbing for a write from a circuit already exists: the scoped `ITenantContext` carries
`UserId`, each engine opens its own transaction and declares `app.human_decision` itself — a screen
only injects the service and awaits it, then reloads. One orphan remains: `/sibyla/reviews` is off the
sidenav but still referenced by the module descriptor and untested.

*The rule that shapes every write in this revisit.* Tenant #1 is FDR-authored until cutover C6 (the
sync-era rule): every engine refuses to mint or decide for an owner with `sync_run` rows. So on the
live corpus each Submit / Save / Integrate is **present, enabled, and refused with the engine's own
sentence** — never simulated, never silently disabled — and every write path is *proven* on a synthetic
native owner in the Browser suite. The prototype's session-only edits are not imitated (unchanged rule
from 2026-08-28). Where the prototype hard-codes a recommendation (Integration, Inventory), Apollo
shows the engine's actual finding or nothing.

*The slices, in dependency order.* Each ends with the render-smoke suite green, the stamp on the
screen, and its own entry here.

| Slice | Scope | Prototype reference | Apollo backing | Writes needed |
|---|---|---|---|---|
| **UI-0 Foundation** | The component layer the Form Standard needs: `SibForm` (seven types, the fixed structure, mobile full-screen), `SibDrawer` (Details: compact multi-column, empty fields hidden, long values full width), `SibContextCards` (≤8 non-empty essentials), `SibReviewPanel` (finding / recommendation), `SibTagToggles` (count pills, fixed zero-count catalogue tags), `SibFilterBar` (hide-under-two rule, Company forced, secondary filters ≤1380 px), `SibModeToggle` / `SibViewSelect`, `SibTiles` one-line with overflow scroll, `SibPager`; typography tokens re-based; `tokens.css` gains the prototype's new scale and the mobile modal rules; a post-write reload pattern; smoke-test helpers for opening a form and asserting drawer-after-actions. | `showProcessingReviewDetail`, `reviewBusinessContext`, `reviewDecisionPanel`, `.association-form`, `.modal-panel` media rules, `setFilterOptions` / `setFilterVisibility`, `.type-toggle-count` | none | none |
| **UI-1 Navigation and headers** | Sidenav re-cut exactly as §2 above (Uploads stays under Workflow: it is Apollo's intake, the prototype has none); Administration as six bundles with View dropdown; module descriptor nav list aligned; `/sibyla/reviews` removed; every list page moved to its header template; labels seeded from the current `PT_TRANSLATIONS`. Inventory is **not** built (see questions). | `index.html` nav, `renderActiveAdminSurface`, `adminBundleModeSelect` | existing pages | none |
| **UI-2 Review forms** | Processing (Review: DPRCHK tags, Search, once-applied period default; Uploaded: Group by Entry/Document, Type, Lines, View), Quality (default tag *All*, secondary Section/Origin/Flow, context cards), Audit (Group by default Issue, Context, Relevancy default Important, grouped table Context / Group / Type / Finding / Lines, grouped View submits one answer). One `SibForm type=Review` for all three: finding, recommendation (`AIProposal` → checklist `ResolutionAction` → `ProposedValue`), answer field, **Submit answer** → `DecideAsync` then `ApplyAsync` when the answer parses as a payload; Attach only for `DOC-EVIDENCE-MISSING`; Document/Entity/Item links disabled when unresolved. | `showQualityDetail`, `showAuditDetail`, `showAuditGroupDetail`, `renderGroupedAudit`, `processingTypeTags` | `ReviewDecisionEngine`, `CaptureBlockingGate`, `QualityService` | `DecideManyAsync` (one answer to a group, one transaction) added to the engine |
| **UI-3 Documents and Integration** | Documents: View form with Essentials (ID, DocID, Date, Due, Pay, Period, Company, Entity, Item, Class/Sub, Currency, LocalAmount) and raw FDCHDR/FDCDTL behind Details; the detail page becomes the form; Attach form over the platform intake's evidence with *Open attachment* disabled when none; **Manual Entry** as a Create form (DocID, DocDate, DueDate, Entity, Item, Amount, VAT, Total, optional attachment) → `DocumentEntryService.EnterAsync`. Integration: Review / Ready / History modes, Year, Object and Actions columns, Integration Review form (essentials → readiness or outcome → answer → *Integrate selected* / *Retry failed line* → Details with Flow / Checklist / Checks / Run); the ID-column action menu adopted **only here, as the trial the norm says it is**, Actions column kept. Integrate stays disabled with its reason until the live push is authorised. | `documentViewerEssentials`, `showDocumentSourceDetails`, `showManualEntryForm`, `showIntegrationDetail`, `integrationIdActionCell`, `activeIntegrationChecks` | `DocumentEntryService`, `ErpPushEngine` (Preflight / Enqueue / Restart / Abandon), ITGCHK rows | **depends on P2-18b**: readiness must come from the ITGCHK interpreter, not coded rules |
| **UI-4 Reconciliation section** | Overview (four flow cards, Settlement Control filters, six indicators); Statements = Bank Checks moved, plus a View form (finding = BNKCHK State, recommendation = Notes); Payables / Receivables: Review/History, Method and Coverage filters, status default *All*, control editor (Status, PayDate, PaidAmount, BankReference, MatchType, WaitReason, CoverageState, PaymentMethod, PayNote, Hold) with the PAYDTL / RCVDTL event ledger and an event Create/Edit form; Match & Review: View dropdown, Match (BNKREC columns and editor), Review (Open/Review/Closed, new columns incl. Relevancy, form leads with flagged issue → recommendation → decision → Save). | `renderReconciliationOverview`, `showBankCheckDetail`, `showManagementEditor`, `showManagementEventEditor`, `showReconciliationMatchEditor`, `showReconciliationEditor` | `ReviewDecisionEngine` (RECREV), `BankMovementResolutionService` | **new**: a settlement-event service (PAYDTL/RCVDTL is the ledger, PAYCTR/RCVCTR derived — the FDR ruling) and a manual BNKREC match service; both under the sync-era refusal |
| **UI-5 Management and Administration** | Entities: View dropdown Master / Balance (ENTBLC, read-only), Edit form (business fields only, codes in Details), Association forms for Items (create / delete ENTITM) and Accounts (ENTBNK); Items editor and Item Entities association; Classification View dropdown with summary cards; P&L Intercompany filter (Include / Exclude / Only, default Exclude, shown only when markers vary). Administration bundles: Accounts (SNCACC / MNGACC / COCACC), Governance (ENTROL, ITMCLS, Period Close with Open / Close / Reopen), Banks, Checklists (DPRCHK / DQLCHK / AUDCHK — Apollo's Type / Relevancy / Apollo-detection columns move here from SibylaAdmin), Configuration Center (User Access, DOCTYP, BNKMAT, EXCRAT, ENTPAY, ENTALS / ITMALS). Project Control: see questions. | `showEntityItems`, `showEntityAccounts`, `entityBalanceRows`, `isIntercompanyRow`, `renderAdminBundle`, `renderConfigurationCatalogue` | ENTITM/ENTALS minting via `CodeIssuer`, `LearningStore.LearnAliasAsync`, `MasterDataConsistencyService` (checks) | **new**: master-data edit service, entity-item association service (minting under P2-05's constraints), declaration write paths for the catalogues and `scope_closure` (Period Close); all under the sync-era refusal |

*Acceptance for the whole revisit.* Every route in the new navigation prerenders green in the Browser
suite (the map grows from 30 routes to the 20 screens × their modes, plus `/documents` and the document
form, which are uncovered today); every form opens through the same component and passes one structural
assertion (essentials before task, drawer after actions); every write is exercised once on a synthetic
native owner and once refused on tenant #1 with the engine's sentence; a mobile prerender at 720 px
shows the full-screen panel; the PT/EN chrome carries the new labels; the typography tokens match the
Branding sheet (sizes, weights, colours) — measured, not eyeballed, by a token test.

*What is deliberately not adopted, and reported back to the interface build as findings:* the
Inventory heuristics (quantities modelled from FDCDTL text, warehouse "Main (assumed)", stock from a
hash — a mockup by its own footer); the hard-coded Integration recommendation sentences; the
Receivables default status *Due / Outstanding* that the exact-match filter can no longer honour; the
Parameters rows still saying Quality default *Relevant* and Audit filter *Source*; the unreachable
`adminIntegrationChecklist` view; the absolute local paths in the current `registry-data.json` summary.

*Dependencies on the day's other findings.* UI-3's readiness column and UI-5's line editing both wait
on **P2-18b** (ITGCHK interpreter, VAT rate list) and **P2-05b** (Quantity / UnitPrice / VATRate on
FDCDTL) — the form fields exist in the prototype, and Apollo must not show a quotient where the FDR
shows a captured rate. Order therefore: **UI-0 → UI-1 → UI-2 → P2-05b/P2-18b → UI-3 → UI-4 → UI-5**.
UI-0 through UI-2 need nothing from the owner and can start now.

*Owner questions raised by this plan (none blocks UI-0 … UI-2):*
- **Inventory:** build the mockup page as the prototype does (labelled *schema pending*, heuristic
  rows) or leave the nav entry out until INVHDR / INVDTL / INVCHK exist? Recommendation: leave it out;
  a page of modelled numbers under the Apollo brand is a claim.
- **Project Control:** the prototype reads its own workbook's ROADMAPCTL / WORKPLANCTL / QUEUECTL. Apollo
  has no such sheets; should it show this plan document's slices, the FDR's SIB* sheets (not synced), or
  nothing yet?
- **Contract:** confirm the Build 060200 @ 56126f8 stamp, or cut a Share 260902 for the same content.
- **Cutover:** the review forms will be refused on tenant #1 until C6. Is a demonstration on a native
  owner acceptable as the acceptance evidence for UI-2, or is C6 to be scheduled first?

**2026-09-02 (evening) — Review of the day's thirteen entries against the FDR as it now stands: Stage 16 Rounds 8–17, the data delta, and the interface build at Stage 06 Round 02.**
The day's entries (P2-05 … P2-19 below) were written against the FDR corpus as synced on the morning
of 2026-09-02. The FDR moved during the day: HEAD is now `56126f8` (2026-09-02 10:17, *Complete Stage 16
controls and roadmap handoff*), which closes Rounds 8–17 of Stage 16. The interface build moved too
(`Interface/` at Stage 06 Round 02, 2026-09-02) — but **no new Share drop was cut**: `Interface/Share`
still ends at *Share 260827*, so the D11 stamp on Apollo's screens remains valid. This entry reads every
change through three lenses — process (Specs/Scripts), data (Editor/Data), interface (build workbook and
prototype) — and says, per entry, what is now stale, what is contradicted, and what is newly required.
Nothing in the code was changed by this review; the corpus **was** re-synced at its end (run
`73c60a31`, green: delta docflg +6, recrev +2 new / 2 updated, bnkmov 2 updated, ctrl_override +6,
G4 cent-exact, G8 1,275/1,277, G12 unchanged at 2/7/12/0/0).

*Data lens — the one-round lag is closed.* The morning sync predated `0e9097d…56126f8`; the delta was
DOCLOG `LG001872`, flag `FL0004232`, RECREV `RR01804`/`RR01805` re-raised with `|r3`/`|r4` ItemKey
suffixes, small ENTPAY/BNKMOV edits. All landed idempotently; no quarantine. One new G11 note to record:
OWNCPN no longer declares *Silêncios Atraentes* live while Apollo still maps it — a tenancy question for
the owner, not a sync defect. One publication finding: the root `Invoice_Registry.xlsx` (2026-09-01,
15 FDCDTL columns, 1,808 lines) lags the pipeline render in `_sandbox_s12r02/` (18 columns, 1,861 lines).
Apollo follows the JSON layer (D15) and is unaffected, but any G10 witness pointed at the root workbook
would be witnessing a stale file.

*Process lens — what Rounds 8–17 changed, and which entries it touches.*

- **FDCDTL gained three captured columns: `Quantity`, `UnitPrice`, `VATRate` (Rounds 9–11).** The rulings
  that bind them are §22.6 *a blank is a fact about the document, and a rate is not a quotient*: a blank
  quantity is a fact, a zero quantity is a fact (a metered allowance not exceeded), a stored unit price is
  **net** (a supermarket receipt prints a gross one — lesson recorded), and a VAT rate is **captured or
  snapped, never `VAT/Net`**. `snap_vat_rate(net, vat, total, country)` answers with one of five bases —
  *snapped* (exactly one declared rate reproduces both amounts), *ambiguous*, *incoherent*, *unresolved*,
  *undeclared* — against `Specs/vat_rates.json` (22 countries, 93 dated rates; §22.7 *the rate list is
  data, and the registry checks it*, `check_vat_rates.py` proposes and never writes). Measured at source:
  rate identified for 1,110 of 1,267 in-scope lines, refused for 126 (98 unresolved, 28 ambiguous);
  quantity and unit price captured on 24 lines; the JSON layer already carries the keys (the generated
  mirror's 594 rows have them, all blank — honestly).
  **P2-05 is stale:** Apollo's `Fdcdtl` has none of the three columns and the sync reads only
  `NetAmount`/`VATAmount`/`TotalAmount`; `DocumentEntryDraft` cannot carry them. They must be added as
  nullable columns with **no default** (a blank stays blank), synced under a bumped transform, and the
  line-completeness CHECKs are unaffected. **P2-18 / P2-21 are contradicted:** `ErpPushEngine` preflight
  derives the rate as `Math.Round(VatAmount / NetAmount × 100)` (ErpPushEngine.cs:101) — the exact
  quotient the FDR ruled wrong — and sends quantity 1 / unit price = NetAmount without labelling it. The
  FDR's own reading is *captured wins; else snap; a refusal is a blocker, not a rate*, with the
  `(1, NetAmount)` default stated on the payload row, zero-net component lines collapsed into the nearest
  **preceding** priced line (`collapse_zero_lines` — a layout heuristic, labelled as such), and the
  supplier country sent as ISO alpha-2 (`iso_alpha2`).
- **Integration readiness is DATA with one interpreter (§30.3, Rounds 4–8).** ITGCHK rows carry a
  `Predicate` in a closed grammar (`INTEGRATION_PREDICATES`); an unknown predicate is a hard error;
  reconciliation is **not** a precondition (the builder refuses any requirement reaching into BNKREC /
  RECREV / BNKMOV); a deliberate non-integration is a declared row — `integration_exclusions.json`, 14
  rows, wording *Out of Scope (declared)* (the 14 zero-value documents, owner ruling 2026-09-01) — and a
  record that will never be sent must stop being asked about. Measured: 497 evaluated, 54 ready, 443
  blocked (434 waiting only on the supplier flow — QX35 is the one measure expected non-zero), 0 sent.
  **P2-18 / P2-21 are stale in approach:** Apollo syncs the 12 ITGCHK rows (G12) but `Preflight`
  evaluates none of them — its problems are coded, which is the defect §30.3 names ("the moment an
  evaluator carries its own copy of the rules, the checklist is decorative and the two drift"). The
  exclusions list is not synced at all, so Apollo would enqueue the 14 declared-out documents. Preflight
  should become an interpreter over the synced ITGCHK predicates (unknown predicate → refuse, never
  satisfied), the exclusions should sync as a declaration table that refuses `Enqueue` with the FDR's
  wording, and preflight must be shown never to consult reconciliation tables. The known-limitations
  ruling stands for Apollo too: *nothing has been sent; every statement about a send is design, not
  observed behaviour; treat the first real rejection as a missing ITGCHK row.*
- **RECREV: re-raise keys and the one-write rule (Round 9).** A re-raised item now carries an `|rN`
  suffix on its ItemKey (`RR01804`/`RR01805`), and `set_recrev_status()` is the only approved writer of
  Status because `Stage` is derived from it and two items had drifted (*two fields that must agree are one
  write*, §19.11). **P2-06 / P2-11 partially stale:** Apollo's RECREV item key (`BNKMOV|{BmCode}|unmatched-line`)
  has no re-raise generation, so a second raise after a decision would collide with, or silently reuse,
  the first key instead of reading as round N. Apollo's `Recrev` mirrors `Status` only (no `Stage`
  column), so the one-write defect does not reproduce here — but `Stage` is not mirrored either, and the
  Match & Review screen the interface build specifies filters on it.
- **Identity: DOCEFL key collision (Round 8, redeployed Round 10).** `EF0000066`/`EF0000067` collided;
  the fix is a uniqueness check in the guard, a refusal in `assign_permanent_code`, and a corrective
  step — and the lesson *a deployment check catches divergence, not absence* (C27 compared two equally
  stale trees). Apollo does not sync DOCEFL, so nothing to re-check in data; the lesson applies to
  Apollo's own migrations-vs-Preview discipline, which `migrate.ps1` already satisfies by applying both
  in one pass (owner ruling 2026-09-01).
- **Bank of Ireland (Round 9):** a fixed-width statement that never prints a sign and prints the balance
  once a day — the running balance is computed and asserted against every printed one. No Apollo
  entry touches this yet (statement ingestion is Phase 3); recorded so the BNKCHK design does not assume a
  per-line printed balance.
- **New measures:** C98 (quantity/unit-price honesty), C99 (rate provenance), QX35/QX36 (readiness /
  blocked), QX37/QX38 (rate refusals), C27 (deployment divergence). **P2-13 / P2-14 stale:**
  `QualityService` tracks none of them and its `NotTracked` list does not name them; the honesty rule of
  that entry (*a measure the source defines and Apollo cannot compute is shown as not tracked, never as
  zero*) requires at least the not-tracked rows now.
- **Capture: a batch must finish its own inbox (Round 9).** Byte-identical copies of already-booked
  documents are swept; a copy that *differs* is left with a loud line — *a reissue is not a re-sync*.
  P2-10's `RecordFailureAsync` categories do not distinguish *identical duplicate (swept)* from
  *differing duplicate (held as evidence)*; add the distinction when the Apollo-native capture path is
  exercised, not before.
- **SKILL.md restructured thematically (§19–§30).** Section numbers cited in earlier entries by round
  (e.g. "§37 integration") now live under §30; the citations in the P2-18 entry below should be read as
  §30.1–30.5. Wording, not substance.

*Interface lens — the norms moved, the Share did not.* The build workbook (Stage 06 Round 02) now
carries a **Form Standard** (View, Edit, Create, Review, Attach, Associate, Details/Inspect), an
**Association Form Standard** (Entity Items, Entity Accounts, Item Entities open as association forms),
mobile full-screen task panels on small or short viewports, review popups that lead with *finding →
recommendation → user input* and close with *Submit answer / Document / Discard*, and a trial in which
the **ID column is the row's action trigger** (Integration). Navigation is 23 options in four sections
— Workflow (Overview, Documents, Processing, Quality, Inventory, Audit, Integration), Reconciliation
(Overview, Statements, Payables, Receivables, Match & Review), Management (Entities, Items,
Classification, P&L), Administration (Accounts, Governance, Banks, Checklists, Project Control,
Configuration Center) — with 20 screen specs. Against Apollo's screens (`/sibyla/{documents,entities,
bank,reviews}`, `/admin/audit`, SibylaAdmin) built on Share 260827:
- **P2-11** has a decide/apply engine and no form; the spec now says exactly what that form is (a Review
  form on Processing / Quality / Audit, one answer per finding, grouped View submitting one answer for a
  group).
- **P2-17**'s audit view is a flat log; the spec's *Audit Review* has a Findings/History toggle, one-line
  metrics, Context grouping and popup detail — and it is where **P2-16**'s flag lifecycle and backlog
  surface.
- **P2-13 / P2-14** put the checklist type/relevancy/Apollo-detection columns on SibylaAdmin; the spec
  splits them — catalogues under *Administration › Checklists*, the working queue under *Workflow ›
  Quality* with Findings/History.
- **P2-06 / P2-08**'s entity–item pairs and IBANs are the spec's Association forms (Entities › Items /
  Accounts), not columns on a list.
Per D11 none of this reopens the screens until the owner cuts a new Share drop. The recommendation is
to cut **Share 260902** from the current build so Apollo adopts the Form Standard in one pass — the
review form alone unblocks P2-11's UI, and the ID-as-action trial is exactly the interaction the
Integrate button needs when the live push is authorised.

*Commercial lens — one naming ruling.* `Sibyla Argus` is the internal name of the Finance Automation
module (naming memo 2026-09-01; Sibyla is the umbrella; Medusa and Calypso keep theirs). External use is
undecided. Apollo's module seam registers the module as `sibyla`; no identifier is renamed on an internal
ruling with the external decision open — recorded here so the display name can follow when it is taken.

**What this review changes in the plan.** Follow-up slices, in dependency order, none started:
- **P2-05b** — FDCDTL `Quantity`/`UnitPrice`/`VATRate` as nullable, no-default columns; sync under a
  bumped transform; `DocumentEntryDraft` carries them; unit price documented as net.
- **P2-18b** — `vat_rates` and `integration_exclusions` synced as declaration tables; `snap_vat_rate`
  transcribed with its five bases; preflight becomes an interpreter over ITGCHK predicates (unknown →
  refuse); the `(1, NetAmount)` default and the collapse rule labelled on the payload; ISO alpha-2
  country; a test proving preflight never reads BNKREC/RECREV/BNKMOV. Supersedes the preflight half of
  the P2-18 entry below; the state machine half stands.
- **P2-06b / P2-11b** — `|rN` re-raise generation on Apollo-raised RECREV keys; mirror `Stage` (derived,
  written with Status in one place).
- **P2-13b** — C98/C99/C27/QX35–QX38 as tracked or not-tracked rows.
- **Screens** — wait for Share 260902; then P2-11 review form, Audit Review, Checklists split,
  Association forms, in one slice under D11.
Owner questions raised by this review: the Silêncios Atraentes tenancy mapping; whether Apollo's screens
should wait for a Share drop or take the build workbook as the contract; the external use of *Argus*.

**2026-09-02 — P2-18 / P2-19 / P2-21 / P2-22 (logic): the ERPLDG state machine, proven on a scripted provider; the live run is the owner's.**
The criteria (14 §3.3): *P2-18 — purchase push to CEGID_IZIBIZI through the full state machine; a
document reaches COMMITTED exactly once; killing the worker between draft-create and finalize
recovers by adopting the draft (drill T9). P2-19 — a deterministic rejection lands in REJECTED, is
never auto-retried, shows the provider error verbatim, and a human edit starts a new audited
attempt-chain; an ambiguous outcome always re-enters via FindExisting. P2-21 — master-data
preflight fails Apollo-side first with an actionable message. P2-22 — per-company flow
configuration; status, errors and retry visible per tenant; sales-side push flagged off.*
The design is discovery 08 §4 (states, ledger, backoff ladder) and §3.1 (the contract).

**The contract.** `IErpPushProvider` adds the push half to `IErpProvider` — draft, lines, finalize,
status — each step reported separately as an `ErpStepResult` whose failure is **classified**
(retryable · auth · deterministic · unknown · ambiguous) so the ledger, not the provider code,
decides the next state. `ErpExistingDocument` gained `IsFinalized`: our own *draft* found on the
provider side is resumed, never adopted as committed. `IzibiziErpProvider` implements the three
documented Cloudware requests (`POST commercial_purchases_documents` → `POST …_document_lines` →
`PATCH … status 1`) under the fiscal-year session and header discipline the connection test proved
live on 2026-09-01, classifying 401 / 429 / 5xx / 4xx / timeout-after-send as §4.3 says.
**Not live-verified**: the endpoints and field names follow the GitBook; the first live run is an
owner-supervised session against a document that can be voided.

**`ErpPushEngine`** (module; ERPLDG is a module table, so its seat is the app role's, not the
worker's). `PreflightAsync` (P2-21) refuses Apollo-side, by name, what the provider would refuse:
a flow not enabled on the connection (sales says "flagged off, the vendor answers are still
open"), a document not yet entered, a receivable on the purchase flow, an open blocking flag, a
supplier with no real NIF ("Apollo never sends the consumidor-final default"), a line whose item
has no provider code on this connection ("map it under the ERP configuration"), a non-EUR document
with no rate. `EnqueueAsync` opens **one chain per (target, flow, document)** — the unique index
makes a second impossible; the idempotency key is `{Provider}:{Company}:{Flow}:{DoclogId}`, the
external reference `APOLLO:{flow}:{doclog}`. `ExecuteAsync`: the attempt is on record before the
first call; **FindExisting is always the first act** — our finalized document is adopted
(recovery), our draft is resumed, somebody else's document is linked as `AlreadyInErp` for a person
to confirm, ambiguity is manual review; then draft → lines → finalize with **every persistence
point committed before the next provider call**. A deterministic refusal is `Rejected` with the
provider's words and is not executable again; transient failures back off on the ladder (1 m ·
5 m · 30 m · 2 h · 12 h) and dead-letter on the fifth; an unclassified error is retried twice; an
auth failure suspends; a timeout after send is ambiguous and re-enters through FindExisting. The
operator actions are recorded human decisions: `RestartAsync` starts a new audited chain on the
same row after a human edit (the ladder counts from the restart, a draft on record is still
resumed), `ConfirmLinkAsync` commits against a linked document without creating anything,
`AbandonAsync`. `ErpAdminService.ResolveMaterialAsync` materialises settings, enabled flows and
the item-code map (`SettingsJson.itemCodes`) for one call, never persisting them.

**Two engine defects the drills caught before any provider did**: the draft id was first persisted
inside the attempt's transaction, so a crash rolled it back — every step now commits; and a
restart reused an attempt number — the chain now counts from its restart.

**Proof: 8 tests (Sibyla suite 93 → 101) over a scripted Cloudware-shaped provider:** preflight
naming the unmapped item and the flagged-off sales flow, and an unknown supplier; **a document
reaching COMMITTED exactly once**, a second enqueue returning the same chain and a second execution
creating nothing more; **drill T9** — the worker dies inside draft-create, before the id reached
the ledger (the hardest window): the recovered attempt finds the unfinalized draft by our
reference, resumes lines and finalize, and the provider shows one draft and one document; a
deterministic rejection kept verbatim, not executable again, then a person's restart audited on
the attempt log and the document committed; a timeout after send re-entering through FindExisting
and adopting the finalized document; a foreign match linked and confirmed by a person, an ambiguous
one sent to manual review, nothing created; transient failures walking the ladder and
dead-lettering on the fifth. Full suite: isolation 41, Sibyla 101, platform 96, browser 68.

**What is not claimed:** the live iziBizi commit (P2-18's "the provider shows exactly one
document" on the real tenant), the ROSE flow (P2-20 — no test subscription on file), the queue
job that drives `ExecuteAsync` (the worker's role cannot write ERPLDG; the executor runs app-side,
and the Integration screen's *Integrate* button stays disabled until the live run), the
per-target circuit breaker, and `QueryStatusAsync` reconciliation on a schedule. The A3/D-67 flow
matrix is honoured by `EnabledFlows` on the connection, with sales refused by the engine itself.

**2026-09-02 — P2-16: OFDGAP as the governed detector with decision memory, the flag lifecycle, the backlog.**
The criterion (14 §3.3): *AUDCHK-family surfaces work: second looks, unmatched backlog, flag
lifecycle/waivers, coverage checks; OFDGAP runs as the governed detector with decision memory — a
dismissed gap never returns; an arriving invoice auto-resolves its gap (05 §5).* The FDR's detector
is `build_document_gaps.py` (Stage 8 R12, scope widened Stage 10 R7 to "monthly by meaning",
receivable side added Stage 12 R6), and its memory is DOCEFL rule EF0000055's permanent DOCFLG
identity per (provider, period) — on the corpus 17 Open, 23 Rejected (dismissed), 3 Resolved.

**`GapDetector`** transcribes the algorithm as a pure function of the live documents: every
Provider or Customer whose InvoiceFrequency *means* monthly (Confirmed when it reads exactly
"Monthly", Assumed when the value says so itself — the caution carried in the output, not hidden
in the scope), providers on the payable side and customers on the receivable side; walk the months
between the entity's first and last AccountPeriod; an interior month with no document is a
candidate unless some document's own DateDoc falls in that month and merely bucketed elsewhere
under the day-of-month rule; nothing before the first or after the last document is ever a gap;
fewer than two periods is no gap at all. **Measured on tenant #1: Apollo finds exactly the FDR's 40
gaps, 31 payable and 9 receivable — the same rows.** `RunAsync` adds the memory: a new candidate
opens an OFDGAP row with its DOCFLG instance (FL code minted, rule EF0000055, AUDCHK,
`GAP-MONTHLY-INVOICE-MISSING`, detected by `apollo:gap-detector`); one a person dismissed
(Rejected) is **kept dismissed and never reopened**; one that stopped being a gap — the invoice
arrived — is **auto-resolved** with the evidence on the instance; one resolved earlier that is a
gap again (a document retired) is reopened honestly; and an owner with sync runs gets its
candidates reported and nothing written. A missing EF0000055 in the catalogue is reported as a
coverage gap, not raised as a finding without its rule.

**`FlagLifecycleService`** is the lifecycle a person drives — Resolve with evidence, Dismiss
(Rejected, the decision memory), Waive — each a recorded human decision on an open instance of
Apollo's own; a **waiver is a finding** (AUDCHK `AUD-FLAG-WAIVED`, "flag closed by waiver rather
than by resolution", recorded as an Annotation for the audit's second look), and the linked
OFDGAP row mirrors the state. **`AuditBacklogService`** gives the screens one set of numbers:
unmatched movements and those over 90 days (AUD-MOVEMENT-UNMATCHED / AUD-UNMATCHED-AGING), open
and open-blocking instances, waivers, gaps open and dismissed.

**Proof: 6 tests (Sibyla suite 87 → 93):** the detector finds the interior gap and not the month
the cross-check explains, the receivable gap against an assumed frequency, and leaves the quarterly
and the single-document entities alone; gaps open with their flags and a second run opens nothing;
the arriving invoice auto-resolves with evidence; **a dismissed gap stays dismissed across runs**
with no second row; a waiver needs a person and a reason, opens the AUDCHK finding, and a closed
instance is not closed again; the sync-era owner gets candidates and no writes; and on the corpus
**every FDR gap is among Apollo's candidates**. Full suite: isolation 41, Sibyla 93, platform 96,
browser 68.

**Not in this slice:** the declared relationship end (`RelationshipEnded`, Stage 13 R31 — a
declaration Apollo's ENTPAY does not carry yet; it can only make Apollo find fewer gaps, never
other ones), the Audit screen's own tiles for the backlog (it follows the prototype's `auditRows`,
D11), and the second-look items a detector re-raises on a resolved flag.

**2026-09-02 — P2-15: processing evidence, per document, per attempt.**
The criterion (14 §3.3): *every automated document decision is traceable: processing evidence
(input hash, skill hash, CLI version, raw agent output, validation result) is retrievable per
document (10 §7.4).* The worker already kept the raw output and the validated result as files
beside the staged original; nothing recorded which skill produced them, under which CLI, or why an
attempt failed, and nothing showed any of it.

**`ProcessingEvidence`** is one attempt's record: the input's SHA-256 as registered at intake; the
**skill hash** — SHA-256 of the prompt as sent with the per-job document path normalised to a
token, plus the contract version, so it is stable across jobs and changes exactly when the prompt
or the contract does; the CLI path after shim resolution and its `--version`; exit code and
duration; the raw stdout's SHA-256 and where the verbatim copy is kept; the validation result and
error; and the outcome. `ClaudeDocumentProcessor` builds it on **every** path — CLI failure,
envelope failure, contract failure, success — and the worker appends it to `docint.evidence_json`
(a jsonb array, one element per attempt) whatever the outcome, so a dead-lettered document keeps
the trail of every attempt that failed it. The CLI became a seam (`IClaudeCli`, the real
`ClaudeCliProcess` behind it) so the processor is testable without spawning Claude. The review
screen (`/documents`) shows the attempts under each document: when, outcome, contract, skill hash,
CLI version, output hash and location, validation.

**Proof: 4 tests (platform suite 92 → 96)** through the real processor, sandbox, evidence files and
contract validation with a canned CLI: a success leaves the full record, the skill hash equals the
processor's own, the raw output is at the path the evidence names, and the prompt the CLI saw named
the sandbox copy while the hash did not; a hostile answer with a smuggled field fails the contract
and leaves the reason and the raw hash but no result; a CLI failure is evidence too; the record
round-trips as an array. Migration `IntakeProcessingEvidence` on Main and Preview. Full suite:
isolation 41, Sibyla 87, platform 96, browser 68 (the harness-concurrency flake failed once under
the parallel run and passed alone, as before).

**Not in this slice:** the skill hash of a *packaged* skill tree at a pinned commit (10 §6.5 —
the worker still carries its prompt inline; when the FDR skills are packaged, the hash covers the
tree), and the evidence of the module-side decisions (entry, ladder, capture), which land in
DOCRQE/RECREV items and the P2-17 audit trail rather than on the intake row.

**2026-09-02 — P2-17: the audit log is append-only, and it has a view.**
The criterion (14 §3.3): *admin/access audit views exist; AUDLOG captures admin actions, access
changes, and automated document decisions, append-only for the admin plane (13 G20 direction).*
Measured first: the application roles (`apollo_app`, `apollo_worker`) held UPDATE and DELETE on
`audlog`, and no screen showed it — the trail was written faithfully and could be rewritten by the
same session that wrote it.

**Append-only, twice** (`AuditLogAppendOnly`, platform context, Main + Preview): UPDATE, DELETE
and TRUNCATE are revoked from every application role that exists on the database (the role names
differ between main and preview, so the revocation is applied to whichever of the known roles are
present), and a trigger refuses both for any role that does not bypass RLS — so a grant slipped
back in would still be caught. The migrator keeps the right, for restores and for the suites' own
cleanup: it is not a plane an application ever runs as. Two AI-harness test helpers that purged
their audit rows through the app-role context now purge as the migrator, which is the only
correct seat for that.

**The view**: `/admin/audit` (organization administration, `AUDLOG` panel) lists the tenant's
trail newest first — when, action key, actor, target, detail — filtered by area (the action key's
first segment: company · storage · erp · document · user …) and actor; operator rows (owner-less)
appear only under the `platform_audit_access` policy, which the interceptor's GUC decides. Access
changes are what the `user.*` and `company.*` keys record; the Sibyla *User Access* sub-view shows
the standing grants themselves.

**Proof: 2 isolation tests (suite 39 → 41) and the render smoke (67 → 68):** the app role appends
and reads its row and is refused UPDATE and DELETE with `insufficient_privilege`; the trigger is on
the table and the migrator may still remove a row. Full suite: isolation 41, Sibyla 87, platform
92, browser 68.

**Not in this slice:** insider controls beyond append-only (G20's separation of the operator from
the auditor is Phase 3's P3-05), and the processing-evidence rows that P2-15 adds to the trail.

**2026-09-02 — P2-13 / P2-14: the quality surfaces' two honesty rules.**
The criteria (14 §3.3): *P2-13 — DQLCHK-family surfaces as tenant-scoped screens from the latest
Share (done 2026-08-31, D11 stamp on record); ranking derived from IssueType; protected
UserRelevancy respected; controls that are not implemented surface honestly as `Not Detected` —
never as green.* *P2-14 — the dashboard's measures carry their declared failure direction and named
populations (05 §5); "not tracked" renders as not-tracked, never as zero.*

**`QualityService`** owns both. `ChecklistAsync` returns the governed DQLCHK catalogue ranked by
IssueType (Inconsistent · Missing · Needs Input · Confirmation) then severity, a person's
`UserRelevancy` outranking the catalogue's `Relevancy` and marked as theirs, and — the honest part
— each control's **Apollo detection state**: `Detected` only for the five issue keys an Apollo
service actually raises (`ITM-ENTITM-MISSING`, `ENTRY-ITEM-MISSING` from P2-05; `DOC-TYPE-AMBIGUOUS`,
`DOC-ORIGINCLASS-DEFAULTED`, `DOC-CAPTURE-FAILED` from P2-10), `Not Detected` for the other 28 the
FDR reports as Live — Apollo says so about itself, not about the FDR, whose own state is carried
beside it. The list of detectors is one static set in the service: adding a detector means adding
its key there, and the screen reads nothing else, so a control cannot drift to green by
implication. `MeasuresAsync` assembles MD01–MD06 (P2-08), the AccountPeriod control's four numbers
(P2-09: in-scope unexplained *must read 0*, out-of-scope *reported not enforced*, documented
*expected large, falling is a warning*, stale stamps *must read 0*), and three counterparty-ladder
counts (provisional names, honest blanks, hammered movements — informational), each with its
population and direction — and lists what Apollo does **not** compute as `Tracked = false` with no
value: QB17 and QD06 (the FDR's hammer-coverage measures) and CaptureQuality. Administration ›
Quality Checklist now shows Type, Relevancy (with "(user)" when a person set it) and *Apollo
detection* per control, and a Measures table beneath, where an untracked measure reads "Not
tracked".

**Proof: 3 tests (Sibyla suite 84 → 87)** plus the render smoke: the checklist ranks Inconsistent
before Missing before Needs Input, marks the one implemented key Detected and the rest Not Detected
(the FDR's Live state kept beside it), and shows a person's Not Relevant as theirs; every tracked
measure carries a value, a direction and a population, and QB17 / QD06 / CQ01 carry no value at
all; on tenant #1 exactly the implemented keys read Detected, the corpus's own UserRelevancy
override (`ITM-COSTCENTRE-MISSING`, Not Relevant) is respected, and Live-but-Not-Detected controls
exist. Full suite: isolation 39, Sibyla 87, platform 92, browser 67.

**Not in this slice:** the Findings/History occurrence view's own ranking column (it follows the
prototype's `isRelevantQualityIssue`, unchanged), and a measures history — a falling AP03 is only a
warning once there is a previous value to fall from (P2-15 evidence persistence is the place).

**2026-09-02 — P2-12: provenance-gated learning — one write service, and the crafted-input suite (RB7).**
The criterion (14 §3.3): *identity rulings land as ENTALS aliases only on acceptance; ENTBNK IBAN
learning only under the unanimity gate; E4 inferences propose-only (never write master data);
own-account IBANs never learned as a vendor's; the crafted-invoice suite (13 G6) shows zero
disallowed learning writes — RB7 green.* The rules are 05 §4.2/§4.3 and the threat assessment's L4
("a single learning path that forgets the gate is a poisoning hole"); the FDR's mechanisms are
`mine_entity_aliases.py` (tier 2: the name the rank-5 gate refused becomes a proposal), the Stage
11 Round 32 unanimity gate and the Stage 10 Round 6 own-account guard in `build_entbnk.py`.

**`LearningStore`** is the one place ENTALS and ENTBNK are written by anything other than the sync.
`ProposeAliasAsync` is the miner's tier 2: `BankMovementResolutionService` calls it whenever a
blank names a payee or payer the registry does not know, and it opens a RECREV *Alias Proposal*
with the text **verbatim** as the subject and "Nothing was auto-applied" in the proposal.
`LearnAliasAsync` is the only path to an ENTALS row: it runs from the apply engine's new
`ENTALS.CodeName=<EntityCode>` target (DOCRQE or RECREV), needs a person, refuses an own company
as the target (that is rank 0's business), and names the ruling on the row. `LearnIbansAsync`
learns from `InferenceSource` only — ranks 0–3, never a provisional name, never a hammered row,
never a tombstone — counts every claim across the population **before** writing anything, refuses
and reports an account two counterparties claim, skips an own account outright, and writes the
unanimity evidence on each learned IBAN. Nothing in the store writes ENTITM or ENTMST: an
inference proposes, and P2-11's engine is the only way a proposal becomes master data. Every write
is announced on the async flow, which is what makes RB7 assertable.

**Provenance at the database** (`SibylaLearningProvenance`, Main + Preview): `entbnk_iban` gains
`learned_evidence`, and a row must carry either the sync run that imported it or the evidence it
was learned on; an Apollo-native ENTALS row must carry its flag. The corpus's 16 IBANs and 32
aliases all carry theirs. And the sync-era rule holds here too: an owner with sync runs is the
FDR's, and the store proposes nothing and writes nothing beside a live sync. `CodeIssuer` now
reuses a counter it created earlier in the same unit of work — two mints in one transaction drew
the same first number before.

**Proof: 6 tests (Sibyla suite 78 → 84), the G6 crafted suite among them.** A population holding
every bait the threat assessment names — an IBAN under a rank-5 name, one under a hammered row,
one claimed by two entities, our own account under a vendor's name, one on a tombstone — and one
honest, unanimous confirmation produces **exactly one write**, the honest one, with its evidence;
the contested account is reported by name, the own account counted as skipped, and the observer
saw the population come through `InferenceSource` with the hammer, provisional and retired
exclusions on. Learning is idempotent and a bare IBAN row is refused by constraint name. A blank
naming an unknown payee raises an alias proposal and no alias; acceptance through the engine
writes exactly one announced alias naming the ruling, and the movement then resolves through it at
rank 2; prose is not an acceptance; an alias needs a person and never targets an own company, and
a bare alias row is refused by constraint name. Tenant #1: refused as the FDR's until cutover, zero
writes observed. Full suite: isolation 39, Sibyla 84, platform 92, browser 67.

**Not in this slice:** tier 1 of the miner (an ENTMST name found verbatim in the text — proposes
what rank 2 would already resolve once an alias exists), ENTITM signature inference and sibling
inheritance (no such inference exists yet to gate, and the door is built for them), and the
intermediary-account exclusion list of Stage 11 Round 33 (a declaration file the corpus does not
yet carry as a table).

**2026-09-02 — P2-11: the review-correct-learn loop — one apply engine, frozen wording, and the gate.**
The criterion (14 §3.3): *accepted DOCRQE/RECREV decisions apply through the single apply engine
(structured payloads only); protected human-state fields are never overwritten; decided items keep
their exact wording (immutability test); queues are exception surfaces — only open,
non-grandfathered blocking DOCFLG instances stop forward actions.* The rules are the Document
Entry and Reconciliation Review Procedures (05 §2.7, §3.8, §3.9) and the phantom-backlog Rules A–H;
the FDR's one engine is `apply_review_decisions.py`.

**`ReviewDecisionEngine`** is the one mutation path for both queues. `DecideAsync` records a
person's decision on an **Open Decision item only** (an Annotation is refused by Rule A; a decided
item is not decided twice — a new question is a new item; an item the FDR raised is decided in the
FDR until cutover, DN-10's read-only default): UserDecision in the procedure's own five words,
UserInput filled or changed but never blanked, **Observations appended if absent** (the only
genuinely irreplaceable content in the registry), DecidedBy and DecisionDate from the tenant's
user, all under P2-07's declared human decision. `ApplyAsync` executes a decided item **from a
machine-readable payload only** — the detector's TargetTable/TargetField/ProposedValue or
`TABLE.Field=value` in UserInput; prose is refused with the reason — and every target goes through
the owner that already exists for it, so a decision can never become a second way of writing a
table: `FDCHDR.AccountPeriod` → the ladder's `RuleAsync` (R4); `ENTMST.AccountPeriodRule` → the
§3 register by instruction, evidence on the entity; `DOCLOG.DocumentType` / `OriginClass` → the
DOCTYP router, refused if the ruled pair does not route either; `ENTITM.EICode=new` → the Item
Entry Flow's fix for P2-05's reported pair, an EI code minted and the mapping created **pending
review**, which closes the line-completeness loop end to end. RECREV **Accept with
`EntryCode=<code>`** makes `Matched (review decision)` — protected, so the ladder refuses to touch
the movement afterwards — and writes the BNKREC pairing; RECREV **Reject** persists a
`review_constraint`, and the engine refuses to accept a pairing a constraint forbids. Two earlier
supersede paths that set DecisionDate on open items were corrected: that is a human field.

**The database holds both protections** (`SibylaReviewGuard`, Main + Preview, DOCRQE and RECREV
alike): the human-state fields change only under a declared human decision or through the sync's
role; once `decided_by` is set, ReviewID, ItemKey, CurrentValue, AiProposal, ProposedValue and the
decision itself are immutable — not even the person may rewrite them — and Observations may only
grow. The AI field and the status still refresh deterministically on open items, as the procedure
says.

**`CaptureBlockingGate`** is `capture_blocking_gate()`: an open (Open / In Review),
non-grandfathered (the FDR's own marker in the instance note), blocking (not Non-Blocking), live
DOCFLG instance — and nothing else — stops a forward action. `DocumentEntryService` consults it
when the draft carries its DOCLOG; the count agrees with `v_blocking_open_instances` on the corpus.
No human approval sits in front of routine work.

**Proof: 10 tests (Sibyla suite 68 → 78):** deciding records the human fields and freezes the
wording — the person's own rewrite of CurrentValue, the decision and a shortened Observations are
refused by trigger name, an appended note lands, a second decision is refused; the human fields
refused to the app role without a person, allowed with one, and to the sync; Annotation, synced and
person-less items refused; prose refused with the item left Decided; `ENTITM.EICode=new` creating
EI000001 pending review and the document then entering; a DOCLOG re-route through the catalogue,
and a ruling that still does not route refused; an AccountPeriod ruling landing as R4 through the
ladder owner; a RECREV Accept protecting the match and writing BNKREC, the ladder then refusing the
row, and a Reject persisting the constraint; an open blocking flag stopping an entry while the same
flag grandfathered, a Non-Blocking one and a Resolved one do not; and the corpus gate count equal
to the view's. Full suite: isolation 39, Sibyla 78, platform 92, browser 67.

**Not in this slice:** the ballot contract's lettered options in the screens (the queue screens
still render the FDR's items read-only — the Decide/Apply actions need a UI, P2-13), flag-sourced
decisions onto the DOCFLG instance (`DOCFLG.Status` as a target — the lifecycle/waiver surface is
P2-16), and `BNKMAT.AutoCloseAllowed` consultation, which has nothing to consult until the matcher
lands (the corpus carries no review-decision match type).

**2026-09-02 — P2-10 (three of four parts): DOCTYP-driven routing, loud failure, and DOCFAI.**
The criterion (14 §3.3): *DOCTYP-table-driven routing; an undocumented type/treatment combination
fails loudly (never guessed); DOCFAI captures layer-1 capture failures; extraction breadth matches
the FDR skill on the agreed sample measure set.* The first three are the capture layers of the
Financial Document Capture Policy (05 §2.1, §2.4) — 1 Document Log → 2 Type Classification → 4
Review Queue → 5 Failure — and they now exist as module services; the fourth is stated below as
what it waits on.

- **`DocumentTypeRouter`** is capture layer 2 and the only thing that reads DOCTYP for meaning:
  a (DocumentType, OriginClass) pair resolves to the catalogue row's Treatment (Include as payable
  `I` or receivable `R` per DocClass; Exclude) or to a **named failure** — "DOCTYP carries no row
  for ('Credit Note', 'External') — an undocumented type/treatment combination is reported, never
  guessed". The extraction contract's small vocabulary maps to the catalogue's names in one place;
  `other` maps to a blank type because "unrecognised is a valid answer" (SKILL.md §5) and blank
  routes to review, never to a default. An origin the document did not establish defaults to
  External — the FDR's own rule — and the routing *says so*.
- **`DocumentCaptureService`** is layers 1, 4 and 5. Every capture gets its DOCLOG row (LG code
  minted through the shared `CodeIssuer`, idempotent on the filename — "any filename already in
  DOCLOG is processed"); an Include routing is handed back for `DocumentEntryService`, and
  `LinkFinancialRecordAsync` makes the join (DOCLOG.FdchdrId/EntryCode) afterwards, refusing to
  re-point a log row that already names a record. An **Exclude** or a failed read
  (`RecordFailureAsync`: Unreadable/Scanned · Insufficient Data · Wrong Document Type) lands in
  **DOCFAI** with the DOCLOG row kept, no financial row, and a High-priority DPRCHK item
  (`DOC-CAPTURE-FAILED`, Block Document). An unrouted type opens `DOC-TYPE-AMBIGUOUS` and a
  defaulted origin `DOC-ORIGINCLASS-DEFAULTED` — the catalogue's own blockers (Block Financial
  Entry) — so the entry is stopped by a finding a person can rule on, not by silence.
  `CaptureResult.ReadyForEntry` is true only for an Include routing with no finding.
- **`CodeIssuer`** now owns code minting for DR, RR, LG and FA codes; the two earlier copies in the
  entry and resolution services were folded into it.

**Proof: 10 tests (Sibyla suite 58 → 68):** the catalogue deciding treatment and flow; the
undocumented pair failing by name and registering a DOCLOG with no type row, no DOCFAI and a
Block-Financial-Entry item with a blank proposal; the blank type routing to review; the defaulted
origin routed but flagged and the confirmed one ready; Exclude landing in DOCFAI (FA000001) with
the log kept and a High / Block-Document item; a read failure recorded as layer 5 with the
catalogue's own failure type; idempotent registration; a capture carried to an entry, linked, and
refused a redo; the sync-era refusal; and the live catalogue read-only — its rows route, and
`Financing` (a DOCLOG vocabulary the catalogue does not carry, 725 rows at first sync) fails
loudly. Full suite: isolation 39, Sibyla 68, platform 92, browser 67.

**The fourth part is not claimed.** *Extraction breadth matches the FDR skill on the agreed sample
measure set* needs two things Apollo does not have: the contract widened from `apollo.extract.v0`
(seven header fields) to the field list §5 hands to the skill build — lines with item candidates,
the stated service period for R1, the origin class, the bill-to and issuer fiscal identities for
the company gate — and **the owner's agreed sample set and measure** to score it against. Both
are decisions before they are code; the routing above is written so that a v1 contract drops
straight into `CaptureFacts` when it lands. Also outside this slice, by role: the worker runs as
`apollo_worker`, which may touch queue and intake tables only, so registration and DOCFAI are
called from the app side — the review acceptance that P2-11's apply engine owns — not from the
worker's dead-letter path.

**2026-09-02 — P2-09: the AccountPeriod ladder R1–R4, one owner, stamps held as columns.**
The criterion (14 §3.3): *the AccountPeriod ladder R1–R4 (05 §3.11) is implemented by exactly one
owning service; rulings are stamped/recorded; an R4 ruling applies to its row only (a ruling is not
a pattern).* The rule is the Accounting Conformance Rule (Stage 10 Round 9): R1 the period the
document states — advance billing books the first month of the range, arrears the last, judged per
document never per provider; R2 the counterparty's `ENTMST.AccountPeriodRule` (blank = Default,
`No Shift`, `Arrears`, `Stated Period` deferring to R1; an unrecognised word never invents a
period); R3 the day-of-month default — on or before the 10th books the prior month — an assumption
labelled as one; R4 a review ruling for that row only. Credit notes take the period they reverse,
which is R1 by another name. Any non-R3 period must be stamped `[AccountPeriod=NNNNNN via <rule>:
<evidence>]`, appended and never rewritten; a stamp naming a period the row no longer holds is a
defect.

**`AccountPeriodService`** is the one owner. `Decide(dateDoc, statedPeriod?, entityRule)` is a
pure function and the only place the ladder is written down; `DocumentEntryService` now calls it
(the P2-05 placeholder that booked R1 as "the document period" is gone) and stamps the rung and its
evidence on the row — **as columns**, `account_period_rule` (R1–R4) and `account_period_evidence`,
which is what discovery 05 §3.11 asked for instead of appended prose. `RuleAsync` records an R4
ruling under a declared human decision (P2-07's door), with its evidence — a bare period is a guess
with a signature — on that row and no other; it refuses a synced row before cutover, because
AccountPeriod there is the FDR's. `ConformanceAsync` is the §5 control: in-scope unexplained
divergences (must be 0), out-of-scope ones (2025, visible not enforced), documented divergences
(expected large — a falling number means stamps are being lost) and stale stamps. The database
holds the shape (`SibylaAccountPeriodProvenance`, Main + Preview): periods are six digits, a rung
is one of R1–R4, and an R4 without evidence is refused.

**The sync reads the FDR's stamps into rungs** (fdchdr transform t4, 1,345 headers re-applied on
both databases): the last stamp decides, in every vocabulary the FDR ever used — `via R1:`,
`STATED-PERIOD override`, `PROVIDER-ALWAYS-ARREARS rule`, the Farminvest rule, `DEFAULT
day-of-month` — and a review disposition naming the period (`[E&L(112) reviewed …: AccountPeriod
NNNNNN …]`) is R4 whatever earlier stamp it sits over. **Measured on tenant #1 (I/R rows):** R1 39
· R2 40 · R3 494 · R4 2 · 176 unstamped. The control: **documented divergences 47** (the rule's
39 has grown, healthily); **out of scope 3** (the 2025 MEO rows, as the rule records); **in-scope
unexplained 13** — the rule shipped with 3 open (`I26030064`, `I26060071`, `I26070056`, still
open) and ten more have accumulated since: `I26030067`, `I26060086`, `I26070078`, `I26080031/32/
34/35/36`, `I26090001/02`. **Source finding for the owner:** each diverges from R3 with no stamp
and no ruling; inventing a reason is precisely what the rule forbids, so they stay reported until a
review session rules on them — and `RuleAsync` is where such rulings land once those rows are
Apollo's.

**Proof: 11 tests (Sibyla suite 47 → 58):** the ladder as a pure function (R3 on the 10th and the
11th and across the year boundary; R1 advance and arrears and the credit-note case outranking an
entity rule; R2 No Shift / Arrears; `Stated Period` and an unrecognised word both falling to R3 and
saying so); the entry path stamping R3, R2 and R1 with their evidence; **an R4 ruling changing its
row and leaving the same entity's sibling untouched**; the ruling refused without a person, without
evidence, with a malformed period, and on a synced row; the three CHECKs by constraint name; the §5
control counting one in-scope, one out-of-scope, one documented and one stale on engineered rows;
and the corpus read-only — every stamp vocabulary yields a rung, the review-marker form included.
Full suite: isolation 39, Sibyla 58, platform 92, browser 67.

**Not in this slice:** reading the stated period off the document (the worker's R1 extraction —
P2-10's contract widening), registering an entity's rule from unanimous history (§3, a
provenance-gated master-data write — P2-12), and the year-end control (roadmap 136).

**2026-09-02 — P2-08: Master Data Consistency as service checks MD01–MD06.**
The criterion (14 §3.3): *Master Data Consistency (05 §3.6) runs as service checks MD01–MD06:
copies repaired silently, declared exceptions reported and never repaired, printed source text
never modified.* The policy (`Specs/Policies/Master Data Consistency Policy.md`, in force from
Stage 11 Round 52) and its enforcer `enforce_master_data_consistency.py` were transcribed onto a
schema that, as discovery 05 predicted, dissolves most of the problem into normalisation:
Class/Subclass/PLMKEY/PLMKO on transactional rows are views and cannot drift, and a dangling id is
impossible behind a foreign key. **What survives was measured before it was written**, and each
population gets exactly the treatment the policy names:

- **MD01 copies — repaired silently.** The one copy left in Apollo is ENTITM's PLMKEY/PLMKO mirror
  of its class row (397 rows on the corpus, 0 drift). Designed to read 0 forever. Drift on an
  Apollo-native row is rewritten in the run; drift on a synced row is an upstream defect and is
  **reported, not repaired** — the sync repairs nothing, and neither does anything downstream of it.
- **MD02 exceptions — reported, never repaired.** An ENTITM row whose SNC account, class or cost
  centre differs from the item dictionary's default is the exception layer doing its job. Corpus:
  **157 rows** (158 SNC-account exceptions — the AWS-on-62211 family — and 7 class exceptions, 0
  cost-centre); the note splits them so an exception nobody meant to create stays visible.
- **MD03 source text — exempt, on the record.** ItemDesc on FDCHDR/FDCDTL/BNKMOV is the line as
  printed or as the bank narrated it. Corpus: **1,130 rows differ from the catalogue** (309 headers,
  655 lines, 166 movements). The exemption and its reason live in the service's note, in code.
- **MD04 dangling — reported, not guessed.** What can dangle in Apollo: a live row on a retired
  item or entity (0), an ENTITM pair on a retired master (**9** — the G5 finding), a PLMKO with no
  chart row for its company (**33**), a superseded-by code naming no item (0).
- **MD05 no item — counted separately.** 538 movements, 0 lines: the classification queue's
  number, never a consistency violation.
- **MD06 line completeness — must read 0.** 594 headers + 594 lines without an EICode, 576 + 576
  with a trio the source could stamp; **Apollo-native rows: 0**, because the database refuses them
  (P2-05).

`MasterDataConsistencyService.RunAsync(companyId?, repair)` returns a report whose every measure
carries its **population, failure direction and treatment** (05 §5) — "must read 0" is not the
same kind of number as "informational", and a dashboard that renders them alike is lying — so
P2-14 can show them without inventing semantics. `repair:false` is a pure read.

**Proof: 9 tests (Sibyla suite 38 → 47)** over a synthetic owner engineered so every measure has
a row: a native drifted copy is repaired and a synced one reported, idempotently (second run
repairs 0); the exception row is counted and left byte-identical; the printed text is counted and
left untouched; the retired-pair and chartless-PLMKO cases are reported by code; the no-item
movement is counted apart; the synced header and line without EICode read 2 with the stampable
count; company scope zeroes every population; and tenant #1 read-only: MD01 reads 0, MD05/MD06
agree with the tables, MD02/MD03/MD04 are non-zero as measured above. Full suite: isolation 39,
Sibyla 47, platform 92, browser 67 (the known harness-concurrency flake failed once under the
parallel run and passed alone, as before).

**Not in this slice:** scheduling the run (it belongs in the worker after every Apollo-native
write, and on the quality dashboard — P2-14), persisting the report (P2-15 evidence), and the
FDR's `Company`-defaulting rule for pre-Stage-14 rows, which Apollo never needs because every
transactional row carries its company.

**2026-09-02 — P2-07: hammered lines — one door for every learning population, and a guard at the database.**
The criterion (14 §3.3): *a `HammerFlag="Y"` row is read by no learning surface (alias mining, IBAN
learning, sibling inheritance, ENTITM signatures, reference tokens, verbatim matching) and
overwritten by no inference — asserted by instrumented tests on every learning path.* The rule is
05 §3.3 §9a: a hammered line is true for itself and evidence for nothing; set only by a recorded
human decision; never overwritten by an inference it was excepted from; carrying its assertion in
its flag. The FDR enforces it through two single owners, `entity_utils.is_hammered()` and
`inference_source()`, which every learning script iterates instead of the raw rows. Apollo does the
same, and adds what a database can hold that a Python convention cannot.

**`InferenceSource`** (module infrastructure, `Learning/`) is the one door: `Movements` (not
hammered, not retired, not provisional, ranks 0–3 or upstream — P2-06's rule folded in),
`Documents` (not hammered, not retired) and `Lines` (the hammer lives on the header and covers
every line under it). It is **instrumented**: every population handed out is announced on the
async flow with its table, purpose and exclusions, so a test can drive a learning path with a
hammered row as its only teacher and assert two things — nothing was learned, and the path came
through this door. Both rank-4 paths of the counterparty ladder (repeated text, reference token)
now obtain their rows here and announce themselves. **Honest scope:** those two are the only
learning surfaces Apollo has today. Alias mining, IBAN learning, sibling inheritance and ENTITM
signatures do not exist yet (P2-12); the door is built so that when they land they cannot bypass
it, and the corpus test already proves the populations they will read contain no hammered, retired
or provisional row.

**The guard** (`SibylaHammerGuard`, Main + Preview). Two writers may change a hammered row's
identity, classification or the flag itself: a person, declared for the transaction as
`app.human_decision` (`HumanDecision.DeclareAsync`, which needs a user on the tenant context and an
open transaction — a system job has nobody to declare); and the sync, recognised by its role
bypassing RLS, which carries the FDR's own rulings in as data. Anything else is refused by name:
`trg_bnkmov_hammer_guard` covers entity, item, match status, matched ref, counterpart, the P2-06
provenance and the flag; `trg_fdchdr_hammer_guard` covers entity, item, EICode, AccountPeriod, the
intercompany pair and the notes; `trg_fdcdtl_hammer_guard` refuses adding, removing or
reclassifying a hammered document's lines. Setting or clearing the hammer is itself a human
decision (rules 3 and 4), and **rule 5 is a CHECK**: a hammered row without its assertion text is
refused even for the person. The corpus's 45 hammered movements and 4 hammered headers all carry
theirs. The ladder's recorder already refused hammered rows in code; the database now refuses the
inference that forgets to ask. Corpus re-synced after the migration: 100 % unchanged, so no
hammered row needed rewriting this run — the sync's seat at the guard is proven by the migrator
write in the tests, not by this pass.

**Proof: 9 tests (Sibyla suite 29 → 38).** Three hammered movements that would have taught a
fourth its name at rank 4 teach nothing, and the observer saw both paths ask `InferenceSource`
with the hammer excluded; the same three unhammered do teach; each population excludes hammered,
retired and provisional rows on all three tables; the ladder refuses a hammered movement; **on the
app role** — the inference's own seat — every guarded write is refused by trigger name, the same
writes land under a declared person, and the migrator (the sync) passes; the header's hammer covers
its lines; the assertion CHECK holds even for the person; `HumanDecision` refuses without a person
or a transaction and then the EF write lands; and tenant #1's populations contain no hammered,
retired or provisional row. Full suite: isolation 39, Sibyla 38, platform 92, browser 67.

**Not in this slice:** rule 4a (a *rule* may hammer only where the exception was human-declared —
no such rule exists in Apollo yet), the review-decision path that will call `HumanDecision`
(P2-11's apply engine), and QB17/QD06, the FDR's measures of hammer coverage (P2-14).

**2026-09-02 — P2-06: the counterparty precedence ladder, as one owner with machine-readable provenance.**
The criterion (14 §3.3): *own-account exclusion applies first (rank 0); a rank-5 amount-only
resolution is recorded provisional and seeds no learning ("a resolution may not learn from a guess"
— RB7); an honest blank beats a plausible name (test asserts blank, not best-guess, on ambiguous
input).* The rule is 05 §3.7 Step 1a, and the FDR implements it across `classify_bnkmov.py`
(ranks 0–2) and `resolve_bnkmov_entity.py` (ranks 3–5, Stage 11 Round 31) with the gates it paid
to discover — BM000128 (an ATM withdrawal named because EUR 70 was unique), BM000314 (a payee the
registry did not know, handed to whoever was owed EUR 477.51), BM000579 ("TRF DE Pagamento",
generic text whose one peer said another name), the EUR 13.85 coincidence that taught three Canon
rows. Apollo transcribes those scripts rather than re-deriving the rule from the prose.

**`CounterpartyResolver`** (module infrastructure) is the single owner. In order: rank 0 — an own
BNKACC IBAN in the text, or any higher signal resolving to the company's own ENTMST row (Role
`Company`, named as OWNCPN declares it) — an internal transfer, never a counterparty; rank 1 — an
ENTBNK IBAN under **unanimity** (an account held by two entities resolves nothing, and says so);
rank 2 — an ENTALS alias by containment, longest wins, two entities' aliases both matching is a
conflict; rank 3 — the FDR's document-reference markers (`FT/FACC/INV./FV/INVOICE <ref>` and the
`P/ <IBAN> <ref>` form) naming exactly one document at this amount within EUR 0.02; rank 4 —
repeated distinctive text (the FDR's generic-bank-vocabulary list, case-folded signature) or a
reference token of 9+ digits seen on ≥3 rows, agreed by every carrier, **learned only from rows
resolved at ranks 0–3, never from a provisional or hammered row**; rank 5 — an exact amount unique
to one document of the agreeing flow (outflow → payable, inflow → receivable) within 45 days,
**last and gated**: refused for the no-counterparty classes (ATM, commission, stamp duty), refused
when the text names a beneficiary or payer the registry cannot resolve (evidence of a new
counterparty, not an absence of evidence), refused against a rank-4 population that reads
otherwise — and **provisional** when it speaks. Every refusal is returned by name; nothing is
silent.

**Provenance on the row, not in prose.** `bnkmov` gains `resolution_rank` (0–5), `resolution_
provisional` and `resolution_evidence` (migration `SibylaCounterpartyProvenance`, Main + Preview),
with the database refusing a provisional rank below 5, a rank outside the ladder, and a rank
without a name. So "learn only from ranks 0–3" is a WHERE clause, which is what discovery 05 §4.3
asked for ("every learning surface needs a machine-readable provenance gate"). **The sync derives
it for the corpus** from the FDR's own markers — the very strings its `amount_tier_resolved()`
tests for decide *provisional*; the Tier 1/3/4 notes give ranks 3–4; `Internal` is rank 0; ranks
1–2 are not flagged at source and stay NULL with the entity set. Transform version 2 re-applied all
2,281 movements on both databases. **Measured on tenant #1:** 203 rank 0 · 118 rank 4 (81 repeated
text, 37 reference token) · **39 rank 5 provisional** · 1,746 named upstream or by authority · 175
blank (25 of them the rank-5 gate's own refusals). **One source finding:** every one of the 39
amount-named rows also carries a later user-ruling note; the FDR's gate keys on the amount marker
and keeps them out of the learning population regardless, and Apollo mirrors that — but a ruled
row that still reads as a guess deserves either the marker withdrawn or the ruling re-stamped at
source.

**`BankMovementResolutionService`** records the answer on an Apollo-native movement. Authority
sits above the ladder and is never overwritten (a protected MatchStatus or a hammered row refuses
outright); a provisional name is **re-derived every time** — a derived value that never re-derives
is a stored guess — while a rank 0–4 name stands; an honest blank opens (or reuses) a RECREV
*Entity Match* item keyed `BNKMOV|{BMCode}|unmatched-line` exactly as the FDR keys it, with the
unknown beneficiary carried as an **alias proposal for a person** and the amount deliberately
unused; a later resolution supersedes the item with the FDR's wording. RR-codes come from
`code_counter` + `code_ledger` (`issued_by = 'apollo'`) as DR-codes did in P2-05, and the same
sync-era ruling applies: an owner with sync runs is refused.

**Proof: 17 tests in `tests/Apollo.Tests.Sibyla` (Sibyla suite 12 → 29)**, a synthetic owner
engineered so every rank and every refusal has a movement: own IBAN beating an alias in the same
text (rank 0 first); known IBAN; contested IBAN resolving nothing; alias; document reference; the
criterion's second clause — a movement only an amount can name is rank 5 provisional and an
identical second movement **does not inherit it**, while text named at rank 3 does teach at rank 4;
generic text teaching nothing; the criterion's third clause — `TRF P/ ARILSON FERN` at a unique
amount stays blank with the name in the review item; ambiguous amount blank; ATM refused; the
BM000579 contradiction refused; protected status untouched; provisional re-derived when the
coincidence stops being unique; the sync-era refusal; the three CHECKs by constraint name; and the
corpus read-only: every FDR amount-named row is provisional in Apollo and none is in the learning
population. Full suite: isolation 39, Sibyla 29, platform 92, browser 67 — all green.

**Not in this slice:** the matcher itself (Step 2 candidates, BNKMAT types, the control equation,
chronology and grace — P2-16/P3), Apollo-native bank import (nothing creates BNKMOV rows yet; the
recorder is proven on synthetic rows), alias acceptance into ENTALS (P2-12, provenance-gated), and
the FDR's hand-curated vendor-pattern table, which is code in the FDR and has no Apollo home until
the owner rules whether it becomes ENTALS rows.

**2026-09-02 — P2-05: line completeness, and the first Apollo-native write path into the FDR schema.**
The criterion (14 §3.3): *every FDCDTL line carries Entity + ItemCode + EICode as NOT NULL FKs; an
unresolvable pair produces a reported exception, never a guessed code — proven by a document
engineered to be unresolvable.* Nothing in Apollo wrote FDCHDR/FDCDTL before this — the sync did,
under the FDR's authority — so P2-05 is also the slice that decides how an Apollo-native row and a
synced row coexist in one table during the sync era. Four things landed, in the order they depend
on each other.

**What the corpus actually says.** 1,861 lines: 0 without ItemCode, **594 without EICode — every
one a bank-generated O/P/F mirror line** (BankExtract); the 1,267 invoice lines (I/R) are 100 %
complete. For 576 of the 594 blanks the (company, entity, item) trio already exists in ENTITM, so
the EICode is a lookup the FDR could stamp, not a ruling anyone still has to make; 18 have no trio
at all. One header, `I26070067`, carries EI000328 — company 11's ruling on a company-12 document —
and is a source tombstone (retired 2026-08-28, kept as the sync's tombstone per 11 §3). **Both are
source findings for the owner**; Apollo reports them (gate G5 now says so every run) and repairs
neither.

**1. The database rule** (`SibylaLineCompleteness`, applied to Main and Preview). For every
Apollo-native row — the discriminator is `sync_run_id IS NULL`, design 11 §5 ownership class A —
`fdcdtl.itmmst_id` and `entitm_id` are NOT NULL (`ck_fdcdtl_line_complete`; the same on `fdchdr`,
since §5a names both). Independently of provenance, **an EICode anywhere must be the ruling on the
pair its row names**: composite foreign keys into `entitm(id, owner, company, item)` for lines and
`entitm(id, owner, company, entity, item)` for headers, and — for the one relation a foreign key
cannot express, the line's entity living on its header — a deferred constraint trigger
(`trg_fdcdtl_pair_matches_header`, judged at commit so the sync's header+lines batch is judged as a
set; a header the caller cannot see under RLS reads as a mismatch, i.e. it fails closed). Its twin
on the header refuses re-booking a document to another entity while its lines carry the old
rulings. The header key is `NOT VALID` because of the tombstoned `I26070067`; it validates at C8
when tombstones are reconciled. **Why a conditional rule and not plain NOT NULL:** the FDR is
canonical (D15) and ships 594 honest blanks; a hard NOT NULL would make the corpus unloadable or
force the sync to repair, both of which the design forbids. At cutover C6 the discriminator goes and
the rule becomes unconditional — that is the owner's call to schedule, not a gap.

**2. The sync learns what is not its own.** Until now the loaders matched, tombstoned and — for
lines — *deleted* every row of the owner, native or not: the next run after any Apollo-native
document would have tombstoned its header and removed its lines. `Existing<T>` now returns only
synced rows and remembers the native rows' natural keys, so a source row arriving under a key an
Apollo-native row holds is **quarantined as a two-writer conflict, never adopted** (generic loader,
fdchdr, fdcdtl). The header and line loaders also pre-check pair identity against the constraints
above and quarantine a rewritten offender with a report line — the alternative was the whole run
aborting on a constraint the operator only sees as an exception. Full corpus re-synced on Main and
Preview after the migration: 100 % unchanged, zero quarantine, G4 cent-exact (net 1,462,271.34 /
vat 43,065.11), and the new G5 lines: *576 of the EICode blanks have an ENTITM trio the source
could stamp* · *every EICode on fdchdr/fdcdtl is the ruling on the pair its row names*.

**3. One owner of resolution, one writer.** `LineClassificationResolver` (module infrastructure)
is the single owner of (company, entity, item) → EICode: entity live in ENTMST, item live in
ITMMST, ENTITM row at the Stage 14 trio grain — and **no fallback tier for the EICode itself**: the
ITMMST default is a classification default, not proof the pair was ruled on, so a missing row is
`PairUnmapped` ("classification unowned"), exactly as 05 §3.3 puts it. Six failure classes
(entity unknown/retired, item unknown/retired, pair unmapped/retired), each a report with the fix
the rule prescribes. A blank class on an existing pair is "pending a per-company ruling" — the line
is complete, the pending state is flagged on it. `DocumentEntryService.EnterAsync` takes a draft
(codes, not printed names: identity is settled before entry) and is **all-or-nothing**: every line
resolves → FDCHDR + FDCDTL in one transaction (`source_system = apollo`, EntryCode in the FDR's own
`{flow}{yyMM}{seq}` shape, AccountPeriod = Period as ladder rung R1 until P2-09 owns the ladder,
non-EUR left `no_rate` for the EXCRAT derivation rather than stamped 1.0); one line fails → nothing
reaches either table, and each failing pair becomes an **open DOCRQE decision under the governed
catalogue's own key** — DQLCHK `ITM-ENTITM-MISSING` (DQ000016) for pair failures,
`ENTRY-ITEM-MISSING` (DQ000041) for item failures, an unbound *Entity Identity* item for entity
failures — with `ProposedValue` and `AiProposal` **null**: the honest blank the rule asks for. An
item still open for the same (company, pair, document) is reused, not duplicated; re-entering the
document after a person creates the mapping supersedes it with the FDR's own wording ("Resolved by
the pipeline since this item was raised; no user action required.") and its original text kept.
ReviewIds are minted from `code_counter` with a `code_ledger` row `issued_by = 'apollo'` in the
same transaction — the C6 mechanism of 06 §1.2 — under a transaction-scoped advisory lock.

**4. The sync-era authority ruling, made explicit in code.** An owner with sync runs is the FDR's
until C6: the service **refuses** to enter or report for it, because a DR-code or EntryCode minted
beside a live sync collides with the FDR's next one at the following run (plan §6 risk 3). Owners
with no FDR history are Apollo-native from their first document. The rule is data-derived (the
presence of `sync_run`) rather than a flag; C6 adds the explicit switch record when it lifts it.
The consequence is stated plainly: **on tenant #1 the write path is proven read-only** — the
resolver runs over the live master data (a real ruled pair resolves to the corpus's EICode; an
engineered unpaired item is reported) — and the write path is proven on a synthetic owner.

**Proof: 12 tests in `tests/Apollo.Tests.Sibyla` (new)**, against the real database on the app
role: the engineered document (line 1 resolves, line 2's item is ruled only in company B — the
trio grain — so nothing is entered and one open item with blanks is reported); the same document
entering in company B; reuse of the open item; supersession on re-entry; entity failures as one
document-level exception; the resolver's six classes; the sync-era refusal; company access; **the
database refusing by constraint name** an incomplete line, an EICode of another item, another
company's EICode and — at commit — another entity's; synced blanks still loadable; and the
tenant #1 read-only demonstration. Suite: isolation 39, platform 92, Sibyla 12, browser 67 (one
harness concurrency test, `GlobalCapacityQueuesASecondConversation…`, failed once in the full run
and passed 3/3 alone and on the untouched baseline — a pre-existing flake in the AI harness, noted,
not touched).

**Deliberately not in this slice:** DOCLOG/DOCTYP routing and DOCFAI (P2-10), the AUDLOG row (the
caller's, in the platform context), the AccountPeriod ladder beyond R1 (P2-09), hammered-line
protection on the new path (P2-07 — no learning surface reads it yet), and a screen: the Quality
screen already lists DQLCHK-routed DOCRQE items, so a reported pair appears there under its
catalogue name without new UI.

**2026-09-01 — P2-04 / RB6: the hostile-document gate at ingest, and a strict extraction contract.**
The plan makes this a gate, not a feature: RB6 is "release-blocking before Phase 2 processing
breadth and absolutely before any non-golden channel" (13 §5), and until now the only thing
standing between an attacker-authored file and the Claude CLI was an **extension allowlist** — a
`.pdf` that was really an executable, a zip or an HTML/script polyglot passed it unread.

Ingestion becomes **accept → checksum → inspect + scan → register → queue**, which is the order
discovery 13 §3.2 asks for, and the two new steps answer different questions:

- **`DocumentContentInspector`** — is this the document it claims to be? Magic bytes at offset 0
  for every binary family (real readers tolerate junk before `%PDF-`; a gate must not, because that
  tolerance is exactly how a polyglot is two files at once), executables/archives/OLE refused under
  any extension, NUL bytes refused in a text type, and a declared content type that *names another
  type* refused — while an unknown one (`application/octet-stream`, `vnd.ms-excel` for a CSV) is
  correctly treated as no claim at all, since policing it would cost real uploads.
- **`IDocumentScanner`** — the malware-scan integration point 13 §3.2 names and leaves abstract, now
  concrete: a seam with a bounded signature sweep behind it, **fail-closed**. Disabled, unreachable,
  or a file too large to read to the end all mean *quarantine*, never "clean".

**Rejected and quarantined are deliberately different outcomes.** A rejected payload is not a
document, so nothing is registered and the staged copy goes; a quarantined one is registered like
everything else — an attempt is evidence worth keeping — but enters **no lane**: not extraction, and
unlike a flagged duplicate, **not the cloud transfer either**, because copying a suspected payload
into the tenant's Nextcloud would spread the thing the gate just caught. Only a person releases it
(`document.quarantine.release`), and releasing re-runs the per-(owner, company) duplicate check the
quarantine held it out of, for the same reason company assignment does.

**The extraction contract stopped being a field checklist.** `ValidateContract` required seven
fields and re-serialized whatever else came with them — so a hostile answer could smuggle payload in
fields nobody asked for (L3) and 500 KB of injected prose in `summary` (L9). `ExtractionContract`
now takes an **exact** field set (an unexpected member fails, by name), shape- and length-checks
every value, refuses control characters in single-line record fields, and writes the canonical form
from the values that passed rather than from the parsed document. It moved to
`Apollo.Platform.Infrastructure/Ingestion` so it is testable without spawning a CLI, and the worker
calls it.

**Corpus: 21 tests, each entry with its asserted safe outcome** (`HostileDocumentCorpusTests`,
`ExtractionContractTests`). Categories 1, 2, 5 and 7 of §3.2 are covered. **Two omissions are
recorded rather than faked:** the real EICAR string is never written to disk (a host antivirus would
quarantine the temp file mid-test and raise an alert on this box — the signature list is
configurable, so the seam is proven with a synthetic one and EICAR stays a manual check), and
categories 3–4 (learning-store poisoning, RB7) have nothing to assert **because Apollo has no
learning store until P2-12** — a test that passes because the code does not exist is not evidence.
RB8 (sender→tenant binding) is likewise still open by scope: no non-golden channel exists yet, which
is precisely the state this gate is meant to be finished before.

One consequence worth knowing: **`gate` now applies to the golden channel too**, so an upload of
bytes that are not really the declared type stops being accepted. The existing duplicate-control
fixtures were uploading the literal string "same bytes" as `invoice.pdf`; they now carry a real
header, which is what they should always have been.

Suite green: isolation 39, platform 92, browser 67. Migration `IntakeQuarantine` applied to Main and
Preview.

**2026-09-01 — P2-03 company triage, and a cross-tenant probe over the duplicate link.** A document accepted without a company now waits in a visible triage pile rather than proceeding as if filed — the company is never guessed. Assigning it **re-runs the Layer 2 duplicate check**, because that check is scoped per (owner, company) and the company was unknown at upload: the same content may already sit in the company now being chosen. Only a document not yet extracted is re-flagged; re-flagging one that already carries a result would hide the result behind the duplicate branch. Assigning twice is refused rather than silently re-filing. Audited as `document.company.assign`.

Five new probes in the release-blocking isolation suite cover DOCINT's duplicate link, which deserves its own file because it is a **self-referencing FK**: PostgreSQL runs FK checks as the table owner without re-applying RLS, so the database *will* accept a `duplicate_of_id` pointing into another tenant. What keeps a pair inside one tenant is the application lookup, running under the EF filter inside an RLS transaction. The probes pin both halves — the FK really is permissive (asserted deliberately, so the day the database grows that protection someone revisits the code that does not rely on it), and the read path really does hide the other tenant's identical checksum, so the value can never be formed. Isolation suite 15 → 20.

**2026-09-01 — `FindExistingAsync` for iziBizi: the mandatory read before any push.** No surveyed ERP offers server-side idempotency, so "check then act" is the only thing standing between an ingested document and a duplicate in a client's ledger — and it is deliberately the first thing built of the push, because it is **read-only** and can therefore be validated against the live instance without creating anything.

It searches by the **supplier's** fiscal identity, per the ruling above: a document typed in by a person carries no key of ours, and searching only by our own reference would make it invisible by construction. Verified live: `commercial_purchases_documents` accepts JSON:API bracket filters (`?filter[attr]=value`); the other syntaxes answer 400 (JA010/JA011). The probe that mattered was a filter for a NIF that does not exist — it returns **zero** rows, proving the filter is applied rather than quietly ignored; one that were ignored would return the whole ledger and every push would believe it had found its match.

The live data also settled the matching rule: iziBizi's `document_no` is the **ERP's own** numbering (`FC 2026/1`) while the supplier's number sits in `external_reference` (`FA 2026B/2352`) — which is also where Apollo stamps its own reference. So server-side filtering is by supplier tax id, and the narrowing by supplier document number and date happens client-side against `external_reference`.

Four outcomes, mapping onto the ledger: `None` (may create), `Ours` (our own interrupted attempt — adopt and commit, this is recovery), `Foreign` (somebody else's document → `AlreadyInErp`, never a create, a person confirms), `Ambiguous` (more than one candidate — the push must not guess). A failed search **throws**: reporting "nothing found" would create a duplicate on the very next line. Exercised end-to-end against the live instance across all four, and covered by 9 fixture tests that need no ERP.

**2026-09-01 — Cegid iziBizi: the connection test now opens a real fiscal-year session.** The test used to stop at a token, which proves credentials but not a usable session — iziBizi scopes all API work to a fiscal year. It now runs the full sequence (token → `fiscal_years_list` → `entity_sub_switch`) and reports which exercises the credentials expose and whether any is open and writable; a closed or read-only year is never chosen silently, because posting into it would fail later at push time where the cause is much harder to see. `ErpConfig.SettingsJson` gained `fiscalYear` (the `fiscal_years_list` id or the year itself); unset means "the first open, writable one". Verified live against `api2.business-pt.cegid.cloud`: *"iziBizi OK — sessão aberta no exercício 2026."* The contract and its two non-obvious traps — the `Content-Type` must carry no parameters, and the action is `sub-switch` — are recorded in `docs/discovery/08`. `local/tools/DiagErpTest` now exercises the **shipped provider** rather than a parallel copy of the sequence, and masks tokens (including nested ones) instead of printing them. Primavera v10 stays blocked on Cegid's installation; this unblocks the iziBizi half of Phase 2 integration.

**2026-09-01 — P2-02 content-duplicate control (first Phase 2 slice).** DOCINT gained `DuplicateOfId` and a `PossibleDuplicate` processing status, implementing discovery 09 §9 row 6 exactly: a re-uploaded file is **registered anyway** — a re-sent invoice is business signal — pointed at the earlier intake it duplicates, and held for a human. Never auto-dropped; deletion stays a human decision. The checksum lookup is scoped per **(owner, company)** as the rule specifies, so the same file filed against a different company is two companies holding one document, not a duplicate. A flagged row does **not** enter the Claude lane (identical bytes yield an identical extraction, and the subscription is the constrained resource) but **does** still transfer to storage — the document is kept whatever the ruling turns out to be. Audited as `document.upload.duplicate`. Five tests in `Apollo.Tests.Platform` cover the resend, the per-company scope, differing content, and the queue-lane split. **The review action landed the same day**: *É duplicado* stamps the row `Duplicate` and keeps both the document and the link — agreeing something is a duplicate is not agreeing to delete it, which is the rule's own distinction; *Não é — processar* clears the link and enqueues the extraction job the row was held out of. The status guard makes the ruling idempotent, so a double-click cannot produce two extraction jobs. Audited as `document.duplicate.confirm` / `.reject`. Eight tests in `Apollo.Tests.Platform`.

**2026-09-01 — FDR Stage 15/16 landed at source, and the sync is BLOCKED on G1.** The corpus
gained an Integration Layer (`ITGMST`/`ITGFLW`/`ITGCHK`/`ITGLOG`/`ITGREC`, schema 40 → 45 sheets).
Apollo has absorbed **none** of it: two consecutive runs (`40323256`, `d935ea80`) fail the same
gate, so `gott_apollo` still holds the Stage 14 corpus untouched.

```
GATE FAIL: G1 DOCEFL: 2 live code(s) absent from ledger: EF0000066, EF0000067
```

**[FACT — the cause, owner's diagnosis 2026-09-01, verified here]** This is **not** two rows that
someone forgot to register. `EF0000066` and `EF0000067` are two distinct rules that **collide on
the ledger key**:

| | EF0000066 | EF0000067 |
|---|---|---|
| BlockingLevel | Blocking | Non-Blocking |
| ReviewPriority | High | Normal |
| RiskFactor | Medium | Low |
| ITGCHK impact covered | Blocker | Warning |

Everything else is identical, including `FlagReasonCategory` ("Integration Requirement Not Met")
and `FlowDimension` ("Integration") — and `CODE_KEY_FIELDS["DOCEFL"]` in `entity_utils.py` is
exactly `("FlagReasonCategory", "FlowDimension")`. Both rows therefore produce the key
`integration requirement not met|integration`; `assign_permanent_code` writes both and the last
one wins. Verified: the two-part key yields **66 distinct keys for 67 rows**, one collision, that
one.

**Neither obvious remedy works.** Registering both is impossible with the current key — one key,
one code. Removing the rows is worse: `EF0000066` is in real use (12 checks in `itgchk.json`, 12
findings, 12 instances in `flag_instances.json`, 24 references in `docrqe.json`, and the hard-coded
fallback in `build_integration_readiness.py`); `EF0000067` is the Warning rule, without instances
only because every check declared so far is `Impact = Blocker`.

**A three-part key resolves it.** Adding `BlockingLevel` gives **67 distinct keys, zero
collisions**, and `BlockingLevel` is populated on all 67 rows — verified. One consequence worth
planning for: a third segment changes the string of all 65 existing DOCEFL keys, so the change to
`CODE_KEY_FIELDS` needs a ledger migration in the same step, or `assign_permanent_code` stops
recognising the old entries and re-issues codes for rules that already have them.

**[CORRECTION]** Apollo's first report of this said "two codes were issued without being registered"
and recommended registering them — a remedy the key makes impossible — and compounded it by
grouping the ITMCLS PLMKEY note below as "the same pattern", which it is not. The error came from
reading the counts (65 ledger, 67 data) without ever opening `entity_utils.py` to see how the key
is built. The counts were right; the conclusion drawn from them was not.

**Repercussions — decided by the owner, against the recommendation below.** The recommendation
published here was: do not import `ITGMST`/`ITGFLW` as operational configuration, nor
`ITGLOG`/`ITGREC` (both empty; ERPLDG is Apollo's execution record); add a G11-shaped gate that
reconciles declared flows against ERPCFG; read `ITGCHK` closely, since its 12 rows of
`SourceField → TargetField` with obligation are the field map the purchase push would otherwise
invent. The reasoning was the Stage 14 precedent — the FDR's own `OWNMST`/`USRMST` were not
imported because they "record and attribute, they do not enforce", and a gate reconciles instead —
plus the observation that `ITGMST` reads `Status: Planned` with `BaseURL: (intake — not yet
supplied)`, a declared intent, while ERPCFG holds a live, verified iziBizi connection.

**[RULED 2026-09-01]** *"o que manda é o invoice skill build, temos de incorporar essas alterações
do nosso lado e não criar 'mapeamentos' ou descartar tabelas."* All six tables are imported as
themselves — the five ITG tables and `OWNCPN`, which the Stage 14 entry below said was left out.
The ruling names the three escapes precisely, and Apollo had reached for two of them: mapping
`ITGMST`/`ITGLOG` onto ERPCFG/ERPLDG, and discarding `OWNCPN` behind a gate. The observation about
`Status: Planned` was accurate and simply not decisive: a declaration that a company intends an
integration is a fact worth storing as the declaration it is.

Shipped in `f01f744` — wave 8, migrations `20260901163007` + `20260901165819`, `SyncEngineWave8.cs`:

- **The ruling is about tables, not fields** (owner clarification, same day — the first pass here
  over-applied it and stored the source's company *name* verbatim). Field-level mapping is the
  sync's ordinary job, and Company is the ordinary case: every other Sibyla table stores the
  resolved Apollo `CompanyId`, and these six now do too, rather than inventing a second way to say
  "company" inside one schema. A name that does not resolve is quarantined, as everywhere else in
  the engine; G11 blocks on an unmapped live OWNCPN company before wave 8 is even reached.
  `FlowRef` stays a source-shaped `Company|TargetSystem|Object` string, because it is a value the
  FDR wrote rather than a key Apollo owns — G12 resolves it at check time instead of rewriting it.
- **Both stay.** `ERPCFG`/`ERPLDG`/`ERPLDA` are Apollo's *execution* machinery; `ITGMST…ITGREC` are
  what the FDR *declares*. Which of the two the purchase push writes to is a decision for the push,
  and deliberately still open.
- **`ITGLOG` is never tombstoned.** It is the idempotency mechanism, not an audit trail. A row that
  stopped appearing at source is not a row that stopped having happened, and tombstoning it would
  let a second run post the same document twice — the Stage 15 Round 3 cold replay that booked
  twelve documents twice, one layer down.
- **`ITGREC`'s IRCode is not unique.** It is fresh per render (the `BRCode` treatment), so a unique
  index on it was a latent abort: a re-render may hand `IR000004`'s code to the row that held
  `IR000003`, and PostgreSQL checks a unique INDEX per row — only a CONSTRAINT can be deferred.
  Uniqueness moved to the comparison's own identity. Fixed while the table is still empty.
- **Gate G12** checks the four things the FDR's builder enforces at source, so a breach means the
  corpus is not the one it published: every flow, requirement, attempt and comparison has an
  `ITGMST` connection; every `FlowRef` resolves; every `EFCode` is a real `DOCEFL` rule; and
  `ExternalID` is unique per company/system/object — the idempotency key, checked *before* the
  first document leaves.

Sync green: owncpn 7, itgmst 2, itgflw 7, itgchk 12, itglog 0, itgrec 0 — no quarantine, no gate
failures. Forced RLS verified through the NOBYPASSRLS app role; the release-blocking meta-tests
pick the six up automatically. 145 tests pass. **The preview database does not have this yet** —
`apollo_preview_migrator` does not exist, so `local/prepare-preview.ps1` has not been run; it picks
the migrations up when it is.

**[DECIDED 2026-09-01] ERPLDG and ITGLOG both stay, with separated roles.** The question was
whether ITGLOG could replace ERPLDG. It cannot, and the reason is structural rather than a missing
column: **ERPLDG is a mutable work item and ITGLOG is an append-only attempt log.** ITGLOG has the
shape of ERPL*DA*, not ERPLDG.

Three of the gaps are not fields at all:

1. **An append-only log cannot be leased.** `LeaseOwner`/`LeaseUntil` is a conditional `UPDATE`
   on one row — the mechanism that stops two workers pushing the same document at once.
2. **The unique index has nowhere to live.** `UNIQUE (erp_config_id, flow, doclog_id)` is what makes
   a second attempt-chain structurally impossible. ITGLOG's own text says idempotency "is a
   property of a stored, compared key"; without a state row there is no key to constrain.
3. **Apollo does not own the write path.** ITGLOG is a mirror written only by `Apollo.Sync` from
   `itglog.json`, and `ILCode` is issued by the FDR's permanent code ledger. A worker cannot mint
   FDR codes inside a transaction.

**So: ERPLDG/ERPLDA are Apollo's execution machinery** (state, lease, retry, redacted snapshots),
**and Apollo proposes the declarative per-attempt row to ITGLOG at the END of a chain**, through the
`*.json` layer — never during. Same separation Apollo already has between ITGMST (declared intent)
and ERPCFG (live, tested connection).

**Blocking on the FDR side before the projection can be written:** ITGLOG's `Outcome` vocabulary is
undeclared — the word appears exactly once in `schema.md`, in the column list, and the table is
empty. Apollo will not invent it; that is the quarantine-not-guess rule applied to a vocabulary.
The field list to take to the skill build is §5's Phase 2 entry.

**One thing Apollo needed and now has (`OWNCPN.Company`, restored).** To propose a row, Apollo must
name the company the way the FDR spells it, and after wave 8 resolved companies to `CompanyId` it
could not: `commst.name` is Apollo's own ("GOTT Solutions" where the FDR writes "Gott"), and
`SyncOptions.CompanyMap` is deliberately many-to-one (the source spells *Silencios Atraentes* with
and without its accent) so it cannot be inverted. OWNCPN is the FDR's owner-company **register**, so
the pair (its name, our id) belongs there and only there — the other five ITG tables reference a
company, they do not declare one.

**A latent sync defect surfaced doing it, and is fixed.** The delta detector compares the SOURCE
row's hash, which is the right question only while the mapper stays put: adding `Company` to the
mapper left all seven rows short-circuiting as "unchanged" with a blank name, because the source row
itself never moved. Design 11 called for a `transform_version` beside the hash and it had never been
implemented. It now is — folded into the stored hash (`t{n}:`) so it costs no column, with v1 left
bare so a bump churns one table instead of all fifty. **Any future mapper change that writes
something new from an unchanged source row must bump its table's version**, or it will write
nothing and report success.

**UI: nothing is forced.** No new Share drop — the latest is still `Share 260827`, already
absorbed. What changed is the interface *spec* workbook and its previews, not the delivered
prototype.

**2026-09-01 — G10 re-run: the two findings above are closed, and one new divergence class replaces them.** Full corpus re-sync green and idempotent (`sync_run a004222f`), G4 cent-exact (fdchdr net 1,397,190.13 / vat 41,481.41; bnkmov −16,809.41), G5/G8/G9/G11 green, the same two Itoorer BICS files still missing at source. The witness's 9,732 field comparisons now surface exactly two:

- **ITMCLS PLMKEY, 2 of 56 rows** — `CL000057` workbook `Revenue - Intercompany` vs json `Intercompany`; `CL000058` workbook `Services - Advisory` vs json `Services`.

**[FACT]** These are new rows: `itmcls` went 54 → 56 in this sync, and both carry an empty
Subclass with a PLMKEY equal to their Class.

**[CORRECTED 2026-09-01, same day — the analysis first published here was wrong, twice over.]**
It claimed these were "the only 2 of 56" rows whose PLMKEY lacked a composed `X - Y` shape, and
inferred from that that the JSON was incomplete. Both halves fail on inspection of
`itemclass_taxonomy.json`:

- **Three** rows have an empty Subclass, not two.
- **PLMKEY is not derived from Class/Subclass at all.** Thirty of the fifty-six rows cannot be
  composed from them — `Bank Fees / Account Maintenance` carries PLMKEY `Banks - Costs` — because
  PLMKEY is an independent field naming a PLMKO, with its own taxonomy, deliberately shared across
  many rows. The `X - Y` shape common to most values is how PLMKO names happen to read, not a
  composition rule. The inference read a coincidence as a pattern and built a conclusion on it.

**What stands, and what does not.** The divergence itself is observation and stands: the witness
reports workbook `Revenue - Intercompany` / `Services - Advisory` against json `Intercompany` /
`Services` for these two rows. **Its cause is undetermined** — with no composition rule there is
nothing to say which side is wrong, and the earlier text asserted a "JSON-side gap" it had no
basis for.

The witness is still left comparing the field, but now for the honest reason: an unexplained
divergence should stay visible until someone explains it, not be excluded because the exclusion is
convenient. Owner ruling stands that this is resolved in the invoice-skill-build repository; Apollo
carries the rows exactly as the JSON publishes them and builds no workaround.

> Recorded rather than quietly edited because the wrong version was committed (`ed0c726`) and acted
> on: it was cited the next day as precedent for a second, unrelated finding, which compounded it.
> A reader who meets the original claim needs to find its retraction next to it.

**2026-08-29 — owner ruling: Share terms everywhere (D11 revisit authorized).** The six earlier screens were migrated to the Share 260827 vocabulary and filters: **Documents** = "Financial source workspace" with the Financial Documents / Bank Statements tabs (Bank Statements = the bank-movements screen), Upload (→ platform uploads) / Manual Entry (disabled until Phase 2), filters Year / Period / EntityCode / Class-Subclass, columns ID · DocID · Date · Period · Entity · Item · Class/Sub · Local; **Entities** = Entity / Country / Search, columns Code · Short Name · Name · FiscalNo · Country · Curr · Role; **Classification** = the three tabs (Entity Items / Item Classification / Classes) with PLMKO / PLMKEY / SNCACC / Cost Center filters and the prototype's per-tab columns; **Processing** = Review (DPRCHK-routed DOCRQE with the "What is unsettled" / Document ID / Capture Date columns, Company/Year/Period/Section/Detail filters, ProcessingType chips with counts, finding + AI-recommendation detail) and Uploaded (DOCLOG with the prototype's status derivation) modes; **Quality** keeps the governed-catalogue view but with Share labels (Checklist/Section/Relevancy/Detail) — the Findings/History occurrence view is decision 2, still open; **Overview** gained the "Financial document intake" panel (Company/Year/Period + the 6 prototype metrics) and its flow cards now point at Audit/Integration. Render smoke suite stays 27/27.

Also this session: **MaxCompanies proven race-safe by test** (6 concurrent creations against cap 3 commit exactly 3 — `1d4e78a`); Sibyla document drill-down page (header facts, lines with EICodes, doclog trail, persisted bnkrec links, AP/AR control state); Home dashboard with corpus counters + last sync run; cross-tenant overlap probe on entmst (two owners sharing EC999999/'OverlapCo' — each sees exactly its own row; isolation suite now 11 green); real README replacing the GitLab boilerplate.
Sign-in via GOTT.IdentityServer → OWNMST context → licence with Sibyla active (superuser-issued, MaxCompanies enforced on company creation; inactive modules visible as presentation pages only) → companies → user access (all/selected) → storage configuration UI (Nextcloud live; OneDrive/Google Drive visible, disabled) with connection test → **ERP configuration UI per company (Cegid Izibizi + Cegid Primavera selectable/configurable; Moloni/TOC Online visible, disabled) with connection test** → document upload → durable queue → worker runs Claude CLI (subscription) with current FDR skills → validated result persisted tenant-scoped → review/correction screen → processing + transfer status visible → audit trail. First full-corpus sync runs for tenant #1. RLS, query filters, and the cross-tenant attack suite active from the first migration. **Acceptance:** the brief's Definition-of-Success list holds for two tenants with overlapping real-world entities; every cross-tenant attempt fails closed; the whole flow builds, tests, and demos locally on Windows without containers or remote pipelines.

### Phase 2 — Gold build: **Ingestion + Processing + Quality + Audit + Integration** *(D13)*
- **Ingestion:** remaining channel adapters ordered by value (Hermes gateway first; WhatsApp/BUZZ/email as adapters), duplicate control, company-unresolved review routing.
- **Processing:** document persistence completeness on the FDR schema under its governed rules (line completeness with EICode, hammered-line protection, precedence ladder, Master Data Consistency); classification and extraction breadth matching the FDR skill; review-correct-learn loop.
- **Quality:** the FDR's quality-control surfaces (DQLCHK/QUACHK-family) as tenant-scoped screens built from the latest Share version at build time.
- **Audit:** processing evidence, decision traceability, AUDCHK-family surfaces, admin/access audit views.
- **Integration:** live push flows to Cegid Izibizi and Cegid Primavera per company configuration, idempotent via the integration ledger, with status/error visibility and retry.
- Continuous corpus sync re-run at each milestone; OneDrive + Google Drive adapters as capacity allows.
**Acceptance:** each pillar demonstrably works end-to-end on tenant #1's live-synced corpus, with the isolation suite green.

### Phase 3 — Hardening & go-live
Skills-assisted security review on release-blocking axes; failure-mode drills (provider outage, expired auth, partial upload, worker kill/restart, duplicate ingestion, ERP rejection); accessibility + keyboard + responsive Playwright regression; CI gates (manual/milestone GitLab pipelines reusing the local scripts); AWS instance provisioning runbook (IIS site, Windows Service, PostgreSQL, backups, key-ring protection, TLS); **cutover: final freeze-and-sync, reconciliation sign-off, Apollo becomes the system of record**; a stub second module (Medusa shell) mounted through `IApolloModule` proving modules add without touching Sibyla or tenant data.

---

## 6. Risks

1. **Two live-moving inputs** (FDR data daily; prototype daily). Contained by D10 (delta sync with reconciliation) and D11 (build-time snapshot per screen, revisit only on instruction) — but both need version stamps recorded per run/screen so "which state is this built from" is always answerable.
2. **Single-instance production** (one AWS box: IIS + worker + PostgreSQL) is a cost-conscious choice with an availability trade-off — mitigated by backups (base + WAL), a documented restore drill, and health monitoring; a second instance or managed Postgres remains a later option without architectural change.
3. **One-way schema doors:** identity-bearing catalogues (EFCodes, item-class codes) get a single issuing authority (the FDR catalogue) and migration preflight checks from migration #1, so code collisions and re-keys are caught before they harden.
4. **ERP scope from day one** widens Phase 1 — contained by limiting the skeleton to configuration + connection test, with live pushes landing in Phase 2.
5. **Hostile documents / prompt injection** — versioned contracts + a malicious-document test corpus before any non-golden channel opens.
6. **Claude subscription throughput** — CLI-under-subscription has rate/usage limits; the queue's back-pressure and per-tenant caps must treat "Claude unavailable/limited" as a first-class retryable state, never a document loss.
