# Sibyla Project TODO — Development Plan v5.0 (FDR + Claude CLI)

Updated: 2026-08-04 (rev. 4) — supersedes v4.0 (2026-07-29, rev. 2).

> **rev. 4 correction note — current as of 2026-08-05.** C1–C19 are decided and applied.
> P1-0d and P1-0e closed the remaining seed, governance, nullability, activation, and repository-
> hygiene items; P1-0 is accepted and fully closed. The six corrected P1-0 drafts are operative
> field-level authority under the documented precedence: `docs/p1-0-corrections.md`, the group
> decisions and sign-off, and the generated `docs/p1-0-vocabularies.md` annex govern where their
> respective higher-precedence decisions or literals apply.
>
> **`docs/PROJECT-STATE.md` is the only authority for current project state.** This rev. 4 plan is
> the execution backlog and historical record; it does not supersede that state file. If any state
> statement here or in another dated review, correction register, sign-off, status report, letter,
> or agent prompt conflicts with `docs/PROJECT-STATE.md`, the state file wins and the other
> statement is stale. Report the contradiction rather than carrying it forward.
>
> Figures below are re-measured at prototype commit `9359c67` (re-confirmed at `6146004`) unless
> marked otherwise. **19 documents are queued and uningested at that pin and will move most of
> them** — re-measure rather than quoting.

This backlog merges two documents:

- **v4.0** (`docs/project-todo.md`, 2026-07-29) — the .NET production harness plan
  written by Miguel Teixeira.
- **FDR Stage 8 R2–R14, Stage 9 R1–R9, and Stage 10 R1–R6** (30 Jul – 3 Aug 2026) —
  built by Luís Nascimento in `Itoorer Master - Invoice Skill Build`.

v4.0 was issued the day before Stage 9 began, so none of the governance machinery
Luís built that week is visible to it. v5.0 folds that work in, resolves the three
places where the two designs contradicted each other, and pulls FDR Layer 5 (bank
movements and reconciliation) forward out of Phase 2.

The v5.0 centre of gravity is unchanged: the FDR's table model becomes the PostgreSQL
schema, its corpus-validated rules become the deterministic gates, and its skill —
adapted to single-document contract mode — runs on headless Claude CLI invoked by the
Sibyla Worker. The .NET application remains the production harness: channels, storage,
queueing, identity, audit, review UI, and export.

Input channels are unchanged (email/WhatsApp/Mattermost via Hermes Gateway/Apolo, plus
web upload). Physical documents are archived in Nextcloud. Excel is an on-demand
export, never a continuous projection.

Status legend:

- `[x]` Done and still valid for v5.0
- `[~]` In progress in the current working tree
- `[ ]` Missing or not started
- `[S]` **Specified and working in the FDR prototype; .NET port not started** (new in v5.0)
- `[!]` Owned engineering work or a standing caution — **never an open decision**. As of rev. 3 every decision in this plan is taken; each `[!]` names its owner.

---

## Decisions Taken for v5.0

Three forks were open between the two documents. All three are now settled and the
backlog below reflects them.

**D1 — DOCRQE is a persisted queue, not a live view.** v4 P1-4 specified DOCRQE as a
recomputed severity-tiered view, "never a capture-forward snapshot." Stage 9 R7
converted it to a persisted table with permanent ReviewIDs, never-delete semantics, and
human decision state that survives a full pipeline rerun. **v5 adopts the persisted
model.** Luís built it that way because recomputation destroyed reviewer work between
rounds; a live view cannot carry a ReviewID for a decision engine to cite, and cannot
record that a rejected proposal must never be re-proposed. Severity tiering survives as
*ordering* over the persisted table. The same model governs RECREV.

**D2 — DOCLOG orphan cleanup happens on the FDR side, before import; LGCode is a
definition question, not a defect.** *(Revised 2026-08-03 after analysing the data — the
original wording of this decision was based on a misreading and is corrected here.)*

Two separate things were conflated. **The LGCode "violation" is not one.** Across all
1,466 DOCLOG rows there are 954 distinct filenames and 954 distinct LGCodes — a perfect
1:1 bijection, with zero filenames mapping to more than one code and zero codes mapping
to more than one filename. LGCode identifies a captured **file**, not a **document**. An
invoice PDF yields one document, so the two coincide and the distinction was invisible;
a bank statement yields dozens of Payroll/Financing/NoDocMov documents that correctly
share their source file's code. All 544 rows sharing an LGCode are `Source = BNK`. The
data is internally consistent — what was mis-specified is the *rule*, generalised from
the invoice case. P1-0 had to decide whether LGCode stayed a capture identifier (with a
separate per-row `DocLogId` as DOCLOG's permanent key) or became a document identifier.
**It became the latter** — see the LGCode entry below and P1-0. The 954 ↔ 954 bijection
quoted above is the pre-re-key reading and is now historical.

**The rows are not one population, and a blanket delete would have destroyed evidence.**
Luís took the set apart on 3 Aug at 10:05 and found three distinct things: **89 rows are
retired identities** — P/F/O EntryCode is a per-period sequence number assigned at
generation time, so re-ingesting an overlapping bank statement renumbers BMCodes and
regenerates the same conceptual rows under new numbers, with append-only DOCLOG keeping
one row per generation; **18 rows were a future-dated loan schedule**, correctly removed
because the registry records what happened, not what is scheduled; and **27 rows carried
a real financial gap** — 2026 Jan–Jun payroll had no FDCHDR row at all, €142,835.50 of
salaries, IRS and TSU matched to a *ledger* reference and therefore invisible to every
reconciliation metric. He rebuilt those entries the same morning. Treatment differs per
population, and the originally proposed blanket delete would have erased the only
surviving trace of the third.

**The real defect underneath all of it was identity, and it appeared three times — all
three are now CLOSED (Luís, 3–4 Aug).**

- **P/F/O (his Roadmap 35).** `BMCode`'s key included `SourceFile`, a field the pipeline
  deliberately *rewrites in place* so BNKCHK's `(BankAccount, SourceFile)` join survives an
  archive rename — two keys for one thing, one of them moving. `SourceFile` was dropped from
  the key in both assigners, and the three `create_bnk_generated_*.py` scripts were
  re-anchored from BMCode onto the movement's natural key via `generated_source_index()`,
  which backfills a `SourceKey` on every generated row. **Proof: all 1,960 BMCodes renumbered
  and every SourceFile changed — 426 of 426 generated documents re-anchored, 0 duplicates.
  Under the old rule 0 of 426 would have been recognised and EUR 639,943.78 silently
  regenerated.**
- **LGCode (his Roadmap 36).** Re-keyed on `(Filename, EntryCode)` — verified unique first,
  refuses to run otherwise — issuing 512 fresh codes and migrating DOCFLG's 509 references in
  the same transaction. **1,475 DOCLOG rows now carry 1,475 distinct LGCodes.** So LGCode
  *became* the per-row identity; no separate `DocLogId` is needed.
- **PAYCODE / RCVCODE (his Roadmap 29).** `verify_code_identity_stability.py` runs on every
  pass: it shuffles the rows, blanks every code, reassigns from the ledger alone and requires
  that not one code moves. **Result: 0 of 481 PAYCODEs, 0 of 245 RCVCODEs, 0 of 119
  EntityCodes, 0 of 1,475 LGCodes and 0 of 1,960 BMCodes move on a cold rebuild with shuffled
  row order.**

The .NET side inherits proven natural keys rather than having to invent them. See P1-0.

**D3 — FDR Layer 5 moves into Phase 1.** Bank movements, reconciliation, and the RECREV
queue are no longer parked. The prototype is materially further along than v4 assumed,
and keeping it in Phase 2 would fork the queue model, the flag machinery, and the
review UI between the document side and the bank side. See the scope note immediately
below.

**D4 — Import the full 2026 history plus intact reference data; 2025 stays out.** P1-1
seeds from the prototype: DOCLOG, FDCHDR/FDCDTL and the Layer 5 tables for 2026 as opening
balances, together with the complete reference layer (ENTMST, ENTALS, ITMMST, ITMALS,
ENTITM, ITMCLS, MNGACC, SNCACC, COCACC, EXCRAT, DOCTYP rules, DOCEFL rules, DOCFLG
instances, `user_observations.json`, `reference_only_documents.json`). The prototype's
2026-only scope rule carries into the import. Consequence accepted: the 2025 leg of the
related-party stream does not come, so the Toorist/Itoorer position cannot be closed
across years until a later full-history round — that is a known, bounded gap, not an
oversight. Consequence gained: the .NET system starts against a governed, reconciled year
and P1-12's export parity can be checked against a workbook that actually exists.

**D5 — `Reference Only` is a first-class document state.** Luís created it on 3 Aug for
documents that must be *findable* but must never reach balances, aging or the matchers:
seven documents, EUR 27,458.62, captured with no EntryCode by design and carried in
`reference_only_documents.json` with everything a future round needs to post them without
re-reading the PDF. It sits beside Posted and Discarded in the .NET schema, with rule
EF0000045 (Information, Non-Blocking) as its classifier. Deliberately **not** folded into
Discard: discard means junk, and these are legitimate evidence someone will want to reread.

**D6 — "Reconciled is not recorded" gets its own BNKMAT match status.** A movement matched
to a *ledger* reference with no FDCHDR EntryCode behind it takes a distinct status (e.g.
`Matched — ledger reference, no entry`) and is **excluded from the reconciliation
percentage**. The structural fix is chosen over the lighter alternative of a flag on an
otherwise-`Matched` row: a flag depends on whoever reads the metric remembering to filter,
whereas a separate status makes the movement impossible to count as clean *by construction*.
This is what concealed EUR 142,835.50 of 2026 Jan–Jun payroll — the cash reconciled cleanly
while the financial-entry layer sat empty, and nothing flagged it *because the movements
looked matched*. Adding a DOCEFL rule on top was considered; it can be layered on later
without schema change if the status alone proves too quiet, but the BNKMAT match-type
governance rule applies — a new type must carry its control logic, tolerance, evidence and
test case before it is added.

**D7 — Grandfathered blocking flags import with their detection date.** 29 DOCFLG
instances are open carrying a blocking level (detected 31 Jul – 2 Aug; 25 still point at
live records). They do not block today because enforcement is prospective. The .NET import
carries each instance's `DetectedAt` and applies the same prospective rule, so they remain
visible and audited without blocking. Behaviour is preserved exactly; the alternative would
have manufactured a go-live queue nobody planned.

**D8 — `ItemClass` is frozen at three values: Decision / Status / Annotation.** The
taxonomy is closed, not an open enumeration. It was tested against the manual grouping
before being wired in and reproduced it to within 3 items (adding `ReviewType='Duplicate'`
to the Decision set closed the gap exactly), and the three classes map cleanly onto the only
question that matters — *could a human decision change anything here?* A new detector must
fit one of the three. Shipping it extensible was considered and rejected: it would weaken
the rule that every detector declares its class before being added, and that discipline is
precisely what stopped the phantom backlog re-forming. A fourth class later is a migration,
and that friction is the point.

**D9 — Payment-control imports are additive, keyed by period.** The prototype hardcodes a
single filename and PAYDTL reads only from it, so the next period's file has no ingestion
path at all. In .NET each import declares its **period** and appends; re-importing the same
period **replaces only that period**, leaving every other period untouched. That makes a
re-import idempotent and preserves history, where a full supersede would silently drop older
periods any time a new file failed to repeat them — and would destroy the record of when
each line first arrived. Applies to both PAYCTR/PAYDTL and the receivable-side mirror.

### Scope note on D3 — what Layer 5 drags with it

Pulling Layer 5 forward is the largest change in v5.0 and it is not self-contained.
Three consequences, stated up front rather than discovered later:

1. **A minimum slice of Layer 6 comes too.** PAYCTR and RCVCTR are not merely
   downstream reports — they supply *ground truth* to the matchers, and per the Stage 10
   R2 decision ground truth outranks heuristic inference. 30 movements are currently
   matched by payment-control ground truth and 7 by receivable-control ground truth.
   Layer 5 cannot be ported honestly without PAYCTR/RCVCTR and their detail tables as
   inputs. **ENTBLC balances, aging, and the payment-priority views stay in Phase 2.**
2. **Go-live moves out.** P1-13's pilot now depends on eleven phases rather than eight.
   This is a deliberate trade, not an oversight.
3. **The reconciliation baseline is 94.6%.** It moved twice on 3 Aug: 93.8% → 91.8% when
   the eligibility gate reverted 13 matches that should never have been allowed, then to
   94.6% as the day's captures and corrections landed. The 91.8% dip is the honest one to
   understand — it is what removing wrong matches looks like — but 94.6% is the figure to
   regression-test against.

---

## What Changed From v4.0 — Corrections of Record

Name and count drift that accumulated between 29 July and 3 August. No decisions
required; these are corrections.

| v4.0 said | v5.0 says | Changed by |
|---|---|---|
| `MNGAPL` | **`MNGACC`** — sheet, `itemclass_mngacc.json`, all script references | Stage 8 R10, 30 Jul |
| `DOCTYPE` (classification-rules table) | **`DOCTYP`** — truncated to the project's standard 6-character table code, consistent with DOCLOG/DOCFAI/DOCEFL/DOCFLG/DOCRQE | Stage 10 R6, 2 Aug |
| "Luís applies the `FlowType` rename" — open | **Closed.** `FlowType` is live in `SKILL.md` and the workbook | Stage 7 R9, reconfirmed S10 R6 |
| "826+ headers" | **1,158 FDCHDR rows** — 481 invoice, 245 receivable, 46 payroll, 77 financing, 309 no-doc-movement | Stage 10 R6, 3 Aug 19:30 |
| "the prototype's `add_flag_evaluation.py` never existed" | **It exists** — rebuilt alongside `build_document_gaps.py`/OFDGAP | Stage 8 R12, 30 Jul |
| Table roster omits `COCACC` | **COCACC exists**, plus `CostCentre` columns on ITMMST and ENTITM | Stage 8 R10 Pt 2 / R11, 30 Jul |
| Table roster omits `DOCFLG` | **DOCFLG is the flag *instance* log** and the lifecycle/audit authority. DOCEFL alone is only the rule catalogue | Stage 9 R1, 31 Jul |
| Review operations: approve/reject/retry/reprocess/needs-attention | **One 5-value vocabulary** across both queues: `Accept` / `Reject` / `Accept with Changes` / `Need More Information` / `Defer` | Stage 9 |

### Current FDR state (3 Aug 2026), for sizing

*Measured 2026-08-04 morning.*

31 sheets. DOCLOG 1,475 (**1,475 distinct LGCodes — per-row identity, proven**) ·
FDCHDR 1,153 (I 482 · R 245 · P 40 · F 77 · O 309) · FDCDTL 1,190 · ENTMST 119 ·
ITMMST 391 · ENTITM 327 · EXCRAT 47 · DOCEFL 45 rules · DOCFLG 2,711 instances
(149 open, 120 Non-Blocking; the 29 carrying a blocking level all pre-date the current
pass and are grandfathered — **actively-blocking 0**) · BNKMOV 1,960 · BNKCHK 168 ·
BNKMAT 11 match types × 14 governance fields · bank-generated documents 426, every one
carrying a `SourceKey` · **Reference Only documents 7** (EUR 27,458.62) ·
**2026 reconciliation 94.6%**.

**The two queues must be read with their `ItemClass` dimension** (added 3 Aug 18:10, see
P1-6). DOCRQE holds 2,668 items of which only **64 are Decision-class and 41 are open** —
the rest are 1,294 live Statuses and 1,310 Annotations, permanently retained and
deliberately not counted as work. RECREV holds 1,201 items, **154 open**, 909 applied,
138 superseded; it is Decision-class throughout by nature. Reporting the raw totals as a
backlog is the error Luís designed out on 3 Aug — do not reintroduce it in .NET.

---

## Phase Map — v4.0 to v5.0

| v5.0 | Phase | Was |
|---|---|---|
| P1-0 | Contract and Schema Design Freeze | P1-0 |
| P1-1 | Schema Migration and Reference-Data Seed | P1-1 |
| P1-2 | Claude CLI Runtime | P1-2 |
| P1-3 | Production Skill v1 | P1-3 |
| P1-4 | Deterministic Pipeline — Persistence, Routing, Gates | P1-4 (split) |
| P1-5 | Flag Governance — DOCEFL/DOCFLG, Blocking, Grandfathering | **new** (carved from P1-4) |
| P1-6 | Review Queues — DOCRQE and RECREV Persisted Model | **new** |
| P1-7 | Decision-Application Engine | **new** (FDR G-01) |
| P1-8 | Reference-Layer Management UI | P1-5 |
| P1-9 | Review and Document UI | P1-6 |
| P1-10 | Archive (Nextcloud) | P1-7 |
| P1-11 | Bank Movements and Reconciliation — FDR Layer 5 | **pulled from Phase 2** |
| P1-12 | Excel Export (On Demand) | P1-8 |
| P1-13 | Channels End-to-End, Durability Minimum, and Pilot | P1-9 |

---

## Current Snapshot — foundations carried over

These remain the harness for v5.0. Items built over the superseded financial schema are
valid as foundations but are re-based onto the v5.0 schema in P1-1/P1-4.

- `[x]` .NET 10 solution (API, Web, Worker, Domain, Infrastructure, Tests) with IIS and Windows Service hosting and publishing scripts.
- `[x]` OIDC/JWT authentication, global identity roles, users/companies/departments/memberships, and server-side `UserChannel` resolution with deny-by-default channel identity.
- `[x]` PostgreSQL job system: `FOR UPDATE SKIP LOCKED` claims, leases, heartbeats, retry backoff, cancellation, dead-lettering, job lanes, and postcondition enforcement.
- `[x]` Append-only audit protection (application + PostgreSQL trigger), correlation IDs, structured logging, readiness and health checks.
- `[x]` Immutable original storage under `/originais/<year>/<id>.<ext>`, SHA-256, company-scoped exact-hash duplicate prevention, local and Nextcloud/WebDAV storage clients.
- `[x]` Validated multi-file web upload with extension, MIME, size, and content policies.
- `[x]` `sibyla.channel-intake.v1` contract, dedicated `sibyla-apolo` service identity (`sibyla.channel-intake.write` only), opaque intake/candidate IDs, idempotent registration/upload/status operations, and the Apolo addendum v1.1.
- `[x]` Text-first PDF helper (`Sibyla.PdfTextExtractor`) and bounded visual-evidence staging (≤4 page images, size/pixel caps) with byte-safe fallback.
- `[x]` Restricted-runtime security patterns and F0 spike evidence (to be re-executed against the Claude CLI runtime in P1-2).
- `[x]` Graph spike evidence: delegated workbook session, table `add-row`, throttling handling, ambiguous-timeout reconciliation, schema fingerprint fail-closed (reused by the on-demand Graph export in P1-12).
- `[x]` Review foundations: review queue and document detail pages, header/line correction, corrections stored as immutable revisions with actor/timestamp/reason (re-based in P1-9).
- `[x]` Deterministic gate foundations: NIF check-digit validation, plausible-date and known-supplier gates, configurable line-sum tolerance, effective-confidence combination, 100% auto-integration threshold (re-based and extended in P1-4).
- `[x]` `docs/hermes-documental-lessons-learned.md` process: human review findings → lessons file → versioned instruction bump → golden-set validation → production. Applies unchanged to the Claude skill.
- `[x]` Provider-neutral integration core and Moloni adapter implemented (Cegid refactored to the `Integration*` ledger, migration `20260723190000_MoloniLedgerConcurrency`). Local fake-test evidence only; **parked for Phase 2**.

### Carried over from the FDR prototype

- `[S]` **13 binding Engagement Rules documents** covering capture, entry, classification, entity and item flows, reconciliation, review, and archiving. A fourteenth (Roles and Responsibilities) is drafted — see P1-0.
- `[S]` **Two-table flag machine** — DOCEFL rule catalogue (43 rules) feeding DOCFLG instance log (2,574 instances), with `blocking_open_instances()` as the single enforcement view.
- `[S]` **Two persisted decision queues** sharing one layout and one vocabulary — DOCRQE (document level) and RECREV (line level).
- `[S]` **Decision-application engine** (`apply_review_decisions.py`) — one engine, both queues, with reject-persistence as a matcher constraint.
- `[S]` **Prospective enforcement with grandfathering** — proven by test case TC5 across Stage 9 R1/R2/R5/R7.
- `[S]` **The evidence discipline itself** — every round closes with a verified `Initial + Expected = Result` control record and byte-stable idempotency rerun. This is the process asset worth porting, not just the code.

---

## P1-0 — Contract and Schema Design Freeze

> **State: ACCEPTED AND FULLY CLOSED 2026-08-05; P1-0d closed C9/C11/C12/C15. `docs/PROJECT-STATE.md` is authoritative
> for state; this header is a summary of it and yields to it.** The freeze
> deliverables exist — `docs/p1-0-design-freeze-summary.md` (19 decisions and a checklist),
> `docs/p1-0-schema-mapping.md`, `docs/p1-0-codes-taxonomy-archive.md`,
> `docs/p1-0-claude-extraction-contract.md`, `docs/p1-0-discard-purge-lifecycle.md`, and
> `docs/p1-0-user-ai-roles-responsibilities-policy.md`. The flow diagram redrawn against them is
> `docs/2026-08-04 Sibyla_Organigrama_Fluxo_v14.svg`; v13 is superseded and nine of its labels
> are stale.
>
> **They are the field-level authority for P1-1, as corrected.** The review of 2026-08-04 tested
> them against live prototype data and found ten defects that would fail on counted rows; C1–C19
> decided all of them and P1-0b applied them on 2026-08-05. Read the drafts **together with**
> `docs/p1-0-corrections.md`, `docs/p1-0-a-group-decisions.md` and `docs/p1-0-signoff.md`, in the
> precedence order: **state > corrections > group-A/sign-off decisions > annex > draft > this
> backlog.**
>
> **All C1–C19 are decided. Nothing is blocked on Luís** — see `docs/PROJECT-STATE.md` §1 before
> reporting otherwise. Where this backlog and a draft disagree, **report the contradiction, do not
> resolve it silently.**
>
> **No P1-0 item remains open.** P1-0d transcribed the rows that existed at the pin and authored
> the three v5.0 additions with per-field provenance. Acceptance still authorizes no migration
> against a shared database.

- `[x]` Write the `sibyla.claude-extraction.v1` contract spec: classification (docClass, documentType, originClass, failure category), parties as printed with fiscal numbers, fiscal header/line fields, basic non-fiscal metadata, `readingMode`, per-field confidence, warning codes (`TEXT_LAYER_ABSENT_VISION_USED`, `UNREADABLE_AFTER_VISION`, `INSUFFICIENT_DATA`, `WRONG_DOCUMENT_TYPE`, `NOT_A_DOCUMENT`), optional operator comment; strict validation rules before persistence. **Review: DOUBT, not clean.** All twelve required elements are present, but `identifierType` is emitted as a required field with its own confidence entry and **has no controlled vocabulary anywhere**, while the draft mandates case-sensitive enum validation. Give it a vocabulary or stop validating it strictly — see `p1-0-review-findings.md` item 1. **CLOSED by S1 (2026-08-05):** `identifierType` is a closed vocabulary — `VAT`, `NIF`, `EORI`, `PassportNumber`, `NationalID`, `Other` — validated case-sensitively, failing closed. Review DOUBT resolved; residual B13 closed.
- `[x]` Design the discard/purge lifecycle for junk intake: `NOT_A_DOCUMENT` → DOCFAI/review; reviewer **Discard** (soft — excluded everywhere, bytes and audit retained); authorized-role **Purge** (bytes permanently deleted, audit tombstone keeps metadata + SHA-256, never automatic, never for items that reached fiscal/archive tables); retained hash auto-discards byte-identical re-submissions with an Information flag. **CLOSED 2026-08-05.** C5 settled the byte-identical capture-event path. P1-0d closed B2/B3/B4 governance with counter-signature for Posted/ReferenceOnly, an exact `RestoreForReview` target, and explicit entry transitions.
- `[x]` **Share the Discard/Purge design with Luís as the mechanism for D2.** The orphan DOCLOG rows are the acceptance case: rows logging the capture of documents that do not exist, absent from both the header tables and `bnk_generated_documents.json`. Measured 129 on 2026-08-03 (57 O, 48 F, 16 P, 8 I). **Three different figures now exist — 129, 182 and 134 — and they are three measurements of three different things.** Write one definition before quoting any of them again; the review flagged this as unreconciled figure drift. His open governance question and this design are the same problem. **CLOSED by S4 (2026-08-05):** orphan DOCLOG has one definition — *an EntryCode resolving to no document in either FDCHDR or the bank-generated set, measured at the pin.* 129/182/134 were three measurements of three different things; any other count is derived and must state what it counts.
- `[x]` Map every FDR **layer 1–5** table and field to the PostgreSQL schema — **roster corrected and extended**: DOCLOG, DOCFAI, **DOCTYP**, DOCEFL, **DOCFLG**, DOCRQE (persisted), ENTMST, ENTALS, ITMMST, ITMALS, ENTITM, ITMCLS, **MNGACC**, SNCACC, **COCACC**, ENTBNK, EXCRAT, FDCHDR, FDCDTL, plus `DOCARC` for non-fiscal basic metadata and the export-run ledger. Layer 5 adds BNKACC, BNKMOV, BNKCHK, BNKMAT, BNKREC, RECREV (see P1-11). Layer 6 minimum slice adds PAYCTR/PAYDTL, RCVCTR/RCVDTL as ground-truth inputs only. **A document's state is Posted / Reference Only / Discarded / Purged** — see D5; `reference_only_documents.json` carries the payload for the second. **This is the item the review hit hardest: eight of the ten proven defects live here** — C1 (aggregate payroll unrepresentable, 0 of 7 rows join), C2 (ENTITM mapped backwards, would reject 100% of rendered rows), C6 (the DOCRQE check rejects 639 pinned rows), C7 (`SourceTextHash` fails all 2,711), C8 (ENTBNK declared PK is not unique — 48 rows, 43 distinct pairs), C9 (DOCTYP coverage), C10 (`PaymentSequence` on the wrong table) and C11 (four empty `EFCode` rows). **`OFDGAP` was reported present in a roster that does not contain it** — C12 rules it is not a table to import but an ungoverned detector. Smaller mismatches in the same pass: `FDCDTL.LineNumber` is invented and exists in no source, `DOCLOG.CaptureQuality` is declared non-nullable and is blank on 1,425 of 1,475 rows, and `CompanyCode` **is** an entity CodeName — the draft says the opposite. **CLOSED 2026-08-05.** C1–C19 applied; A1–A7 decided in `docs/p1-0-a-group-decisions.md` — `CounterpartyCodeName`, stored `FlowType`, generated `NormalizedDocumentID` with a version discriminator in the unique index, `ProposedMatchBTCode`, `ImportBatch` PK, `FailureCategory` deleted. Group B enters the same migration train.
- `[x]` Design permanent-code issuance as database-backed non-cycling sequences and append-only bindings. **CLOSED 2026-08-05:** D2/C13 identity rules stand; P1-0d measured BRCode's production key as `(Company, BMCode, EntryCode, FindingDiscriminator)` with the pin-equivalent `(BMCode, EntryCode)` at 2,072 distinct / 0 collisions. MatchGroupID is run-scoped and has no bucket. BR issuance remains P1-1b.
- `[ ]` ~~Settle first what each code identifies — LGCode is empirically a *capture/file* identifier (954 filenames ↔ 954 codes, 1:1), not a document identifier, so DOCLOG needs its own per-row `DocLogId`. Decide capture-identifier vs document-identifier (~544 new codes + a `SourceCaptureCode` back-pointer) and write P1-1's constraint to match.~~ Closed by Luís's Roadmap 36 re-key on 3–4 Aug: 1,475 DOCLOG rows now carry 1,475 distinct LGCodes, so LGCode *is* the per-row identity and no second key is added.
- `[x]` **Natural keys received and proven (Luís, 3–4 Aug).** Bank-generated P/F/O documents key on the movement's natural key (`SourceKey`, with `SourceFile` excluded because the pipeline mutates it, and the occurrence tiebreaker recomputed — a real BCP statement lists two byte-identical lines on one day, so merging them under-generates). DOCLOG keys on `(Filename, EntryCode)`; LGCode is now per-row and no `DocLogId` is required. PAYCODE/RCVCODE derive from the fiscal document plus payment sequence and are proven stable under a shuffled cold rebuild. **Port these keys; do not re-derive them.** **Independently re-verified against the pin on 4 Aug and all three stand** — 1,475 rows / 1,475 distinct LGCodes / 1,475 distinct `(Filename, EntryCode)`; `SourceKey` present on 426/426 generated rows with `SourceFile` deliberately excluded; PAYCODE/RCVCODE stable, and the draft's company-scoping is a *safe superset* of the prototype's global key. The occurrence tiebreaker is load-bearing on real data: a BCP statement lists exactly one pair of byte-identical lines, and without the tiebreaker those two merge. **This was the expensive part and it is correct.** *(superseded note follows)*
- `[ ]` ~~Get the natural key per bucket from Luís before designing the sequences.~~ Three prototype code families are not stable identities today: **P/F/O EntryCode** (a per-period sequence — a re-ingested overlapping statement mints new codes for rows that already exist), **LGCode** (inherits identity from a filename that is sometimes a placeholder standing in for many rows), and **PAYCODE/RCVCODE** (reassigned on every rebuild — his Roadmap 29, and the reason 465 stale queue items could not be told apart from renamed ones). He has proposed `(FiscalDocumentID, payment sequence)` for PAYCODE; the P/F/O and DOCLOG equivalents are outstanding. Without them the sequences will be issued against the wrong key and reproduce the defect in PostgreSQL.
- `[x]` Resolve the `DOCTYPE` name collision — decided with Luís (2026-07-29) and **applied on both sides**: the FDCHDR/FDCDTL I/R/P/F/O column is `FlowType` everywhere; the classification-rules table is **`DOCTYP`** (Luís truncated to the standard 6-character code on 2 Aug; v5 adopts that spelling).
- `[x]` Fix the seeded DocClass/DocumentType taxonomy as data (DOCTYP rows). **C9 CLOSED 2026-08-05 by P1-0d:** all 19 pinned tuples, including every DTCode and six source fields, are transcribed with per-field `extracted` provenance from `document_type_rules.json` blob `7e687f6f`; the additive Duplicate/External rule remains Include/ArchiveOnly.
- `[x]` Fix the Nextcloud business-copy layout per class: fiscal keeps `Empresa/Ano/Mês/Fornecedor/...`; decide Legal sub-path (by counterparty vs by subtype); Period always derives from the document date. **C16 — three defects**: the class segment must be literal (`{Company}/Payables/{YYYY}/{MM}/{Counterparty}/`, not a generic `{DocClass}`), a controlled BNKACC token replaces the free-text bank string in both directory and filename, and the codes archive is corrected so it stops contradicting the mapping. **CLOSED by S3 (2026-08-05):** the Legal sub-path is `{Company}/Legal/{Subtype}/{Counterparty}/` — subtype first, because it is already closed vocabulary in DOCTYP, so the segment is controlled rather than free text.
- `[x]` **Formalize the "User and AI Roles and Responsibilities" policy** as the fourteenth Engagement Rules document. Draft exists at `Specs\Roles_and_Responsabilities_Policy.txt`. It sits **above** and supersedes the capture, entity/item entry, financial entry, and reconciliation procedures where they conflict. The clause that binds v5 directly: **DOCRQE and RECREV are exception-and-learning queues, not mandatory work queues** — which makes P1-9's review UI a triage surface, not a clear-the-backlog surface. **Over-claimed: the pre-flight was stated, never performed — 8 of 13 documents were unassessed.** It has now been performed, and C19 is the outcome. Two findings: the supersession authority was **added during adaptation** (the source says a cross-cutting policy *above* four procedure families; the adapted text says *supersedes*, with no stated basis), and the policy conflicts materially with **eight documents it does not claim**, all for one reason — it asserts "only deterministic .NET code executes" over a Python prototype that has no .NET in it. **C19a scopes the policy to Sibyla**, which dissolves all eight without amending anything and removes the need for an FDR-side pre-flight. **C19b restores the confidence / consistency / risk threshold clause** dropped in adaptation — it was the only link between this policy and the built threshold layer, and without it changing a tolerance no longer triggers Policy Change Validation at all. **CLOSED 2026-08-05** by C19a/C19b, applied in P1-0b.
- `[x]` **Decided (D4): import the full 2026 history as opening balances, plus the complete reference layer; 2025 stays out.** Scope at 2026-08-04: DOCLOG 1,475 · FDCHDR 1,153 · FDCDTL 1,190. **FDCHDR is reported as both 1,158 and 1,153 across sources — re-measure before it is used as an import target**, and re-measure all three after the 19 queued documents are ingested. Includes the 2026 Jan–Jun payroll rows Luís rebuilt on 3 Aug — six months that previously had no FDCHDR entry at all. Bears on it: whether the **2026-only scope rule** carries into the import, and whether the 2026 Jan–Jun payroll rebuild (Luís, 3 Aug 10:45 — six months that had no FDCHDR row at all) is stable enough to take.
- `[x]` **D7 decided, mechanism half-built — the single highest-consequence defect the review found. Was ticked; un-ticked by C3.** The decision stands: import each DOCFLG instance with its `DetectedAt` and enforce prospectively. What did not stand is the comparand. The draft invented `EnforcementStartsAt` and left it unseeded, and the obvious fix — seed it from DOCEFL's existing `EffectiveFrom` — **grandfathers nothing**: `EffectiveFrom` is populated on all 45 rules but records when Luís *wrote* the rule in FDR, and every one of the 29 open blocking instances was detected on or after its own rule's date. Seeding that way makes **all 29 block on day one** — exactly the go-live queue D7 exists to prevent, and it breaks P1-11 acceptance. **C3**: `EnforcementStartsAt` is a Sibyla enforcement concept, distinct from `EffectiveFrom`, seeded for all 45 rules to the Sibyla enforcement-start timestamp — all 2,711 instances grandfathered, 0 actively blocking, matching the prototype. Keep the column per-rule so a rule added after go-live carries its own later start; preserve `EffectiveFrom` as provenance. **One sub-decision is deliberately still open**: `EnforcementStartsAt` is mutable while `IsGrandfathered` is derived once and persisted, so a later edit silently drifts stored verdicts. The two unresolved options are to make the column immutable after import/first use, or to transactionally recompute every affected persisted verdict with audit. C3 chooses neither; resolve this before P1-1 migration design. **CLOSED 2026-08-05.** C3’s open half is decided: `EnforcementStartsAt` is **immutable after import or first use**, enforced by a named reject trigger; persisted `IsGrandfathered` verdicts are never recomputed; a rule correction is a new DOCEFL rule version. See `docs/p1-0-a-group-decisions.md`.
- `[x]` Add a **`RelatedParty` flag on ENTMST** and the dual-condition governed rule. **C15 CLOSED 2026-08-05 by P1-0d:** the extracted classifications are `CL000005` (`Banks / Financing`) and `CL000025` (`Revenue / Intercompany`); entity relationship alone never matches or excludes a movement.
- `[x]` Decide how the parked `Integration*` ledger's foreign keys to `Document`/`ExtractionRevision` are preserved or re-pointed to the FDR tables. **Review: OK** — this is one of the ~40 claims tested and found clean. **CLOSED 2026-08-05** — verified clean in the review and unchanged by C1–C19; the parked ledger keeps its existing non-null restrictive FKs.
- `[x]` **Apply C1–C19 to the six drafts** (see the correction register below), then re-read, then sign off. Miguel accepts the phase; Luís confirms the six marked corrections. Acceptance is not migration authorization, and **no sign-off is asked for against uncorrected drafts**. **DONE 2026-08-05.** P1-0b applied all nineteen (14 full, 5 structural); Miguel accepted the phase the same day with the C9/C11/C12/C15 carve-out — `docs/p1-0-signoff.md`. Luís’s confirmations are a courtesy channel, never a gate.

### P1-0c — the correction register (C1–C19)

> **This is the gate.** Each entry names the draft and section it lands in; the reasoning is already
> written in `docs/p1-0-corrections.md`, so this is an edit pass, not a redesign. Tick an item only
> when the correction is **in the draft text**, not when it has been read and agreed.
>
> Three of them change schema shape and must land before anything is written against the mapping:
> **C1** (junction table), **C3** (`EnforcementStartsAt`), **C4** (generated vocabulary).
>
> **Seven markers need Luís's confirmation** — C1, C2, C3, C4, C12, C19, and the pinned
> `Receivable` singular. They were decided on his behalf against pinned data and stay reversible
> until he sees them. Do not treat his silence as assent.

- `[x]` **C1 — aggregate payroll: split identity from referential integrity.** Remove the FK from `FDCHDR.SourceKey`, keep it as the identity anchor, add `SourceKeyHash` as a stored generated column and move the partial unique index onto it, add the `FdcHdrBankMovement` junction with `UNIQUE (Company, BMCode)`, and name `SourceBMCode` in the mapping — it appears **zero times** in either draft today. *Acceptance: re-ingest an overlapping statement; all generated documents re-anchor, zero duplicates, all 7 aggregates resolve.* → `p1-0-schema-mapping.md` §4/§8, `p1-0-codes-taxonomy-archive.md` §1. **Needs Luís.**
- `[x]` **C2 — ENTITM stores `CodeName`; the rendered `EntityCode` is a derived export.** Import accepts both surfaces and rejects only an *unresolvable* value, never an EC code as such. **Rewrite decision 18, do not amend it** — it is inverted, not incomplete. Then **generalize the check**: every table whose column list was derived from a *rendered* sheet gets re-checked against its JSON. → `p1-0-schema-mapping.md` ENTITM, `p1-0-design-freeze-summary.md` decision 18. **Needs Luís.**
- `[x]` **C3 — `EnforcementStartsAt` is a Sibyla concept, not FDR's `EffectiveFrom`.** Closed by the immutable-after-import-or-first-use decision and named reject trigger; persisted verdicts are never recomputed and corrections create a new rule version. → `p1-0-schema-mapping.md` §9 + DOCFLG/DOCEFL, summary decision 9.
- `[x]` **C4 — pinned literals win, and the vocabulary annex is generated, not written.** `docs/p1-0-vocabularies.md` is normative for pinned imports: every pinned CHECK, enum and domain derives from it, the import fails closed on an unlisted value, and it is regenerated and diffed at each new pin. Target-only extensions are allowed only when C1–C19 explicitly decide them (for example D6), are versioned and fail-closed, and are never invented or presented as pinned values. **`Waived` joins the DOCFLG lifecycle as a terminal state** — 861 rows, 32% of the table, not an edge case. → every draft that names a literal. **Needs Luís** (the singular `Receivable`, and `Waived` vs `Resolved` semantics).
- `[x]` **C5 — byte-identical resubmission registers a capture event, not a DOCLOG row.** Resolves the `_to_delete/` conflict between the Archiving Policy and Discard/Purge §5. → `p1-0-discard-purge-lifecycle.md`.
- `[x]` **C6 — DOCRQE/RECREV: constrain openness, not the terminal state.** The draft's `Status='Recorded'` requirement rejects 639 pinned rows. One constraint, nothing more. → `p1-0-schema-mapping.md`.
- `[x]` **C7 — keep the pinned `md5-12` hash and add a discriminator.** The declared `char(64)` fails all 2,711 rows. → `p1-0-schema-mapping.md`.
- `[x]` **C8 — ENTBNK: the key is right; the source data is now corrected.** Comma-joined accounts/methods remain the correct render-parity shape and expand into defined child tables. The five duplicate key pairs that differed only in `Flag` were merged at source on Luís's behalf under Miguel's 2026-08-06 authorization, retaining both notes, in prototype commit/pin `b91768513fc638381fbde91f0b576b08220a98f6`. The post-control is 43 rows / 43 distinct keys / 0 duplicate groups / 0 duplicate rows. *(The review's multiple-account interpretation was withdrawn.)* → `p1-0-schema-mapping.md`.
- `[x]` **C9 — DOCTYP coverage.** All 19 exact governed tuples and DT codes are in `p1-0-codes-taxonomy-archive.md`, with per-field extraction provenance and source/blob SHA; O8-D2 explicitly excludes the uncoded twentieth source row `Bank Statement | External | Exclude`, so the import manifest remains 19 and the authored Bank-Statement `Include | ArchiveOnly` rule remains unchanged.
- `[x]` **C10 — `PaymentSequence` moves to the detail.** `build_paydtl.py` exists for the partial-payment case and one FiscalDocumentID already has two PAYDTL rows; the declared header key and the `PaymentSequence=1` alternate key cannot both hold. → `p1-0-schema-mapping.md` PAYCTR/PAYDTL.
- `[x]` **C11 — add a sentinel DOCEFL row for the four empty `EFCode` DOCFLG rows.** `EF0000000` is in the schema draft with complete per-field `authored|absent` provenance and the four FL placements; EFCode stays non-nullable.
- `[x]` **C12 — OFDGAP is a governed detector, not a table.** O8-D1 re-authors the never-used row once as `EF0000053`; it is in the schema draft with its verified-free allocation rule, exact Monthly-only semantics, `ItemClass=Decision`, complete per-field provenance, inactive seed, and governed activation guard.
- `[x]` **C13 — FL: use the key the code actually uses; drop the sequentiality claim.** → `p1-0-codes-taxonomy-archive.md`.
- `[x]` **C14 — make the DOCFLG succession invariant verifiable instead of literal.** `AGENTS.md` requires a state change to close its predecessor and point at its successor; DOCFLG has no successor pointer today, which puts the lifecycle authority itself in breach of a non-negotiable. → `p1-0-schema-mapping.md` DOCFLG.
- `[x]` **C15 — RelatedParty.** Extracted `CL000005`/`CL000025` are in the codes draft and the dual-condition Information/Non-Blocking rule is in the schema draft.
- `[x]` **C16 — Nextcloud archive layout: three defects.** Literal class segment; controlled BNKACC token in directory and filename; adopt the mapping's stricter version and correct the codes archive so the two stop contradicting each other. → `p1-0-codes-taxonomy-archive.md`, `p1-0-schema-mapping.md` DOCARC.
- `[x]` **C17 — D9 period replacement over a current-state table.** `UpsertControlSnapshot` plus `RejectControlGoverningPeriodRegression` now define atomic same-period replacement and whole-batch rejection of governing-period regression. → `p1-0-schema-mapping.md` PAYCTR/RCVCTR.
- `[x]` **C18a — adopt an explicit reconciliation-rate definition and re-baseline.** P1-11 acceptance demands reproducing 94.6% ±0.1% and **no draft defines numerator or denominator**; six plausible definitions computed at the pin returned 47.8%–58.3% and none approaches 94.6%. New definition: *distinct BNKMOV movements carrying at least one non-`Unmatched` BNKREC match, over all BNKMOV movements*; internal transfers count as reconciled; per D6 the ledger-reference-no-entry status is excluded from the numerator once it exists. **Baseline 55.9%** (≈1,096 of 1,960), computed as a view or generated column, never as prose. *Accepted cost, recorded deliberately: this abandons the prototype comparison. State the definitional difference wherever either figure appears.* → `p1-0-schema-mapping.md` BNKREC/BNKMOV, P1-11 acceptance.
- `[x]` **C18b — three prose invariants get schema objects.** `ReferenceOnly` is barred by CHECK/FK; BNKMOV evidence is company-scoped and official statements must prove DOCLOG/DOCARC classification, route, and retained disposition; `MatcherProposal`, permissions, queue/result FKs, and `RejectPreviouslyRejectedProposal` force every matcher through one rejection-aware path. Rejection identity is `(Company, SourceRecordType, SourceRecordID, CandidateFingerprint)` across all matchers; MatcherKind is audit metadata and cannot bypass a rejection. → `p1-0-schema-mapping.md`.
- `[x]` **C19 — Roles policy: scope it to Sibyla, restore the threshold clause.** See the policy item above. → `p1-0-user-ai-roles-responsibilities-policy.md`.

### P1-0 — what the review says this backlog got wrong about itself

- `[!]` **[standing caution — self-assessment]** **Five rows were over-claimed by the freeze's self-review**, and are corrected in place above: `RelatedParty` (Drafted → a column, no rule), grandfathering D7 (Drafted → half), the Roles policy (pre-flight "stated" → stated, never performed; 8 of 13 documents unassessed), the codes/LGCode proof (one code family silently exempt), and the required flow SVG (a cross-check of a *superseded* diagram substituted for a redraw — v14 has since been drawn, which closes it).
- `[!]` **[standing caution — decisions, now closed]** The review found four decisions frozen in name only. They are now operative: C3 uses immutable `EnforcementStartsAt`; C15 names extracted CL codes and a governed rule; the hash-index swap is P1-1b; DOCFLG snapshots `ItemClass` at detection and never recomputes it.
- `[!]` **[standing caution — one claim to stop repeating]** rev. 3 asserted *"there are no unresolved domain-choice questions and no other external blockers"* before verifying the mapping. Those choices are now closed; the durable lesson is that a completeness claim is not evidence of completeness.


## P1-1 — Schema Migration and Reference-Data Seed

- `[x]` **Gate (D2) CLEARED.** The natural keys are settled and proven — see D2 above, and independently re-verified against the pin on 4 Aug. The identity model is correct and can be inherited.
- `[x]` **Gate CLEARED 2026-08-05: P1-0 fully closed.** P1-0b applied C1–C19 and P1-0d closed the remaining C9/C11/C12/C15 rows, A2, governance, and A3 measurement.
- `[x]` **Historical C1–C19 draft-correction gate CLOSED 2026-08-05.** P1-1a is active; its corrected prompt is `docs/AGENT-PROMPT-v5-P1-1.md` on `feature/p1-0d-follow-up`.
- `[ ]` ~~Gate (D2): the natural keys must be settled before the migration train is written.~~ Not a data-cleaning precondition — a design one. Issuing PostgreSQL sequences against the wrong natural key reproduces the prototype's renumbering defect in the target system, where it is far more expensive to fix.
- `[ ]` **Not a gate: the residual DOCLOG rows.** They import harmlessly under grandfathering and, per Luís's 10:05 analysis, most are honest audit traces of superseded generations rather than errors. Do not "clean" them without reading that analysis first.
- `[ ]` Add EF Core migrations for the layer 1–5 tables, `DOCARC`, code sequences, and the export ledger; retire the superseded financial schema in the same migration train, applying the P1-0 decision so the parked `Integration*` ledger keeps valid references throughout.
- `[ ]` **Add the `FdcHdrBankMovement` junction table (C1)** with `ComponentOrder`, `UNIQUE (Company, BMCode)` and `UNIQUE (Company, EntryCode, BMCode)`, and **remove the FK from `FDCHDR.SourceKey`** while keeping it as the identity anchor. Index the identity through the stored `SourceKeyHash`, not the raw string — a pay run above roughly 37 movements exceeds PostgreSQL's btree limit and the index fails at insert time. `UNIQUE (Company, BMCode)` encodes a measured property, not an assumption: if a future round breaks it, that is a real finding and must fail loudly rather than be absorbed.
- `[ ]` **P1-1 migration prerequisite for SourceKeyHash:** preflight PostgreSQL `pgcrypto` and require `digest(bytea, text)` before creating the generated column or index. If the extension cannot be enabled or the function is unavailable, abort the migration and seed; do not fall back to a different expression or the raw SourceKey index.
- `[ ]` **Add `EnforcementStartsAt` per DOCEFL rule, distinct from `EffectiveFrom` (C3)**, seeded for all 52 imported rules to the Sibyla enforcement-start timestamp; derive and persist `IsGrandfathered` at import from the instance's own `DetectedAt`; implement the named immutable-after-import-or-first-use reject trigger and no recompute path.
- `[ ]` **Derive every CHECK constraint, enum and domain from `docs/p1-0-vocabularies.md` (C4)**, not from prose in a draft and never from memory. Hyphenation is load-bearing: `Non-Blocking`, not `NonBlocking`. Include `Waived` as a DOCFLG terminal state with its own waiver evidence and authority. **The import fails closed on an unlisted literal** — an unknown value is a finding, not a row to coerce.
- `[x]` Implement the validation service (**port of the documented check set — the script never existed**): unique DOCLOG filenames; no (CodeName, DocumentID) collisions on payables; header↔detail within 2 cents and no zero-line rows; every CodeName resolves to ENTMST with non-blank FiscalNo; every (Period, Currency) in use has an EXCRAT row; every DOCLOG row has a DocumentType; Flag↔FlagCategory both-or-neither; every (DocumentType, OriginClass) has a DOCTYP rule and matches its Treatment — **scoped to captured documents, `Source <> 'BNK'` (C9)**. The session-derived counts **640 rows** and **1,425 of 1,475** are indicative, not authoritative. The prospective `DOCLOG.CaptureQuality` rule is exact: imported historical `NULL`/`absent` values are excluded from the warning; missing values on non-imported rows warn; every new Sibyla capture must provide `CaptureQuality` at capture time. Implemented per `docs/p1-1a-d2-status.md` and committed/pushed as `719407cfb8c5ba6f143699fe893ba2a812c5102e`.
- `[x]` **Add the orphan-DOCLOG check to the validation service** — implemented as a warning/report-only check per `docs/p1-1a-d2-status.md`; a DOCLOG row whose EntryCode resolves to no document in unified FDCHDR is retained and reported. This is the test that surfaced the 182 rows on 3 Aug.
- `[x]` **Add the code-uniqueness check as a `(Filename, EntryCode)` ↔ LGCode bijection.** Implemented as a failure check in both directions per `docs/p1-1a-d2-status.md`. P1-0 settled the definition: LGCode is the permanent DOCLOG row identity on that pair, no `DocLogId` is added, and the naive "no permanent code may appear against more than one EntryCode" test is **wrong** — it would reject internally consistent data and must not be implemented. *(superseded note follows)*
- `[ ]` ~~Add the code-uniqueness check, written to the definition P1-0 settles. If LGCode remains a capture identifier, the constraint is `(LGCode ↔ Filename)` 1:1 and DOCLOG carries a separate per-row `DocLogId`.~~ The conditional is resolved — see the item above and `docs/p1-0-codes-taxonomy-archive.md`.
- `[ ]` Import Luís's live reference data from `Invoice Skill Build/Editor/Data/*.json` with permanent codes preserved — **roster corrected**: ENTMST, ENTALS, ITMMST, ITMALS, ENTITM, ITMCLS, **MNGACC**, SNCACC, **COCACC**, EXCRAT, **DOCTYP** rules, DOCEFL rules, **DOCFLG instances**, and **`user_observations.json`** (see P1-6). **`document_gaps.json` / OFDGAP is deliberately not on this list** — C12 rules it a detector to reimplement, not a table to import. O8-D1 fixes the DOCEFL import manifest at all 52 source rows `EF0000001`–`EF0000052`, with sentinel `EF0000000` and inactive authored Monthly-gap `EF0000053` seeded alongside. O8-D2 keeps the DOCTYP import manifest at 19 and explicitly excludes the uncoded twentieth source row. **Re-measure every row count after the 19 queued documents are ingested**; the older figures in this backlog are historical.
- `[x]` **O8 closed; P1-1b Scope 1 is ready to restart at preflight.** At immutable pin `b91768513fc638381fbde91f0b576b08220a98f6`, all 49 roster blobs match, the direct surface is exactly 48 rostered JSON files, C8 is 43/43/0/0, source DOCEFL is exactly 52 consecutive codes through `EF0000052`, and `EF0000053` is free in both ledger and instances. O8-D1 imports all 52 source rules with honest provenance and re-authors only the inactive Monthly-gap target row to `EF0000053`. O8-D2 explicitly excludes the uncoded twentieth source DOCTYP row while retaining the governed 19-row import manifest and authored `Bank Statement | External | Include | ArchiveOnly` rule. No importer/source/test implementation, import, container, or database work ran; ordered Scopes 2–8 remain unstarted.
- `[x]` **O9 governed and closed on the record (2026-08-06); implementation remains pending.** O9-P makes historical completeness gaps honest SQL `NULL` / `absent` plus named warnings to improve over time, while identity-class controls remain fail-closed. O9-D1…D5 govern the three nullable columns, exact six-literal `ReviewPriority` CHECK, and imported-row terminal-evidence exemption. This records decision did not implement or apply the corrective migration, validator changes, importer, tests, or any database work; Scope 1 has not yet restarted and Scopes 2–8 remain unstarted.
- `[x]` **O10 contract gate closed on the record (2026-08-06); fresh reimplementation remains pending.** O10-D1 makes imported DOCFLG snapshot `ItemClass` nullable with honest SQL `NULL` / `absent`, retains the composite FK under `MATCH SIMPLE`, mandates a plain `EFCode` FK, and requires complete snapshots prospectively for non-imported instances. O10-D2 makes governed rule assignment atomically complete NULL snapshots only, with actor-stamped audit and an exact completion count; non-null snapshots are never overwritten. All five findings from rejected `b324a3e…` are binding remediation requirements. That commit remains neutralized evidence, never code to resurrect. Scope 1 still awaits the fresh step-3 implementation and mandatory independent Accept verdict.
- `[ ]` **Non-blocking data-quality improvement — `ENTMST.DirectDebit absent`.** Expected initial baseline **119** imported historical rows at honest SQL `NULL` / `absent`; measure at import and work down over time only through governed, audited assignment. Never default or backfill.
- `[ ]` **Non-blocking data-quality improvement — `ENTMST.TaxIdVerificationStatus unverified`.** Expected initial baseline **119** imported historical rows at honest SQL `NULL` / `absent`; measure at import and work down over time through the governed verification flow. No `Unverified` sentinel.
- `[ ]` **Non-blocking data-quality improvement — `DOCEFL.ItemClass unassigned`.** Expected initial baseline **52** imported source rows at honest SQL `NULL` / `absent`; measure at import and work down over time through the governed audited assignment command restricted to the closed D8 vocabulary.
- `[ ]` **Non-blocking data-quality improvement — `DOCFLG terminal without ResolutionEvidence`.** Expected initial baseline **221** imported terminal rows at honest SQL `NULL` / `absent`; measure at import and work down over time through governed evidence append, without copying notes or rewriting history.
- `[ ]` **Non-blocking data-quality improvement — `DOCFLG snapshot ItemClass absent`.** Expected initial baseline **2,787** imported instances at honest SQL `NULL` / `absent`; measure at import and work down only through the governed atomic rule-assignment command. Each operation completes NULL snapshots only, never overwrites a non-null snapshot, and its audited completion count must equal the finding reduction.
- `[ ]` Execute the P1-0 history decision; if importing, run the validation service over the imported set, apply grandfathering per P1-5, and flag prototype-era quirks rather than silently normalizing them.
- `[ ]` Acceptance: seeded database reproduces the FDR reference sheets exactly through the P1-12 renderer; validation service passes clean, including the two new checks. **Render parity is why C4 adopts the pinned literals verbatim** — normalizing a value here silently breaks P1-12 acceptance downstream.
- `[ ]` **Acceptance, added by C1: re-ingest an overlapping bank statement and re-run the join test.** The pending July extract is the natural candidate and is already queued in the prototype. Require: every generated document re-anchors, zero duplicates, all 7 aggregates resolve through the junction, and `UNIQUE (Company, BMCode)` holds. Run it **before** the mapping is committed to — it is a free acceptance test of SourceKey anchoring on live data, and it may add aggregate payroll documents, which is C1's whole blocker.

## P1-2 — Claude CLI Runtime

Unchanged from v4.0.

- `[ ]` Implement the Worker extraction handler that stages the transient job directory (text layer and/or bounded page images only) and invokes headless `claude -p` with the versioned Sibyla skill under the restricted local account; stdout is the only channel back; bounded execution time; fail-closed cleanup.
- `[ ]` Validate every response strictly against `sibyla.claude-extraction.v1` before persistence; malformed, incomplete, oversized, or inconsistent responses fail safely to review, never partially persist.
- `[ ]` Persist per-run metadata on the immutable revision: model ID, skill version, instruction version, reading mode, token usage, reported cost, duration.
- `[ ]` Keep extraction concurrency configurable on the existing Extraction lane; on Anthropic API outage jobs stay queued and lease-retry — no fallback extractor, no silent degradation.
- `[ ]` Manage the Claude API key in the approved secret store only; define the egress policy for the CLI host account; add cost caps and usage alerting from day one.
- `[ ]` Re-execute the full security acceptance battery against the Claude CLI runtime on the target host: restricted account privileges, prompt injection embedded in a malicious PDF/image, concurrent runs without state corruption, post-reboot acceptance without interactive login.
- `[!]` **[owner: .NET agent + infra]** Restricted-token/service-identity design recorded in `docs/p1-2-o5-restricted-identity-design.md`, amended 2026-08-07 twice (per-job template grants Modify to **both** restricted identities on the one job directory, never a sibling's; the writable Claude CLI cache is per-job, inside the job directory, wiped by the same fail-closed job cleanup, never a static shared directory; `BLOCKED-P1-2` distinguished from `HOST-SESSION` for the acceptance checks that need the not-yet-built P1-2 handler; ACLs otherwise grant only helper read/execute and transient-job-directory access, no network-capable SIDs beyond what the CLI requires, no application/database/secret access). Everything automatable off-host is prepared and has now been through **two** independent-REJECT remediation passes (first pass: strip-not-preserve inheritance, PS5.1-safe fail-closed process-tree kill/verify and process-argument construction, idempotent egress-mechanism removal, positive-control validation, fail-closed secret-store account matching; second pass: idempotent password rotation with fail-closed expiry verification, the per-job Claude CLI cache lifecycle above, RW-vs-Deny coexistence, shared restricted-process environment scrubbing, local-account UserName/Domain normalization, broad-explicit-grant stripping on already-protected paths, and write-probe cleanup evidence — full disposition tables in the status doc) — provisioning scripts, acceptance-battery runner, and injection fixtures under `deploy/o5/`, `scripts/run-o5-acceptance.ps1`, `scripts/generate-o5-fixtures.ps1`, and `tests/fixtures/o5/` (control record and both REJECT-finding disposition tables: `docs/p1-2-o5-prep-status.md`). That candidate went through a third-pass independent review returning **ACCEPT with 2 Low advisories** (fail-closed `-Verify`/`-ExpectAbsent` parameter guard on `provision-egress.ps1`; creation-inside-cleanup-boundary for job/concurrency directory creation), both fixed in the same uncommitted candidate, and a subsequent **focused independent confirmation of those two fixes also returned ACCEPT** (2026-08-07) — full disposition and re-measurement in the status doc. **O5 preparation's own review cycle is resolved; O5 itself remains open** — it closes only on Miguel's target-host validation session, per `docs/deployment/o5-target-host-runbook.md`; production remains gated on that pending session, including the handler-dependent checks that stay `BLOCKED-P1-2` under the accepted design.
- `[ ]` Acceptance: security battery green; concurrency and reboot drills pass on the target host.

## P1-3 — Production Skill v1

- `[ ]` Derive the production skill from the FDR skill in single-document contract mode: no batch pipeline, no JSON table writes, no filesystem archive moves — reading, classification, and extraction only.
- `[ ]` Preserve the corpus-validated rules verbatim: text-first reading with mandatory visual fallback before any unreadable verdict; explicit-statement-only classification for payment/collection notices; entity identity by tax ID, never printed name; Invoice-Receipt iff payment is proven; Cancelled Invoice included with negated amounts; payout/settlement records excluded; "a wrong-but-plausible value is worse than an honest blank".
- `[ ]` Extend classification to the full P1-0 DocClass taxonomy, including basic-metadata extraction for Legal and Procurement classes.
- `[ ]` Distill `Specs/Lessons/lessons-learned.md` corpus knowledge into skill instructions v1. **Note the file is now ~186 KB, roughly 2.5× its 23 July size**, with a Stage 10 R6 section covering the new matcher rules. The skill remains stateless — lessons enter only through versioned skill updates.
- `[ ]` Build and version the golden document set, seeded from the FDR corpus: textless-but-legible scans, collection notices, foreign-currency invoices, missing supplier NIF, document-date typos, brand-vs-legal traps, plus Legal/Procurement samples.
- `[ ]` Require golden-set validation before any model, skill, or instruction change reaches production.
- `[ ]` Acceptance: golden-set precision recorded per field, document type, and reading mode; no regression versus prototype behaviour on the corpus samples.

## P1-4 — Deterministic Pipeline (Persistence, Routing, Gates)

Flag governance has moved out to P1-5; queues to P1-6.

- `[ ]` Persist validated extraction results transactionally: DOCLOG row (with DOCFAI routing for failures), classification, and — per DOCTYP rules — FDCHDR/FDCDTL rows or a DOCARC record.
- `[ ]` **Implement the `Reference Only` state (D5)** — captured, archived and searchable, but issued **no EntryCode by design**, so it cannot reach FDCHDR, PAYCTR, ENTBLC, the matchers or aging even by accident. Classified by rule EF0000045 (Information, Non-Blocking), which is deliberately distinct from "routed to FailureTable": these processed fine and were *chosen* not to be posted. Retain the full payload (both dates, net, VAT, total, counterparty, fiscal number, contract, archive name) so a later round can post one without re-reading the PDF.
- `[ ]` Route `NOT_A_DOCUMENT` results to DOCFAI/review at Low/Medium severity; auto-discard byte-identical re-submissions of previously discarded/purged items via the retained SHA-256, flagged at Information level.
- `[ ]` Implement entity resolution in .NET against live reference tables at write time (no multi-pass convergence): ENTALS aliases first, FiscalNo-anchored ENTMST matching, billed-to company excluded from the counterparty candidate pool.
- `[ ]` Add the `counterparty_not_internal_company` gate with the regression test where the internal company's NIF appears in the customer/`Contribuinte` field; intercompany documents require human confirmation.
- `[ ]` Support provisional counterparty entities with a pending tax identifier (synthetic `{CODENAME}001`): always unverified, flagged Medium, excluded from automatic approval until the real NIF is recorded. **Port the FDR's `[ISO2]-SYN-######` regime alongside it** — compliant non-EU is an accepted Information-level outcome; non-compliant is actively blocking.
- `[ ]` Implement VAT-splitting consistency gates: per-line VAT authoritative when stated; blended rate splits proportionally to line net amounts; reverse-charge/exemption record zero with a self-assessment flag; stamp duty on its own detail line, reconciled in the total but flagged non-VAT.
- `[ ]` Implement the currency gate: persist currency; when it differs from EUR (or the destination account currency), require a matching EXCRAT entry for the period before auto-approval; missing rate → visible `NO RATE`, fail closed to review. **27 FDCHDR rows currently carry `NO RATE`** (2024/2025 periods) and need a backfill decision at import.
- `[ ]` Implement date rules: Period = YYYYMM of DateDoc; two-tier AccountPeriod (≤10th ⇒ previous month; evidence-based service-period override); `DateDue` = stated, else DateDoc for invoice-receipts, else DateDoc+30d with an assumption flag; `DatePay` only with documentary proof.
- `[ ]` Implement EntryCode issuance per P1-0 design at persistence time.
- `[ ]` Implement semantic duplicate detection on (canonical CodeName, normalized DocumentID) — filename similarity is never evidence in either direction; exact matches are strong signals regardless of issue date.
- `[ ]` Add the periodic Maintenance job that re-detects duplicates and re-resolves aliases across the full history.
- `[ ]` Never overwrite a specialized DocumentType or any human-origin classification with automated defaults; recataloging, re-extraction, and fingerprint recalculation preserve human decisions.
- `[ ]` Acceptance: gate unit/integration tests green, including the misattribution regression; a second run over processed documents produces no unintended changes.

## P1-5 — Flag Governance: DOCEFL / DOCFLG, Blocking, Grandfathering

**New phase.** v4's P1-4 described a single-table flag engine (regex rules, worst match
wins, `AutoReviewable`, Bot approval) that Stage 9 R1 superseded. This phase ports the
replacement.

- `[S]` `[ ]` Implement **DOCEFL as the rule catalogue** — 43 rules today, each carrying flow dimension, severity, blocking level, and routing target. Rules are data, administered in P1-8, never code.
- `[S]` `[ ]` Implement **DOCFLG as the instance log** — the lifecycle and audit authority (2,574 instances, 172 open). An instance has its own identity and status; it is never recomputed away.
- `[S]` `[ ]` Implement the **blocking vocabulary**: `Block Document`, `Block Financial Entry`, `Block Classification`, `Block Reconciliation`. These are not v4's "gates" — reconcile the two concepts deliberately so the system has one enforcement mechanism, not two that disagree.
- `[S]` `[ ]` Implement **`blocking_open_instances()` as the single enforcement view**, consulted in code by the capture, entity, and item flows before any posting or classification. In the prototype this was honoured "by discipline" for one round before being wired; in .NET it is wired from the start. DOCRQE displays blocking but never enforces it.
- `[S]` `[ ]` Implement **prospective enforcement with grandfathering** — a new rule does not retroactively block rows that predate it. Port the TC5 test case: a blocked document cannot post; a grandfathered one does not block.
- `[ ]` **Supersede logic must not be append-only.** A Stage 10 R6 defect worth not repeating: the reconciliation dimension left instances Open forever once their finding stopped being produced, because nothing superseded them against the detector's current finding set. 26 stale findings accumulated. Every dimension supersedes against its own current findings.
- `[ ]` Flag writes idempotent (strip prior marker before append); human review never destroyed.
- `[ ]` Acceptance: a blocked item is refused by code rather than discipline; grandfathered rows import without raising; a detector whose finding disappears supersedes its instance within one run.

## P1-6 — Review Queues: DOCRQE and RECREV Persisted Model

**New phase**, implementing decision D1.

- `[S]` `[ ]` Implement **DOCRQE (document level) and RECREV (line level) as persisted tables** sharing one layout and one vocabulary. Permanent ReviewIDs; never-delete semantics; human decision state survives a full pipeline rerun.
- `[S]` `[ ]` Implement the **5-value decision vocabulary**: `Accept`, `Reject`, `Accept with Changes`, `Need More Information`, `Defer`. `Accept with Changes` carries structured single-field master-data edits and is the reason the apply-engine exists.
- `[S]` `[ ]` Implement the **structural fields**: `Status`, `DecidedBy`, `DecisionDate`, `AppliedReference`, `TargetField`, plus a finding discriminator on the RECREV key so a second detector on the same line does not collide with the first.
- `[S]` `[ ]` **Implement `ItemClass` — Decision / Status / Annotation — as a first-class column** (Luís, 3 Aug 18:10). **CORRECTED by S2 (2026-08-05): a DOCFLG instance SNAPSHOTS the rule's `ItemClass` at detection and is never recomputed.** Freeze decision 15 and this item said opposite things; snapshot wins, for the same reason C3 chose immutability — a decision someone already took must not change underneath them. The original wording follows and is superseded: ~~derived at *creation* from what a human could actually change, and recomputed every run so a rule change propagates without a migration. **Decision**: a DOCEFL rule raised it, it gates something (BlockingLevel ≠ Non-Blocking), it targets master data, or it is a Duplicate. **Status**: a live state on a control table the pipeline recomputes every run, clearing when the fact changes. **Annotation**: provenance with no target, no blocking level, nothing to decide. **Only Decision items are ever Open**; Status and Annotation are never Open, may use any governed terminal state, remain fully visible, and are never counted as work outstanding. A reviewer entering a UserDecision on any item overrides the classifier immediately: human engagement outranks classification.
- `[ ]` **This is not cosmetic.** Without it the prototype's queue read as 2,616 open items against 9 decided; with it, 164 open of 180 Decision-class, beside 1,290 Statuses and 1,187 Annotations. Porting the queue without `ItemClass` means inheriting a 93%-noise backlog on day one — and, in Luís's words, *the cost of a phantom backlog is not the phantom items, it is the real one hiding behind them.*
- `[ ]` **A finding that clears is CLOSED with evidence, not annotated and left open.** The prototype's Document Entry Review Procedure originally mandated annotate-without-closing, which is what generated 630 ghost items. Retained ≠ Open.
- `[ ]` Severity tiering (Critical/High/Normal/Low/Information) as **ordering over the persisted table**, not a recomputation of it. `Critical` is reserved for actively-blocking DOCFLG-linked items.
- `[S]` `[ ]` Implement the **Observations mechanism** — a note written once and keyed to the list of ReviewIDs it applies to, surfaced as a column on both queues (`user_observations.json` in the prototype). This is how a standing user instruction persists across rounds instead of being re-litigated, and it is the mechanism by which the "learning from user input" flow in the Roles and Responsibilities policy actually works.
- `[ ]` Acceptance: a decision recorded on either queue survives two full pipeline reruns byte-identically; an Observations note stamped on ten items renders on all ten.

## P1-7 — Decision-Application Engine

**New phase** (FDR ref: G-01). The prototype's `apply_review_decisions.py`. Stage 10 R1
identified this as the bottleneck for everything downstream of the queues — decisions
were being captured with nothing to execute them.

- `[S]` `[ ]` Build **one engine serving both queues**, executing `Accept` and `Accept with Changes` against target tables: flags, structured single-field master-data updates, and RECREV links.
- `[S]` `[ ]` Implement **reject-persistence as a matcher constraint** — a rejected proposal must never be re-proposed by any matcher, on any subsequent run. This is a hard constraint, not a convention.
- `[ ]` Map decisions to DOCFLG status transitions so an applied decision closes its flag instance.
- `[ ]` Every application writes an audit record correlated to the ReviewID and the actor.
- `[ ]` Acceptance: a real decision on each queue executed end-to-end by the engine; reject a proposal from matcher A and prove both reruns and matcher B receive SQLSTATE `23514` for the same company/source/candidate identity even though MatcherKind differs; control record `Initial + Expected = Result`; idempotency byte-stable.

## P1-8 — Reference-Layer Management UI

- `[ ]` CRUD and review screens for ENTMST (roles, provisional/pending-NIF state, **`RelatedParty` flag**), ENTALS/ITMALS aliases, ITMMST, ENTITM, ITMCLS/**MNGACC**/SNCACC/**COCACC** (read-mostly), and **DOCTYP/DOCEFL rule administration** with audit on every change.
- `[ ]` EXCRAT management: monthly rate entry from real authoritative sources (ECB/BdP), inverted-quote handling, EUR→EUR identity rows, carry-forward for the running month displayed and flagged as such; estimated rates impossible to enter as confirmed.
- `[ ]` Surface auto-created items (`AutoCreated`, flagged) and provisional entities in a review list; classification is sticky once set — manual edits survive reprocessing.
- `[ ]` **COCACC needs real data before its validation means anything** — it currently holds one placeholder row (`"01"`/`"TBD"`) and every CostCentre value is uniform, so the consistency check passes by construction.
- `[ ]` **75 provider entities are missing `InvoiceFrequency`** — surface as a review list; it is an input to the year-end settlement control in P1-11.
- `[ ]` Acceptance: a correction made in the UI affects the next processed document immediately, without any batch rerun.

## P1-9 — Review and Document UI

- `[ ]` Severity-ordered review queue over the **persisted** DOCRQE and RECREV tables with filters by DocClass, source, company, and flag category.
- `[ ]` **Per-field review view — one line per (document, field).** `DocID`, the field needing review, its current value, and the proposed value where one exists, so multiple lines can exist per document and a reviewer can filter to "all documents missing FiscalNo" or "all documents where the entity needs identification" in one pass. Today one queue item bundles several field problems inside prose text and `TargetField` is blank on v1 detectors; this is where `TargetField` becomes real, populated, rendered data.
- `[ ]` **RECREV per-field view, reconciliation flavour.** Must serve: (a) bank movements whose entity the system cannot identify, where a user stating the entity lets matching proceed immediately; (b) multiple open invoices from one entity where confirming an exact match on one triggers re-review of that entity's whole matching sequence.
- `[!]` **[owner: .NET agent — P1-9 data model]** **FIFO unwind is a correctness requirement, not a convenience.** For an entity that always invoices the same amount (typical of direct debits), FIFO assignment can be wrong when an already-paid document was inserted later without its payment proof — the system, believing it open, matches it against a more recent payment intended for a more recent invoice. An exact-match user confirmation must be able to unwind and re-sequence the entire FIFO chain for that entity. The data model must permit this even before the UI exposes it.
- `[ ]` Decision operations expose the 5-value vocabulary; corrections stored as immutable revisions with actor, timestamp, and reason.
- `[ ]` Implement **Discard** (any reviewer; soft removal from all views/exports/archive, bytes retained) and **Purge** (authorized role only; permanent byte deletion with an audit tombstone retaining metadata + SHA-256) per the P1-0 lifecycle; purge is the sole deletion path, is audited, and is blocked for any document that ever reached a fiscal or archive table.
- `[ ]` Populate `SupersedesRevisionId` on reprocessed revisions and correlate reprocess audits to the reprocess operation, not the intake correlation.
- `[ ]` Attributed, audit-backed document comment log preserved from the current implementation.
- `[ ]` **Design the queue as a triage surface, not a backlog to clear**, per the Roles and Responsibilities policy and the `ItemClass` split in P1-6. A queue nobody can clear is a queue that must be filterable by exception, not paged through.
- `[ ]` Acceptance test: no automated recataloging, re-extraction, or recalculation ever overwrites a human-origin classification or correction.

## P1-10 — Archive (Nextcloud)

Unchanged from v4.0.

- `[ ]` On approval, write the business copy to Nextcloud under the P1-0 class-based layout; CodeNames only in filenames; `{Company}_{DocumentType}_{Entity}_{Period}_{NN}.ext` convention for fiscal documents. **Archive folder follows `DateDoc`, never `AccountPeriod`** — proven in the prototype's 3 Aug end-to-end test (51 files archived): AccountPeriod is the *service* period, so a 2026-01-05 invoice for December service files as 202512 and lands where nobody looks for it. The registry keeps AccountPeriod for accounting; the archive keeps DateDoc for finding things.
- `[ ]` **Filename comparisons are case-insensitive, everywhere.** A case-sensitive guard in the prototype's archive-repointing script silently skipped rows storing `.pdf` where the disk held `.PDF`, leaving them pointing at a dead path — the exact defect the script existed to prevent.
- `[ ]` Confirm the business copy before the document transitions to Archived; the immutable original is never moved or mutated.
- `[ ]` Reprocessing never duplicates a business copy; a Worker kill during archive copying recovers to exactly one confirmed copy.
- `[ ]` Acceptance: exactly-one-copy proof under crash/retry conditions.

## P1-11 — Bank Movements and Reconciliation (FDR Layer 5)

**Pulled forward from Phase 2 per decision D3.** The prototype scripts are the spec —
but the *current* scripts, not the 29 July snapshot. See the re-baseline note below.

### Tables and ingestion

- `[S]` `[ ]` Implement BNKACC, BNKMOV, BNKCHK, BNKMAT, BNKREC; activate ENTBNK.
- `[S]` `[ ]` **BNKMOV is exclusively sourced from official bank extracts.** No other process may write to it — RECREV structurally cannot. This invariant held through every Stage 9 round and must hold in .NET.
- `[S]` `[ ]` Port the statement parsers (BPI, BCP, Revolut) and balance-continuity checks. Current coverage: 1,960 movements; BNKCHK 111 Okay, 35 Waiting, 10 Missing, 0 Unbalanced.
- `[ ]` Minimum Layer 6 slice for ground truth: PAYCTR/PAYDTL and RCVCTR/RCVDTL as matcher **inputs**. ENTBLC balances, aging, and payment-priority views remain Phase 2.
- `[ ]` **Recurring payment-control imports — build per decision D9: additive, keyed by period.** Each import declares its period and appends; re-importing a period replaces only that period. Idempotent by construction, and history survives. Replaces the prototype's single hardcoded filename, which left the next period's file with no ingestion path.

### Matching

- `[S]` `[ ]` Implement BNKMAT as **governed match types** (11 types × 14 fields: control logic, tolerances, required evidence, test cases). Adding a type requires satisfying the governance columns — no free-text match types anywhere.
- `[S]` `[ ]` **Ground truth outranks heuristic inference** (Stage 10 R2 decision). Audit evidence is retained on override.
- `[S]` `[ ]` Port the tiered matchers with their current match statuses: exact (687), approximate (14), period aggregate (8), balance netted (42), FIFO inferred (17), payment-control ground truth (30), receivable-control ground truth (7), review decision (15), payout reconciliation (80), FX-tolerant (1), internal (177).
- `[S]` `[ ]` **Port the eligibility gate** (`sanitize_ineligible_matches.py`) — it asks not "which claimant wins" but "should this movement have been allowed to claim a document at all". Three rules: the bank's own recurring charge cannot settle a document; movement direction must agree with the document's **signed** total (so a credit note is correctly settled by money coming in); same-currency approximate matches get a **proportional** tolerance, because the flat €15 allowance exists to absorb FX spread and is only justified across currencies.
- `[S]` `[ ]` **Ground-truth tolerance is 5% capped at EUR 50** (EUR 0.50 floor), not a flat 10%. **The cap matters more than the percentage** — an unbounded band admits more nonsense the larger the payment, which is exactly backwards. Applies to both `match_bnkmov_payctrl_groundtruth` and its receivable-side mirror.
- `[S]` `[ ]` **Exact beats approximate.** When any candidate matches the control amount to the cent, approximate candidates are excluded from the pool outright. A tolerance exists so rounding or FX does not *lose* a real match; it was never meant to let a plausible line *compete* with an exact one.
- `[S]` `[ ]` **Own-account identity guard** (`sanitize_own_account_transfers.py`) — any movement whose description names an IBAN listed in BNKACC yet claims a supplier document is released to Internal and paired with its receiving leg. Reasons about identity rather than amounts: an amount can coincide, a destination account that is our own cannot. Released lines are **Internal, not Unmatched** — we know exactly what they are.
- `[S]` `[ ]` **Reverting a match must retract what it wrote** — clearing DatePay values sourced from a movement that no longer claims the document, rather than leaving documents looking paid while their bank line sits unmatched.
- `[S]` `[ ]` **Settlement-event collapsing**: several movements on one day against one financing document are judged as a summed event against the document total. Sums at or under the document total are an instalment plan, not a duplicate settlement. This single rule accounted for 87 of 108 blocking flags.
- `[ ]` **Via Verde sum-of-charges matcher** — charges precede invoicing and one invoice bundles many sub-entity lines, so a single bank charge can combine several transactions. Match a period's summed charges against the invoice's overall total (N:1), never line-level; treat any Open/Overdue Via Verde payable as a timing artifact, not real debt.
- `[ ]` **Related-party stream handling.** A whole class of movements will never have a fiscal document: intercompany and shareholder funding. 2026 figures — €433,500 in from Itoorer/Toorist against €260,442.75 to Factor Matriz, €18,450.00 to Confidencial, €17,155.00 to Diogo Pinto de Sousa and €2,425.75 to Miguel Teixeira; net €135,026.50. Every matcher assuming "an unmatched movement is a missing document" is structurally wrong here. Resolve by classification (P1-0's `RelatedParty` flag) plus a Non-Blocking Information DOCEFL rule so new movements are auto-recognised on arrival, not surfaced as fresh High-priority unmatched lines every month.
- `[!]` **[standing caution — do not regress]** **Dual-role trap — do not widen the related-party rule into an entity-level ignore.** Confidencial Consultadoria is both a related party *and* a genuine supplier with real monthly invoices, normally settled and matched. Miguel Teixeira likewise appears in payroll. An entity-level ignore makes real payables disappear.
- `[ ]` **"Reconciled" is not "recorded" — build per decision D6.** A **distinct BNKMAT match status** (`Matched — ledger reference, no entry`), **excluded from the reconciliation percentage**. Chosen over a flag on an otherwise-`Matched` row because a separate status makes the movement impossible to count as clean by construction, rather than relying on whoever reads the metric to filter. Satisfy the match-type governance rule when adding it: control logic, tolerance, required evidence and test case. This is what concealed a six-month, €142,835.50 payroll hole in the prototype — the cash reconciled while the entry layer was empty, and nothing flagged it *because the movements looked matched*.
- `[ ]` **Year-end settlement control** — every 2026 document settles in 2026 or early 2027, so any 2026 FDCHDR still open after ~2027-03 is an anomaly. The companion to PAYCTR/RCVCTR aging.
- `[ ]` Accounting treatment for the related-party stream (shareholder loan / current account / capital contribution) is a **business decision, not a matching problem** — it is what actually resolves these lines. Route to the D-series decision register.

### Reconciliation flags and review

- `[S]` `[ ]` Implement the reconciliation DOCEFL dimension and the `Block Reconciliation` blocking level (P1-5).
- `[S]` `[ ]` Port the detectors: single-leg transfer, out-of-band FX, blank match type, duplicate movement, unmatched aging, amount-mismatch groups, broken links.
- `[ ]` Verify the **sign-convention risk** flagged in Stage 10 R1 (finding R-05): BNKREC group totals may sum opposite conventions for DO versus CC accounts. Documentation-inferred only; verify against code before porting.
- `[ ]` Link internal-transfer legs to each other — a single-legged transfer currently passes every control.
- `[ ]` Define the Stripe payout auto-close tolerance, currently recorded as "not numerically defined yet".

### Re-baseline

- `[!]` **[standing caution — regression baseline, FDR side]** **The prototype's reconciliation baseline is 94.6% as at 3 Aug 19:30.** It went 93.8% → 91.8% → 94.6% in one day: the eligibility gate reverted 13 wrong matches, then the day's captures and corrections lifted it past the original figure. Regression-test the *prototype* against 94.6%; understand the 91.8% dip as the shape of removing bad matches, never as a target.
- `[!]` **[standing caution — the Sibyla baseline is a different number, and that is not a regression]** **94.6% is not reconstructible from BNKREC.** Six plausible definitions were computed at the pin — 47.8%, 53.4%, 54.3%, 54.3%, 55.9%, 58.3% — and none comes close; the 865 `Unmatched` rows are genuinely unresolved, each carrying a BMCode, a non-zero amount and an explicit *"no bank/document link yet"* flag. So the acceptance criterion below, as originally written, was **unrunnable**. C18a adopts an explicit definition and re-baselines to **55.9%**. **Anyone comparing 55.9% to 94.6% will conclude the port collapsed — it did not; they measure different populations.** Never quote either figure without its definition. Ask Luís for his definition when he returns: if his metric measures something real over a narrower population, both numbers should be reconciled rather than one quietly replaced.
- `[!]` **[standing caution — porting discipline]** **Port from the current scripts, not the 29 July snapshot.** Everything in the eligibility-gate, settlement-event, and match-retraction bullets above post-dates v4.0's "prototype matcher scripts serve as specs" note.
- `[ ]` Acceptance: **actively-blocking flags reproduce the prototype's 0** and no more — which now depends on C3 being applied, since seeding `EnforcementStartsAt` from `EffectiveFrom` would leave all 29 blocking on day one and fail this criterion outright; ~~94.6% reconciled reproduced ±0.1%~~ **superseded by C18a — the reconciliation rate reproduces 55.9% ±0.1% under the explicit definition, computed as a view or generated column, never asserted as prose**; every detector's instances route to the correct queue and declare their `ItemClass`; grandfathering applied to pre-existing rows.

## P1-12 — Excel Export (On Demand)

- `[ ]` Implement the export renderer with FDR sheet parity (render order, formats, permanent codes, values only, no formulas, no totals rows) — **roster corrected and extended**: DOCRQE, DOCLOG, FDCHDR, FDCDTL, ENTMST, ENTALS, ITMMST, ITMALS, ENTITM, ITMCLS, **MNGACC**, SNCACC, **COCACC**, EXCRAT, DOCFAI, **DOCTYP**, DOCEFL, **DOCFLG**, plus Layer 5: BNKACC, BNKMOV, BNKCHK, BNKMAT, BNKREC, RECREV. The prototype workbook currently renders **31 sheets**.
- `[ ]` UI export button offering both targets per export: downloadable .xlsx snapshot, or Graph refresh of the shared workbook (delegated auth via DPAPI-protected MSAL token cache, workbook session, schema-fingerprint check, replace-table-contents semantics, EntryCode as the idempotency column).
- `[ ]` Record every export in the export-run ledger (actor, timestamp, target, row counts, outcome); no continuous synchronization remains anywhere; nothing writes back from Excel.
- `[ ]` **A decision must round-trip.** The 5-value vocabulary and the Observations column render to Excel and survive re-import; this is the check that the two systems have not forked their decision models.
- `[ ]` Acceptance: exported workbook byte-comparable to the renderer golden output; repeated Graph refresh is idempotent; distinct-entity-string check confirms no formatting divergence against the database.

## P1-13 — Channels End-to-End, Durability Minimum, and Pilot

### Channels

- `[ ]` Configure the existing Hermes `orquestrador` (Apolo) profile to call the Sibyla channel-intake API per the addendum.
- `[ ]` Verify email, WhatsApp/Baileys, and Mattermost documents reach Sibyla with trusted sender/message metadata; Mattermost uses `group_sessions_per_user` isolation.
- `[ ]` Complete WhatsApp LID + E.164 dual-alias authorization per `docs/whatsapp-lid-and-e164-authorization-plan.md`: strict `digits@lid` grammar (byte-exact, no trimming), separate allowlist rows per alias, TDD test set, guarded and evidence-backed allowlisting and rollback; intake stays fail-closed on identity rejection.
- `[ ]` Verify both email intake forms end-to-end: attached documents, and direct-download links from authorized sources under the addendum's safe-downloader rules; route login-required links and unlisted domains to manual triage.
- `[ ]` Back up `HERMES_HOME` (including the Baileys session); verify gateway auto-start and reboot session preservation.
- `[ ]` Admin UI for users, companies, departments, memberships, channels, and allowlists; membership changes effective without a new token; cross-company/forged-metadata/disabled-membership tests.

### Durability minimum for go-live

- `[ ]` Expired-lease job recovery; dead-letter alerting and operator replay controls.
- `[ ]` Periodic PostgreSQL↔Nextcloud reconciliation.
- `[ ]` Off-host backups (PostgreSQL, Nextcloud, `HERMES_HOME`, DPAPI material, configuration) and a restore drill into an empty environment.
- `[ ]` Worker-kill drills during extraction and archive copy.
- `[ ]` Metrics: documents/day, auto-approval rate, reading mode, model cost, job duration/state, reconciliation divergence, **bank-reconciliation rate against the 94.6% baseline**, and **queue backlog reported on the Decision-class denominator only**.

### Pilot and go-live

- `[ ]` Run the Luís + Miguel pilot with real mixed-class documents across all channels, **including at least one full bank statement cycle** now that Layer 5 is in scope.
- `[ ]` Manually spot-check a sample of auto-approved successes against originals before enabling automatic approval — a plausible aggregate match rate is not evidence of correctness.
- `[ ]` Second-run idempotency check over the pilot corpus.
- `[ ]` Quantitative acceptance criteria (accuracy, review rate, latency, reliability, cost) recorded; explicit business, security, and operational go-live approval.

---

## Phase 2 — Parked (do not start without re-planning)

Reduced from v4.0: Layer 5 has moved into Phase 1.

- `[ ]` **FDR Layer 6 remainder** — ENTBLC balances, aging, payment-priority and receivables-control views, dispute/hold representation on PAYCTR. The ground-truth slice of PAYCTR/RCVCTR is in P1-11.
- `[ ]` **"Net Balance Match"** reconciliation type over ENTBLC, validating PAYCTR/RCVCTR internal consistency. Design seed only.
- `[ ]` **ERP integrations** — acceptance and activation of the already-implemented neutral core + Moloni adapter: sandbox/live acceptance with real OAuth credentials, rate-limit measurement, operational alerting (`[!]` a Worker outage longer than 14 days expires the Moloni refresh token — alerting is mandatory before production), controlled draft-invoice pilot, and re-binding the `Integration*` ledger to the FDR schema. No rebuild — the code is parked, not deleted.
- `[ ]` **Deep Legal/Procurement metadata and alerts**: `EffectiveDate`/`ExpiryDate`/`RenewalDate`/`NoticePeriodDays`/`AutoRenews`, expiry-alert job, full-text search decision, employment-contract counterparty modeling, avença→invoice linkage.
- `[ ]` **`financial-agent` proposal engine**: target-host acceptance and operator UI (implementation exists and is parked); financial Q&A read models and endpoints; calibration expansion beyond the P1-3 golden set.
- `[ ]` **Item-dimension and archive-dimension DOCEFL rules** (FDR refs KL-E6, KL-9b) — the two flag dimensions Luís has not yet built.
- `[ ]` **Full-history reconciliation (2025 + 2026 read together).** The 2025 related-party leg (~50 movements, €877,861.22 gross in against ~€147,458 of outflows) is the counterpart that makes the intercompany stream balance across years. Closed as out-of-scope under the 2026-only rule, but the two legs must eventually be read together.

---

## Decisions Register — all closed

Every item that previously waited on Miguel or Luís is resolved. Kept with its resolution
so the reasoning survives.

| # | Was | Resolved |
|---|---|---|
| A1 | Natural key for a bank-generated P/F/O document | Movement natural key via `SourceKey`; `SourceFile` excluded, occurrence tiebreaker recomputed (Luís, Roadmap 35) |
| A2 | `DocLogId` beside LGCode, or LGCode per-document | LGCode re-keyed on `(Filename, EntryCode)`; 1,475 rows / 1,475 codes; no `DocLogId` needed (Roadmap 36) |
| A3 | Confirm RCVCODE mirrors the PAYCODE key | Proven: 0 of 245 RCVCODEs move under shuffled cold rebuild (Roadmap 29) |
| B3 | Import history vs fresh start; 2026-only or 2025 | **D4** — full 2026 + reference layer; 2025 out |
| — | `Reference Only` third document state | **D5** — first-class state, rule EF0000045 |
| B2 | Enforce "reconciled is not recorded" | **D6** — own match status, excluded from the rate, emits a Decision item |
| — | Grandfathered blocking flags on import | **D7** — import with `DetectedAt`, honour prospective enforcement |
| B1 | Is `ItemClass` final? | **D8** — frozen at three values; a fourth is a migration, and that friction is intended |
| — | Recurring payment-control import convention | **D9** — additive, keyed by period; re-import replaces only that period |

## Standing Items From the FDR Register (no .NET dependency)

Luís's Stage 10 R1 review produced a register of ~30 business decisions (D-01…D-30)
requiring user or accountant input. They are not restated here — `Specs\Stage10_Round1_
Roadmap_OpenItem_Review.md` §B.4 is the authority. The ones that gate .NET work:

- `[x]` **Decided (D8): `ItemClass` is frozen at Decision / Status / Annotation.** A closed enumeration, not an extensible one. Every new detector must declare which of the three it emits, and must fit one of them.
- `[ ]` **Company dimension on capture** — the prototype's inbox is shared between Gott and Itoorer with no signal in the folder, and capturing by location rather than content would book Itoorer revenue as a Gott cost (his Roadmap 33). The .NET `counterparty_not_internal_company` gate covers this side; confirm whether the prototype needs its own copy.
- `[x]` ~~12 residual blocking flags = 4 real cases~~ — **all closed 3 Aug.** Worth reading before porting the matchers: MEO I26030034 was never a conflict (BM000214 was a transfer to Gott's own BCP account, and the PAYCTRL row agreed with inference to the cent — the cause was a flat 10% tolerance, not a policy gap); Radius closed when the user supplied the document. Luís's recorded lesson: *a measurement error dressed as a policy question is more dangerous than either, because a policy question invites a decision rather than an investigation.*
- `[ ]` **I26070039** — €20,000.00 dated 2026-07-20, deleted in Stage 10 R6 with no mirror receivable and no bank movement. Re-create from evidence if the outstanding BPI July statement shows it.
- `[ ]` **6 ITMMST rows with blank SNCACC**, and SNCACC reviewed against the chart of accounts from LMD's accounting team (his Roadmap 26) — awaits accountant input.
- `[ ]` **202601–202606 cross-check** of every Gott financial document against the accounting records (his Roadmap 27).
- `[ ]` **Two documents left in the prototype inbox by design** (his Roadmap 34): `Hydra iT - Fatura FV2502560.PDF` (2025-11-13, genuinely uncaptured but outside 2026 scope) — capture or formally exclude; and `Doc_GBA_FA_48_2026_Signed.pdf`, parked as redundant but byte-different from the archived copy at the same size, almost certainly the signed variant.
- `[ ]` **Real cost-centre list** — COCACC validation is vacuous until it exists.
- `[ ]` **No `EnteredBy`/`EnteredAt` anywhere** in the prototype (FDR ref KL-38). The .NET harness supplies actor identity natively, so this closes on migration — worth noting as a case where the port *fixes* a prototype gap rather than inheriting it.

---

## Definition of Done for v5.0 Work

- `[ ]` Behaviour matches a v5.0 acceptance requirement and does not preserve superseded architecture without an explicit reason.
- `[ ]` Claude reads and proposes; only deterministic .NET code decides, persists, and executes side effects after validation and approval.
- `[ ]` Scoped API operations resolve and enforce current server-side memberships.
- `[ ]` Business-significant state changes and external calls are auditable and correlated.
- `[ ]` Jobs are idempotent and tested for process termination, timeout, retry, and duplicate delivery; a second run produces no unintended changes.
- `[ ]` Human-origin classifications and corrections are never overwritten by automation.
- `[ ]` **A recorded decision survives a full pipeline rerun.** Queue state is persisted, never recomputed.
- `[ ]` **A rejected proposal is never re-proposed** by any matcher on any subsequent run.
- `[ ]` **New rules do not retroactively block imported history.** Grandfathering is applied and evidenced.
- `[ ]` A wrong-but-plausible value is worse than an honest blank: missing rates, identifiers, or evidence fail closed to review, never get estimated.
- `[ ]` **Metrics are honest.** A number that improves because a check was removed is a regression, not progress — the 93.8%→91.8% reconciliation correction is the reference case.
- `[ ]` **An item is only Open if a human decision could change something.** A finding the pipeline resolves is CLOSED with evidence, not annotated and left open. *(Luís's phantom-backlog rules A and B — the old wording of his Core Rule 4 mandated the defect and created 630 ghost items.)*
- `[ ]` **"% worked" is reported on the Decision denominator only.** A percentage across a whole file measures a register, not a backlog.
- `[ ]` **A state change closes its predecessor and names its successor's ReviewID.** Otherwise every change silently adds a permanently-open ghost.
- `[ ]` **Before adding a detector, state which `ItemClass` it emits.** A detector that cannot say whether its output is a Decision, a Status or an Annotation does not yet know what it is for.
- `[ ]` **"Reconciled" is not "recorded".** A movement matched to a ledger reference rather than an FDCHDR EntryCode counts as reconciled in every metric while leaving no financial entry behind it. Such movements are reported as their own class, never counted as clean — this is what hid a six-month, €142,835.50 payroll gap in the prototype.
- `[ ]` Database changes use reviewed EF Core migrations and PostgreSQL-specific integration tests.
- `[ ]` Skill, instruction, or model changes pass golden-set validation before production.
- `[ ]` Configuration contains no passwords, tokens, or client secrets in source control.
- `[ ]` Documentation and runbooks are updated with every configuration, deployment, or workflow change.
- `[ ]` `dotnet build GOTT.Sibyla.slnx` passes with no warnings; `dotnet test tests\Sibyla.Tests\Sibyla.Tests.csproj` passes with no failures.
- `[ ]` Every round closes with a verified `Initial + Expected = Result` control record — the FDR discipline, adopted.
- `[ ]` The relevant phase acceptance proof is recorded before the item is marked complete.

---

## Superseded Work — Do Not Continue

From the previous architectures (v3.2 list still applies):

- Redis queues, outbox dispatch, Kanban sync, folder watchers, `.eml` drops, direct IMAP.
- Whole-workbook Graph download/edit/upload; application-permission Graph assumptions; direct user-triggered Excel commits.
- Permission grants/document routes as primary ACL; mutable review fields on a single intake record.

Superseded by v4.0:

- `hermes -p documental-agent` as the extraction engine and `sibyla.extraction.v3.1` as the production contract (retained only as reference; the security harness and staging pipeline are reused by P1-2).
- The continuous `ExcelCommit` workflow on the document critical path; replaced by the P1-12 export ledger.
- The v3.2 financial document schema where the FDR schema replaces it.

Newly superseded by v5.0:

- **DOCRQE as a recomputed live view** (v4 P1-4). Replaced by the persisted queue model — see D1.
- **The single-table flag engine** (v4 P1-4: regex rules, worst match wins, `AutoReviewable`, Bot approval cleared on change). Replaced by the DOCEFL/DOCFLG two-table machine with blocking levels and grandfathering — see P1-5.
- **The approve/reject/retry/reprocess/needs-attention decision vocabulary** (v4 P1-6). Replaced by the shared 5-value vocabulary — see P1-6.
- **`MNGAPL` and `DOCTYPE` as table names.** Now `MNGACC` and `DOCTYP`.
- **The claim that `add_flag_evaluation.py` never existed** (v4 P1-4). It was rebuilt on 30 July.
- **"826+ headers" and the 93.8% reconciliation figure** as planning baselines. Now 1,158 and 94.6% (3 Aug 19:30).
- **Layer 5 as Phase 2 work** — see D3.
