# Sibyla — Development Plan v6.0

**Status:** proposed, awaiting approval
**Date:** 2026-08-10
**Supersedes:** `docs/project-todo.md` (v5.0 rev. 4) and `docs/project-todo-v4.md` (v4.0)
**Author:** planning pass requested by Miguel — "step back, look at the project as a whole, cut the fat"

> **This document does not authorise implementation.** It is a re-plan. Nothing in it has been
> executed. Read §1 and §3 first; if you disagree with those, the rest does not matter.

---

## 1. Why v6 exists

v5.0 is not wrong. It is too large to finish, and it optimises for the wrong thing.

The evidence, taken from the repository as it stands on 2026-08-10:

**The project has not shipped a user-visible feature since late July.** The last commit that
changed application code rather than schema DDL is `fix: authorize strict WhatsApp LID senders`
(2026-07-24); the last substantive feature work is the Moloni reconciliation and sales pull
(2026-07-23). Everything from 5 August onward is schema DDL and governance records.

**Of the last 25 commits, roughly 20 are `docs:` or `records:` and 4 touch code.** P1-1b — whose
job is "import the reference data" — has been running since 5 August and has never reached
Scope 1. No importer exists anywhere in the repository. Its records file,
`docs/p1-1b-status.md`, is 476 KB and roughly 60,000 words.

**The cost is measurable against a control.** P1-1a ran *before* the per-piece gate structure
existed: 18 files, 9,665 insertions, 1,764 lines of DDL, 601/601 tests green, **2h55 wall clock,
accepted on the first pass**. After the gate structure was introduced, Piece C's entire code
delta was "1 role, 2 new SECURITY DEFINER functions, 1 CREATE OR REPLACE, 2 EXECUTE grants,
0 new tables/columns/triggers/constraints/indexes", and Piece D attempt #2's inventory is
**0 new test methods**. Six candidates were rejected and byte-identically restored. Piece D's
*test plan* — before any code — was independently reviewed twice and corrected twice.

**Meanwhile a working application already exists and is idle.** The July stack is roughly 73,000
lines of Infrastructure plus a Blazor UI with `DocumentUpload`, `DocumentReviewQueue`,
`DocumentReviewDetail`, `Suppliers`, `Companies`, `CegidOperations` and `DocumentTypeSettings`
pages; a Hermes extraction pipeline (`HermesProcessInvoker`, `ExtractDocumentJobHandler`,
`ExtractionResponseValidator`, `PdfTextExtractorProcess`); a cataloging service; a review
service; a Cegid izibizi client with OAuth, purchase push, PDF upload and a 15-minute sales
poller; a Moloni adapter; and a PostgreSQL job engine with leases and dead-lettering. It is
being routed around rather than used.

**The pinned corpus is a moving target, and we are paying to chase it.** The P1-1b preflight
re-proves "49/49 roster blobs, 0 mismatches" against prototype pin `b917685`. In the live FDR
folder today, `flag_evaluation.json` holds 62 rows (the governed set was 45, the pin had 52),
`document_type_rules.json` holds 20, and `data.json`'s `document_log` holds 1,586 rows (the pin
had 1,475). Every hour spent proving byte-identity against a snapshot is an hour spent on data
we have now decided not to import.

**The root cause is a category error.** "Invoice Skill Build" (the FDR) was a POC. Its job was to
discover the table structure and to train the extraction and reconciliation skill. It did that
job, and it did it well. But the project then treated the POC's *data* as a migration target and
its *row counts* as acceptance criteria. That converted a finished discovery exercise into an
open-ended data-engineering programme, and it is what has stalled delivery.

### The one-line correction

> Sibyla's Phase 1 goal is **one document, arriving through a real channel, on a real day,
> ending up in Cegid izibizi** — not 1,475 historical documents ending up in PostgreSQL.

---

## 2. Decisions locked by Miguel (2026-08-10)

| # | Decision | Consequence |
|---|---|---|
| **V6-D1** | **The July stack is the spine; the FDR table structure is the schema.** | The working pipeline stays and is re-pointed onto the FDR tables. The FDR *port programme* stops. |
| **V6-D2** | **First ERP target is Cegid izibizi.** | Primavera, PHC and Moloni move to Phase 2 behind a mechanism spike each. |
| **V6-D3** | **No document data is imported.** Documents enter one at a time through the defined channels, as day-to-day work. Example documents may be seeded for development and testing. | P1-1b Scopes 1–8 are cancelled. All row-count acceptance criteria are withdrawn. |
| **V6-D4** | **Only Layer 2 (Entity Item Classification) and Layer 4 (Auxiliary Calculation) data is imported**, because they are the base of day-to-day operations. | ~4,500 reference rows instead of ~17,000 transactional rows. See §5. |
| **V6-D5** | **Security hardening is deferred.** Agent SOUL/SKILL SHA validation and equivalent controls are not Phase 1 work while agents and skills are still being improved. | See §8 for what is deferred and what is *not*. |

| **V6-O1** | **Verification replaces ceremony.** Pipeline green → merge, with an adversarial pass that comments rather than blocks. The stop list covers *code that governs irreversible behaviour* — not runtime events, which are application features. | Decided 2026-08-10, corrected same day. Detail below. |

### V6-O1 — the review method, decided 2026-08-10

The v5 gate structure (go-ahead → 20-item checklist-to-test mapping → written mapping approval →
TDD → fresh-session adversarial review → byte-identical neutralisation on reject → amendment →
remediation mapping → second approval → new candidate → second review) is **withdrawn in full**.

What replaces it:

**The normal path.** Implement, pipeline green, open the merge request, Miguel merges. No go-ahead
document, no test-mapping pre-approval, no approval round-trip. On a rejected change, **fix
forward** — no byte-identical restoration, no amendment document.

**An adversarial pass on every merge request, as comments — never as a gate.** A second agent
session with fresh context, one prompt, minutes. It exists because agent-written code fails in a
specific way: plausible and confidently wrong about things the agent cannot observe. The pipeline
does not catch that; a fresh reader does. Findings go in as comments, the author fixes what holds
and answers the rest, and the merge is not blocked waiting for a verdict.

**The stop list — code whose merge causes irreversible effects.** Four things need Miguel's
explicit yes before merge:

1. **Destructive migrations** — DDL that deletes or rewrites data when it runs.
2. **The ERP write path** — idempotency keys, `external_reference` derivation, and above all the
   approval gate itself. If this ships wrong, every subsequent human approval pushes wrong data.
3. **Permanent code issuance** — EntryCode, LGCode, the CodeLedger sequences. Codes are never
   reassigned and gaps are never reused; a defect here poisons the ledger permanently.
4. **Pointing a company's configuration at a live tenant** rather than a sandbox. A deploy
   decision, not a runtime one.

Note what these are *not*: they are not "writing to the ERP" or "processing a real document".
Those are runtime events, governed by the application — see below. The stop list is about **code
that changes how and when those events happen**. It is short on purpose and does not grow by
precedent.

#### Two control planes, not one

This distinction was got wrong in the first draft of V6-O1 and is worth stating plainly, because
conflating the two produces nonsense in both directions.

**The code plane — the merge request.** What ships. Governed by V6-O1 and by the pipeline.

**The runtime plane — the application.** What each individual document does. Governed by the flow
organigram: a blocking DOCFLG instance stops the affected action *before it runs* (§9 of the
organigram — financial entry, classification, reconciliation, archive, **external integration**);
the finding persists in the DOCRQE queue; a human decides in the UI (§11); only then do archive
and integration proceed.

A document is never approved by merging a branch, and code is never shipped by approving a
document. The runtime gates are **features already in the backlog** — `S2-12`
(`blocking_open_instances()` wired in from the start), `S3` (the DOCRQE queue and the five
decisions), `S4-2` (*"only a document with no open blocking flags and an explicit human approval is
eligible to push"*). They are built, not administered.

**The twenty-minute rule.** If a review takes longer than roughly twenty minutes, the change is too
large. Split it. Do not review harder. The v5 collapse was not caused by reviewing — it was caused
by reviewing 20-item checklists against 800-line prompts. Small changes make review cheap without
any policy at all.

#### Why this is not "less review"

The v5 apparatus was not irrational. It was built when there was no CI, no running system, and no
processed document — when the only available signal was a human reading a diff. Its reviews found
real defects. The failure was that those defects lived in DDL over tables with no rows and no
reader: `attnum` drift, byte-exact catalog invertibility, deferred-trigger timing. The review
worked; the work should not have existed.

That constraint is gone. `.gitlab-ci.yml` runs restore, Debug and Release builds and the test suite
on every merge request and branch push. S2 adds the golden set — ~30 real documents and one pass
rate, re-run on every skill or model change.

So V6-O1 moves verification **from humans reading diffs to machines running the system**. Ceremony
— go-aheads, mappings, approvals, neutralisations — goes to zero. Verification — tests, pipeline,
golden set, running the thing — goes **up**. These are different axes, and v5 conflated them.

**"Tests green" means a green pipeline, not a local run.** That is what makes the single-pass rule
safe.

#### If this turns out to be wrong

Two signals, both measurable from git. A phase that produces more governance words than code lines
has regressed. A one-page `docs/STATE.md` that starts growing means the problem was never the files.

If a real defect appears — an unstable golden-set pass rate, or something wrong reaching izibizi —
tighten **verification**: more golden documents, contract tests at the ERP boundary. Do **not** add
approval steps. Conceding that is how v5 happened.

#### The one place "proof of concept" does not apply

PoC describes the code, not the data. S4 writes into real companies' accounting, and a document
pushed with the wrong counterparty is an accounting problem, not a PoC bug. That is why the stop
list exists and why it contains exactly those two items — not a concession to ceremony, but the
only place where being wrong is not reversible.

**Reverses if:** a production defect traces to a review that was skipped — and then by tightening
verification first, and the stop list only if verification cannot catch that class of defect.

## 3. What the system is, in one page

Strip the plan back to the loop that has to work every morning:

```
  a document arrives            →  email · WhatsApp · Mattermost · web upload · watched folder
  it is recognised              →  is this a document at all? whose? what type?
  it is read                    →  agent extracts fields; deterministic code validates
  it is classified              →  supplier/customer resolved · lines mapped to accounts
  it is persisted               →  DOCLOG + FDCHDR + FDCDTL, in PostgreSQL, audited
  uncertainty is surfaced       →  flags raised; a human reviews only what is doubtful
  the human's answer is learned →  the correction updates the reference layer
  it is pushed                  →  Cegid izibizi, idempotently, with the original PDF attached
  it is filed                   →  original stored immutably; non-fiscal documents archived
```

Nine steps. Every one of them has code already written for it somewhere in the repository, in the
July stack or the FDR prototype. **Phase 1 is the work of making those nine steps run in sequence
for one document — not the work of building anything genuinely new.**

Steps 6 and 8 carry the control that matters, and it lives **in the application**: a blocking flag
stops the affected action before it runs, the finding waits in a persisted queue, a human decides,
and only an approved document with no open blocking flags becomes eligible to push. That is
`S2-12`, `S3` and `S4-2` — a feature to be built, not a process to be administered, and not to be
confused with the merge-request rules in §2.

Two properties make this loop worth building rather than buying:

- **It learns.** Step 7 is the difference between a document robot and a tool people abandon. A
  supplier resolved by hand once must resolve automatically forever after. The reference layer is
  not static configuration; it is the accumulated memory of every correction. This is why
  Layer 2 is the one thing worth importing.
- **The AI proposes and never decides.** Inherited from v3.2 and kept without change: agents
  read, transcribe and propose; only deterministic .NET code validates, persists, applies
  approvals and executes side effects. This is not a security control — it is what makes the
  system auditable and what lets the skill keep changing without destabilising the ledger.

---

## 4. Architecture — one platform, three modules

Sibyla is the first module of a platform that will also carry CRM agents (Medusa — task manager,
email analysis) and Marketing agents (Tantalus — SEO, content creation, content posting). The
practical risk is that Sibyla's Phase 1 shapes the shared infrastructure so tightly around
documents that the other two modules require a rewrite.

The mitigation is cheap and should be applied now, not later: **name the shared kernel, and keep
document-specific concepts out of it.**

| Shared kernel | Already exists as | Used by |
|---|---|---|
| Job engine — queue, claim, lease, heartbeat, retry, dead-letter | `PostgresJobQueue`, `JobDispatcherHostedService` | all three |
| Audit — append-only, trigger-enforced | `EfCoreAuditWriter` + PostgreSQL trigger | all three |
| Identity, companies, memberships, departments | `Sibyla.Domain/Identity`, `Companies` | all three |
| **Entity registry — ENTMST/ENTALS** | FDR Layer 2 | Sibyla **and Medusa** (a supplier is a CRM contact) |
| Channel intake — email, WhatsApp, Mattermost | `ChannelIntakeService`, `sibyla.channel-intake.v1` | Sibyla **and Medusa** |
| Agent runtime — staged invocation, per-job I/O, timeouts, limits | `HermesProcessInvoker`, `WindowsProcessLimits` | all three |
| Contract validation — strict schema boundary on agent output | `ExtractionResponseValidator` pattern | all three |

Three rules that cost nothing today and save a rewrite later:

1. **The entity registry is platform-level, not Sibyla-level.** ENTMST is the single record of who
   a counterparty is. Medusa's contacts and Tantalus's audiences point at it; they do not copy it.
2. **The job engine takes a payload it does not understand.** No document-shaped fields on `Job`.
3. **Channel intake produces a generic "something arrived from someone on some channel" record.**
   Routing to a module is a separate decision. A WhatsApp message may be an invoice (Sibyla) or a
   client request (Medusa); the intake layer must not assume.

Beyond those three rules, **no work on Medusa or Tantalus in Phase 1**, and no abstraction built
in anticipation of them. Their brainstorming and design material already exists in
`Itoorer Master - S02 - Medusa - CRM Task Manager` and `GOTT.Marketing.Agent` and stays there.

---

## 5. Data — what is imported, and what is not

Per V6-D3 and V6-D4, using the FDR layer map (`Specs/Data Schema/layer-map.md`).

### Imported — Layer 2, Entity Item Classification (~4,490 rows)

The base of day-to-day operations: without it, every document arrives at a system that knows
nobody and nothing.

| Table | Source file | Rows |
|---|---|---|
| ENTMST — entity master | `entities.json` | 119 |
| ENTALS — entity aliases | `entals.json` | 7 |
| ITMMST — item master | `productmaster.json` | 391 |
| ITMALS — item aliases | `itmals.json` | 4 |
| ENTITM — entity × item classification | `entity_products.json` | 327 |
| ENTBNK — entity bank accounts | `entbnk.json` | 47 |
| ITMCLS — item classes | `itemclass_taxonomy.json` | 51 |
| MNGACC — management accounts | `itemclass_mngacc.json` | 34 |
| SNCACC — SNC chart of accounts | `sncacc.json` | 3,509 |
| COCACC — cost centres | `cocacc.json` | 1 (placeholder) |

### Imported — Layer 4, Auxiliary Calculation (75 rows)

| Table | Source file | Rows |
|---|---|---|
| EXCRAT — period-end FX rates | `exchangerates.json` | 75 |

### Imported as configuration, not as data (~82 rows)

These are rule catalogues, not transactional records. Without them the pipeline cannot classify
or flag anything, so they are seeded as configuration — versioned in the repository, editable
through the admin UI, and **not** subject to import-provenance machinery.

| Table | Source file | Rows | Note |
|---|---|---|---|
| DOCTYP — document-type rules | `document_type_rules.json` | 20 | one row is uncoded; seed the 19 coded rows |
| DOCEFL — evaluation flag rule catalogue | `flag_evaluation.json` | 62 | grew from 45 → 52 → 62 since the pin |
| CodeLedger high-water marks | `permanent_code_ledger.json` | 7 buckets | prevents newly issued codes colliding with FDR codes if history is ever loaded |

> **Flagged for confirmation.** Miguel's instruction named Layers 2 and 4 only. DOCTYP and DOCEFL
> are in Layer 1 but are configuration rather than document data, and the pipeline is inert
> without them. v6 assumes they are seeded. Say if that is wrong.

### Not imported (~17,000 rows, and everything attached to them)

`document_log` (1,586) · `header` (484) · `details` (930) · `receivable_header` (245) ·
`receivable_details` (268) · `flag_instances` (3,184) · `docrqe` (2,752) · `bnkmov` (2,034) ·
`bnkrec` (2,146) · `bnkchk` (168) · `recrev` (1,263) · `payctr` (484) · `paydtl` (195) ·
`rcvctr` (245) · `rcvdtl` (161) · `entblc` (959) · `payroll` (60) · `bnk_generated_documents` (440)

Withdrawn together with that data, because each exists only to serve it:

- The five data-quality baselines **119 / 119 / 52 / 221 / 2,787** and their tracking items
- The **55.9% ± 0.1%** reconciliation baseline, the six candidate definitions behind it, and the
  standing caution about comparing it to the prototype's 94.6%
- **Grandfathering** — `EnforcementStartsAt` seeding, `IsGrandfathered`, the immutable reject
  trigger, and the TC5 grandfathering test. With no historical flag instances, nothing needs
  grandfathering; rules simply apply from go-live
- The **pinned-blob roster preflight** (49/49 blobs, 48/48 files, byte-identity proofs)
- `ImportBatch`, `ImportEvidenceRow`, the `ImportedHistory` evidence mode, and USEROBS lossless
  import with per-ordinal Pending targets
- The C8 duplicate-ENTBNK merge, the O8/O9/O10 completeness governance records, and the
  fail-closed-on-unlisted-vocabulary import rule

Layer 2 and Layer 4 rows are loaded by a plain idempotent seeder — read JSON, upsert by natural
key, log what changed. It re-runs safely. It is not a migration programme.

---

## 6. Phases

Durations assume V6-O1 as decided, one to two developers with agent assistance, and are
**estimates, not commitments**. They exist so that a phase running at triple its estimate is
visible as a signal rather than absorbed silently.

### S0 — Stop, ground, restart · ~3 days

Establish what actually works today, on a real machine, with real eyes on it.

- Close P1-1b. Piece D attempt #2 merges as-is if tests are green, or is abandoned. **No further
  independent review cycle.** The three accepted migrations (`P11bPieceA/B/C`) stay in the
  database — they are additive and paid for.
- Decide V6-O1.
- Stand up a dev environment and run the July stack end-to-end: upload one real invoice PDF
  through `DocumentUpload.razor`, watch it through `ExtractDocumentJobHandler`, and record what
  happens at each of the nine steps in §3.
- Write **one page** — not a status file — naming which of the nine steps work, which are broken,
  and which are missing. This replaces `PROJECT-STATE.md` as the live status document.
- Archive the process corpus: move the 31 `AGENT-PROMPT-v5-P1-*.md` files, `p1-1b-status.md`,
  the `p1-0-*.md` set and `project-todo*.md` to `docs/archive/`. They stay in git history and
  remain readable; they stop being load-bearing.

**Exit:** a one-page honest picture of the nine steps, and a decided review method.

### S1 — Reference layer live · ~1 week

Worth stating plainly, because it reframes what P1-1a and Pieces A/B/C actually bought: **every
table this phase needs already exists in the database.** `P11aSchemaSql.cs` creates ENTMST,
ENTALS, ITMMST, ITMALS, ENTITM, ENTBNK, ITMCLS, MNGACC, SNCACC, COCACC, EXCRAT, DOCTYP, DOCEFL,
DOCFLG, DOCLOG, DOCRQE, FDCHDR, FDCDTL and DOCARC. The schema work was not wasted — it was
finished in one afternoon on 5 August. What never happened is anything putting data in it or
reading it. S1 is a seeder and a screen, not a migration.

- Idempotent seeder for the eleven Layer 2 + Layer 4 tables in §5.
- Configuration seed for DOCTYP, DOCEFL and the CodeLedger high-water marks.
- Admin UI over the reference layer: browse, search, edit, create. Extend the existing
  `Suppliers.razor` rather than building new. Entities and aliases first; items, item classes and
  accounts second; FX rates third.

**Exit:** an operator can search "CTT" in Sibyla and see the entity, its aliases, its bank
accounts, its usual items and the accounts those items post to. Re-running the seeder changes
nothing.

### S2 — Single-document intake on the FDR schema · ~3 weeks

The largest phase, and the one that converts the FDR port from a programme into a feature.

**The recommended shape — two records, not one.** Rather than rewriting the July services onto
the FDR tables, split the responsibilities:

- `Document` / `ExtractionRevision` / `FileAsset` (July) remain the **intake record**: the file,
  its hash, its storage location, its extraction attempts, its raw agent output.
- `DOCLOG` / `FDCHDR` / `FDCDTL` (FDR) become the **registry record**: the accounting truth,
  keyed by `LGCode` on `(Filename, EntryCode)`.
- One `Document` maps to one `DOCLOG`. The cataloging service is the bridge.

This keeps the upload, storage, hashing, duplicate detection, agent invocation, contract
validation and PDF text extraction code exactly as it is — all of it already works and none of it
is document-registry-specific — and confines the change to `DocumentCatalogingService` (237
lines) and what it writes. The alternative, rewriting every July service onto FDR tables
directly, is larger and buys a purity the operator never sees.

Work:

- Re-point `DocumentCatalogingService` to write DOCLOG + FDCHDR + FDCDTL transactionally.
- Classification against the DOCTYP rules, replacing the `FinancialDocumentType` enum path.
  `Treatment` (Include/Exclude) and `ProcessingRoute` (PostFiscal/ArchiveOnly/ReferenceOnly/
  Review) decide where the document goes.
- Entity resolution at write time against ENTMST/ENTALS, by NIF then by alias then by name.
  Provisional entities (`[ISO2]-SYN-######`) when nothing matches.
- The `counterparty_not_internal_company` gate — a supplier must not be one of our own companies.
  This is the class of error the Tranquilidade case exposed
  (`docs/document-cataloging-remediation-plan.md`) and it is worth fixing properly here.
- Line classification against ENTITM → ITMCLS → MNGACC → SNCACC.
- FX via EXCRAT when the document is not in EUR.
- DOCEFL rule evaluation producing DOCFLG instances; `blocking_open_instances()` wired in from
  the start, not applied by discipline.
- `NOT_A_DOCUMENT` routing and byte-identical resubmission auto-discard (both already partly
  built).
- A seeded example set of roughly 20–30 real PDFs across purchase, sale, credit note, receipt,
  foreign-currency, multi-page and unreadable-scan cases, used as the **golden set** for skill
  quality — a regression harness, not a migration target. Any change to the skill or the model
  re-runs it.

**Exit:** drop one real invoice PDF into the web upload. A DOCLOG row, an FDCHDR row and FDCDTL
lines appear, with the supplier resolved, the lines mapped to accounts, and flags raised on
whatever was uncertain. Do it for all 30 golden documents and record the pass rate. **That
number, not 55.9%, becomes the project's quality metric.**

### S3 — Review, correct, learn · ~2 weeks

- DOCRQE as the persisted review queue over real DOCFLG instances, severity-ordered.
- Review UI: extend `DocumentReviewQueue.razor` and `DocumentReviewDetail.razor` to the per-field
  view and the five decisions — Accept · Reject · Accept with Changes · Need More Information ·
  Defer.
- **The learning path, which is the point of this phase.** "Accept with Changes" on an entity or
  item writes back to Layer 2 — a new ENTALS alias, a new ENTITM mapping, a corrected account.
  The next document from the same supplier resolves without a human.
- Comment log on the document (already built, keep).
- Discard and purge with tombstones.

**Exit:** an operator corrects a mis-resolved supplier once; the next document from that supplier
is resolved automatically, with the alias visible in the admin UI. Measure the golden-set pass
rate before and after ten corrections.

### S4 — Cegid izibizi push · ~2 weeks

- Re-point `IntegrateDocumentJobHandler` and the Cegid purchase writer from `Document` to
  FDCHDR/FDCDTL. The API client, OAuth, product-code derivation, `external_reference`
  idempotency, PDF upload and `WriteUnknown` handling stay as built.
- Approval gate: only a document with no open blocking flags and an explicit human approval is
  eligible to push.
- Sales pull poller re-pointed to the registry tables.
- Operational prerequisites, which are blockers and need owners now: the shared Data Protection
  certificate and key ring; per-company izibizi credentials; the fiscal-year subentity
  (`to_subentity_id`) for each company; a DPAPI token cache initialised under the worker's
  Windows account.
- Decide draft vs closed on `supplierInvoices/insert` — deferred since July, and now on the
  critical path.

**Exit:** an invoice uploaded on Monday morning is in Cegid izibizi by Monday afternoon, with its
PDF attached, its `external_reference` recorded, and re-pushing it creating nothing.

### S5 — Channels · ~2 weeks

The loop works; now let documents arrive the way they actually arrive.

- Configure Hermes `orquestrador` (Apolo) to call `sibyla.channel-intake.v1`.
- Email intake — both forms, including the safe link downloader with a domain allowlist.
- WhatsApp via Baileys, including the LID and E.164 dual-alias authorisation.
- Mattermost.
- Admin UI for users, companies, departments, memberships, channels and allowlists.
- `HERMES_HOME` backup including the Baileys session.

**Exit:** forward an invoice from a phone by WhatsApp; it appears in the review queue within a
minute, attributed to the right company and the right sender.

### S6 — Archive and non-fiscal documents · ~2 weeks

- Nextcloud/WebDAV archive wired to the pipeline (client foundations already exist).
- DOCARC for non-fiscal documents: bank statements, leases, insurance policies, employment and
  supplier contracts, licences, financing agreements. `ProcessingRoute = ArchiveOnly` — these are
  filed and findable and **never reach FDCHDR**.
- Legal path `{Company}/Legal/{Subtype}/{CounterpartyCodeName}/`.
- Periodic PostgreSQL ↔ Nextcloud reconciliation.

Note this absorbs `docs/general-document-archive-scope-plan.md` entirely. Its `DocumentFamily` /
`CorporateDocumentType` proposal is superseded by DOCTYP + `DocClass` + `ProcessingRoute` +
DOCARC, which already do the same job. Its genuinely new asks — `EffectiveDate` / `ExpiryDate` /
`RenewalDate` / `NoticePeriodDays` / `AutoRenews`, expiry alerting, full-text search — are Phase 2.

**Exit:** a lease PDF arrives by email, is archived under Legal, is findable by counterparty, and
produced no fiscal entry.

### S7 — Pilot · 4 weeks running

- One company. Real documents, every day, through real channels, by the people who will use it.
- Durability minimum, and no more: expired-lease recovery, dead-letter alerting and replay,
  off-host backup with one tested restore, one worker-kill drill, a small metrics set.
- Spot-check a sample against the ERP weekly.
- Keep one list: what broke, what a human had to fix, what the system should have known.

**Exit:** four weeks of daily use. **That list — not the v5 backlog — determines Phase 2.**

### Rough shape

| Phase | Estimate | Cumulative |
|---|---|---|
| S0 Stop, ground, restart | 3 days | 0.5 wk |
| S1 Reference layer | 1 wk | 1.5 wk |
| S2 Single-document intake | 3 wk | 4.5 wk |
| S3 Review, correct, learn | 2 wk | 6.5 wk |
| S4 Cegid izibizi push | 2 wk | 8.5 wk |
| S5 Channels | 2 wk | 10.5 wk |
| S6 Archive | 2 wk | 12.5 wk |
| S7 Pilot | 4 wk running | ~16 wk |

Roughly four months to a company running its daily document flow through Sibyla into izibizi.
Against v5.0's eleven phases before the pilot, with no estimates attached to any of them.

**S2 and S4 are the ones that matter.** If everything else slips and those two land, the project
has a product. The reverse is not true.

---

## 7. What is cut, and what would bring it back

Nothing here is being called worthless. Each item is deferred with the condition that would
justify reinstating it, so this is a decision that can be revisited on evidence rather than
re-argued from scratch.

| Cut / deferred | Why now | Reinstate when |
|---|---|---|
| **Bulk import of the FDR corpus** (P1-1b Scopes 1–8) | Locked by V6-D3. The POC's data was never the deliverable | An accounting or legal requirement to hold 2026 history *inside* Sibyla appears. The FDR workbook remains the record until then |
| **Bank reconciliation, Layer 5** (BNKMOV/BNKCHK/BNKMAT/BNKREC/RECREV, 11 governed match types, statement parsers, Via Verde, Stripe tolerance) | v4.0 had it in Phase 2 and v5.0's D3 pulled it forward. It is a second product, and it needs posted documents to reconcile against — which S2–S4 produce. Statements are still **archived** from S6 | S7 pilot has produced a few months of real posted documents. It is then a Phase 2 phase of its own, and the FDR matchers port across largely intact |
| **Layer 6 ground truth** (PAYCTR/PAYDTL/RCVCTR/RCVDTL, `ControlGoverningPeriod`, `UpsertControlSnapshot`, the SQLSTATE 23514 period-regression trigger) | Exists mainly to feed the bank matchers | With Layer 5 |
| **Grandfathering machinery** | Nothing historical to grandfather | With any historical import |
| **Multi-provider-per-company + Excel-as-provider** (0..N connections, `WorkbookDefinition`, per-flow toggles, `SalesOut`, RF6 divergence detection) | `SalesOut` has no consumer by the plan's own admission; Excel-as-provider was designed for an `ExcelCommit` critical path that v4.0 already declares superseded. Keep the one-provider-per-company selector that works | A second live ERP is in production for a company that also needs the first |
| **Moloni activation** | Blocked on the client's API subscription and developer credentials; no attachment upload endpoint | Credentials arrive. The adapter is built and parked; this is a business blocker, not an engineering one |
| **Primavera / PHC** | No integration mechanism researched. Both are commonly integrated over SQL Server, ODBC or file exchange rather than REST, so the current HTTP-adapter abstraction may not fit at all | A customer commitment exists. **Do a two-day mechanism spike before promising anything** — this is the largest unpriced risk in the roadmap |
| **Excel export parity** (P1-12, byte-comparable output against the 31-sheet prototype workbook) | Parity with the prototype is what forces verbatim column spellings and load-bearing hyphenation through the entire schema. A plain on-demand export costs a fraction | Someone's actual work depends on that exact workbook |
| **The per-piece review ceremony** | §1 and V6-O1 | A production defect traces to a review that was skipped |
| **`SourceKeyHash` generated column + `pgcrypto` fail-closed preflight** | Premature optimisation for a btree index limit at ">roughly 37 movements" in a pay run — a Layer 5 concern | With Layer 5 |
| **The matcher governance triad** (`MatcherProposal`, `RejectedMatcherConstraint`, `RejectPreviouslyRejectedProposal`) | Governs matchers that do not exist yet | With Layer 5 |
| **`ArchiveNameLedger`** with per-context non-reusable sequences and fail-closed overflow | S6 can name archived files without a ledger | Filename collisions actually occur |
| **Deep Legal/Procurement metadata and alerting** (`EffectiveDate`, `ExpiryDate`, `RenewalDate`, `NoticePeriodDays`, `AutoRenews`, expiry jobs, full-text search) | S6 files and finds them; alerting is a further step | S7 shows people want contract expiry alerts — likely, and cheap once archiving works |
| **`docs/document-cataloging-remediation-plan.md` Phase 3** (`SupplierExtractionProfile`, hint transport, `OperatorHintJson`, reprocessing path) | Written against the superseded `sibyla.extraction.v3.1` / `hermes -p documental-agent` stack. Its *root cause* — supplier resolution gated behind `Direction != Unknown` — is fixed directly in S2 | The golden set shows per-supplier hints are the remaining gap after S2 |

### Security — deferred, and the line

Per V6-D5, hardening waits. Specifically deferred: agent SOUL/SKILL SHA validation (which the
docs already de-escalate — "an exact checksum match is not a handoff or installation gate");
re-running the full security acceptance battery on every change; the API-key secret store; egress
policy; cost caps and usage alerting.

**Not deferred, because they are architecture rather than hardening and are expensive to retrofit:**

- The AI-proposes / code-decides boundary (§3). This is not a security control.
- Per-job input and output directories for agent invocation — already built, and it is also how
  concurrency works.
- Append-only audit — already built and trigger-enforced.
- Company scoping on every document. The shared Gott/Itoorer inbox is a documented hazard, and a
  document attributed to the wrong company is a correctness bug, not a security bug.

**O5** — the restricted-token / service-identity validation on the target host, owner Miguel,
carried since v3.2 — stays open and stays a **production gate**, not a development gate. Nothing
in S0–S6 waits on it. It must close before S7 runs on real company data. The preparation scripts
already exist and have been reviewed four times; they need one session on the target host.

---

## 8. How we work from here

Six rules, replacing the current process corpus.

1. **One status document, one page.** Created in S0, updated in place. If it exceeds two pages it
   is being used as a diary and needs cutting. `PROJECT-STATE.md` and `p1-1b-status.md` go to
   `docs/archive/`.
2. **A phase is done when its exit criterion is demonstrated, not when its checklist is ticked.**
   Every exit criterion in §6 is a thing you can watch happen on a screen. Demonstrate it to a
   person.
3. **Plan documents are capped at the phase you are in, plus a sketch of the next.** v5.0 planned
   fourteen phases in detail and delivered one. Detail beyond the next phase is a liability — it
   is written from assumptions the current phase is about to invalidate.
4. **Verification replaces ceremony** (V6-O1, decided 2026-08-10). Pipeline green → merge, with
   an adversarial pass that comments rather than blocks. Four things need an explicit yes:
   destructive migrations, the ERP write path, permanent code issuance, and live-tenant
   configuration — *code that governs irreversible behaviour*, never runtime events, which are
   application features. Fix forward. A review past twenty minutes means the change is too large.
5. **The golden set is the quality metric.** ~30 real documents, run on every skill or model
   change. One number, honest, trending. It replaces the five data-quality baselines and the
   55.9% reconciliation figure.
6. **Decisions are recorded in one table, in one file, one line each** — decision, date, reason,
   what would reverse it. Not one document per decision. The v6 decision table in §2 is the
   start of it.

### First actions after approval

1. ~~Decide V6-O1.~~ **Done 2026-08-10** — see §2.
2. Close P1-1b — merge or abandon Piece D, no further review cycle.
3. Archive the process corpus to `docs/archive/`.
4. Run one real PDF through the July stack and write the one-page picture.
5. Name owners for the two izibizi prerequisites — the Data Protection certificate and key ring,
   and the pilot company's credentials. Defined in `docs/deployment/izibizi-activation.md`; both
   are week-1 work with no engineering dependency.

---

## 9. Risks

| Risk | Assessment |
|---|---|
| **The July stack is more broken than it looks.** It has not run since 23 July and the schema has changed underneath it (`P11aFdrSchema`, three Piece A/B/C migrations) | This is why S0 exists and why it is three days rather than an afternoon. If S0 finds the July stack unrecoverable, the hybrid option — a thin new pipeline on the FDR tables — comes back on the table and S2 roughly doubles |
| **Cegid izibizi activation needs host provisioning nobody owns yet** | Downgraded 2026-08-10. The secret-protection *code* shipped in July (`5f679a5`); the account is active with API access; the pilot is one company. What remains is a certificate, a key-ring directory and two config values — week-1 work, not a schedule risk. It stays on the list only because B1 blocks B2 and nothing else can start until someone owns it |
| **The extraction skill regresses when re-pointed to the FDR classification model** | The golden set exists precisely to make this visible within hours instead of weeks. Build it early in S2, not at the end |
| **Layer 2 data is stale.** It comes from a POC last touched in early August, and the FDR folder has kept moving | Accept it. It is reference data that operators correct as they work, and S3's learning path is exactly the mechanism for correcting it. Do not build a sync-with-FDR process |
| **Primavera / PHC turn out to need a fundamentally different integration model** | Real, and unpriced. The `Integration*` neutral core assumes an HTTP adapter shape. Spike before committing to a customer |
| **The process discipline that produced the ceremony reasserts itself** | The gate structure was a rational response to a real quality problem. Cutting it without replacing it invites the defects back. §8 rules 2, 4 and 5 are the replacement: demonstrated exits, scoped reviews, one honest metric. If quality drops, tighten those — do not restore the neutralisation ritual |

---

## Appendix A — v5.0 phase disposition

| v5.0 | Phase | v6.0 |
|---|---|---|
| P1-0 | Contract and Schema Design Freeze | **Done.** Kept as reference; sign-off carve-outs C9/C11/C12/C15 closed by V6-D3 |
| P1-1 | Schema Migration and Reference-Data Seed | **Split.** P1-1a + Pieces A/B/C kept as shipped DDL. Scopes 1–8 cancelled (V6-D3). Reference seed → **S1** |
| P1-2 | Claude CLI Runtime | → **S2**, minus the O5 gate, which stays a production gate |
| P1-3 | Production Skill v1 | → **S2**, with the golden set as its acceptance |
| P1-4 | Deterministic Pipeline | → **S2** |
| P1-5 | Flag Governance | → **S2**, minus grandfathering |
| P1-6 | Review Queues | → **S3** |
| P1-7 | Decision-Application Engine | → **S3**, reduced to the learning write-back path |
| P1-8 | Reference-Layer Management UI | → **S1** |
| P1-9 | Review and Document UI | → **S3** |
| P1-10 | Archive (Nextcloud) | → **S6** |
| P1-11 | Bank Movements and Reconciliation | **Deferred to Phase 2** (back where v4.0 had it) |
| P1-12 | Excel Export | **Deferred.** Plain export only if asked for |
| P1-13 | Channels, Durability, Pilot | → **S5** + **S7** |
| — | ERP push | **New in Phase 1 → S4.** v5.0 had no ERP work in Phase 1 at all. This is the largest single correction in v6 |

> The last row is worth pausing on. v5.0 placed all ERP integration in Phase 2, which meant the
> stated core loop — document arrives, is catalogued, reaches the ERP — could not be completed by
> Phase 1 by construction. Eleven phases of registry work stood in front of a leg that was never
> scheduled. That, more than any individual piece of over-engineering, is why the project felt
> like it was moving away from delivery.

## Appendix B — documents superseded or archived by v6

**Superseded:** `docs/project-todo.md` (v5.0) · `docs/project-todo-v4.md` (v4.0) ·
`docs/general-document-archive-scope-plan.md` (absorbed into S6) ·
`docs/document-cataloging-remediation-plan.md` (root cause → S2; Phase 3 deferred) ·
`docs/multi-provider-per-company-plan.md` (deferred)

**Archive to `docs/archive/`, keep in git history:** the 31 `AGENT-PROMPT-v5-P1-*.md` files ·
`docs/p1-1b-status.md` (476 KB) · `docs/p1-2-o5-prep-status.md` · `docs/PROJECT-STATE.md`
(128 KB) · the 14 `docs/p1-0-*.md` files except the schema mapping · the 4 `docs/p1-1a-*.md`
files · both `HANDOFF-*.md` files · `docs/p1-0b-status.md` through `p1-0e-status.md`

That is roughly 55 documents and over 800 KB of governance record moved out of the working set.

**Keep live:** `AGENTS.md` (trim to the AI-proposes/code-decides boundary and the repo
conventions) · `docs/p1-0-schema-mapping.md` — the schema reference, accurate and genuinely
useful, and the main thing P1-0 produced that survives · `docs/p1-0-vocabularies.md` (the
controlled domains it generates are still what the CHECK constraints are built from) ·
`docs/cegid-integration.md` · `docs/moloni-integration.md` · `docs/deployment/*` ·
`docs/hermes-*-agent-instructions.md` — the extraction and Portuguese fiscal-QR rules are good
work and port to the new engine; the engine around them is changing · this document

**Needs correcting rather than archiving:** `docs/cegid-integration.md` still describes the
pre-refactor Cegid-specific ledger and a "Moloni not implemented" placeholder, both superseded by
the `Integration*` neutral core. It is the operational doc for the S4 target, so it should be
brought current at the start of S4 rather than left to mislead.
