# P1-0 — residual findings, triaged

> **HISTORICAL TRIAGE — NOT CURRENT BLOCKERS.** The A/B/C labels below record how findings were
> triaged at the time. A1–A7 and all group-B scope questions were subsequently decided in
> `docs/p1-0-a-group-decisions.md`; P1-0d applied the relevant closure. Read
> `docs/PROJECT-STATE.md` for current state and do not execute the old blocking directions below.

Companion to `p1-0-review-findings.md` and `p1-0-corrections.md` (C1–C19). These were the items the
review raised that did **not** make the ranked top nineteen, plus what the C2 generalization check
turned up. Evidence base: prototype at `9359c67`, re-confirmed at `6146004`. The evidence below is
preserved as history.

**Historical triage:** **A — then classified as blocking the import** · **B — then classified as
blocking production** · **C — then assigned to P1-0b without consultation**. A1–A7 and group B are
subsequently decided, not current blockers.

Counts: **A = 7 · B = 15 · C = 18**.

---

## The C2 generalization check — result

Every table's pinned JSON field set was compared against the schema-mapping draft, across 23 tables.
**Only five fields are present in the data and absent from the draft**, all on FDCHDR:
`CompanyCodeName`, `ProviderCodeName`, `ProviderName` (payables) and `CompanyCodeName`,
`EntityCodeName` (receivables).

That is a good result and it means the *omission* direction is nearly clean.

**But the reverse direction is not, and it is worse.** Checking what the draft declares against what
exists turned up a class the review had only seen instances of: **the draft declares columns that do
not exist in the source and does not mark them as production-only.** Confirmed absent from the pinned
JSON: `Entity` and `FlowType` on FDCHDR, `NormalizedDocumentID`, `FailureCategory` on DOCFAI,
`BCCode` on BNKCHK, `PayDtlCode` / `RcvDtlCode`, `LineNumber` on FDCDTL, `SuccessorReviewID` on
DOCRQE, `ItemClass` on DOCFLG.

Some of those are legitimate production additions. The problem is that **the draft never distinguishes
"existing source field" from "column we are adding"** — and that distinction is precisely what an
importer needs in order to know what to read.

---

## A — blocks the import (7)

**A1 · The FDCHDR identity index names three components that do not exist as stored fields.**
The draft's source-fiscal partial unique index is
`(Company, Entity, NormalizedDocumentID, FlowType) WHERE SourceKey IS NULL`. Against the pin:
FDCHDR's stored fields are `AccountPeriod, Company, CompanyCodeName, Currency, DateDoc, DateDue,
DatePay, DocumentID, EntryCode, Filename, FiscalNo, Flag, ItemCode, ItemDesc, NetAmount, PLMKEY,
PLMKO, ProviderCodeName, ProviderName, TotalAmount, VATAmount`.

- **`Entity` does not exist.** It is `ProviderCodeName` on payables and `EntityCodeName` on
  receivables.
- **`FlowType` does not exist.** It is derived from which collection the row sits in (payables → I,
  receivables → R, bank-generated → P/F/O). Derivable, but the draft never says so.
- **`NormalizedDocumentID` is defined nowhere** — described as "a stored, versioned deterministic
  normalization" with no column, no algorithm and no version discriminator.

Three of four components of the primary identity of the central fiscal table are unresolved. This is
the single largest residual item and arguably belongs above several of the nineteen.

**A2 · Source vs production columns are not distinguished.** See the list above. Every declared column
needs a marker: *source field*, *derived at render*, or *production-only addition*. Without it the
importer cannot tell what to read, and the review found two of these the hard way (`LineNumber`, and
the ENTITM inversion that became C2).

**A3 · `MatchGroupID` and `BRCode` are typed `code` with no bucket.** Both exist in BNKREC (2,072
rows). A `code` needs a `CodeLedger` bucket, a natural key and a generation rule; neither has any.

**A4 · `RECREV.ProposedMatchType short NULL FK BNKMAT` names no target column.** BNKMAT's key is
`BTCode`; its unique is `MatchType`. The FK cannot be created as written.

**A5 · `BNKREC.MatchType` and `ReconciliationMatchType` coexist with no rule.** C4 established that
their vocabularies are **disjoint** — BNKMAT governs 11 named types, BNKREC records 12 different
strings. Two columns, two vocabularies, one FK, no consistency constraint.

**A6 · `ImportBatch` has no declared primary key** but the draft declares `unique (Id, CompanyCode)`
depending on it, and its columns exist only as prose.

**A7 · `DOCFAI.FailureCategory` is invented.** DOCFAI's pinned fields are `Category, Date, FACode,
Filename, Reason` — 3 rows. The draft declares both `Category` and `FailureCategory` with no stated
relationship; only the first exists.

---

## B — blocks production (15)

**B1 · Byte deletion is scoped to the wrong unit — this one can destroy evidence.** Purge deletes
"every byte object belonging to **the capture**", while the design makes byte-identical captures
distinct rows sharing one canonical `(company, hash, length)` record. If the byte store is
content-addressed — the natural implementation of that design — **purging one discarded capture
destroys the bytes of a `Posted` sibling.** The eligibility check reasons over duplicate
relationships; the deletion step does not. **This is the most severe item on either list.**

**B2 · Discard authority is unbounded.** "Any reviewer may choose Discard", with no state
precondition — so one reviewer can discard a `Posted` fiscal record, removing it from every
operational, export and archive selection, with no counter-signature and no reversal path.

**B3 · "Restore for review" names no target state.** The disposition enum is closed and has no
un-discarded value.

**B4 · No entry transition into any disposition state is defined.** Only `Discarded → Purged` exists.
A freshly captured document, a malformed-response capture, and a valid `NOT_A_DOCUMENT` awaiting
review are all in no legal state.

**B5 · `CompanyRegistry.Active` is never enforced.** `CompanyCode` is globally unique, so a **retired**
registry row remains a valid FK target. "Inactive claims fail closed" is application-only.

**B6 · Polymorphic pointers have no FK, no company scope, no mechanism.** `RelatedRecordType` /
`RelatedRecordID` and `SourceRecordType` / `SourceRecordID` are plain text. The blocking predicate is
stated; the blocking itself has no database mechanism.

**B7 · Append-only is asserted five times with zero triggers** — CodeLedger, ManualEntryEvidence,
ArchiveNameLedger, DOCFLG terminal rows, human observations. No trigger, no revoked privilege.

**B8 · `DOCLOG.DocumentType` / `OriginClass` have no FK to DOCTYP**, though DOCTYP declares exactly
that unique key and owns fiscal eligibility. Now implementable as a partial FK scoped per C9.

**B9 · `ITMMST.SNCACC` / `ENTITM.SNCACC` have no FK to `SNCACC`; `CostCentre` has no FK to `COCACC`.**

**B10 · The sequence bootstrap record and the EntryCode sequence registry have no tables.** Both are
prerequisites of the seed, so this blocks P1-1b rather than P1-1a.

**B11 · `ArchiveNameLedger`'s "case-insensitive unique Filename within the destination directory" is
not implementable** — there is no directory column.

**B12 · Counterparty naming is three names for one thing** — `{CounterpartyCode}`, `{Counterparty}`,
`{Entity}` across paths and filename patterns, none mapped to a defined column.

**B13 · `identifierType` is a required contract field with no controlled vocabulary**, while the same
draft says "validate all enums case-sensitively".

**B14 · The contract's canonical example ships a synthetic fiscal number** (`[ISO2]-SYN-######`) as
Claude output. That value is never printed on a document, and no validation rule rejects the pattern.
It models the fabrication `AGENTS.md` forbids, in the one place an implementer copies from.

**B15 · Warning `field` is validated as a JSON Pointer and the producer is never told to emit one.**
No example exists.

---

## C — apply in P1-0b, no consultation needed (18)

Wording, counts and internal tidiness. Each is a one-line fix.

1. Party `fieldConfidence` omits `role`, the one party field the draft promises to validate.
2. The five-warning maximum is unreachable — the pairing rule caps it at two. Fix the justification.
3. String limits are invoked ("no longer than their declared limits") and declared for only two fields.
4. "Every key is required unless marked optional" — no key is ever marked optional.
5. Atomicity is asserted only for the rejection path, never for accept-side projection.
6. Rejection and its evidence routing are described as atomic, then split across two transactions.
7. Purge "never a queue side effect" vs "is retryable" — name the retry actor.
8. The two-cent tolerance is "configured" with no config table; a constant is fine, say so.
9. Currency/country uppercase and amount signs asserted with no CHECK.
10. BNKREC group totals asserted with no constraint or generated column.
11. §6 is titled "Layer 6 ground-truth slice" and contains **Layer 5** tables; RECREV likewise misfiled.
12. `Documents` (plural) used once against `Document` everywhere else.
13. DOCFLG "19 workbook columns" against a rendered list of 20 — `ItemClass` is misfiled into the
    source block and does not exist in the JSON.
14. BNKMOV `Occurrence` sits in the source-column block; it is a production persistence of a transient.
15. Purge idempotency precondition as written is not executable — a hash cannot be computed from
    absent bytes; match recorded asset metadata instead.
16. `DOCLOG.Source` declares a six-value superset against three pinned values — mark it forward-looking.
17. `DOCLOG.CaptureQuality` declared non-nullable, blank on 1,425 of 1,475 rows.
18. `DOCRQE.TargetField` exists (2,668 rows) and is unrendered but unflagged; `Status` includes
    `Applied`, absent from the stated lifecycle.

---

## Recommendation

Work **A** now — seven decisions, and A1 alone probably deserved a place in the nineteen. Fold **B**
into the P1-0b assignment as items to specify, with **B1 raised to the top**: it is the only finding
in either document that can silently destroy data a user still needs. **C** goes into P1-0b as a
tidy-up list with no consultation.
