GOTT · Sibyla · approved 2026-08-11 · reconciled 2026-08-21: P2-3's decision point re-confirmed at S2-6 — the S2 slice-2 governed registry commit (CommitDocumentRegistry / RegistryCommitService) takes the resolver's output and records nothing about why, so the MatchDecision emission still belongs inside the S2-6 resolver; P2-2 closed without consolidation and realized in code — the three S1-9c screens shipped read-only against the separate tables (ITMCLS !65 · MNGACC !66 · COCACC !69), no ReferenceValue; P2-4 closed for EXCRAT and remains deferred for other rate-like tables; P2-6's trigger exists as query 03 of the S1.5-1 contract set

Phase 2 Structural Plan

The seven items master-data-and-ui-plan.md deliberately leaves out of S1.5 — each with the concrete event after which deferring it stops being cheap.

This reconciled HTML twin is current; master-data-phase2-plan.md is authoritative.

2026-08-11 · Approved alongside the main plan. P2-3 retains the only open decision point inside Phase 1, at S2-6.

0 · The seven, and when each stops being cheap

Deferring work is only honest if you say when it stops being cheap. Each item carries a decision point — the observable event after which it gets materially more expensive. A date would be a guess; the event is not.

#ItemDecision pointCost if missed
P2-2Closed without consolidation — ITMCLS, MNGACC and COCACC stay separate; no ReferenceValue closedDecided before S1-9c; realized by !65/!66/!69 — do not reopenNone accepted; separate tables preserve their domain boundaries
P2-3Matching records decisions, not just outcomes inside Phase 1S2-6 — when the resolver is first writtenProvenance cannot be reconstructed after the fact
P2-1ExternalIdentity absorbs the integration bindingsA third ERP provider, or the first cross-table binding bugTwo mechanisms for one fact
P2-4Effective dating for the rate-like tablesTaken 2026-08-12 — built with EXCRAT at 43ad0e7The most expensive retrofit on this list — and it was avoided
P2-5Separate Tenant from CompanyThe first customer with two legal entities on one subscriptionAudit every tenancy-filtered query
P2-6Contract — drop LegacyCode and the shimsWhen nothing reads it (measurable)Dead column, small ongoing confusion
P2-7BNKACCBankAccount / CashAccountLayer 5 bank reconciliationNone — already correctly sequenced by V6-D4

1 · Phase 1 decisions

P2-2 · Closed without consolidation

ITMCLS, MNGACC and COCACC remain separate. Do not create ReferenceValue, and do not reopen the decision at S1-9c. A discriminator-based table would blur domain boundaries and invite nullable special cases as the domains evolve; the accepted cost of separate descriptors is smaller than that coupling.

Realized in code, 2026-08-17. The three descriptor screens shipped read-only against their existing tables — ITMCLS (!65), MNGACC (!66) and COCACC (!69) — with no ReferenceValue table. SNCACC remains separately justified as a hierarchical chart of accounts and was never part of this consolidation decision.

P2-3 · Matching records decisions, not just outcomes

The plan of record stores what was learned — a new ENTALS row, a new ENTITM mapping. It does not store why.

MatchDecision
  Id            uuid PK
  DocumentId    uuid NOT NULL FK
  SubjectType   varchar(16) NOT NULL   -- 'Entity' | 'Item'
  ResolvedToId  uuid NULL              -- null = no match, went to review
  Strategy      varchar(24) NOT NULL   -- 'Nif' | 'Alias' | 'NormalizedName' | 'Human'
  Confidence    numeric(4,3) NULL
  CandidateJson jsonb NULL             -- what else was considered, and its score
  DecidedBy     uuid NULL              -- null = automatic
  DecidedAt     timestamptz NOT NULL
Why it matters more than it looks. Step 7 is already the argument for the maintenance UI — the difference between a system that learns and a tool people abandon. But a learning path nobody can audit is one nobody will trust with the second error. After a Tranquilidade-class mistake the question is never what did it match; it is why did it match that, and what else did it consider? CandidateJson is the whole answer — free to capture at the moment of decision, impossible to reconstruct afterwards. It is also the only possible denominator for measuring whether resolution is improving.
Why the decision point is S2-6. That is where the NIF → alias → name resolver is written. The table is about a day's work then, and emission is a one-line change inside code being written anyway. Retrofitting means starting the history from zero, having discarded every decision made in between.

Scope discipline: a write-only append log with no UI in Phase 1. No screen, not in the review queue. One table, one insert, no reader until somebody needs one — that is what keeps it cheap enough to justify doing early.

2 · The rest

P2-1 · ExternalIdentity absorbs the integration bindings

IntegrationPartyBinding caches a party's external id per provider. Under M-D5 that is exactly ExternalIdentity with Scheme = 'cegid.party'. Same for IntegrationProductBinding and items.

Deferred on principle, not on effort. Both are live and written at step 8. The entire risk argument for the S1.5 collapse is that the Cegid and Moloni surfaces are not disturbed — folding these in during S1.5 would spend precisely the safety margin that made the collapse defensible.

One design note for when it happens: items need a subject that is not an entity. Prefer a second table — ExternalItemIdentity — over a polymorphic SubjectType. A polymorphic key cannot carry a real foreign key, and on this schema referential integrity is worth more than the deduplication.

P2-4 · Effective dating for the rate-like tables

M-D7 records the decision; this is the implementation. EXCRAT and anything rate-like get ValidFrom / ValidTo and a no-overlap exclusion constraint. ENTMST and the code lists do not — they get audit events.

The asymmetry is the point. A rate is a statement about a period; asking for “the rate” with no date is meaningless. An entity is a thing that changes; asking for “the entity” with no date is the normal case. Two different problems — and letting one design bleed into the other is how schemas end up bitemporal everywhere and comprehensible nowhere. Decision point: when EXCRAT is first built. Free at creation; adding it to a populated rate table means inventing ValidFrom values that were never recorded. Taken 2026-08-12: S1-5 (43ad0e7, migration 20260811142000_S1ExcratEffectiveDating) built EXCRAT with ValidFrom/ValidTo as generated STORED columns and the GiST exclusion constraint EX_EXCRAT_NoOverlappingRatePeriod. Closed for EXCRAT; still deferred for everything else rate-like.

P2-5 · Separate Tenant from Company

CompanyRegistry carries two jobs: the billing and authentication boundary, and the legal entity that issues and receives invoices. They coincide today. They stop coinciding the first time a group with several legal entities buys one subscription.

The decision point is a sales fact, not an engineering one — the first multi-entity customer. Named here so somebody recognises it when it arrives, rather than discovering it as a data-model emergency during onboarding. Cheap insurance meanwhile: keep the tenancy filter and the company filter as two distinct expressions in code even while they resolve to the same value. Separating them later is then a change of definition rather than an audit of every query.

P2-6 · Contract — drop LegacyCode and the shims

Phase C of the S1.5 migration. Decision point: when nothing reads it — and that is measurable. The query exists as query 03 of the S1.5-1 schema contract set, so “is anything still reading LegacyCode?” is answered by the suite rather than by memory.

P2-7 · BNKACCBankAccount / CashAccount

Already correctly deferred by V6-D4; arrives with Layer 5 bank reconciliation. Restated only for completeness, and to repeat the standing instruction: do not touch BankAccount in Phase 1. It is the source side of payment; ENTBNK is the counterparty side. They are not duplicates.

3 · Considered and rejected

Recorded so they are not re-proposed.

ProposalWhy not
Make ENTMST bitemporalAn entity is not a statement about a period (P2-4). An audit log answers the real questions at a fraction of the query complexity
Polymorphic SubjectType on ExternalIdentityCannot carry a real FK. Two tables, per P2-1
Fold SNCACC into ReferenceValueA tree with hierarchy semantics and an open scoping question (O-3), read at step 8
Rekey any internal join to business codesM-D2. This is the condition the whole exercise exists to remove
Do the integration-binding fold inside S1.5Spends the exact safety margin that makes the collapse defensible (P2-1)

4 · What would change this document

If this happensThen
A multi-entity customer signsP2-5 moves to Phase 1 and becomes the largest item on it
A third ERP integration is scopedP2-1 moves ahead of P2-4
P2-2 is closedITMCLS, MNGACC and COCACC remain separate; S1-9c does not reopen the decision
S2-6 ships without MatchDecisionP2-3 stops being deferrable and becomes debt with a visible interest rate: every day of resolution history not captured is not recoverable