# P1-0 — review findings before sign-off

**Date:** 2026-08-04 · **Reviewer:** assisted review commissioned by Miguel Teixeira, standing in for
Luís Nascimento (unavailable).
**Prototype pin verified:** `9359c67` ("Add Stage 10 Round 7 roadmap review") — the same SHA the
freeze recorded. Read-only throughout; no financial or personal value was copied out.

> **Re-checked against `6146004` (2026-08-04 17:04), the commit after the pin.** It adds 19 files,
> all under `Inputs/`: 18 supplier invoices and one July bank statement. **No `Editor/Data/*.json`,
> no `Scripts/`, no `Specs/` changed**, and every count below re-measures identically (DOCLOG 1,475 /
> 1,475 · DOCFLG 2,711 · DOCEFL 45 · DOCRQE 2,668 · RECREV 1,201 · BNKGEN 426 · BNKMOV 1,960 ·
> ENTBNK 48 · DOCTYP 19 · OFDGAP 27). **Every finding in this report stands unchanged.**
>
> The new inputs are not yet ingested. Two consequences worth planning around are noted in Part 4.

**Scope.** Every item on the freeze summary's own checklist, plus the three items the freeze
assigned to Luís. Structural claims about the prototype were tested against the live
`Editor/Data/*.json` and `Scripts/`, not against the drafts' description of them.

---

## Bottom line

> **HISTORICAL — accurate at 2026-08-04, superseded as state.** All nineteen findings were decided
> as C1–C19 on 2026-08-04 and applied to the drafts on 2026-08-05; P1-0 was accepted on 2026-08-05
> with a four-item carve-out. **Do not quote this document as current status** — see
> `docs/PROJECT-STATE.md`. The evidence and reasoning below remain valid.

**Do not sign off yet.** The design is sound where it matters most and wrong in a large number of
mapping details.

- **The identity model holds.** D2's three closures were re-verified against real data and all three
  stand. This was the expensive part and it is correct.
- **The field-level mapping does not.** Ten defects would fail on import at the pin — not
  "might fail", *would* fail, on counted rows.
- **Four of the nineteen "captured decisions" are frozen in name only** — the decision text exists,
  the operative value or mechanism does not.
- **Luís's three items are not clean.** The schema mapping has blocking defects; the Discard/Purge
  design directly contradicts a binding Engagement Rules document; the Roles policy claims
  supersession authority it was not given and drops the clause that connects it to the threshold
  layer.

Signing P1-0 as-is would hand P1-1 a migration train that fails on first contact with the data.

| | Count |
|---|---|
| Blocking defects (proven against pinned data) | 10 |
| Blocking design/governance defects (analysis) | 9 |
| Decisions frozen in name only | 4 |
| Checklist rows over-claimed by the self-review | 5 |
| Claims verified clean | ~40 |

---

# Part 1 — Miguel's items

The sixteen P1-0 backlog items. Verdict scale: **OK** · **DOUBT** (defensible but under-specified) ·
**PROBLEM** (must be fixed before sign-off).

### 1. `sibyla.claude-extraction.v1` contract spec — **DOUBT**

All twelve required elements are present. Three gaps:

- `identifierType` is emitted as a required field with its own confidence entry, but **has no
  controlled vocabulary anywhere**, while the draft says "validate all enums case-sensitively". An
  enum with no enumeration.
- Party `fieldConfidence` covers printedName / fiscalNumber / countryCode / identifierType but
  **omits `role`** — the one party field the draft promises to validate.
- The canonical example ships a **synthetic fiscal number** (`[ISO2]-SYN-######` shape) as Claude
  output. That value is never printed on a document. Showing it as canonical models exactly the
  fabrication `AGENTS.md` forbids, and no validation rule rejects the pattern from an extractor
  response.

**Also:** the warning maximum of five is stated and justified, but the pairing rule elsewhere in the
same draft makes **more than two warnings unreachable**. The bound is dead. Harmless, but it means
the justification is wrong.

### 2. Discard / Purge lifecycle — **PROBLEM** (see also Part 2C)

Discard-as-soft-exclusion, purge authorization, hard eligibility denial and the tombstone are all
correctly specified. Three defects:

- **§1 defines `Discarded` as "reviewer-confirmed junk"; §5 sets it automatically** with no reviewer,
  no reason and no actor — while §4's tombstone *requires* an actor and reason. No system-actor
  concept exists to fill them.
- **"Every capture event has its own Document/DOCLOG identity" is false** by the drafts' own rules:
  the malformed-response path and the auto-discarded resubmission path both create no DOCLOG.
- **The disposition enum is declared closed and has no member for two live populations** — capture
  failures (DOCFAI / `_FailureTable`, ~30 DOCLOG rows with blank EntryCode) and the 134 residual
  stale P/F/O rows awaiting a governance decision. Neither is Posted, ReferenceOnly, Discarded or
  Purged.

**State-machine holes:** no entry transition into *any* state is defined (only `Discarded → Purged`);
"restore for review" names no target state; Discard has no state precondition, so one reviewer can
discard a Posted fiscal record with no counter-signature and no reversal path.

### 3. Share the Discard/Purge design with Luís — **PROBLEM**

The backlog names the acceptance case precisely: the residual DOCLOG rows, which D2 splits into
**three populations needing three different treatments**. The draft's entire treatment is one
sentence — "evidence to classify, not candidates for blanket deletion". Correct principle, **no
discriminator, no classification rule, no test**. The 134 rows Luís deliberately left get no
disposition.

### 4. Full schema mapping (layers 1–5 + Layer 6 slice) — **PROBLEM**

The strongest draft, and still the one carrying the most blocking defects. See Part 2A for the
data-proven failures. Design-level defects:

- **DOCFLG has no `SuccessorReviewID`.** DOCRQE and RECREV both have one. `AGENTS.md`: *"A state
  change closes its predecessor and identifies its successor."* DOCFLG is the lifecycle authority —
  the worst table to omit it from — and its own lifecycle includes `Superseded`.
- **`ReferenceOnly` is not structurally barred** from FDCHDR/PAYCTR/aging, only checked at import
  time. D5 requires it to be impossible "even by accident". A ReferenceOnly document still owns a
  `Document` row that `EvidenceMode=NativeExtracted` can legally anchor.
- **BNKMOV's origin invariant is asserted, not anchored.** FDCHDR got four enforced evidence anchors;
  BNKMOV — where the invariant matters most — got a sentence. No FK to any statement or import
  evidence row.
- **The retained global unique `Document` hash index is admitted incompatible** with the canonical
  `(company, hash, length)` model it must coexist with for this entire train. What happens to the
  second byte-identical capture in the interim is unstated.
- **Byte-identical resubmission vs. the DOCLOG key contradict each other.** The lifecycle draft's
  acceptance case requires two byte-identical capture events to coexist; the mapping rejects a second
  row with the same filename and blank EntryCode as a collision. Both cannot hold.
- **Severity has no enumeration.** `Priority varchar(24)` / `ReviewPriority varchar(32)`, no value
  list, no "Critical is reserved for actively-blocking items" rule. A freeze that closes `ItemClass`
  and leaves the ordering dimension open.
- **"A rejected proposal is never re-proposed" has no schema object.** It is a non-negotiable in
  `AGENTS.md` and prose in three drafts. No constraint table, key or predicate.
- **The reconciliation percentage is never defined** — no numerator, no denominator — while P1-11
  acceptance demands it reproduce 94.6% ±0.1%.
- **Layer numbering is wrong.** §6 is titled "Layer 6 ground-truth slice" and contains
  PAYCTR/RCVCTR/PAYDTL/RCVDTL, which are **Layer 5** in the prototype's own `layer-map.md`. Layer 6
  is the bank group. RECREV is likewise misfiled.

### 5. Permanent-code issuance — **PROBLEM**

The allocation procedure is correct and complete: non-cycling sequences, `ON CONFLICT DO NOTHING
RETURNING`, new-statement winner read, full-key comparison, burned gaps, bootstrap-once under
exclusive lock, runtime `setval` forbidden. Defects:

- **`FL` has no natural key** in a `CodeLedger` whose primary key is `(Bucket, NaturalKeyHash)`. It is
  described as "sequential, assigned once at creation" — which the ledger cannot express. Worse, the
  text-rule *successor* case has a key byte-identical to its predecessor's, so
  `ON CONFLICT DO NOTHING` returns the **old** code instead of minting the successor.
- **`FL` sequentiality contradicts the burn rule.** "FL codes remain sequential permanent identities
  across reruns" vs "gaps are expected gravestones". The acceptance test as written fails on any
  rollback.
- **PAY/RCV and LG ledger buckets are not company-scoped** while their tables are constrained on
  `(CompanyCode, …)`. Two companies sharing a FiscalDocumentID collide on one ledger row. *(Cannot
  happen today — see Part 2A item 4 — but the constraint is what a freeze is for.)*
- **No isolation level is specified.** The winner-read recovery holds under READ COMMITTED; the draft
  never says so.

### 6. DocClass / DocumentType taxonomy as DOCTYP rows — **PROBLEM**

Structurally right (one row per `(DocumentType, OriginClass)`, no multi-valued cells). Three defects,
all in the seed:

- **Five conditional "Fiscal eligible" values on six rows are typed into a `bool`** — "yes, only with
  payment evidence", "yes when it supports a payable", "explicit rule only", "bank-generated rule
  only", "no; movement pipeline owns BNKMOV". The draft handled exactly this shape correctly for
  BNKMAT and not here. Six of thirty-six seed rows lose their meaning.
- **The seed drops eight live prototype DocumentTypes** (Wrong Document Type, Unreadable/Scanned,
  Insufficient Data, Cancelled Invoice, Tax-TSU, Tax-IRC, Tax-IES, Tax-VAT) while claiming "it does
  not discard specialized existing types".
- **No mapping from the prototype's actual `Treatment` values** (Include / Exclude) to the draft's
  closed set (PostFiscal / ArchiveOnly / ReferenceOnly / Review).

### 7. Nextcloud business-copy layout — **PROBLEM**

Class-specificity, Legal subtype-first, the document-date period rule, suffix stability and both
fail-closed overflow gates are all correct. Three defects:

- **Payables/Receivables is not class-specific.** `{Company}/{YYYY}/{MM}/{CounterpartyCode}/…` has no
  class segment, while every other class has a literal one. Combined with the shared filename
  pattern, a Payables invoice and a Receivables invoice for the same entity and month land in the
  same directory *and* share one `NN` suffix stream — reintroducing the exact generic-layout collapse
  the cross-check claims was superseded.
- **The bank path embeds the raw account number twice** in the path, against BNKACC's own rule that
  sensitive account values are tokenized at rest and never logged.
- **`ArchiveNameLedger` uniqueness disagrees between the two drafts** — the mapping includes
  `CompanyId`, the codes archive drops it. The codes-archive version is the one an implementer would
  build from the allocation procedure, and it is the one that lets two companies couple their version
  streams.

### 8. Roles and Responsibilities policy — **PROBLEM** (see Part 2B)

### 9. History import D4/D9 — **PROBLEM**

Import mechanics are well specified. Two defects:

- **D9's period replacement is unimplementable against the declared key.** PAYCTR/RCVCTR are unique
  on `(CompanyCode, FiscalDocumentID, PaymentSequence)` with `ImportPeriod` *excluded*. A
  FiscalDocumentID appearing in two periods — invoiced 202606, paid 202607, the normal case —
  collides, and no conflict rule is stated.
- **The D4 "complete reference layer" enumeration omits ENTBNK and BNKMAT**, both of which are in the
  roster it claims to import completely.

### 10. Grandfathering D7 — **PROBLEM · highest-consequence single defect**

The predicate is exact and correct. But **no draft says what `EnforcementStartsAt` is set to for the
43–45 imported prototype DOCEFL rules.**

If it defaults to null or epoch, the 29 open blocking instances detected 31 Jul – 2 Aug are **not**
grandfathered and block on day one — which is precisely the go-live queue D7 was written to prevent,
and it breaks the P1-11 acceptance criterion that actively-blocking flags reproduce the prototype's
zero.

Decision 9 of the nineteen exists as a column, not as a value.

### 11. RelatedParty on ENTMST — **PROBLEM**

The backlog asks for related-party funding to be *identified by rule rather than ignored by note*,
and P1-11 names the mechanism: a **Non-Blocking Information DOCEFL rule** so new movements are
auto-recognised on arrival.

The draft delivers `RelatedParty bool`, `RelatedPartyKind varchar(32) NULL` and one sentence. **No
DOCEFL rule. `RelatedPartyKind` has no value list. "Classified funding item/movement" — the thing the
matching rule depends on — is defined nowhere:** no ITMCLS seed row, no item code, no movement class.

The self-review marks this row "Drafted". It is a column, not a rule.

### 12. Integration ledger FKs — **OK**

Existing non-null restrictive `IntegrationCommit` FKs preserved exactly; historical rows not
repointed; FDCHDR's four evidence anchors are exhaustive and enforced per mode, with an explicit bar
on fabricating an ExtractionRevision to become integration-eligible. Clean.

### 13–16. Closed items (`[x]`) — **OK, re-verified**

DOCTYPE→DOCTYP collision resolved and applied on both sides; D4 and D7 decisions recorded; natural
keys received. See Part 2A for the data verification of the natural keys.

---

# Part 2 — Luís's items

## 2A · Schema and key mapping, verified against the pinned prototype

### The good news, verified

| Claim | Result |
|---|---|
| LGCode is a per-row identity | **CONFIRMED** — 1,475 rows, 1,475 distinct LGCodes, 1,475 distinct `(Filename, EntryCode)`, zero duplicate pairs. The pre-migration collision (954 codes, one covering 48 rows) is fully repaired. |
| Bank SourceKey excludes SourceFile | **CONFIRMED** — present on 426/426 generated rows; the key function's docstring says "deliberately WITHOUT SourceFile". |
| PAYCODE/RCVCODE derivation | **CONFIRMED**, and the draft's company-scoping is a *safe superset* of the prototype's global key. Zero cross-company FiscalDocumentID overlap today. |
| `ItemClass` is exactly three values | **CONFIRMED** — no fourth value. The "Information" worry was a false alarm: it is a DOCRQE **Priority**, and DOCEFL/DOCFLG use **ReviewPriority = Informational**. Three distinct fields. |
| DOCFLG renders 19 columns | **CONFIRMED** exactly, and `PriorRelatedRecordID` is correctly excluded. |
| BNKMOV has no `Source` column | **CONFIRMED** absent from JSON and renderer. |
| Two byte-identical statement lines exist | **CONFIRMED** — exactly one group of two, same account and date. With the occurrence tiebreaker all 1,960 keys are distinct; without it, those two merge. The tiebreaker is load-bearing on real data. |
| ~30 further uniqueness/parity checks | Clean — RECREV 21 columns in the exact order listed, DOCLOG 16, DOCEFL 20, BNKMOV 23, SNCACC parent-chain fully resolvable, ENTMST/EXCRAT/ITMCLS/MNGACC/ENTALS/ITMALS all unique as declared, USEROBS structure and all 47 review targets resolve. |

**D2 stands.** The identity work is correct and the .NET side can inherit it.

### The blocking defects — each proven on counted rows

**A1 · Aggregate payroll documents are not representable. Highest priority.**

Seven documents aggregate a whole pay run. Component counts: 2, 2, 2, 3, 3, 11, **15**. The prototype
stores them as two parallel string fields — `SourceBMCode` (comma-joined) and `SourceKey`
(components joined by the literal separator `" + "`).

The mapping gives FDCHDR one scalar `SourceKey` with
`FOREIGN KEY (Company, SourceKey) REFERENCES BNKMOV(Company, SourceKey) ON DELETE RESTRICT`.

Join test: **419 of 419** single-movement keys resolve. **0 of 7** aggregate keys resolve. The FK
fails on every aggregate row. A junction table is required; the words "junction" and "many-to-many"
appear nowhere in either draft, and **`SourceBMCode` appears zero times**.

The codes archive states the problem correctly — *"the machine key is a composite of every component
SourceKey. A single-code reader is invalid"* — and the mapping then maps a single-row FK. The two
drafts contradict each other on the exact case the freeze flagged as most expensive to get wrong.

**A2 · `ENTITM.EntityCode` is mapped backwards.**

Decision 18 of the nineteen states the rendered `EntityCode` label "carries CodeName semantics" and
that import "must reject an EC code".

Verified: `entity_products.json` has **no `EntityCode` field at all** — the stored field is
`CodeName`. The workbook builder then looks up `codename_to_entitycode` and writes
**`ENTMST.EntityCode` (`EC######`) values** into the rendered column, and resolves `FiscalNo` from
that EC code, confirming EC semantics.

The draft's rule would **reject 100% of rendered rows**. The *ledger* key `(CodeName, ItemCode)` is
right — 327/327 unique, 327/327 CodeNames resolve. Only the rendered-column semantics are inverted.

**A3 · `SourceTextHash char(64)` — all 2,711 DOCFLG rows fail.** Actual length distribution is
`{12: 2711}`, lowercase hex — the prototype's `md5-12` convention. The draft declares a 64-char
lowercase-hex column and check.

**A4 · `NonBlocking` vs `Non-Blocking`.** Every pinned value in `docrqe.json`, `recrev.json`,
`flag_instances.json` and `flag_evaluation.json` is **hyphenated**. Every CHECK constraint and the
blocking predicate in both drafts use the unhyphenated literal. **Every gating predicate is false
against pinned data.** One character; total enforcement failure.

**A5 · The DOCRQE class/status check rejects 639 pinned rows.** The draft requires
`Status=Recorded` for Status and Annotation items. Actual: 515 Superseded + 122 Superseded + 2 Applied
= **639 Status/Annotation rows are not Recorded**. (The other direction holds cleanly: zero
non-Decision rows are Open, zero are blocking.)

**A6 · `ENTBNK` declared PK `(CodeName, Company)` is not unique.** 48 rows, 43 distinct pairs, **5
duplicate groups**. Multi-account entities are represented as multiple rows, not comma-joined cells —
the draft's "the renderer comma-joins them back" model inverts the pinned representation.

**A7 · DOCTYP coverage fails on 640 DOCLOG rows.** Pairs absent from the seed: NoDocMov/External 366,
Financing/External 125, Duplicate/External 93, Payroll/External 56. The draft's own acceptance check
fails at the pin, and its seed does not close the gap (its Payroll and NoDocMov rows are `Internal`;
`Duplicate` is absent entirely).

**A8 · `OFDGAP` is missing from the roster.** A real layer-5 sheet — `document_gaps.json`, 27 rows,
7 rendered columns led by `GPCode`, fed by `build_document_gaps.py` and by ENTMST's
`InvoiceFrequency` / `ExpectedInvVal`. **Zero mentions** of OFDGAP, `document_gaps` or `GPCode` in
either draft. The roster was reported complete.

**A9 · `PAYDTL` multi-payment breaks the declared key.** One FiscalDocumentID already has two PAYDTL
rows (195 rows, 194 distinct IDs) — `build_paydtl.py` exists for exactly the partial-payment case.
The unique key plus an alternate key fixing `PaymentSequence=1` cannot both hold for it.

**A10 · Four DOCFLG rows carry an empty `EFCode`**, against "all current identity fields are NOT NULL
and non-empty" plus the composite `(EFCode, ItemClass)` FK. They fail on import.

### Smaller mismatches worth fixing in the same pass

- `DOCFLG.RelatedRecordID` is a composite `LG######/<suffix>` on **1,284** of 1,362 DOCLOG-typed rows,
  bare `LG######` on 74, non-LG on 4 — and 23 composites carry an LG prefix that no longer resolves.
  The draft calls it "the current FK target".
- `FDCDTL.LineNumber` is **invented** — no line-number field exists in either detail collection and
  the renderer emits none, yet the draft's unique key depends on it and lists it as a source column.
- `DOCLOG.CaptureQuality` is declared non-nullable but is null or blank on **1,425 of 1,475** rows.
- `CompanyCode` **is** an entity CodeName — both control tables' value sets are exact subsets of
  ENTMST CodeName where Role='Company'. The draft says the opposite.
- `DOCRQE.SuccessorReviewID` does not exist in the pinned data; `TargetField` does exist (2,668 rows)
  and is unrendered but unflagged; `Status` includes `Applied` (12 rows), absent from the draft's
  lifecycle.
- `ItemClass` is placed in DOCFLG's *source* field list; it is not one of the 19 and does not exist in
  the JSON. It belongs in the production-only block.
- `COCACC` is no longer empty (1 row); the prototype's own prose still says zero.

## 2B · Roles and Responsibilities policy — the pre-flight, performed

The thirteen binding documents were read and assessed one by one.

**The decisive finding: supersession authority was added during adaptation, not inherited.** The
source draft says this "should be added as a **cross-cutting policy above**" four procedure families —
an umbrella, complementary. The adaptation converts "above" into "**supersedes … where they
conflict**", with no stated basis. That single change is what makes this pre-flight necessary.

**It names five superseded documents and conflicts with eight more it does not claim.** Those
conflicts are therefore unresolved, not resolved. The supersession list was inherited verbatim from
the source and never widened for the eight documents adopted after it.

**Where it conflicts (by document):**

| Document | Verdict | The collision |
|---|---|---|
| Code Change Governance | **CONFLICT, unclaimed** | Policy: AI "never writes or moves a file". CCG's deployment step *is* the AI copying a validated script into live `Scripts/`. If "writes a file" covers source code, CCG has no performer. |
| Financial Document Capture | **CONFLICT, claimed** | Seven *unconditional* Review Queue triggers, narrowed by the policy's materiality test. Also: capture is performed by the pipeline; the policy says AI never captures. |
| Document Entry Line Classification | **CONFLICT, unclaimed** | §7.6 is an unconditional stop on missing/unclear/conflicting mapping. Both rules now bind and disagree. |
| Entity Entry Flow | **CONFLICT, claimed** | `Flag / Review Notes` is defined as *AI-entered*; and the synthetic-FiscalNo path runs under a DOCEFL rule with `AutoActionAllowed=Yes`, `ReviewOwnerRole=AI` — the AI issuing an identifier, which the policy forbids twice. Superseding invalidates that rule version and the live non-EU synthetic entities behind it. |
| Item Entry Flow | **CONFLICT, claimed** | Controlled auto-creation of ITMMST items and ENTITM mappings is live, binding behaviour with auto-created rows on file. The policy forbids the AI to persist or allocate an ItemCode. No replacement named. |
| Bank Movement Reconciliation | **CONFLICT, claimed imprecisely** | The precedence clause says "Bank Reconciliation procedures" — no document has that title and **two** candidates exist. Eleven unconditional RECREV triggers narrowed. And `apply_review_decisions.py` *executes* accepted decisions as an AI-run script. |
| Document Entry Review | **CONFLICT, unclaimed** | Core Rule 2 permits AI to apply non-blocking, certain changes. The policy forbids AI to apply anything. |
| Reconciliation Review | **CONFLICT, unclaimed** | **RECREV has no `ItemClass` field** (1,201 rows). "Only Decision-class items may be Open" is unimplementable on it as specified, and its as-built population is one open item per unmatched line. |
| Document Archiving | **CONFLICT, unclaimed** | The entire archiving flow is `mkdir` + `mv` performed by the session, with a whole section written about the AI doing it. Superseding leaves archiving — the closing step of every capture round — with no performer. |
| Data Update Governance · Skill Review · Policy Change Validation | **AMENDS, unresolved** | Each gains a new unmet obligation (authentication; skill-log propagation; a broadened PCV trigger that PCV's own text does not carry). |

**Dropped from the source — ranked by risk:**

1. **Confidence / consistency / risk thresholds** *(highest)*. Present twice in the source, absent
   from the new text. This was the **only clause tying the policy to the built threshold layer** —
   BNKMAT tolerance and date-window columns, DOCEFL `RiskFactor`/`FlagCategory`/`ReviewPriority`, the
   FX tolerance, the exact-match bands. With it gone, **a tolerance change no longer triggers the
   Policy Change Validation pre-flight** — precisely the class of change most able to silently widen
   autonomous action.
2. **Named review triggers: duplicate payment, balances, "an approved closed result."** Duplicate
   settlement risk is a Mandatory RECREV Review Condition and a live Block Reconciliation flag rule.
3. **The user's threshold-based review duty.** With (1), nothing now defines what quantitatively
   reaches a human.
4. DOCRQE/RECREV as named learning inputs — weakens the learning half of the operative clause.

**Authority claimed with no mechanism:** actor identity and authentication (asserted throughout;
three of the thirteen documents explicitly disclose its absence as a known gap); "Rules of
Engagement" is never bound to either the thirteen documents or the same-named `.docx`; "a state
change closes its predecessor" asserted universally, defined in two places; supersession itself has
no retirement procedure — while Skill Review §3.5 requires *"do not leave conflicting rules in
place."*

**The operative clause survives, strengthened**, now backed by a real mechanism. Two fixes needed
before adoption: restore the human-outranks-the-classifier carve-out, and resolve the RECREV
`ItemClass` gap.

## 2C · Is Discard/Purge usable on the prototype side?

**No. It needs a portability annex.** Roughly a third is directly portable, a third needs a named
substitute, and a third is not portable — **and the document gives the implementer no way to tell
which sentence is which.** Four hard stops appear before a line of code is written, none flagged in
the text.

| Mechanism | Portable? |
|---|---|
| Tombstone store | **As specified.** Cheapest, cleanest item — append-only JSON + rendered sheet. |
| State enum | **With a substitute** — persisted-and-merged `Disposition` on DOCLOG, validated like `CaptureQuality`. Must be persisted, not re-derived every render, or it is recomputed away. |
| Hash retention | **With a substitute** — the prototype computes **no content hash of source documents** today; duplicate detection is filename + size. Adding SHA-256 at capture is cheap and would upgrade duplicate detection to the byte-exact test the design assumes throughout. |
| Eligibility denial ("ever received an EntryCode") | **Not portable.** FDCHDR is recomputed each render, rows have been hard-deleted, and **EntryCode is absent from the permanent code ledger**. The question is unanswerable from any store that exists. Needs an append-only EntryCode issuance ledger first. |
| Authorization | **Not portable.** No roles, no authentication, no privilege separation. The honest substitute is procedural — a recorded user instruction plus a control-log entry — and must be **labelled as evidence of intent, not as a control**, or the design's strongest claim ("an administrator cannot override it") becomes false the moment it is ported. |
| Concurrency safety | **Not applicable** — single-threaded pipeline. The atomicity half has real substitutes; the isolation half has none and needs none. The design should say so. |
| Purge execution | **Not portable.** Device sessions cannot delete files — `_to_delete/` exists precisely because the human must empty it. No deletion receipts exist and none can. |
| §1 flow entities (ExtractionRevision, ExtractionAttempt, malformed routing) | **Not portable** — .NET-only, mixed inline with FDR-shared entities with no marker distinguishing them. |

**Two direct conflicts with binding rules:**

- **The `_to_delete/` conflict.** The Document Archiving Policy says a byte-identical re-appearance of
  an already-archived file is swept to `_to_delete/` and **"gets no new DOCLOG row."** The design says
  the identical case registers a **new** capture event, auto-Discarded, with an Annotation finding.
  Two binding rules, one case, opposite outcomes. *(The identity basis also differs: the prototype
  matches on filename + size, the design on byte-exact SHA-256.)*
- **Confirmed duplicates.** A confirmed duplicate that is a distinct physical file **is** archived and
  its DOCLOG row **carries an EntryCode** pointing at the real document. The design has neither the
  archived-duplicate case nor a duplicate that legitimately holds an EntryCode — and that EntryCode
  would trip its own purge-denial predicate.

**And the constraint the design is built on is not currently true.** *Nine scripts hard-delete DOCLOG
rows*, at least five wired into the pipeline's STEPS, so they replay on every full rebuild. "DOCLOG is
never deleted" is the stated binding constraint; adopting it requires converting those five to
disposition changes. The design does not mention them.

---

# Part 3 — Ranked blockers

Fix before sign-off, hardest evidence first.

1. **A1** — aggregate payroll SourceKey is structurally unrepresentable (0 of 7 rows join). Needs a junction table.
2. **A2** — `ENTITM.EntityCode` semantics inverted; the import rule would reject 100% of rows.
3. **Item 10** — `EnforcementStartsAt` has no seed value; the 29 grandfathered instances block on day one.
4. **A4** — `NonBlocking` vs `Non-Blocking`: every gating predicate false against pinned data.
5. **2C** — the `_to_delete/` conflict: two binding rules, opposite outcomes for one case.
6. **A5 / A3 / A6 / A7 / A9 / A10** — six mapping defects that fail on counted rows at import.
7. **A8** — OFDGAP missing from a roster reported complete.
8. **2B** — supersession authority added in adaptation; eight documents conflict unclaimed; the threshold clause dropped.
9. **Item 5** — FL has no ledger key and its successor case collides with its predecessor.
10. **Item 11** — RelatedParty is a column where the backlog asked for a rule.
11. **Item 4** — DOCFLG has no successor pointer, violating an `AGENTS.md` non-negotiable on the lifecycle authority itself.
12. **Item 6 / 7 / 9** — DOCTYP conditional eligibility truncated to bool; Payables/Receivables archive path not class-specific; D9 period replacement unimplementable against the declared key.

## Decisions frozen in name only

| # | Decision | What is missing |
|---|---|---|
| 9 | Grandfathering against "an explicit rule enforcement start" | The comparand has no value for any imported rule |
| 10 | RelatedParty "but matching requires a funding classification too" | The funding classification does not exist anywhere |
| 13 | The hash index "replaced only in a reviewed later migration" | "Later" is undefined, and two acceptance cases depend on it |
| 15 | "DOCFLG snapshots rule ItemClass" | Silently reverses P1-6's "recomputed every run"; the reversal is captured nowhere |

## Over-claimed self-review rows

`RelatedParty` (Drafted → a column), `Grandfathering D7` (Drafted → half), `Roles policy`
("pre-flight stated" → stated, never performed; 8 of 13 documents unassessed), `Codes and LGCode`
(one code family silently exempt from the proof), `Required flow SVG` (cross-check of a superseded
diagram substituted for a redraw).

**Historical finding, subsequently closed:** the review disproved the claim that the freeze had no
unresolved domain choices. It identified `EnforcementStartsAt`, RelatedParty funding
classification, the PAYCTR period-conflict rule, and the severity enumeration; the later P1-0
decision set closed all four. See `docs/PROJECT-STATE.md` for current state.

---

# Part 4 — Limits of this review

- **What was substituted, not replaced.** Luís's structural verification against the prototype was
  performed and is in Part 2A. What could **not** be substituted is his *domain judgement*: whether a
  given prototype quirk is a defect to fix or behaviour to preserve. Where the two disagree, this
  review assumed the pinned data is right and the draft is wrong — that assumption is itself his to
  confirm.
- **The prototype moves.** All counts are at `9359c67`, re-confirmed at `6146004`. Re-measure rather
  than quoting them — 19 unprocessed documents are already queued and will move most of them.
- **The pending July bank statement is an opportunity, not a risk.** Ingesting an overlapping
  statement regenerates P/F/O documents — the exact scenario D2's re-key was built to survive. Run it
  and re-run the join test *before* P1-1 commits to the mapping: it is a free acceptance test of
  SourceKey anchoring on live data, and it may add aggregate payroll documents, which is blocker A1.
- **Ingesting the 18 invoices will move the figure table below.** Re-measure after that round rather
  than reconciling the current numbers twice.
- **One artefact was written to the prototype**, unintentionally and harmlessly: reading
  `entity_utils.py` produced `Scripts/__pycache__/entity_utils.cpython-310.pyc`. `__pycache__/` is
  gitignored, so it is not a repository change. No data file, script or spec was touched.
- **One earlier finding withdrawn.** An automated pass reported the v14 flow diagram as missing. It
  exists — the reviewer was reading a partial file listing. No action.

## Figure drift to reconcile

These conflict across the backlog, `project-evolution.html` and the pinned data. The pinned column is
authoritative at `9359c67`.

| Figure | Backlog | Evolution doc | Pinned |
|---|---|---|---|
| DOCEFL rules | 45 / 43 | 43 | **45** |
| DOCFLG instances | 2,711 / 2,574 | 2,574 | **2,711** |
| DOCFLG open | 149 / 172 | — | **149** |
| DOCRQE Decision-class | 64 open 41 / 164 open of 180 | 164 open of 180 | **64, of which 41 open** |
| FDCHDR | 1,158 / 1,153 | — | *re-measure* |
| Orphan DOCLOG | 129 / 182 / 134 | — | *three different measurements of three different things — needs one definition* |
