Flip360 CRM · Live Sign in
Pre-Payment Pilot mode. Real CMs · real providers · real conversations. No money moves until Stripe live-wire (AF-9.5). Every activation carries pre_payment_pilot=1. Pilot status →
Flip360 CRM
v1.1 (DRAFT) — CEO causality correction. v1.0 described the CRM as the trigger surface for deal progression. That was wrong. The correct model: handshakes drive deals, engine drives money, CRM observes. v1.0 superseded same day. See the change log for the full correction record.
Overview L1 · Page L2 · Tools L3 · Seams L4 · $ Rules Debt Sign-off

Known EA debt — named, not solutioned

Five places in the current code where the constitution is currently violated. Logged by the EA function as part of v1.1. Remediation is future sprint scope, not part of this document.

Naming is not solutioning. The EA function lists every place where the constitution above is currently violated. Remediation is the job of properly-scoped sprints after Steer Co signs off the constitution. Solution architects design the fixes; the EA function gates them.
EA-Debt-01 Critical REMEDIATED Sprint EA-1 + EA-2 + EA-3 + EA-4 · full 4-handshake chain live <strong>Chain closed.</strong> H1+H2+H3a+H3b+H4 all on production. iOS Secure Enclave / Android StrongBox attestation remains the only chain-related hardening pending; that is a hardware-key sprint, not a chain-protocol sprint.
Principle violated: Rule 1 — handshakes drive deals
Where it lives: Migration 0005 defines receipt_events, device_keys, causation_receipts, chain_blocks, witness_proofs. Sprint EA-1: H1 (INTENT_SIGNED). Sprint EA-2: H2 (ACK_BY_REFEREE / DECLINED_BY_REFEREE) with full prev_event_hash chain-link enforcement. Sprint EA-3: H3a (INTAKE_BY_PARTNER) + H3b (SETTLEMENT_WITNESSED). Sprint EA-4 (same day): H4 (OUTCOME_CONFIRMED_BY_REFEREE) via POST /chain/v1/h4 — closes the chain. All five endpoints enforce the same 14-step pipeline (parse → shape → canonical match → idempotency → receipt lookup → chain-link → receipt-status guard → device lookup → revocation → identity binding → Web Crypto signature verify → freshness → D1 batch write → CRM consumer → single-egress audit). H4 identity binding: the signing device’s contact_id must equal envelope.referee_contact_id, AND that contact_id must equal the signed_by_contact_id on the receipt’s most recent ACK_BY_REFEREE event — i.e. the H4 signer must be the same person who signed H2 (else 401 WRONG_REFEREE). H4 carries the SIGNED outcome_matches boolean in the canonical envelope — the customer cryptographically declares whether the referrer should be paid. Receipt status flow: AWAITING_ACK → ACKNOWLEDGED (H2 yes) → BOUND_TO_DEAL (H3a) → SETTLED (H3b) → OUTCOME_CONFIRMED (H4 yes) or OUTCOME_WITHHELD (H4 no). Deal handshake_stage flow: H1_INTENT → H2_ACK → H3a_INTAKE (0.60) → H3b_SETTLED (0.80) → H4_OUTCOME (1.00). Commission engine now fires legitimately at H4 — the H3b accrual was an estimate; H4 is the cryptographically-signed customer verdict that authorises payment. The optional release_commission field lets the customer waive cooling-off in the same signed message. Six EA-4 negative tests on production all rejected correctly with audit rows written.
Consequence today: <strong>Rule 1’s 4-handshake chain is complete and court-defensible.</strong> Every step from referrer intent (H1) through customer outcome (H4) carries a signed envelope, a prev_event_hash chain-link, and a single-egress audit row. EA-Debt-02 (engine consumes chain) is fully unblocked end-to-end — stage advancement is fully chain-driven, no CRM click required from LEAD through WON+H4_OUTCOME. The only items now blocking a real-world deployment are: (a) the four user-facing signing UI pages (UI-1 sprint), (b) device key self-service enrolment (ENROL-1), (c) commission payout surface (PAY-1), (d) hardware-key attestation (hardening, not protocol).
Remediation: <strong>SHIPPED — full chain on production 2026-06-28.</strong> Walking skeleton (EA-1), first link (EA-2), partner side (EA-3), customer close (EA-4) all landed in one day. Honest scope: H1 + H2 + H3a + H3b + H4. Chain protocol is feature-complete. Next sprint = UI-1 (the four signing pages) to make the chain field-deployable.
Court-defensible receipt · H1 (Sprint EA-1)
receipt_code: 6HW3635
receipt_id: 14 · event_id: 59
event_hash: eh_cc4f8758676cde45013fae6b14c99605…
signed_at: 2026-06-28 06:32:46 UTC · host: cosaiflip360.org
Court-defensible receipt · H1+H2 chain link (Sprint EA-2)
receipt_code: 7QVDSCM
receipt_id: 15 · event_id: 61
event_hash: eh_190fe5cddcebac1906fc2f6ab3a779ab…
signed_at: 2026-06-28 06:45:01 UTC · host: cosaiflip360.org
Court-defensible receipt · H3a CHAIN_BROKEN rejection (Sprint EA-3 · forged prev hash against real ACKNOWLEDGED receipt 7QVDSCM)
receipt_code: audit#18
receipt_id: 0 · event_id: 0
event_hash: eh_190fe5...→eh_deadbeef… (rejected 409)…
signed_at: 2026-06-28 21:45 UTC · host: cosaiflip360.org
Court-defensible receipt · H3b BAD_RECEIPT_STATE rejection (Sprint EA-3 · premature settlement against ACKNOWLEDGED)
receipt_code: audit#19
receipt_id: 0 · event_id: 0
event_hash: ACKNOWLEDGED · not BOUND_TO_DEAL (rejected 409)…
signed_at: 2026-06-28 21:45 UTC · host: cosaiflip360.org
Court-defensible receipt · H3b BAD_VALUE rejection (Sprint EA-3 · deal_value_cents > $1B sanity ceiling)
receipt_code: audit#17
receipt_id: 0 · event_id: 0
event_hash: deal_value_cents=999999999999999 (rejected 400)…
signed_at: 2026-06-28 21:40 UTC · host: cosaiflip360.org
Court-defensible receipt · H4 BAD_RECEIPT_STATE rejection (Sprint EA-4 · H4 against ACKNOWLEDGED — must be SETTLED)
receipt_code: audit#24
receipt_id: 0 · event_id: 0
event_hash: ACKNOWLEDGED · not SETTLED (rejected 409)…
signed_at: 2026-06-28 22:00 UTC · host: cosaiflip360.org
Court-defensible receipt · H4 SHAPE rejection (Sprint EA-4 · satisfaction must be 1..5)
receipt_code: audit#22
receipt_id: 0 · event_id: 0
event_hash: satisfaction=99 (rejected 400)…
signed_at: 2026-06-28 22:00 UTC · host: cosaiflip360.org
Court-defensible receipt · H4 SHAPE rejection (Sprint EA-4 · outcome_matches must be a boolean)
receipt_code: audit#21
receipt_id: 0 · event_id: 0
event_hash: outcome_matches missing (rejected 400)…
signed_at: 2026-06-28 22:00 UTC · host: cosaiflip360.org
EA-Debt-02 Critical REMEDIATED Sprint EA-2.5 (chain writer); v1.1.6 truth-up; v1.2.0 EA-5a (writes hit canonical); <strong>v1.2.1 EA-5b shipped: CRM reads now hit canonical via single overlay seam</strong> <strong>v1.2.2 closes EA-5 end-to-end.</strong> Writes (v1.2.0) + reads (v1.2.1) + reconciliation of 5 pre-existing mismatches (v1.2.2) all shipped. Overlay <code>crm_deal_stage</code> retained as audit-only safety net (preferred-but-fallback, never primary). <strong>Production divergence: 0 / 15 named deals.</strong>
Principle violated: Rule 1 — one source of truth for deal stage; CRM is observer for everything else
Where it lives: EA v1.2.0 (Sprint EA-5a) cutover (2026-06-28): saveDealStage() in src/data/crm-overlay.ts:352 now writes BOTH (a) the canonical deals row — updating handshake_stage, handshake_completeness, and current_stage_id atomically via the v1.2.0 collapse rule — AND (b) the overlay row in crm_deal_stage. Overlay write is retained as a safety net during the transition; will be retired in EA-5b once CRM read paths are rewired off the overlay.

Mechanism: the v1.1.7 audit named the '9-stage vs 5-stage vocabulary' problem. The v1.2.0 SQL discovery showed the canonical deals table already has BOTH columns natively (handshake_stage TEXT and current_stage_id INTEGER via pipeline_stages). The cutover is therefore a write-fan-out, not a schema migration: every CRM stage change updates both columns through the new pure collapse function in src/data/stage-vocab.ts.

All 8 historical CRM writer call sites (advance, void, restore, move, bulk void/restore, two undo branches) now write canonical, because they all go through the single saveDealStage() entry point.
Consequence today: <strong>Before v1.2.0:</strong> new CRM stage changes only landed in the overlay, drifting canonical further away from the user&#39;s view with every edit. <strong>After v1.2.0:</strong> new edits write both halves; the existing 6 doc-vs-canon mismatches (named in <code>docs/evidence/ea5a_pre_cutover_sql.md</code>) remain frozen in place and require a separate business-led reconciliation sprint to resolve, because picking which column is &#39;right&#39; for each of those 6 deals is a business decision, not a technical one. The 9 deals that were already consistent stay consistent. Court-defensibility (Rule 3) is restored for new writes; legacy mismatches are documented rather than overwritten.
Remediation: <strong>EA-5b SHIPPED (v1.2.1, 2026-06-28):</strong> CRM reads now go through canonical via a single architectural seam. <code>loadOverlay()</code> in <code>src/data/crm-overlay.ts</code> now loads <code>canonicalDealStages</code> from <code>SELECT uid, handshake_stage, handshake_completeness FROM deals WHERE uid IS NOT NULL AND handshake_stage IS NOT NULL</code> alongside the existing overlay tables. <code>effDeals()</code> and <code>effDealByUid()</code> — the two functions every CRM screen funnels through — now apply canonical-first precedence: canonical wins if present, overlay is fallback, seed is last-resort. <code>crm-hydrate.ts</code> uses the same precedence for the DEALS array hydration so the in-memory view stays aligned. Net effect: /crm/pipeline, /crm/deals, /crm/deals/:id, and contact-page deal lists all observe canonical truth. The overlay write in <code>saveDealStage()</code> is intentionally retained as an audit-log safety net — never authoritative, never read before canonical — so we can detect any divergence post-cutover. <strong>EA-5c SHIPPED (v1.2.2, 2026-06-28):</strong> production ledger check showed 0 ledger_entries and 0 commission_events across all 5 residual mismatches (the v1.2.0 cutover had already fixed the 6th, DEAL-00891) — removing the CFO/compliance gating condition. New <code>POST /crm/admin/reconcile-stage/:id</code> admin endpoint re-applies the v1.2.0 collapse rule against the deal&#39;s existing <code>handshake_stage</code> via <code>setDealStageByHandshake()</code> (which gained an optional <code>changedBy</code> parameter so the <code>deal_stage_history</code> audit row carries <code>changed_by=&#39;ea-5c-reconciliation&#39;</code> rather than masquerading as a sales gesture). Five POSTs against production reconciled deals 1005, 1006, 1007, 1009, 1012. Post-cutover SQL: <strong>0 / 15 mismatches across named deals.</strong> Six audit rows in <code>deal_stage_history</code> (one v1.2.0 cutover labelled <code>platform-admin</code>, five v1.2.2 reconciliations labelled <code>ea-5c-reconciliation</code>). <strong>Proof of the full v1.2.0 + v1.2.1 + v1.2.2 cutover:</strong> <code>docs/evidence/ea5a_post_cutover_sql.md</code> (write-side) · <code>docs/evidence/ea5b_read_proof.md</code> (read-side) · <code>docs/evidence/ea5c_reconciliation_proof.md</code> (reconciliation).
Court-defensible receipt · H1 → deal #1014 created at LEAD (chain-write proof)
receipt_code: E252LIS
receipt_id: 17 · event_id: 64
event_hash: eh_17f53efd2c489adb901ec83cb9911018…
signed_at: 2026-06-28 07:0X UTC · host: flip360.pages.dev
Court-defensible receipt · H2 ack=yes → deal #1014 advanced to QUALIFIED
receipt_code: E252LIS
receipt_id: 17 · event_id: 65
event_hash: eh_570760f0b07a65c32cfa117a3c7fda7b…
signed_at: 2026-06-28 07:0X UTC · host: flip360.pages.dev
Court-defensible receipt · H2 ack=no → deal #1015 closed LOST
receipt_code: E25SAW9
receipt_id: 18 · event_id: 67
event_hash: eh_1bf058027fdcf81513a91f809c228b6e…
signed_at: 2026-06-28 07:0X UTC · host: flip360.pages.dev
EA-Debt-03 High REMEDIATED Sprint EA-Debt-03 SHIPPED (v1.2.3, 2026-07-03) — Path A: strip CRM-driven forward progression at the type level Kanban drag-drop wrote <code>deals.handshake_stage</code> directly AND fired the commission engine (real <code>ledger_entries</code>) from a CRM click. A salesperson’s drag could mint money. Now: kanban is a read-only visualisation, <code>/advance</code> and <code>/move</code> are 410 Gone, and <code>saveDealStage()</code> is type-guarded at the write seam.
Principle violated: Rule 1 — handshakes drive deals; engine drives money; CRM observes
Where it lives: app.get(′/crm/pipeline′) historically rendered a drag-drop kanban. Cards were <div draggable="true"> objects wired to POST /crm/deals/:id/move. The /move handler called saveDealStage() AND triggerCommissionForStageMove() — the latter writes real commission_event rows and ledger_entries via insertLedgerEntry() (idempotency key deal-{dealId}-stage-{triggerStageId}). A second endpoint, POST /crm/deals/:id/advance, did the same via a big purple button on the deal detail page. Both bypassed the chain entirely.
Consequence today: <strong>Before v1.2.3:</strong> two CRM-driven write paths (<code>/advance</code>, kanban <code>/move</code>) were the exact opposite of Rule 1 — the CRM was the driver, not the observer. Court-defensibility (Rule 3) was compromised because <code>deal_stage_history.changed_by</code> carried only the role string (<code>role=‘sales’</code>) — no way to distinguish a legitimate admin gesture (void/restore) from a Rule-1-violating drag. <strong>After v1.2.3:</strong> the CRM cannot progress a deal. All progression is chain-first (POST /chain/v1/h1..h4). The 4 legitimate CRM writes that remain (void, restore, bulk void, bulk restore, and two undo branches) are read-side administrative gestures, not chain progression — they are kept and now type-guarded by <code>DealStageWriteSource</code>.
Remediation: <strong>SHIPPED v1.2.3 (Path A locked):</strong> (1) <code>src/data/crm-overlay.ts</code> — added <code>export type DealStageWriteSource = ‘chain-consumer’ | ‘ea-reconciliation’ | ‘admin-void’ | ‘admin-restore’</code>, <code>ALLOWED_SOURCES</code> Set, and <code>assertDealStageWriteAllowed()</code>. Added mandatory <code>source</code> parameter to <code>saveDealStage()</code> — the single choke-point every legacy CRM writer flows through. Signature is now <em>6-arg required</em> (breaking change enforced at TypeScript level, not just runtime). Audit label in <code>deal_stage_history.changed_by</code> is derived from the <em>validated</em> <code>source</code> value — court-defensible. (2) <code>POST /crm/deals/:id/advance</code> — retired to <strong>410 Gone</strong> with JSON payload <code>{ retired: true, rule: ‘EA-1’, chain_endpoints: {H1..H4} }</code> for API callers; form-post callers redirect to deal detail with <code>?toast=rule1_retired</code>. (3) <code>POST /crm/deals/:id/move</code> — same 410 Gone treatment. (4) <code>/crm/pipeline</code> UI rewired: cards are now <code>&lt;a href="/crm/deals/{id}"&gt;</code> links (not drag objects). Blue "drag" banner replaced with green <em>"Kanban is read-only · EA Constitution Rule 1"</em> banner. DnD JavaScript removed entirely. (5) All 6 remaining <code>saveDealStage()</code> call sites (single/bulk void, single/bulk restore, 2 undo branches) explicitly pass <code>‘admin-void’</code> or <code>‘admin-restore’</code>. (6) <code>src/engine/chain-consumer.ts</code> unchanged — confirmed it uses <code>setDealStageByHandshake()</code> not <code>saveDealStage()</code>, so chain writes were never affected.
Court-defensible receipt · POST /crm/deals/:id/advance now returns 410 Gone (JSON payload cites Rule 1)
receipt_code: EA-DEBT-03
receipt_id: undefined · event_id: undefined
event_hash: undefined…
signed_at: 2026-07-03 local smoke · host: localhost:3000
Court-defensible receipt · POST /crm/deals/:id/move now returns 410 Gone (kanban read-only)
receipt_code: EA-DEBT-03
receipt_id: undefined · event_id: undefined
event_hash: undefined…
signed_at: 2026-07-03 local smoke · host: localhost:3000
Court-defensible receipt · /crm/pipeline: <code>draggable="true"</code> occurrences = 0 (was: 15+ per render)
receipt_code: EA-DEBT-03
receipt_id: undefined · event_id: undefined
event_hash: undefined…
signed_at: 2026-07-03 local smoke · host: localhost:3000
EA-Debt-04 Medium REMEDIATED Subsumed under EA-Debt-06 v1.2.0; collapse rule applied at the canonical-first read seam in v1.2.1 EA-5b; <strong>v1.2.2 EA-5c reconciled the 5 residual mismatches — production divergence now 0 / 15</strong> Vocabulary split is now an aesthetic / UX-copy concern, not a data concern. The collapse rule is the single source of truth at write, read, and reconciliation seams. Zero divergence between the two vocabularies anywhere on production.
Principle violated: Rule 2 — same business object spoken about in two vocabularies
Where it lives: /crm/pipeline uses 9-stage handshake vocabulary (H1_INTENT, H2_ACK, H3a_INTAKE, H3b_SETTLED, H4_OUTCOME, COOLING_OFF, ELIGIBLE, PAID, CLAWBACK). /crm/deals + pipeline_stages table use 5-stage classic vocabulary (LEAD, QUALIFIED, ENGAGED, WON, LOST). Chain (migration 0005) uses 14 event types of which 5 are handshakes proper.
Consequence today: Three vocabularies for one business object — cognitive load on every reader — was the original framing. <strong>v1.2.0 + v1.2.1 reframe:</strong> the canonical <code>deals</code> row carries both vocabularies on the same row (<code>handshake_stage</code> TEXT + <code>current_stage_id</code> INTEGER FK), kept atomically consistent by the collapse rule in <code>src/data/stage-vocab.ts</code>. Writes (v1.2.0) and reads (v1.2.1) both pass through that rule. So the two vocabularies are now two views of one truth, not two truths.
Remediation: <strong>SHIPPED (v1.2.0 + v1.2.1):</strong> single collapse function (<code>collapseHandshakeToClassic</code>) owns the mapping; called by both <code>saveDealStage()</code> (write) and the canonical-first read precedence in <code>effDeals</code>/<code>effDealByUid</code>/<code>crm-hydrate</code>. Any future UI consolidation (showing one vocabulary or the other, or both, in any given screen) is now a copy/UX decision, not a data-integrity decision.
EA-Debt-05 Medium REMEDIATED Sprint EA-2.5 · EA-Debt-05 fold-in Canonical entry point shipped; legacy alias kept for EA-Debt-02 call sites
Principle violated: Rule 1 + Rule 2 — function name embeds the inverted causality
Where it lives: src/engine/stage-trigger.ts historically exposed triggerCommissionForStageMove(dealId, stageId, role) — a name implying 'a stage move happened, fire commission'. The correct framing is 'a chain event was received, advance lifecycle, conditionally fire commission'. Sprint EA-2.5 ships the new canonical entry point onChainEventReceived(db, receiptId, eventId) in the same file. It looks up the bound deal via causation_receipts.bound_deal_id and the event type via receipt_events.event_type, maps to CrmHandshakeStage via eventTypeToHandshakeStage(), then delegates to the existing pure logic. The chain consumer (src/engine/chain-consumer.ts) is the only caller — the chain ingestion path now drives commission, not the CRM. The legacy export name is preserved as triggerCommissionForStageMoveLegacy (aliased back) so the 8 CRM stage-writer call sites keep compiling until EA-Debt-02 retires them.
Consequence today: The canonical engine entry point now matches Constitution Rule 1 in both shape and name. New engineers reading <code>onChainEventReceived</code> understand chain = trigger source. Legacy alias makes EA-Debt-02 quantifiable: 8 remaining CRM-driven calls are the precise EA-Debt-02 retirement target.
Remediation: Done. Function renamed + new signature shipped; old name kept as deprecated alias for transitional compatibility with EA-Debt-02. Aliased export and call sites will be removed together when EA-Debt-02 closes.
EA-Debt-06 Critical REMEDIATED Surfaced v1.1.6; v1.1.7 audit (partial); v1.2.0 EA-5a schema discovery + write-path cutover; <strong>v1.2.1 EA-5b shipped: CRM reads now hit canonical via single overlay seam</strong> Canonical <code>deals</code> table holds BOTH vocabularies natively (<code>handshake_stage</code> TEXT + <code>current_stage_id</code> FK → <code>pipeline_stages</code>). v1.2.0 made writes dual-write atomically; v1.2.1 made reads canonical-first across every CRM screen via the <code>effDeals</code>/<code>effDealByUid</code> overlay seam; <strong>v1.2.2 reconciled the 5 residual mismatches via the new admin endpoint. Production divergence: 0 / 15 named deals.</strong>
Principle violated: Rule 1 (one source of truth) + Rule 2 (named seams) + Rule 3 (court-defensible)
Where it lives: v1.2.0 schema discovery (2026-06-28): PRAGMA table_info(deals) showed canonical already has handshake_stage TEXT, handshake_completeness REAL, and current_stage_id INTEGER on the same row. Migration 0026 (EA-2.5) added the first two; current_stage_id has been there since migration 0001. The v1.1.6 framing of 'Model A vs Model B' was technically wrong: the in-memory DEALS seed + overlay tables are not a second model — they are a stale read-aside cache that was never authoritative. The v1.1.7 audit was also incomplete — it read current_stage_id as if it were a stage name (it's an INTEGER FK) and reported the wrong divergence picture. The actual divergence, JOIN-ed against pipeline_stages, is 9/15 agree and 6/15 disagree across the 15 named deals (not 13 — chain-driven deals also carry uids). Full SQL citations: docs/evidence/ea5a_pre_cutover_sql.md.

What ships in v1.2.0:
(1) src/data/stage-vocab.ts — new pure module. Functions: collapseHandshakeToClassic(), targetClassicStageCode(), bestStageLabel(), handshakeProgress(), isVocabularyConsistent(), getCollapseRuleTable(). No I/O. Single source of truth for the rule table.
(2) setDealStageByHandshake() in src/engine/db.ts — atomic writer for both columns + audit history.
(3) saveDealStage() in src/data/crm-overlay.ts rewired to dual-write canonical + overlay.
Consequence today: <strong>The honest correction:</strong> three consecutive sprints (v1.1.4, v1.1.5, v1.1.6) wrote EA narrative without enough D1 verification. v1.1.7 introduced the SQL-citation discipline to stop the drift — and v1.2.0 then caught v1.1.7 itself getting the data picture wrong (read INTEGER column as stage name). This is exactly how the discipline is supposed to work: each sprint cites SQL, the next sprint runs the SQL again and corrects the record. <strong>Where v1.2.0 leaves us:</strong> new CRM edits now go to canonical (the dual-write fans out). The 9 deals where the two columns already agree stay agreeing. The 6 deals where they disagree are frozen until business sign-off picks the authoritative column per deal (EA-5c). The 17 unnamed pre-EA-2.5 legacy rows are untouched (they have no <code>uid</code> so the new write path doesn&#39;t reach them; they also have no UI surface today).
Remediation: <strong>EA-5a SHIPPED (v1.2.0):</strong> (a) pure collapse function exported from <code>src/data/stage-vocab.ts</code>; (b) atomic writer <code>setDealStageByHandshake()</code>; (c) <code>saveDealStage()</code> dual-write. <strong>EA-5b SHIPPED (v1.2.1, 2026-06-28):</strong> CRM reads are now canonical-first via a single architectural seam. <code>loadOverlay()</code> in <code>src/data/crm-overlay.ts</code> grew an 11th batch query that loads <code>canonicalDealStages</code> (uid → {handshake_stage, handshake_completeness}) from the live <code>deals</code> table. <code>effDeals()</code> and <code>effDealByUid()</code> (the two functions every <code>/crm/*</code> deal screen flows through) now apply canonical-first precedence: canonical wins if present, overlay is fallback, seed is last-resort. <code>crm-hydrate.ts</code> uses the same precedence for the DEALS array hydration. <strong>The overlay write in <code>saveDealStage()</code> is intentionally retained as an audit-log safety net</strong> — never authoritative, never read before canonical, but present so any future divergence is detectable (PMO can also use the overlay as an undo-log if needed). The four overlay tables (<code>crm_deal_stage</code>, <code>crm_deal_overlay</code>, <code>crm_deal_new</code>, <code>crm_deal_archive</code>) are kept for audit-of-record; only the read priority changed. <strong>EA-5c SHIPPED (v1.2.2, 2026-06-28):</strong> the CFO/compliance gating condition evaporated when the CTO ran the ledger-impact check against production D1 — <strong>0 ledger_entries and 0 commission_events</strong> across all 5 residual mismatches. The reconciliation was therefore documentation-grade, not money-grade. New admin endpoint <code>POST /crm/admin/reconcile-stage/:id</code> re-runs the collapse rule against each deal&#39;s existing <code>handshake_stage</code>; <code>setDealStageByHandshake()</code> gained an optional <code>changedBy</code> parameter so the resulting <code>deal_stage_history</code> audit row carries <code>changed_by=&#39;ea-5c-reconciliation&#39;</code>, distinguishable in court from a sales gesture. Five POSTs against production reconciled deals 1005/1006/1007/1009/1012; post-cutover SQL confirms <strong>0 / 15 mismatches</strong> across all named deals. <strong>Audit-of-record:</strong> <code>docs/evidence/ea5a_pre_cutover_sql.md</code>, <code>docs/evidence/ea5a_post_cutover_sql.md</code>, <code>docs/evidence/ea5b_read_proof.md</code>, <code>docs/evidence/ea5c_reconciliation_proof.md</code>. EA-Debt-04 (vocabulary split) is subsumed under this debt and closed by v1.2.1.
Audience: Steer Co (Matt, Carla, Corrina) + future hires + auditors. Source of truth: docs/ENTERPRISE_ARCHITECTURE.md (version controlled). EA function: Claude under delegated authority from CEO Matt.
Matt's page