# P1-0 draft — codes, taxonomy, and Nextcloud layout

Status: accepted P1-0 field-level draft. Prototype identity reference: `invoice-skill-build` detached at
`9359c67c4ef0101218d7e0ffff1986114ba5cc7a`, especially `Scripts/add_row_codes.py`,
`Scripts/verify_code_identity_stability.py`, `entity_utils.generated_source_index()`, and
`Scripts/build_bnkmov.py`. Only the explicitly authorized controlled DOCTYP and ITMCLS code-table
rows are reproduced, with per-field source/blob provenance.

## 1. Permanent-code issuance

Codes are identities, not display row numbers. Each bucket is owned by a real PostgreSQL
`SEQUENCE`; application code never scans `max(code)`, fills a gap, depends on input order, or
silently selects a winner. PostgreSQL `nextval()` is atomic across sessions and deliberately
non-transactional: once returned, a value is not returned by `ROLLBACK`, failed commit, session
loss, or crash. Sequence caching may create additional gaps after a crash. Gaps are expected
gravestones, not evidence of missing rows; runtime `setval()`/sequence restart and `CYCLE` are
forbidden.

Static code families have one non-cycling sequence each. EntryCode has one non-cycling sequence
per `(Prefix, YY, MM)` bucket, created from validated components by a privileged deterministic
database routine and registered uniquely before use. Allocation and idempotent binding are:

1. Canonicalize the natural key and read its `CodeLedger` row by `(Bucket, NaturalKeyHash)`.
   Compare the stored canonical key byte-for-byte before returning its code.
2. On a miss, take the bucket's `nextval()` and execute `INSERT INTO CodeLedger (...) VALUES (...)
   ON CONFLICT (Bucket, NaturalKeyHash) DO NOTHING RETURNING Code, NaturalKey, KeyVersion`. A
   returned row is the binding won by this transaction. The separate unique `(Bucket, Code)`
   constraint is not named as the conflict target: a code collision is an invariant failure, not
   an idempotency outcome.
3. If `RETURNING` yields no row, the competing natural-key insert won. Issue a new statement after
   that winner commits, `SELECT ... FROM CodeLedger WHERE Bucket = @bucket AND NaturalKeyHash =
   @hash FOR SHARE`; do not rely on the original statement snapshot to see the winner. Compare the
   full canonical key, key version, and immutable identity fields, then return its code. The losing
   sequence number remains burned. If the winner cannot be read, a serialization/deadlock retry
   may repeat the whole allocation transaction under the same idempotency key; a hash mismatch,
   code collision, exhausted format, or missing bucket fails closed to review.

Because `nextval()` is outside transaction rollback while the ledger insert is transactional,
rollback or crash between the two can leave a number with no ledger row. That is the precise
mechanism that makes non-reuse true. A transactional counter-table design is rejected: an
`UPDATE NextValue` in the same transaction would roll back and could reuse the claimed "burned"
number. Codes and successful ledger bindings are never updated or reassigned.

Canonical keys are versioned, length-prefixed JSON arrays (not delimiter concatenation). For the
pinned FDR-key version, each scalar reproduces `entity_utils.natural_key()` semantics: null becomes
empty text, other values use their invariant source representation, runs of whitespace collapse,
surrounding whitespace is removed, and Unicode case-folding is applied. Movement dates,
two-decimal Amount in generated-document SourceKey, and recomputed occurrence follow their exact
source functions. Production-only key versions use NFC plus only their declared field-specific
normalization. The ledger stores key version, canonical JSON, and SHA-256 so a hash collision is
detected by comparing the full canonical key rather than trusted.

### Buckets and natural keys

| Code | PostgreSQL owner | Format | Natural key |
|---|---|---|---|
| EC | ENTMST | `EC` + 6 digits | `(FiscalNo, CodeName)` |
| EI | ENTITM | `EI` + 6 digits | `(CodeName, ItemCode)`; `CodeName` is the FDR source field despite the misleading rendered label `EntityCode` |
| LG | DOCLOG | `LG` + 6 digits | `(Filename, EntryCode)` |
| FA | DOCFAI | `FA` + 6 digits | Native: `(ExtractionAttemptId, FailureKind)`; imported prototype rows: `Filename` under `ImportedFailure` |
| DT | DOCTYP | `DT` + 6 digits | `(DocumentType, OriginClass)` |
| EF | DOCEFL | `EF` + 7 digits | `(FlagReasonCategory, FlowDimension)`; version changes retain the code |
| FL | DOCFLG | `FL` + 7 digits | `(EFCode, RelatedRecordType, RelatedRecordID, SourceTextHash, DetectedAt)` |
| EX | EXCRAT | `EX` + 6 digits | `(Period, From, To)` |
| SN | SNCACC | `SN` + 6 digits | `Codigo` |
| CL | ITMCLS | `CL` + 6 digits | `(Class, Subclass)` |
| MA | MNGACC | `MA` + 6 digits | `PLMAINKEY` |
| IC | ITMMST | `IC` + 5 digits | Existing source ItemCode is preserved on import; for native rows the code identifies immutable `ItemMasterId` (UUID), not mutable descriptive/classification content |
| AL | ENTALS | `AL` + 6 digits | `(Company, normalized Alias)` |
| IL | ITMALS | `IL` + 6 digits | `(Company, normalized Alias)` |
| CC | COCACC | `CC` + 6 digits | `CostCentreCode` |
| BM | BNKMOV | `BM` + 6 digits | `(BankAccount, MovDate, DocDate, Description, Amount, Occurrence)`; this is the pinned `add_row_codes.py`/cold-rebuild ledger order |
| BT | BNKMAT | `BT` + 6 digits | `MatchType` |
| PAY | PAYCTR | `PAY` + 6 digits | `(CompanyCode, FiscalDocumentID)`; one current-state header per document |
| RCV | RCVCTR | `RCV` + 6 digits | `(CompanyCode, FiscalDocumentID)`; one current-state header per document |
| DR | DOCRQE | `DR` + 5 digits | `ItemKey` |
| RR | RECREV | `RR` + 5 digits | `ItemKey` including finding discriminator |
| BR | BNKREC | `BR` + 6 digits | `(Company, BMCode, EntryCode, FindingDiscriminator)`; pin-equivalent `(BMCode, EntryCode)` measured 2,072 distinct / 0 collisions; issuance begins in P1-1b |

The pin directly supplies the keys above for EC, EI, LG, FA, DT, EF, EX, CL, MA, BM, AL, IL,
BT, PAY, RCV, DR, RR, and CC. ITMMST is the exception: the prototype already treats `ItemCode` as
the row's identity but does not ledger a content-derived regeneration key. Descriptions and
classifications are mutable and legitimate duplicates exist, so production must not manufacture
one. An imported IC code binds to its verified source row; a native IC code binds once to the
new row's immutable UUID under the creation command's idempotency key.

`MatchGroupID` is deliberately absent from the bucket table. It is a run-scoped grouping label over
a recomputed view, explicitly non-permanent, and must never be quoted as stable identity or cited
in a decision record. BRCode alone retains a reconciliation-row bucket under the measured key
above; its allocation implementation remains P1-1b.

FL is not a UUID and is not sequential: the pinned range already contains gravestone gaps. Its
natural key is `(EFCode, RelatedRecordType, RelatedRecordID, SourceTextHash, DetectedAt)`.
`SourceTextHash` distinguishes changed evidence and `DetectedAt` is the explicit tiebreaker for the
residual collisions. The production detector binds that complete idempotent key before requesting
a new FL so retries cannot mint two codes. An FL remains permanent and stable across reruns; gaps
are governed by the same non-reuse rule as every other bucket.

LGCode recommendation is closed: LGCode is the permanent DOCLOG row identity, keyed on
`(Filename, EntryCode)`. There is no `DocLogId`. P1-1 must validate a bijection between that pair
and LGCode. It must not implement “one LGCode per EntryCode”; EntryCode may legitimately be shared
by duplicate-copy evidence, while LGCode remains unique per DOCLOG row.

### EntryCode

EntryCode is `{PREFIX}{YY}{MM}{NNNN}` with `PREFIX=I|R|P|F|O`. Its sequence bucket is
`(PREFIX, YY, MM)`. `YY/MM` always comes from `DateDoc`, not AccountPeriod, import time, filename,
or processing time. `NNNN` is monotonically issued, never filled from a deleted/retired gap, and
the bucket refuses overflow above 9999. ReferenceOnly, Discarded, and Purged documents never
receive an EntryCode.

For source fiscal documents, semantic identity is `(Company, canonical Entity, normalized
DocumentID, FlowType)`. For bank-generated P/F/O documents, identity is the movement `SourceKey`,
not EntryCode or SourceFile. Seven proven aggregate payroll cases carry comma-separated
`SourceBMCode` pointers, but the machine key is a deterministic composite of every component
SourceKey in official movement order. `FDCHDR.SourceKey` remains readable identity text; its stored
SHA-256 `SourceKeyHash` is the indexed identity, and ordered component links live in
`FdcHdrBankMovement`. A single-code reader or scalar BNKMOV foreign key is invalid. The occurrence
tiebreaker is recomputed from official-statement order and retained; dropping it merges distinct
byte-identical movements.

The pinned prototype has two serializations over the same movement facts and they must not be
silently conflated. BMCode's ledger key uses `(BankAccount, MovDate, DocDate, Description, Amount,
Occurrence)`. `entity_utils.movement_natural_key()` serializes generated-document `SourceKey` as
`(BankAccount, MovDate-date, DocDate, Amount-rounded-to-2-decimals, Description, Occurrence)` with
the prototype's whitespace/case normalization; an aggregate SourceKey joins the component keys in
official movement order. Production stores a key-version discriminator and the canonical parts,
not merely an opaque hash, so either representation can be verified and a collision fails closed.

PAYCODE/RCVCODE identify their current-state headers by company and fiscal-document identity.
`PayDtlCode`/`RcvDtlCode` identify detail rows by company, fiscal document, and deterministically
derived payment sequence. Their validation is not order-preserving: copy rows, shuffle
deterministically, blank all codes, rebuild using only the ledger, then demand zero movement,
including the existing partial-payment case. Apply the same bijection, coverage, and shuffled-cold-
rebuild test to EC, LG, BM, DR, and RR. The verifier is read-only and fails before persistence or
export.

### Import bootstrap

Preserved imported codes are installed before their sequences are enabled for runtime allocation.
The bootstrap transaction takes an application advisory lock for the code-bootstrap operation and
an `ACCESS EXCLUSIVE` lock on `CodeLedger`, verifies the complete imported code/key bijection, and
inserts every imported binding. For each bucket it verifies that no native binding or prior
initialization exists, computes the greatest verified imported suffix, and creates the non-cycling
sequence with `START WITH greatest_suffix + 1`; if the empty sequence was pre-created, the same
one-time transaction may use `ALTER SEQUENCE ... RESTART WITH greatest_suffix + 1` while holding
the sequence's exclusive DDL lock. It then records the initialized bucket/high-water mark before
commit and enables runtime allocation only after all buckets pass.

This is bootstrap-only monotonic initialization, never a repair mechanism. Once a bucket is
initialized or any runtime `nextval()` has occurred, `setval()`, `ALTER SEQUENCE ... RESTART`,
backward movement, and reconstruction from `max(code)` are forbidden. A rerun reads and verifies
the recorded initialization; disagreement fails closed instead of changing the sequence.

## 2. DOCTYP seed taxonomy

DOCTYP is data, not a code enum embedded in routing. Pinned-import vocabularies come only from the
generated annex: pinned `DocClass` is `Payables|Receivable`, `OriginClass` is
`External|Internal`, and `Treatment` is `Include|Exclude`. Production routing is a separate
`ProcessingRoute=PostFiscal|ArchiveOnly|ReferenceOnly|Review`; it does not overwrite the pinned
Treatment value. `PostFiscal` is allowed only for an explicit fiscally eligible rule,
`ArchiveOnly` routes to DOCARC, `ReferenceOnly` implements D5, and `Review` cannot auto-post.
These target-only values are allowed because C1–C19 explicitly decide them; they are versioned,
validated fail-closed, and never described as values observed at the pin. No draft may invent an
additional pinned or target-only literal.

### Extracted pinned DOCTYP rows

Source: `Editor/Data/document_type_rules.json` at prototype pin
`9359c67c4ef0101218d7e0ffff1986114ba5cc7a`, blob
`7e687f6fba92a237bf3ea1a132026922176e2698`.

Every field in every row below has per-field seed provenance **`extracted`**. The marker applies to
each of the six named columns independently; no value is inferred or normalized.

O8-D2 re-verified that these 19 rows are byte-for-byte unchanged as the first 19 entries at
immutable pin `b91768513fc638381fbde91f0b576b08220a98f6`, where the source blob is
`b7560e9ea0c403e608b73ed3a3a4f6b53ce956cb`. Its uncoded twentieth source row,
`Bank Statement | External | Exclude`, is explicitly excluded because a blank `DTCode` cannot be
imported without inventing a permanent code. The import manifest therefore remains exactly 19.
The accepted authored target rule `Bank Statement | External | Include | ArchiveOnly` below is
unchanged and is not a translation of the excluded source row.

| DocumentType (`extracted`) | OriginClass (`extracted`) | Treatment (`extracted`) | Notes (`extracted`) | DTCode (`extracted`) | DocClass (`extracted`) |
|---|---|---|---|---|---|
| Invoice | External | Include | Standard accounts-payable invoice received from a third-party supplier. Included in InvoiceHeader/InvoiceDetails. | DT000001 | Payables |
| Invoice-Receipt | External | Include | Accounts-payable invoice received from a third-party supplier where the source document also confirms payment (DatePay set). Included in InvoiceHeader/InvoiceDetails. | DT000002 | Payables |
| Payment Notice | External | Include | Pre-payment notice ('Aviso') received from a third-party supplier ahead of the formal invoice/receipt - not itself a tax invoice, but the only payable document available for this charge. Included in InvoiceHeader/InvoiceDetails; flag if the real invoice/receipt later arrives separately so it isn't double-counted. | DT000003 | Payables |
| Credit Note | External | Include | Credit note received from a third-party supplier, offsetting a prior invoice. Included in InvoiceHeader/InvoiceDetails with a negative/offsetting amount. | DT000004 | Payables |
| Invoice | Internal | Include | Outgoing/accounts-receivable invoice generated by one of our own entities (Gott or Itoorer) billing a customer or the other of our two entities (see the Gott/Itoorer intercompany note in SKILL.md section 10) - not a payable. Included in ReceivableHeader/ReceivableDetails instead of InvoiceHeader/InvoiceDetails. Added Stage 3 Round 1 (July 2026), replacing the prior 'Wrong Document Type'/Exclude/FailureTable routing now that AR processing is in scope. | DT000005 | Receivable |
| Invoice-Receipt | Internal | Include | Same as the Invoice/Internal row above, for the case where the outgoing document also confirms payment (DatePay set, e.g. a Portuguese Fatura-Recibo). Included in ReceivableHeader/ReceivableDetails. | DT000006 | Receivable |
| Wrong Document Type | Internal | Exclude | An Internal-origin document that is genuinely not a usable receivable at all (e.g. a delivery note, internal memo, or other non-financial document generated by one of our own entities) - not simply an ordinary outgoing invoice, which now routes to ReceivableHeader/ReceivableDetails instead (see the Invoice/Internal and Invoice-Receipt/Internal rows above, added Stage 3 Round 1). Excluded from both InvoiceHeader/InvoiceDetails and ReceivableHeader/ReceivableDetails, routed to FailureTable. | DT000007 | Receivable |
| Wrong Document Type | External | Exclude | A document received from a third party that is not a payable at all and is not simply an AR mismatch (e.g. a delivery note, statement, marketing material, or other non-financial document misfiled into the batch). Excluded from InvoiceHeader/InvoiceDetails, routed to FailureTable for manual confirmation nothing payable was missed. | DT000008 | Payables |
| Unreadable/Scanned | External | Exclude | pdftotext extraction and a visual read both failed to yield usable content, so origin could not be confirmed from the source text - OriginClass defaulted to External (the overwhelming majority of this batch) and flagged for manual confirmation. Excluded from InvoiceHeader/InvoiceDetails, routed to FailureTable regardless of origin. | DT000009 | Payables |
| Unreadable/Scanned | Internal | Exclude | Same as the External row above, for the case where a human reviewer later confirms an unreadable document was actually one of our own outgoing documents. Excluded from InvoiceHeader/InvoiceDetails, routed to FailureTable. | DT000010 | Receivable |
| Insufficient Data | External | Exclude | Text is readable but key fields (amounts/dates/parties) are missing, so origin could not be confirmed with confidence - OriginClass defaulted to External (the overwhelming majority of this batch) and flagged for manual confirmation. Excluded from InvoiceHeader/InvoiceDetails, routed to FailureTable regardless of origin. | DT000011 | Payables |
| Insufficient Data | Internal | Exclude | Same as the External row above, for the case where a human reviewer later confirms an insufficient-data document was actually one of our own outgoing documents. Excluded from InvoiceHeader/InvoiceDetails, routed to FailureTable. | DT000012 | Receivable |
| Cancelled Invoice | Internal | Include | Added Stage 3 Round 5 (July 2026), REVISED Stage 3 Round 6 (July 2026): an Internal-origin (Gott/Itoorer-issued) document carrying a diagonal 'Anulado' (Cancelled/Voided) watermark - detectable in pdftotext output only as scattered fragments ('An'/'ul'/'ad'/'o') rather than contiguous text, easy to miss without specifically checking - paired with the printed disclaimer 'Este documento não tem valor contabilístico' (this document has no accounting value). Round 5 excluded these entirely to FailureTable; Round 6 changed this per explicit user instruction ('add a line with negative value for the same document') to instead INCLUDE the document in ReceivableHeader/ReceivableDetails with its amounts negated - mirroring the existing Credit Note/External rule's negative-offsetting-amount treatment, on the AR side instead of AP. Still logged in DocumentLog (DocumentType=Cancelled Invoice) so the invoice-number sequence and the cancellation itself both stay visible, but it now also nets against the ledger rather than disappearing from it. Discovered when a Round 5 batch member (gott_ft_2026-23.pdf) showed the watermark, which prompted re-checking two previously-included 'no accounting value' rows from earlier rounds (gott_ft_2025-75.pdf, gott_ft_2026-17.pdf) - both also carry the same watermark. See lessons-learned.md for the full investigation, the superseding-invoice evidence found for each cancelled document, and add_cancelled_negative_lines.py for the Round 6 migration that applied the revised treatment to all 5 known cases. | DT000013 | Receivable |
| Payroll-Receipt | Internal | Include | Added Stage 7 Round 3: dedicated document type for an internally-generated payroll receipt/payslip document. Internal origin (we generate it ourselves) but Payables DocClass (it represents a cost -- salary paid out -- not revenue), same ledger side as the existing Payroll/Financing/NoDocMov (P/F/O) rows already in FDCHDR. | DT000014 | Payables |
| Tax-TSU | External | Include | Added Stage 7 Round 3: dedicated document type for a TSU (Taxa Social Unica) filing received from the tax authority. External origin, Payables (a cost we owe). | DT000015 | Payables |
| Tax-IRC | External | Include | Added Stage 7 Round 3: dedicated document type for an IRC (corporate income tax) filing received from the tax authority. External origin, Payables. | DT000016 | Payables |
| Tax-IES | External | Include | Added Stage 7 Round 3: dedicated document type for an IES filing received from the tax authority, matching this round's new dedicated 'Taxes - IES'/060301 MNGAPL account (see this file's own MNGAPL rebuild). External origin, Payables. | DT000017 | Payables |
| Tax-VAT | External | Include | Added Stage 7 Round 3: dedicated document type for a VAT-related document received FROM the tax authority (e.g. a refund) -- External origin AND Receivable DocClass, since this is money coming IN from a third party, unlike the Internal/Payables VAT settlement row below. | DT000018 | Receivable |
| Tax-VAT | Internal | Include | Added Stage 7 Round 3: dedicated document type for our OWN VAT settlement filing -- Internal origin (we generate the filing) but Payables DocClass, since VAT owed to the tax authority is a cost, not revenue. Distinct from the External/Receivable VAT-refund row above -- same tax, opposite money direction. | DT000019 | Payables |

### Extracted ITMCLS funding rows

Source: `Editor/Data/itemclass_taxonomy.json` at the same pin, blob
`6fcfcf1403b68c38583bb937f50767be0ed3d1ef`. Every field below is independently marked
**`extracted`**.

| Class (`extracted`) | Subclass (`extracted`) | PLMKEY (`extracted`) | PLMKO (`extracted`) | CLCode (`extracted`) |
|---|---|---|---|---|
| Banks | Financing | Banks - Financing | 050101 | CL000005 |
| Revenue | Intercompany | Revenue - Intercompany | 070101 | CL000025 |


The intended seed is the union of the complete pinned 19 rows, transcribed below with their DTCode
and source fields, and the additive target rows that follow. `Bank|Legal|Procurement|Other` DocClass values,
the `Unknown` classification outcome, and ProcessingRoute values are explicit target-only
extensions with no import translation. The pinned base must retain all eight types the
previous draft dropped: `Wrong Document Type`, `Unreadable/Scanned`, `Insufficient Data`,
`Cancelled Invoice`, `Tax-TSU`, `Tax-IRC`, `Tax-IES`, and `Tax-VAT`. It also retains
`Invoice-Receipt` and `Payroll-Receipt` exactly; neither is renamed. Bank-generated `Payroll`,
`Financing`, and `NoDocMov` are not classification seed rows merely because they appear in DOCLOG.

| DocClass | DocumentType seed | Allowed OriginClass | Treatment | Processing route | Fiscal eligible | Eligibility condition |
|---|---|---|---|---|---|---|
| Payables | Invoice | External | Include | PostFiscal | yes | — |
| Payables | Invoice-Receipt | External | Include | PostFiscal | yes | payment evidence required |
| Payables | Credit Note | External | Include | PostFiscal | yes | — |
| Payables | Debit Note | External | Include | PostFiscal | yes | — |
| Payables | Receipt | External | Include | PostFiscal | yes | must support a payable |
| Payables | Payment Notice | External | Include | Review | no | — |
| Payables | Collection Notice | External | Include | Review | no | — |
| Payables | Payroll-Receipt | Internal | Include | PostFiscal | yes | — |
| Payables | Financing | Internal | Include | PostFiscal | yes | explicit rule required |
| Receivable | Invoice | Internal | Include | PostFiscal | yes | — |
| Receivable | Invoice-Receipt | Internal | Include | PostFiscal | yes | payment evidence required |
| Receivable | Credit Note | Internal | Include | PostFiscal | yes | — |
| Receivable | Debit Note | Internal | Include | PostFiscal | yes | — |
| Receivable | Receipt | Internal | Include | PostFiscal | yes | — |
| Receivable | Collection Notice | Internal | Include | Review | no | — |
| Other | Duplicate | External | Include | ArchiveOnly | no | preserve the real EntryCode and archive the physically distinct duplicate; this is not D5 ReferenceOnly |
| Bank | Bank Statement | External | Include | ArchiveOnly | no | movement pipeline owns BNKMOV |
| Legal | Employment Contract | Internal, External | Include | ArchiveOnly | no | — |
| Legal | Customer Contract | Internal, External | Include | ArchiveOnly | no | — |
| Legal | Supplier Contract | Internal, External | Include | ArchiveOnly | no | — |
| Legal | Bank Loan Agreement | External | Include | ArchiveOnly | no | — |
| Legal | Lease | Internal, External | Include | ArchiveOnly | no | — |
| Legal | Leasing/ALD | External | Include | ArchiveOnly | no | — |
| Legal | Insurance Policy | External | Include | ArchiveOnly | no | — |
| Legal | License/Permit | External | Include | ArchiveOnly | no | — |
| Legal | NDA | Internal, External | Include | ArchiveOnly | no | — |
| Legal | Power of Attorney | Internal, External | Include | ArchiveOnly | no | — |
| Procurement | Estimate/Quotation | External | Include | ArchiveOnly | no | — |
| Procurement | Purchase Order | Internal, External | Include | ArchiveOnly | no | — |
| Procurement | Product Data Sheet | External | Include | ArchiveOnly | no | — |
| Procurement | Proposal | Internal, External | Include | ArchiveOnly | no | — |
| Procurement | Order Confirmation | External | Include | ArchiveOnly | no | — |
| Other | Unknown | External, Internal | Exclude | Review | no | — |

Each comma-separated value shown in an `Allowed OriginClass` cell is migration shorthand only.
Seed/import expansion creates one DOCTYP row, and therefore one DTCode, for every
`(DocumentType, OriginClass)` pair. No PostgreSQL DOCTYP row stores a comma-separated or otherwise
multi-valued OriginClass.

Migration imports the prototype's complete active 19-row rule layer before adding the target
rows; it does not discard specialized existing types. Classification coverage is checked only for
captured DOCLOG rows (`Source <> 'BNK'`). EF0000045 classifies the first-class
ReferenceOnly state at Information/Non-Blocking. `ReferenceOnly` is a disposition, not a synthetic
DocumentType: the record keeps its evidenced DocClass/DocumentType and full payload while receiving
no EntryCode. A human-origin type is sticky and automation cannot replace it with a default.

## 3. Nextcloud business-copy layout

All path components use controlled CodeNames/type slugs, never raw OCR party names. Period derives
from the document/statement date; missing/ambiguous date fails to review. Existing files are
grandfathered; no retrospective move occurs without a separately approved migration. Originals
remain immutable and a business copy is confirmed before Archive state.

| Class | Layout |
|---|---|
| Payables | `{Company}/Payables/{YYYY}/{MM}/{CounterpartyCodeName}/{ControlledFilename}` |
| Receivables | `{Company}/Receivables/{YYYY}/{MM}/{CounterpartyCodeName}/{ControlledFilename}` |
| Bank | `{Company}/Bank/{YYYY}/{MM}/{BankAccountToken}/{BankAccountToken}_{YYYYMM}_vNNNN{extension}` |
| Legal | `{Company}/Legal/{Subtype}/{CounterpartyCodeName}/{ControlledFilename}` |
| Procurement | `{Company}/Procurement/{YYYY}/{MM}/{CounterpartyCodeName}/{DocumentType}/{ControlledFilename}` |
| Other | `{Company}/Other/{YYYY}/{MM}/{DocumentType}/{ControlledFilename}` |

Legal is deliberately **subtype-first**, then counterparty. Legal retrieval begins with the kind
of obligation; the counterparty remains the next path segment and a searchable metadata field.
This is the P1-0 Legal-path decision.

`CounterpartyCodeName` is the single canonical counterparty token in every path and filename. For
fiscal records it maps to `FDCHDR.CounterpartyCodeName`; for DOCARC records it maps to
`DOCARC.CounterpartyCodeName`. It is never a raw printed party name, a fiscal number, or the
ambiguous aliases `{CounterpartyCode}`, `{Counterparty}`, or `{Entity}`.

Fiscal filenames remain
`{Company}_{DocumentType}_{CounterpartyCodeName}_{YYYYMM}_{NN}{extension}`.
Legal/Procurement/Other use
`{Company}_{DocumentType}_{CounterpartyCodeName}_{YYYYMM}_{NN}{extension}`.
For those four families, `NN` is a stable two-digit value `01..99`, allocated monotonically within
the complete destination context: the case-folded controlled destination directory plus the
case-folded controlled basename before the suffix (extension excluded). It is never reused,
including after rollback, failed archive, Discard, or Purge. Allocation request 100 fails closed to
review before a business copy is written; the system never widens, wraps, fills a gap, or overwrites
an existing name implicitly.

Bank filenames use only the controlled BNKACC token, never a raw account number or IBAN, and the
stable archive version `vNNNN` (`v0001..v9999`) within `(Company, BankAccountToken, YYYYMM)`.
Every distinct official statement, additional segment, or
correction has its own immutable capture identity and receives the next version; retrying the same
idempotency/capture identity returns its existing version. A correction never replaces the earlier
statement. Allocation request 10000 fails closed to review before archive write and leaves the
protected original available.

An append-only `ArchiveNameLedger` binds the version to the canonical destination context and the
immutable Document/capture identity. Each context uses a non-cycling PostgreSQL sequence with the
same burn-on-rollback rule as permanent codes. Concurrent callers first resolve the existing
idempotency binding; on a miss they allocate and insert under unique constraints on both
`(CompanyId, ContextHash, CaptureIdentity)` and `(CompanyId, ContextHash, Version)`. A conflict loser re-reads and verifies
the winning full context and capture identity; mismatch, exhaustion, or an occupied unowned path
fails closed. The Nextcloud create uses no-overwrite/create-if-absent semantics. An already-existing
object is accepted only when its content hash and ledger owner exactly match the same binding.

True extensions and original bytes are preserved; filename comparison is case-insensitive. The
ledger and burned values are never deleted, so a suffix cannot be recycled after any lifecycle
transition.

ReferenceOnly follows the layout of its real DocClass with `DocumentState=ReferenceOnly` in
metadata and no EntryCode. Discarded items have no business copy; their immutable intake bytes
remain in protected original storage. Purged items have no bytes and only the tombstone described
in `docs/p1-0-discard-purge-lifecycle.md`.
