feat(dimensions): PR9 cutover — cost_center/project become GENERATED columns, dual-write removed (#870)

The dual-write window ends (dev_docs/dimensions_implementation_plan.md PR9):
journal_entry_lines.cost_center/project are now GENERATED ALWAYS AS
(NULLIF(dimensions->>'1'/'6','')) STORED — divergence from the bag is
impossible by construction instead of by convention.

- migration 20260702230000: drift pre-flight (refuses cutover on
  inconsistent data; prod verified 0 drift across 593k rows), column swap
  (DROP metadata-only + one-rewrite ADD pair), and atomic redefinition of
  the two SQL writers — retag_line_dimensions (SET dimensions only) and
  bulk_book_transactions (INSERT names the bag only)
- TS writers stripped of the mirror spread: engine buildLineInserts
  (covers create/update/reversal), storno-service (reversal + correction),
  SIE import bulk insert, sandbox seed
- lineDimensionColumns() removed from dimension-resolver — nothing derives
  mirrors in TypeScript anymore; normalizeLineDimensions + the deprecated
  cost_center/project INPUT aliases stay (API contract, they normalize
  into the bag); JournalEntryLine ROW type keeps the fields (generated
  columns still SELECT)
- immutability carve-out unchanged BY DESIGN: its whole-row diff already
  subtracts dimensions/cost_center/project on both sides, which is exactly
  what makes it correct with generated columns (BEFORE-trigger NEW carries
  not-yet-recomputed mirror values)
- audited every reader (v1 journal-entries, MCP query_journal filters +
  group_by, rc-basis-gaps) — reads are untouched; no index, view, or
  constraint referenced the TEXT columns, so DROP COLUMN cascades nothing
- new pg suite: generated derivation, explicit-mirror-write rejection,
  draft-update recompute; existing retag/substrate/bulk-book suites
  updated to bag-only writes (their mirror assertions now exercise the
  generation expression)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-07-02 23:28:42 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 163fbd8222
commit fb3f0a9cee
12 changed files with 713 additions and 78 deletions
+2 -4
View File
@@ -7,7 +7,6 @@ import { createLogger } from '@/lib/logger'
import { checkRateLimit } from '@/lib/auth/rate-limit-http'
import { truncateIp } from '@/lib/api/v1/with-api-v1'
import { ensureSandboxAgentProfile } from '@/lib/sandbox/ensure-agent'
import { lineDimensionColumns } from '@/lib/bookkeeping/dimension-resolver'
// Anonymous sign-in is enabled in all environments so visitors can try the
// product; a per-/24 cap on the seed endpoint keeps a single network from
@@ -463,8 +462,8 @@ export async function POST(request: Request) {
// 10. Create journal entry lines. The P&L line carries demo dimensions
// ({"1":"BUTIK","6":"P001"}) so the register's "antal taggade rader",
// voucher-detail badges, and the dimension P&L report light up in the
// sandbox. Mirror columns derived via lineDimensionColumns — never set
// independently of the dimensions map.
// sandbox. cost_center/project are GENERATED from the bag since the PR9
// cutover — writing them explicitly would error.
const revenueDims = { '1': 'BUTIK', '6': 'P001' }
const { error: jelError } = await supabase
.from('journal_entry_lines')
@@ -491,7 +490,6 @@ export async function POST(request: Request) {
credit_amount: 15000,
sort_order: 1,
dimensions: revenueDims,
...lineDimensionColumns(revenueDims),
},
{
journal_entry_id: je1.id,