Primary Database
Source of truth untuk write, versioning, relationship, idempotency, policy, dan ledger.
Strong consistencyDesain fleksibel untuk perubahan bisnis: JSON sebagai write model evolutif, Manticore sebagai read/search projection, dan tabel typed untuk integritas transaksi serta komisi.
Source of truth untuk write, versioning, relationship, idempotency, policy, dan ledger.
Strong consistencyProfil, role, capability, requirement, dan konfigurasi sparse yang dapat berkembang tanpa DDL rutin.
Flexible schemaProjection terdenormalisasi untuk pencarian, filter, CMS, dan dashboard. Disposable dan dapat di-reindex.
Eventually consistentMasterdata, AWB, dan Billing tetap menjadi pemilik data masing-masing. S&DP menyimpan external ID.
No authoritative duplicationOwnership per service dan akses melalui API/event.
Role taxonomy dan default capability, bukan table-per-subclass.
Behavior dipilih berdasarkan capability, workflow, dan policy.
Satu party dapat menerima banyak role dan capability tanpa menggandakan identity.
v3.0.0 — Snowflake 64-bit Identifier Migration: all internal PK/FK/cross-reference IDs are application-generated signed BIGINT Snowflakes for direct Manticore document-ID compatibility. Layout: 41-bit milliseconds since 2025-01-01, 10-bit worker, 12-bit sequence; valid through 2039-09-07. JSON APIs use decimal strings and JavaScript BigInt, never Number. ERP financial scope is constrained by the current PRD/BRD evidence shown in the traceability matrix.
Signed 64-bit Snowflake IDs; typed account relationships; external business IDs remain strings.
Foreign keys on non-partitioned tables; application validation documented where Aurora partitioning forbids FKs.
Idempotency, optimistic versions, transactional outbox, CDC timestamps, and keyset/batch bounds.
deleted_at is the only soft-delete truth; is_active is lifecycle/partition state only.
Audit scope: current schemas.sql, scenario-queries.sql, and partition-operations.sql. Sources: Bill Karwin's SQL Antipatterns taxonomy, AWS Aurora/MySQL guidance, internal DBE Rule-of-Thumb, and internal portable partition SOP. “Conditional” means the design is safe only when the stated application/runbook control is enforced.
| Rule / risk | Source | Status | Evidence & control |
|---|---|---|---|
| Jaywalking / multi-valued attributes | SQL Antipatterns | Pass | Account hierarchy uses typed rows in account_relationship; JSON arrays are bounded profile/capability documents, not relational FK lists. |
| Polymorphic association | SQL Antipatterns | Documented exception | Core domain links are typed. outbox_event.aggregate_type + aggregate_id remains an intentional polymorphic integration envelope; producers enforce a closed aggregate-type registry and validate ownership. |
| EAV / metadata tribbles | SQL Antipatterns | Pass | Stable invariants are typed; sparse profile/policy payloads use bounded versioned JSON. Search fields are projected to Manticore, avoiding JSON scans on Aurora. |
| Naive tree / hierarchy | SQL Antipatterns | Pass | MP-to-mitra relations are adjacency edges with validity and status; no comma-separated ancestry path. |
| Missing PK/FK, duplicate rows | SQL Antipatterns + MySQL | Documented exception | Non-partitioned tables use PK/FK. Partitioned tables include partition columns in every PK/unique key; Aurora's partition/FK restriction requires same-service validation plus dedup keys. |
| Random/oversized identifier mismatch | Manticore + client interoperability | Pass | Application-generated signed BIGINT Snowflakes map directly to Manticore document IDs; JSON transports decimal strings. |
| Rounding / money errors | SQL Antipatterns + PRD | Pass | Amounts use DECIMAL(19,4), never float; ledger pins the policy version and immutable calculation facts. |
| Implicit columns / ambiguous joins | DBE RoT | Pass | No SELECT *; examples use explicit projection and explicit INNER JOIN. |
| Database wall-clock predicate | DBE RoT | Pass | UTC cutoffs are application-bound literals/parameters; no NOW() or CURRENT_TIMESTAMP() in WHERE. |
| Unbounded reads / oversized IN | DBE RoT | Pass | Relay walkthrough uses an ordered LIMIT 10 workset, within the maximum batch/IN bound of 100; cursor pagination is required and broad reports are offloaded. |
| Soft-delete state duplication | DBE convention | Pass | deleted_at alone means deletion. is_active means ongoing/final lifecycle and never means deleted. |
| CDC precision | DBE / DEH | Pass | created_at/updated_at use microseconds; updated_at has ON UPDATE CURRENT_TIMESTAMP(6) and an ETL index. |
| Partition portability and pruning | Partition SOP | Pass | LIST COLUMNS(partition_key,is_active), YYYYMM, explicit active/inactive pairs, no catch-all, and predicate examples include both columns. |
| Future partition / retention safety | Partition SOP | Pass | Runbook creates both next-month partitions before opening; archives first and drops only the inactive partition. |
| Partition row movement | Partition SOP | Pass | Settlement and outbox publish examples atomically bind updated_at, recompute partition_key, change is_active, and use the previous composite locator. |
| Outbox concurrency / poison payloads | Messaging best practice | Pass | FOR UPDATE SKIP LOCKED LIMIT 100, dedup key, aggregate version, receipt acknowledgement; only error code/reference stored, not raw logs. |
Review conclusion: executable SQL gaps found by independent static review were corrected: explicit seed columns, lookup CDC fields/indexes, single deletion state, policy validity, bounded outbox workset, and atomic partition movement. Remaining exceptions are documented: partitioned-table FKs and the polymorphic outbox envelope. Application controls still require implementation verification.
Source calibration: the authoritative S&DP PRD is ClickUp doc 8crmjdc-130258, current page 8crmjdc-3278538 (predecessor 8crmjdc-3073858). The supplied workspace contains no separately identified S&DP BRD; therefore BRD coverage below is marked Not supplied, never inferred. Rows marked Partial show the bounded v3 ERD support and name the missing product aggregate.
| Use case / source | Actors & system | ERD mapping | Coverage |
|---|---|---|---|
| PRD — public self-registration, referral/induk, unique agent identity | Calon Agen, MP, S&DP | account; account_relationship(REFERRED_BY / MANAGED_BY); outbox_event | Covered |
| PRD — CMS registration by Sales/RM and creator attribution | Sales/RM, S&DP | account.created_by_account_id; role/profile JSON; activity_event | Covered |
| PRD — RN document review → S&P location approval → advanced-data activation | Retail Network, S&P, candidate | account.status/version; bounded verification JSON; activity_event; outbox_event | Partial — approver attribution and state transition are executable; dedicated approval-decision/reason aggregate is not modeled |
| PRD — active agent sync to Masterdata Customer/Partner/Gerai Partner | S&DP, Masterdata Core, OPS | external_account_id; account role JSON; outbox_event/projection_receipt | Covered as integration; Masterdata owns replicas |
| PRD — receive/scan existing AWB and idempotently mark DROP PARTNER | Agent, AWB Service | operational_transaction; activity_event; external AWB ID; idempotency key | Covered |
| PRD — create resi and retain immutable commission inputs | Agent, AWB/Tariff/SPK | operational_transaction typed money + JSON facts; outbox_event | Partial — S&DP stores transaction reference/facts; sender, recipient, package and tariff remain owning-service data |
| PRD — pickup/drop-off handover, cutoff and proof | Agent, SPK/Hub | activity_event(AWB_HANDED_OVER); transaction facts; external attachment reference; outbox | Partial — schedule/evidence lifecycle is external; no local handover aggregate |
| PRD — configure effective commission/bonus scheme | RN, Head Commercial, MP | policy_document version, validity, status and JSON definition; audit event | Covered for calculation policy |
| PRD — threshold approval of commission/bonus scheme | RN, Head Commercial | Policy status/version + approval activity_event | Partial — decision/reason/threshold snapshot needs a dedicated approval aggregate before implementation |
| PRD — commission estimate/final, pending/ready, reconciliation and correction | Agent, S&DP, Finance | activity_event + pinned policy_document + ledger_entry; reversal as a new ledger row | Covered |
| PRD — AP settlement / payout and Finance references | Billing, ERP AP, Finance | Ledger subledger/cost/GL/period; billing_external_id; AP voucher; journal batch; settlement state | Covered as AP integration; AP voucher and journal batch are exercised |
| PRD — Manticore search/list/dashboard projection | CMS, Agent Dashboard | outbox_event → projection_receipt → Manticore; authoritative detail remains Aurora/owner | Covered |
| PRD — role-specific menus, Owner/Admin provisioning, wallet/rekening | Owner, Admin, CMS roles | Account role/capability JSON and external IAM/payment references only | Partial — no credential, RBAC policy, wallet or bank-account aggregate in this ERD |
| PRD — KPI, tiering, penalty, claim charging | Agent, CMS, Claim/Finance | Immutable operational/activity facts can feed derived projections | Partial/out of bounded v3 scope — no authoritative KPI/tier/claim aggregate |
| BRD use cases | Business stakeholders | No source mapping possible | Not supplied — attach the S&DP BRD doc/page ID to complete bidirectional traceability |
Gap decision: this matrix does not invent missing tables. Approval history, handover evidence, wallet, KPI/tier, and claim aggregates require confirmed ownership/lifecycle from source requirements before extending the ERD.
Read Manticore
Text search, filters, cursor pagination, and non-authoritative dashboard projection.
Read Aurora / owning service
Hydrate external identity, AWB, billing, and ERP data from the owning service when needed.
Never decide from search index
Evaluate Aurora state and versioned policy.
Never decide from search index
Use typed ledger fields, pinned policy, ERP AP voucher, and Billing callback state.
| Data | Source of Truth | Stored by S&DP v3.0.0 |
|---|---|---|
| Customer/account identity, PIC, contact | Masterdata | external_account_id + bounded profile JSON |
| Address, region, attachments | Masterdata / Attachment | External IDs + S&DP verification status only |
| Agent role, hierarchy, lifecycle | S&DP | account + account_relationship |
| AWB, sender, recipient, delivery status | AWB Service | external_transaction_id + immutable commission facts |
| Commission policy and accrual/reversal | S&DP | Versioned policy_document + typed ledger_entry |
| ERP AP vendor/subledger & cost center mapping | ERP / Finance Master | subledger_code, cost_center_code, branch_code reference snapshots |
| ERP GL account mapping | ERP / Finance Master | debit_account_code, credit_account_code, financial_period snapshot used by accrual |
| Invoice, payment, settlement | Billing / ERP AP | billing_external_id, ap_voucher_number, journal_batch_id, settlement state |
| Internal 64-bit identifiers | S&DP application / shared Snowflake allocator | Signed BIGINT; same value used as Manticore document ID |
| Search projection | Derived | Manticore; rebuildable and non-authoritative |
Tambah role, capability, document requirement, workflow, dan policy sebagai data.
Zero DDLBuat policy version baru dengan validity period. Ledger lama tetap menunjuk versi lama.
Zero DDLTambah field JSON, naikkan schema version, validasi dengan JSON Schema, lalu update projection.
Zero DDL primary*Jika belum tersedia sebagai invariant typed, lakukan migration dan update accounting rules.
DDL layakTambah relationship type dan metadata selama constraint existing cukup.
Zero DDLTambah pemetaan debit/credit account code melalui policy/configuration yang tervalidasi; kolom typed v3.0.0 tetap.
Zero DDLDimensi yang belum memiliki invariant typed tetap memerlukan review model dan migration; jangan dipaksakan ke JSON.
DDL layak* Perubahan searchable attribute mungkin tetap memerlukan perubahan konfigurasi/index Manticore. Zero-DDL database tidak berarti zero-governance.
Alur tulis selalu selesai di AWS Aurora MySQL 8.0 terlebih dahulu. Manticore diproyeksikan asinkron dan tidak dipakai untuk keputusan approval atau uang.
Self-service stores a typed REFERRED_BY edge. Validated ERP vendor and cost-center references populate subledger_code and cost_center_code when applicable.
Touches: account, account_relationship, outbox_event
Document status lives in bounded JSON; approval audit is an idempotent typed activity.
Touches: account, activity_event, outbox_event
AWB activity is idempotent. Accrual pins activity/policy and records subledger, branch/cost dimension, financial period, and GL debit/credit mappings.
Touches: operational_transaction, activity_event, policy_document, ledger_entry
Billing callback settles the accrual and stores ERP AP voucher / journal batch references. Projection receipts make consumer processing auditable.
Touches: ledger_entry, outbox_event, projection_receipt
See USER-JOURNEYS.md for six complete sequence diagrams and exact columns touched.
The complete executable runbook is partition-operations.sql: application computes partition_key=YYYYMM(updated_at), DBE pre-creates one _active and one _inactive LIST COLUMNS partition per month, and retention archives then drops only inactive partitions.
Snippets are abbreviated for reading. The transactionally complete, rerunnable examples are in scenario-queries.sql; DDL and helper functions are in schemas.sql.
Read-only, exact release artifacts embedded in this explorer. Choose the Aurora MySQL DDL, executable walkthrough, or partition runbook. Use the copy button to copy the displayed artifact.
-- S&DP Counter-Design Schema v3.0.0 (AWS Aurora MySQL 8.0 - ERP Subledger & Financial Dimension Alignment)
-- Architectural Refinement: Resolves Generic Record / Polymorphic Association Antipattern.
-- Features: Account (Party/Identity Model), Operational Transaction (Explicit Logistics Aggregate),
-- Application-generated signed 64-bit Snowflake IDs (BIGINT; Manticore-compatible), Zero-DDL Lookup Tables,
-- Monthly LIST COLUMNS partitioning (partition_key + is_active), DEH Snowflake ETL CDC timestamps (created_at, updated_at), and soft delete (deleted_at).
-- Run with: mysql -u root -p < schemas.sql
SET FOREIGN_KEY_CHECKS = 0;
-- 1. Domain Lookup Tables (Zero-DDL Extensions)
CREATE TABLE IF NOT EXISTS account_type_lookup (
code varchar(64) PRIMARY KEY,
description varchar(255) NOT NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
KEY idx_account_type_lookup_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS account_status_lookup (
code varchar(64) PRIMARY KEY,
description varchar(255) NOT NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
KEY idx_account_status_lookup_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS relationship_type_lookup (
code varchar(64) PRIMARY KEY,
description varchar(255) NOT NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
KEY idx_relationship_type_lookup_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS activity_type_lookup (
code varchar(64) PRIMARY KEY,
description varchar(255) NOT NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
KEY idx_activity_type_lookup_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS policy_status_lookup (
code varchar(64) PRIMARY KEY,
description varchar(255) NOT NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
KEY idx_policy_status_lookup_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS ledger_status_lookup (
code varchar(64) PRIMARY KEY,
description varchar(255) NOT NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
KEY idx_ledger_status_lookup_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS ledger_entry_type_lookup (
code varchar(64) PRIMARY KEY,
description varchar(255) NOT NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
KEY idx_ledger_entry_type_lookup_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT IGNORE INTO account_type_lookup (code, description) VALUES ('AGENT','SNDP Agent/Mitra'),('PARTNER','Managing Partner'),('CMS_USER','Internal Staff');
INSERT IGNORE INTO account_status_lookup (code, description) VALUES ('DRAFT','Draft'),('PENDING','Pending Verification'),('ACTIVE','Active'),('SUSPENDED','Suspended'),('REJECTED','Rejected');
INSERT IGNORE INTO relationship_type_lookup (code, description) VALUES ('REFERRED_BY','Referral Relationship'),('MANAGED_BY','Hierarchy Management'),('CMS_CREATED_BY','Creator Attribution');
INSERT IGNORE INTO activity_type_lookup (code, description) VALUES ('AWB_CREATED','AWB Origin Creation'),('AWB_RECEIVED','Drop Point Handover Receive'),('AWB_HANDED_OVER','Courier Pickup Handover'),('DOCUMENT_VERIFIED','Verification Action'),('AGENT_APPROVED','Approval Action');
INSERT IGNORE INTO policy_status_lookup (code, description) VALUES ('DRAFT','Draft Policy'),('ACTIVE','Active Production Policy'),('RETIRED','Retired Policy');
INSERT IGNORE INTO ledger_status_lookup (code, description) VALUES ('PENDING','Pending Accrual'),('POSTED','Posted Accrual'),('SETTLED','Settled Payment'),('REVERSED','Reversed Accrual');
INSERT IGNORE INTO ledger_entry_type_lookup (code, description) VALUES ('COMMISSION_ACCRUAL','Earned Commission Accrual'),('COMMISSION_REVERSAL','Commission Reversal'),('SETTLEMENT','Settlement Payment');
SET FOREIGN_KEY_CHECKS = 1;
-- 2. Snowflake ID Contract (application-generated; database never allocates IDs)
-- SQL/API representation: signed 64-bit integer. JSON APIs MUST serialize IDs as decimal strings;
-- JavaScript consumers parse with BigInt, never Number, to avoid IEEE-754 precision loss.
-- Bit layout: 41-bit milliseconds since custom epoch 2025-01-01T00:00:00Z, 10-bit worker_id,
-- and 12-bit per-millisecond sequence. Range: 1..9223372036854775807 (sign bit is always zero).
-- Valid timestamp window: 2025-01-01T00:00:00Z through 2039-09-07T15:47:35.551Z.
-- Capacity: 1,024 workers and 4,096 IDs/worker/ms. Worker IDs require a leased unique allocation.
-- On sequence exhaustion, wait for the next millisecond. On clock rollback, stop issuing IDs until
-- the clock catches up or fail closed; never generate from an earlier millisecond.
-- 3. Identity & Party Domain (Fowler Party Pattern: Account & Account Relationship)
CREATE TABLE IF NOT EXISTS account (
account_id bigint NOT NULL PRIMARY KEY CHECK (account_id > 0),
account_type varchar(64) NOT NULL, -- AGENT, PARTNER, CMS_USER
external_account_id varchar(128) NOT NULL, -- External IAM / Masterdata ID
subledger_code varchar(64) NULL, -- ERP AP Subledger Vendor Code (e.g. VEND-AG-9021)
cost_center_code varchar(64) NULL, -- ERP Cost Center Attribution (e.g. CC-HUB-3914)
created_by_account_id bigint NULL CHECK (created_by_account_id IS NULL OR created_by_account_id > 0), -- FK Creator Attribution
schema_version int NOT NULL DEFAULT 1 CHECK (schema_version > 0),
document json NOT NULL, -- Canonical profile, capabilities & documents
status varchar(64) NOT NULL DEFAULT 'PENDING',
is_active tinyint(1) NOT NULL DEFAULT 1,
version bigint NOT NULL DEFAULT 1 CHECK (version > 0),
deleted_at datetime(6) NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
UNIQUE KEY uq_account_type_ext (account_type, external_account_id),
CONSTRAINT fk_account_account_type FOREIGN KEY (account_type) REFERENCES account_type_lookup(code),
CONSTRAINT fk_account_account_status FOREIGN KEY (status) REFERENCES account_status_lookup(code),
CONSTRAINT fk_account_created_by FOREIGN KEY (created_by_account_id) REFERENCES account(account_id),
KEY idx_account_status (account_type, status, deleted_at),
KEY idx_account_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS account_relationship (
relationship_id bigint NOT NULL PRIMARY KEY CHECK (relationship_id > 0),
source_account_id bigint NOT NULL CHECK (source_account_id > 0),
target_account_id bigint NOT NULL CHECK (target_account_id > 0),
relationship_type varchar(64) NOT NULL,
valid_from datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
valid_to datetime(6) NULL,
status varchar(64) NOT NULL DEFAULT 'ACTIVE',
is_active tinyint(1) NOT NULL DEFAULT 1,
metadata json NOT NULL,
deleted_at datetime(6) NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
CONSTRAINT fk_account_rel_source FOREIGN KEY (source_account_id) REFERENCES account(account_id),
CONSTRAINT fk_account_rel_target FOREIGN KEY (target_account_id) REFERENCES account(account_id),
CONSTRAINT fk_account_rel_type FOREIGN KEY (relationship_type) REFERENCES relationship_type_lookup(code),
CONSTRAINT fk_account_rel_account_status FOREIGN KEY (status) REFERENCES account_status_lookup(code),
KEY idx_account_rel_target (target_account_id, relationship_type, status, deleted_at),
KEY idx_account_rel_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 4. Operational Logistics Aggregate (ERP Subledger & Financial Dimensions + LIST COLUMNS Partitioning)
-- IMPORTANT: The partitioned operational tables intentionally do NOT have foreign keys.
-- MySQL/MariaDB disallow FKs on partitioned InnoDB tables; application writes must validate
-- referenced IDs and lookup codes in the same service transaction before the INSERT/UPDATE.
-- partition_key is application-maintained from updated_at using YYYYMM (e.g. 202608).
-- Every update MUST recompute partition_key and is_active. This can move a row between partitions.
CREATE TABLE IF NOT EXISTS operational_transaction (
transaction_id bigint NOT NULL CHECK (transaction_id > 0),
partition_key bigint NOT NULL, -- app-maintained YYYYMM derived from updated_at
is_active tinyint NOT NULL DEFAULT 1, -- 1 = ongoing, 0 = settled/final
partition_created_time datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
actor_account_id bigint NOT NULL CHECK (actor_account_id > 0), -- Account performing transaction
transaction_type varchar(64) NOT NULL, -- AWB_CREATED, AWB_HANDED_OVER
branch_code varchar(64) NULL, -- ERP Branch/Hub Cost Center Dimension
financial_period int NOT NULL, -- ERP Financial Period (YYYYMM)
external_transaction_id varchar(128) NOT NULL, -- AWB / Order ID
idempotency_key varchar(128) NOT NULL,
amount decimal(19,4) NOT NULL DEFAULT 0.0000 CHECK (amount >= 0),
currency char(3) NOT NULL DEFAULT 'IDR',
status varchar(64) NOT NULL DEFAULT 'PENDING',
facts json NOT NULL,
occurred_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
settled_at datetime(6) NULL,
deleted_at datetime(6) NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (transaction_id, partition_key, is_active),
UNIQUE KEY uq_op_txn_idempotency_partition (idempotency_key, partition_key, is_active),
KEY idx_op_txn_part_active (partition_key, is_active, status),
KEY idx_op_txn_actor_time (actor_account_id, occurred_at),
KEY idx_op_txn_ext_id (external_transaction_id, transaction_type),
KEY idx_op_txn_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PARTITION BY LIST COLUMNS (partition_key, is_active) (
PARTITION p202608_active VALUES IN ((202608, 1)),
PARTITION p202608_inactive VALUES IN ((202608, 0)),
PARTITION p202609_active VALUES IN ((202609, 1)),
PARTITION p202609_inactive VALUES IN ((202609, 0)),
PARTITION p202610_active VALUES IN ((202610, 1)),
PARTITION p202610_inactive VALUES IN ((202610, 0))
);
-- 5. Audit Activity Log (Immutable Operational Events)
CREATE TABLE IF NOT EXISTS activity_event (
activity_id bigint NOT NULL CHECK (activity_id > 0),
partition_key bigint NOT NULL, -- app-maintained YYYYMM derived from updated_at
is_active tinyint NOT NULL DEFAULT 1,
partition_created_time datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
actor_account_id bigint NOT NULL CHECK (actor_account_id > 0),
activity_type varchar(64) NOT NULL,
external_transaction_id varchar(128) NOT NULL,
idempotency_key varchar(128) NOT NULL,
facts json NOT NULL,
occurred_at datetime(6) NOT NULL,
recorded_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
deleted_at datetime(6) NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (activity_id, partition_key, is_active),
UNIQUE KEY uq_activity_idempotency_partition (idempotency_key, partition_key, is_active),
UNIQUE KEY uq_activity_ext_partition (activity_type, external_transaction_id, partition_key, is_active),
KEY idx_act_actor_time (actor_account_id, occurred_at),
KEY idx_act_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PARTITION BY LIST COLUMNS (partition_key, is_active) (
PARTITION p202608_active VALUES IN ((202608, 1)),
PARTITION p202608_inactive VALUES IN ((202608, 0)),
PARTITION p202609_active VALUES IN ((202609, 1)),
PARTITION p202609_inactive VALUES IN ((202609, 0)),
PARTITION p202610_active VALUES IN ((202610, 1)),
PARTITION p202610_inactive VALUES IN ((202610, 0))
);
-- 6. Financial Ledger & Outbox Aggregates
CREATE TABLE IF NOT EXISTS policy_document (
policy_id bigint NOT NULL PRIMARY KEY CHECK (policy_id > 0),
policy_code varchar(128) NOT NULL,
policy_version int NOT NULL CHECK (policy_version > 0),
definition json NOT NULL,
valid_from datetime(6) NOT NULL,
valid_to datetime(6) NULL,
status varchar(64) NOT NULL DEFAULT 'DRAFT',
is_active tinyint(1) NOT NULL DEFAULT 1,
deleted_at datetime(6) NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
UNIQUE KEY uq_policy_code_ver (policy_code, policy_version),
CONSTRAINT fk_policy_status FOREIGN KEY (status) REFERENCES policy_status_lookup(code),
KEY idx_policy_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS ledger_entry (
ledger_entry_id bigint NOT NULL CHECK (ledger_entry_id > 0),
partition_key bigint NOT NULL, -- app-maintained YYYYMM derived from updated_at
is_active tinyint NOT NULL DEFAULT 1,
partition_created_time datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
activity_id bigint NOT NULL CHECK (activity_id > 0),
policy_id bigint NOT NULL CHECK (policy_id > 0),
accrual_dedup_key binary(32) NOT NULL, -- SHA-256 computed by application from business identity,
beneficiary_account_id bigint NOT NULL CHECK (beneficiary_account_id > 0),
subledger_code varchar(64) NOT NULL, -- ERP AP Subledger Vendor/Partner Code
debit_account_code varchar(64) NOT NULL, -- ERP GL Debit Account (e.g. 6100-COMMISSION-EXPENSE)
credit_account_code varchar(64) NOT NULL, -- ERP GL Credit Account (e.g. 2100-PARTNER-PAYABLE)
financial_period int NOT NULL, -- ERP Accounting Period (YYYYMM)
ap_voucher_number varchar(128) NULL, -- ERP Accounts Payable Voucher Number (APV)
journal_batch_id bigint NULL CHECK (journal_batch_id IS NULL OR journal_batch_id > 0), -- ERP Journal Batch ID for GL Posting
amount decimal(19,4) NOT NULL CHECK (amount >= 0),
currency char(3) NOT NULL,
entry_type varchar(64) NOT NULL,
status varchar(64) NOT NULL DEFAULT 'PENDING',
billing_external_id varchar(128) NULL,
metadata json NOT NULL,
settled_at datetime(6) NULL,
deleted_at datetime(6) NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (ledger_entry_id, partition_key, is_active),
UNIQUE KEY uq_ledger_accrual_partition (accrual_dedup_key, partition_key, is_active),
UNIQUE KEY uq_billing_ext_partition (billing_external_id, partition_key, is_active),
KEY idx_ledger_beneficiary (beneficiary_account_id, status, created_at),
KEY idx_ledger_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PARTITION BY LIST COLUMNS (partition_key, is_active) (
PARTITION p202608_active VALUES IN ((202608, 1)),
PARTITION p202608_inactive VALUES IN ((202608, 0)),
PARTITION p202609_active VALUES IN ((202609, 1)),
PARTITION p202609_inactive VALUES IN ((202609, 0)),
PARTITION p202610_active VALUES IN ((202610, 1)),
PARTITION p202610_inactive VALUES IN ((202610, 0))
);
CREATE TABLE IF NOT EXISTS outbox_event (
event_id bigint NOT NULL CHECK (event_id > 0),
partition_key bigint NOT NULL, -- app-maintained YYYYMM derived from updated_at
is_active tinyint NOT NULL DEFAULT 1,
partition_created_time datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
aggregate_type varchar(64) NOT NULL, -- controlled integration-envelope discriminator
aggregate_id bigint NOT NULL CHECK (aggregate_id > 0), -- polymorphic envelope reference; service validates supported type/ID pairs
outbox_dedup_key binary(32) NOT NULL, -- SHA-256 computed by application from aggregate identity + event type,
aggregate_version bigint NOT NULL CHECK (aggregate_version > 0),
event_type varchar(128) NOT NULL,
payload json NOT NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
available_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
published_at datetime(6) NULL,
attempt_count int NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
last_error_code varchar(64) NULL,
last_error_ref varchar(512) NULL, -- external observability/S3 reference; no raw logs in RDBMS,
deleted_at datetime(6) NULL,
PRIMARY KEY (event_id, partition_key, is_active),
UNIQUE KEY uq_outbox_agg_partition (outbox_dedup_key, partition_key, is_active),
KEY idx_outbox_unpublished (is_active, available_at, created_at),
KEY idx_outbox_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PARTITION BY LIST COLUMNS (partition_key, is_active) (
PARTITION p202608_active VALUES IN ((202608, 1)),
PARTITION p202608_inactive VALUES IN ((202608, 0)),
PARTITION p202609_active VALUES IN ((202609, 1)),
PARTITION p202609_inactive VALUES IN ((202609, 0)),
PARTITION p202610_active VALUES IN ((202610, 1)),
PARTITION p202610_inactive VALUES IN ((202610, 0))
);
CREATE TABLE IF NOT EXISTS projection_receipt (
projection_name varchar(64) NOT NULL,
event_id bigint NOT NULL CHECK (event_id > 0),
aggregate_id bigint NOT NULL CHECK (aggregate_id > 0),
aggregate_version bigint NOT NULL,
processed_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
deleted_at datetime(6) NULL,
created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (projection_name, event_id),
-- Event existence is validated by the relay because outbox_event is partitioned.
KEY idx_proj_version (projection_name, aggregate_id, aggregate_version),
KEY idx_proj_deh_etl (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- v3.0.0 Application Partition Contract (introduced in v2.2.0)
-- 1) Before INSERT/UPDATE derive partition_key = EXTRACT(YEAR_MONTH FROM updated_at in UTC).
-- 2) Keep is_active=1 until final settlement; set it to 0 atomically with settlement.
-- 3) Every update supplies both partition_key and is_active, allowing the engine to move the row.
-- 4) Create the next month active/inactive pair before the month opens; no catch-all partition is used.
-- 5) Retention archives first, then drops only pYYYYMM_inactive with ALTER TABLE ... DROP PARTITION.
-- v3.0.0 DB Rule-of-Thumb Contract (introduced in v2.3.0)
-- deleted_at is the single soft-delete source of truth: NULL = live; non-NULL = deleted.
-- Deliberately no is_deleted column: a boolean would duplicate deleted_at and permit inconsistent state.
-- Nullable columns intentionally omit DEFAULT NULL; NULL is implicit in MySQL.
-- Application must bind UTC DATETIME(6) values for WHERE cutoffs; do not use NOW()/CURRENT_TIMESTAMP() in WHERE.
-- Application must use explicit projections (never SELECT *) and an explicit INNER JOIN/LEFT JOIN keyword.
-- Application caps IN lists and worker batches at 100 and uses cursor/keyset pagination for large result sets.
-- Reporting and broad historical/cron workloads run in the analytics platform, not on Aurora operational tables.
-- Partitioned-table cross-reference validation remains service-side due the MySQL/MariaDB FK limitation.
-- outbox_event aggregate_type + aggregate_id is an intentional polymorphic integration-envelope exception;
-- producers validate a closed aggregate-type registry and aggregate ownership before insertion.
-- accrual_dedup_key = SHA-256(activity_id | policy_id | beneficiary_account_id | entry_type), computed by application.
-- outbox_dedup_key = SHA-256(aggregate_type | aggregate_id | aggregate_version | event_type), computed by application.
-- Raw error payloads/logs are stored in observability/S3; outbox keeps only code + external reference.
-- v3.0.0 PRD/BRD-scoped ERP Financial Reference Contract
-- ERP/Finance Master owns vendor/subledger, cost center/branch, GL account, and period validity.
-- S&DP stores validated references/snapshots required to explain commission accrual and settlement.
-- No generic ERP inventory, procurement, HR, manufacturing, sales-order, or tax aggregates are modeled.
-- v3.0.0 Snowflake Identifier Contract
-- All internal PK/FK/cross-reference *_id values are application-generated signed 64-bit Snowflake IDs.
-- BIGINT UNSIGNED is forbidden because Manticore and several clients use signed int64.
-- External business identifiers remain varchar; SHA-256 deduplication keys remain binary(32).
-- journal_batch_id is an internal normalized Snowflake reference; opaque ERP batch codes belong in varchar metadata.
-- Executable Walkthrough v3.0.0 (ERP Subledger & AP Settlement Walkthrough) (DB Rule-of-Thumb + LIST COLUMNS Partitioning on AWS Aurora MySQL 8.0)
-- Run schemas.sql first, then:
-- mysql -u root -p < scenario-queries.sql
USE sndp;
-- 0. Seed Managing Partner Account & Commission Policy
INSERT IGNORE INTO account(account_id, account_type, external_account_id, subledger_code, cost_center_code, status, document)
VALUES (215574410218721281, 'PARTNER', 'MD-MP-001', 'VEND-MP-001', 'CC-HQ-01', 'ACTIVE',
'{"roles":[{"code":"MANAGING_PARTNER"}],"properties":{"referralCode":"MP-001"}}');
INSERT IGNORE INTO account(account_id, account_type, external_account_id, status, document)
VALUES (215574410218721286, 'CMS_USER', 'IAM-RN-001', 'ACTIVE',
'{"roles":[{"code":"RETAIL_NETWORK_APPROVER"}],"properties":{"employeeId":"EMP-RN-001"}}');
INSERT IGNORE INTO policy_document(policy_id, policy_code, policy_version, definition, valid_from, status)
VALUES (215574410218721282, 'AWB_HANDOVER_COMMISSION', 1,
'{"basis":"flat","amount":7500.00,"currency":"IDR","activityType":"AWB_HANDED_OVER"}',
'2026-08-01 00:00:00.000000', 'ACTIVE');
-- 1. Self-register Agent Account
START TRANSACTION;
INSERT IGNORE INTO account(account_id, account_type, external_account_id, subledger_code, cost_center_code, status, document)
VALUES (215574410218721283, 'AGENT', 'MD-AGENT-001', 'VEND-AG-9001', 'CC-CGK-01', 'PENDING',
'{"identity":{"customerAccountId":"MD-AGENT-001"},"roles":[{"code":"MITRA_SNDP"}],"capabilities":[],"properties":{"registrationChannel":"SELF","referralCode":"MP-001"},"documents":[]}');
INSERT IGNORE INTO account_relationship(relationship_id, source_account_id, target_account_id, relationship_type, metadata)
VALUES (215574410218725376, 215574410218721283, 215574410218721281, 'REFERRED_BY', '{}');
INSERT IGNORE INTO outbox_event (event_id, partition_key, aggregate_type, aggregate_id, outbox_dedup_key, aggregate_version, event_type, payload)
VALUES (215574410218725377, 202608, 'ACCOUNT', 215574410218721283, UNHEX(SHA2('ACCOUNT|019ff17e-0000-7000-8000-000000000003|1|AgentRegistrationSubmitted', 256)), 1, 'AgentRegistrationSubmitted', '{"channel":"SELF"}');
COMMIT;
-- 2. CMS Registration Account (Attributing created_by_account_id FK)
START TRANSACTION;
INSERT IGNORE INTO account(account_id, account_type, external_account_id, subledger_code, cost_center_code, created_by_account_id, status, document)
VALUES (215574410218721284, 'AGENT', 'MD-AGENT-002', 'VEND-AG-9002', 'CC-CGK-01', 215574410218721286, 'PENDING',
'{"identity":{"customerAccountId":"MD-AGENT-002"},"roles":[{"code":"MITRA_SNDP"}],"properties":{"registrationChannel":"CMS"},"documents":[]}');
INSERT IGNORE INTO outbox_event (event_id, partition_key, aggregate_type, aggregate_id, outbox_dedup_key, aggregate_version, event_type, payload)
VALUES (215574410218725378, 202608, 'ACCOUNT', 215574410218721284, UNHEX(SHA2('ACCOUNT|019ff17e-0000-7000-8000-000000000004|1|AgentRegistrationSubmitted', 256)), 1, 'AgentRegistrationSubmitted', '{"channel":"CMS"}');
COMMIT;
-- 3. Verify Document & Approve Agent Account
START TRANSACTION;
UPDATE account
SET document=JSON_SET(document, '$.documents', JSON_ARRAY(JSON_OBJECT('type', 'NIB', 'attachmentId', 'ATT-9001', 'verificationStatus', 'VERIFIED'))),
status='ACTIVE', version=version+1
WHERE account_id=215574410218721283 AND status='PENDING';
INSERT IGNORE INTO activity_event(activity_id, partition_key, actor_account_id, activity_type, external_transaction_id, idempotency_key, facts, occurred_at)
VALUES (215574410218725379, 202608, 215574410218721286, 'DOCUMENT_VERIFIED', 'ATT-9001', 'verify:ATT-9001', '{"documentType":"NIB"}', '2026-08-11 09:00:00.000000');
INSERT IGNORE INTO activity_event(activity_id, partition_key, actor_account_id, activity_type, external_transaction_id, idempotency_key, facts, occurred_at)
VALUES (215574410218725380, 202608, 215574410218721286, 'AGENT_APPROVED', 'APP-AGENT-001', 'approve:APP-AGENT-001', '{}', '2026-08-11 09:01:00.000000');
INSERT IGNORE INTO outbox_event (event_id, partition_key, aggregate_type, aggregate_id, outbox_dedup_key, aggregate_version, event_type, payload)
VALUES (215574410218725381, 202608, 'ACCOUNT', 215574410218721283, UNHEX(SHA2('ACCOUNT|019ff17e-0000-7000-8000-000000000003|2|AgentApproved', 256)), 2, 'AgentApproved', '{"status":"ACTIVE"}');
COMMIT;
-- 4. Operational logistics: application derives partition_key from updated_at and keeps is_active current
INSERT IGNORE INTO activity_event(activity_id, partition_key, actor_account_id, activity_type, external_transaction_id, idempotency_key, facts, occurred_at)
VALUES (215574410218725382, 202608, 215574410218721283, 'AWB_CREATED', 'AWB-2026-001', 'awb:AWB-2026-001:create', '{"service":"REG","weightGrams":1200}', '2026-08-11 10:00:00.000000');
INSERT IGNORE INTO operational_transaction(transaction_id, partition_key, actor_account_id, transaction_type, branch_code, financial_period, external_transaction_id, idempotency_key, facts, occurred_at, is_active)
VALUES (215574410218725383, 202608, 215574410218721283, 'AWB_CREATED', 'HUB-CGK-01', 202608, 'AWB-2026-001', 'awb:AWB-2026-001:create', '{"service":"REG","weightGrams":1200}', '2026-08-11 10:00:00.000000', 1);
-- Commission eligibility is earned by the handover event, not by AWB creation.
INSERT IGNORE INTO activity_event(activity_id, partition_key, actor_account_id, activity_type, external_transaction_id, idempotency_key, facts, occurred_at)
VALUES (215574410218725384, 202608, 215574410218721283, 'AWB_HANDED_OVER', 'AWB-2026-001', 'awb:AWB-2026-001:handover', '{"service":"REG","weightGrams":1200,"branchCode":"HUB-CGK-01"}', '2026-08-11 12:00:00.000000');
-- 5. Commission accrual: the application calculates accrual_dedup_key; SHA2 here is test-fixture only.
-- IDs are application-generated Snowflakes; production binds :accrual_dedup_key (BINARY(32)); it never applies a function to a WHERE predicate.
-- 5. Commission Accrual Calculation for Beneficiary Account
INSERT IGNORE INTO ledger_entry(ledger_entry_id, partition_key, is_active, activity_id, policy_id, accrual_dedup_key, beneficiary_account_id, subledger_code, debit_account_code, credit_account_code, financial_period, amount, currency, entry_type, status, metadata)
SELECT 215574410218725385, 202608, 1, a.activity_id, p.policy_id,
UNHEX(SHA2(CONCAT(CAST(a.activity_id AS CHAR), '|', CAST(p.policy_id AS CHAR), '|', CAST(a.actor_account_id AS CHAR), '|COMMISSION_ACCRUAL'), 256)),
a.actor_account_id, 'VEND-AG-9001', '6100-COMMISSION-EXPENSE', '2100-PARTNER-PAYABLE', 202608,
CAST(JSON_UNQUOTE(JSON_EXTRACT(p.definition, '$.amount')) AS DECIMAL(19,4)),
JSON_UNQUOTE(JSON_EXTRACT(p.definition, '$.currency')),
'COMMISSION_ACCRUAL', 'POSTED',
JSON_OBJECT('policyCode', p.policy_code, 'policyVersion', p.policy_version, 'awbId', a.external_transaction_id)
FROM activity_event AS a
INNER JOIN policy_document AS p ON p.policy_code = 'AWB_HANDOVER_COMMISSION'
AND p.status = 'ACTIVE'
AND a.occurred_at >= p.valid_from
AND (p.valid_to IS NULL OR a.occurred_at < p.valid_to)
WHERE a.activity_type = JSON_UNQUOTE(JSON_EXTRACT(p.definition, '$.activityType')) AND a.external_transaction_id = 'AWB-2026-001';
-- 6. Billing Settlement Callback for Account
UPDATE ledger_entry
SET status='SETTLED', is_active=0, partition_key=202608, updated_at='2026-08-12 08:00:00.000000',
billing_external_id='BILL-SETTLE-7788', ap_voucher_number='APV-2026-08-00788',
journal_batch_id=215574410218725386, settled_at='2026-08-12 08:00:00.000000'
WHERE ledger_entry_id=215574410218725385 AND partition_key=202608 AND is_active=1
AND entry_type='COMMISSION_ACCRUAL' AND status='POSTED' AND billing_external_id IS NULL;
SELECT CAST(ledger_entry_id AS CHAR) AS ledger_entry_id, status, is_active, subledger_code, debit_account_code, credit_account_code, ap_voucher_number, amount, currency FROM ledger_entry WHERE billing_external_id='BILL-SETTLE-7788' AND partition_key=202608;
-- 7. Outbox relay. Production binds :relay_cutoff_utc and :batch_size (<=100), both computed by application.
-- The literals here merely make this walkthrough executable; no function is evaluated in WHERE.
-- Explicit columns only; LIMIT 10 is a bounded worker batch, not OFFSET pagination.
START TRANSACTION;
CREATE TEMPORARY TABLE claimed_outbox_event (
event_id bigint NOT NULL PRIMARY KEY,
partition_key bigint NOT NULL,
previous_is_active tinyint NOT NULL
) ENGINE=MEMORY;
INSERT INTO claimed_outbox_event (event_id, partition_key, previous_is_active)
SELECT event_id, partition_key, is_active
FROM outbox_event
WHERE is_active=1 AND published_at IS NULL
AND available_at <= '2026-08-12 23:59:59.999999'
ORDER BY available_at, created_at, event_id
LIMIT 10 FOR UPDATE SKIP LOCKED;
SELECT o.event_id, o.aggregate_type, o.aggregate_id, o.aggregate_version, o.event_type, o.payload, o.available_at, o.created_at
FROM outbox_event AS o
INNER JOIN claimed_outbox_event AS c
ON c.event_id=o.event_id AND c.partition_key=o.partition_key AND c.previous_is_active=o.is_active
ORDER BY o.available_at, o.created_at, o.event_id;
INSERT IGNORE INTO projection_receipt (projection_name, event_id, aggregate_id, aggregate_version)
SELECT 'manticore-agent', o.event_id, o.aggregate_id, o.aggregate_version
FROM outbox_event AS o
INNER JOIN claimed_outbox_event AS c
ON c.event_id=o.event_id AND c.partition_key=o.partition_key AND c.previous_is_active=o.is_active;
UPDATE outbox_event AS o
INNER JOIN claimed_outbox_event AS c
ON c.event_id=o.event_id AND c.partition_key=o.partition_key AND c.previous_is_active=o.is_active
INNER JOIN projection_receipt AS r ON o.event_id=r.event_id AND r.projection_name='manticore-agent'
SET o.published_at='2026-08-12 23:59:59.999999', o.is_active=0,
o.partition_key=202608, o.updated_at='2026-08-12 23:59:59.999999',
o.attempt_count=o.attempt_count+1, o.last_error_code=NULL, o.last_error_ref=NULL
WHERE o.published_at IS NULL;
DROP TEMPORARY TABLE claimed_outbox_event;
COMMIT;
-- 8. v3.0.0 partition movement contract (introduced in v2.2.0): application recomputes partition_key from updated_at.
-- This example marks the operational transaction settled and moves it from p202608_active
-- to p202608_inactive. In a later month, partition_key would use that later YYYYMM instead.
UPDATE operational_transaction
SET status='SETTLED', settled_at='2026-08-12 08:00:00.000000',
is_active=0, updated_at='2026-08-12 08:00:00.000000', partition_key=202608
WHERE external_transaction_id='AWB-2026-001'
AND partition_key=202608 AND is_active=1;
SELECT partition_key, is_active, status, COUNT(*) AS rows_in_partition
FROM operational_transaction
GROUP BY partition_key, is_active, status;
-- 9. Partition-pruning evidence: must target only p202608_active.
EXPLAIN FORMAT=TREE
SELECT transaction_id, actor_account_id, external_transaction_id, transaction_type, status, occurred_at, updated_at
FROM operational_transaction
WHERE partition_key=202608 AND is_active=1
AND actor_account_id=215574410218721283;
-- S&DP ERD v3.0.0 — Portable LIST COLUMNS partition operations
-- v3.0.0 query standard: explicit projections; all production cutoffs are app-bound UTC DATETIME(6) parameters.
-- Target engines: Aurora MySQL / MySQL 8 / MariaDB / OceanBase-compatible common subset.
-- Run the pre-create section BEFORE a target month begins. No catch-all partition is used by design.
-- ============================================================================
-- APPLICATION WRITE CONTRACT
-- ============================================================================
-- The application owns partition_key and is_active; no trigger computes them.
-- partition_key = YYYYMM produced from updated_at in UTC (e.g. 202608).
-- is_active = 1 while ongoing; is_active = 0 only when final/settled.
-- On every update, recalculate partition_key from the new updated_at and write both fields.
-- Moving a row between partitions is intentional and accepted by the SOP.
-- Example application-owned update (use prepared parameters in application code):
-- UPDATE operational_transaction
-- SET status = :status,
-- settled_at = :settled_at,
-- is_active = :is_active,
-- updated_at = :updated_at,
-- partition_key = :yyyymm_from_updated_at
-- WHERE transaction_id = :transaction_id
-- AND partition_key = :previous_partition_key
-- AND is_active = :previous_is_active;
-- ============================================================================
-- AUTO-PARTITION / PRE-CREATE (example: prepare 202611)
-- ============================================================================
-- Run the matching ALTER for every partitioned table before the period starts.
ALTER TABLE operational_transaction ADD PARTITION (
PARTITION p202611_active VALUES IN ((202611, 1)),
PARTITION p202611_inactive VALUES IN ((202611, 0))
);
ALTER TABLE activity_event ADD PARTITION (
PARTITION p202611_active VALUES IN ((202611, 1)),
PARTITION p202611_inactive VALUES IN ((202611, 0))
);
ALTER TABLE ledger_entry ADD PARTITION (
PARTITION p202611_active VALUES IN ((202611, 1)),
PARTITION p202611_inactive VALUES IN ((202611, 0))
);
ALTER TABLE outbox_event ADD PARTITION (
PARTITION p202611_active VALUES IN ((202611, 1)),
PARTITION p202611_inactive VALUES IN ((202611, 0))
);
-- ============================================================================
-- AUTO-RETENTION (only after archive/export verification)
-- ============================================================================
-- Never drop the active partition. Drop only an inactive partition eligible under policy.
-- Log table, period, archive evidence/checksum, operator/job, and timestamp before execution.
-- Example: retain 202608 data only after confirmed archive:
-- ALTER TABLE operational_transaction DROP PARTITION p202608_inactive;
-- ALTER TABLE activity_event DROP PARTITION p202608_inactive;
-- ALTER TABLE ledger_entry DROP PARTITION p202608_inactive;
-- ALTER TABLE outbox_event DROP PARTITION p202608_inactive;
-- ============================================================================
-- VERIFICATION
-- ============================================================================
SELECT TABLE_NAME, PARTITION_NAME, PARTITION_METHOD, PARTITION_EXPRESSION,
PARTITION_DESCRIPTION, TABLE_ROWS
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME IN ('operational_transaction', 'activity_event', 'ledger_entry', 'outbox_event')
ORDER BY TABLE_NAME, PARTITION_ORDINAL_POSITION;
-- Validate that application updates drive the expected partition target:
EXPLAIN FORMAT=TREE
SELECT transaction_id, actor_account_id, external_transaction_id, transaction_type, status, occurred_at, updated_at
FROM operational_transaction
WHERE partition_key = 202608 AND is_active = 1
AND actor_account_id = 215574410218721283;
-- Expected plan: only p202608_active is read.
-- Retention safety gate — expect zero before dropping an inactive partition unless archived:
SELECT COUNT(*) AS unarchived_inactive_rows
FROM operational_transaction
WHERE partition_key = 202608 AND is_active = 0;
-- MySQL/MariaDB limitation: partitioned InnoDB tables do not support foreign keys.
-- Application/service validation is mandatory for actor_account_id, activity_id, policy_id,
-- beneficiary_account_id, lookup codes, and outbox receipt event references.
-- Non-partitioned reference/master tables retain engine-enforced foreign keys.
Hindari dual-write DB + Manticore pada request yang sama.
Projection membawa sourceVersion; event lama tidak boleh menimpa event baru.
Deduplicate berdasarkan event ID atau aggregate ID + version.
JSON memakai schema_version, JSON Schema, validation, dan migration strategy.
Manticore harus dapat dibangun ulang sepenuhnya dari primary DB.
Gunakan decision table/DSL terbatas, bukan arbitrary SQL atau JavaScript.
Validate subledger_code, cost center/branch, GL mappings, and open financial_period against ERP/Finance Master before writing the accrual.
Only AP subledger, financial dimensions, accrual GL mapping, AP voucher, and journal batch references are in scope; no generic ERP modules.