docs(cache): remove static dataset invalidation
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# PostgreSQL Physical Schema Specification
|
||||
|
||||
- Status: Decision-ready draft — stakeholder approval required
|
||||
- Scope: Data foundation, demo identity persistence, and analytics invalidation state
|
||||
- Status: Approved specification — implementation not yet authorized
|
||||
- Scope: Data foundation, demo identity persistence, and immutable dataset cache identity
|
||||
- Implementation authorization: Not granted
|
||||
- Last updated: 2026-08-20
|
||||
- Visual companion: [`PHYSICAL-SCHEMA-ERD.html`](./PHYSICAL-SCHEMA-ERD.html)
|
||||
@@ -26,7 +26,7 @@ Define the implementation-ready PostgreSQL shape for the normalized ZarinPal arc
|
||||
2. A terminal belongs to exactly one merchant.
|
||||
3. A payment session belongs to exactly one merchant and one terminal; a composite foreign key proves that the terminal belongs to that merchant.
|
||||
4. A payment attempt belongs to exactly one session and has a positive sequence unique within that session.
|
||||
5. A bootstrap session belongs to exactly one completed import lineage; a live session has no synthetic import run.
|
||||
5. Every MVP payment session belongs to exactly one completed import lineage.
|
||||
6. Attempt provenance is inherited through its session rather than duplicated on every attempt.
|
||||
7. PSP and issuer-bank codes are reference entities. Switch response codes remain nullable PSP-scoped text because no authoritative meaning catalogue was supplied.
|
||||
8. Payer-card tokens are nullable, merchant-scoped through the session relationship, and never globally unique.
|
||||
@@ -56,7 +56,7 @@ Define the implementation-ready PostgreSQL shape for the normalized ZarinPal arc
|
||||
| `started_at` | `timestamptz` required | run start |
|
||||
| `completed_at` | `timestamptz` nullable | terminal completion/failure time |
|
||||
|
||||
A partial unique index on `(source_sha256, source_schema_version, importer_revision)` where `status = 'completed'` makes a completed import idempotent while permitting explicit retry after a failed run. Terminal state requires `completed_at`; non-terminal state forbids it.
|
||||
A partial unique index on `(source_sha256, source_schema_version)` where `status = 'completed'` makes a completed source snapshot idempotent across importer rebuilds while permitting explicit retry after a failed run. `importer_revision` remains provenance, not business identity. Terminal state requires `completed_at`; non-terminal state forbids it.
|
||||
|
||||
#### `import_issues`
|
||||
|
||||
@@ -206,10 +206,9 @@ The ownership FK guarantees that the user belongs to the recorded merchant. Chec
|
||||
| `session_status` | `text` required | non-empty source lifecycle code |
|
||||
| `created_at` | `timestamptz` required | source Tehran-local value normalized to UTC |
|
||||
| `expires_at` | `timestamptz` required | source `expire_in` normalized to UTC |
|
||||
| `source_origin` | `text` required | `bootstrap` or `zarinpal_live` |
|
||||
| `import_run_id` | `bigint` nullable | restrictive FK to `import_runs` |
|
||||
| `import_run_id` | `bigint` required | restrictive FK to the completed `import_runs` lineage |
|
||||
|
||||
The origin check requires an import run for `bootstrap` and forbids one for `zarinpal_live`. Timestamp ordering is not enforced because source ordering anomalies are preserved as evidence rather than rewritten; expiry-before-creation, if found during exact validation, is reported according to the import specification.
|
||||
Timestamp ordering is not enforced because source ordering anomalies are preserved as evidence rather than rewritten; expiry-before-creation, if found during exact validation, is reported according to the import specification.
|
||||
|
||||
Approved indexes:
|
||||
|
||||
@@ -238,27 +237,11 @@ Approved indexes:
|
||||
|
||||
Unique `(payment_session_id, try_seq)` is the attempt identity and lookup index. A check requires `issuer_bank_id` and `payer_card_key` to be either both null or both populated, matching the authoritative data guide and observed missingness. No timestamp-order check is used because documented anomalies are retained. No standalone payer-token, bank, PSP, or status index is added until an approved measured query justifies it.
|
||||
|
||||
### Analytics invalidation
|
||||
### Static dataset cache namespace
|
||||
|
||||
#### `merchant_analytics_revisions`
|
||||
The MVP performs no normal payment-fact writes after the one-time bootstrap, so the physical schema contains no analytics-revision tables and no insert/update/delete invalidation triggers. The importer permits one completed dataset in the MVP; Redis keys include that completed `import_runs.id` plus its immutable source checksum/schema version, merchant, period, section, and metric-definition version.
|
||||
|
||||
`merchant_id bigint` PK/FK to `merchants`; `source_revision bigint` required default `0` and non-negative; `updated_at timestamptz` required.
|
||||
|
||||
#### `cohort_analytics_revisions`
|
||||
|
||||
`category_id bigint` PK/FK to `merchant_categories`; `source_revision bigint` required default `0` and non-negative; `updated_at timestamptz` required.
|
||||
|
||||
Revision values are cache namespace markers, not transaction counts. They may advance more than once in one business operation without changing correctness.
|
||||
|
||||
## Database-managed revision contract
|
||||
|
||||
Direct ZarinPal inserts may bypass Go, so invalidation cannot rely only on application callbacks. Versioned PostgreSQL statement-level `AFTER INSERT` triggers with transition tables update each affected merchant and category once per insert statement:
|
||||
|
||||
- session insert: use merchant/category relationships from the inserted sessions;
|
||||
- attempt insert: join inserted attempts to their sessions, merchants, and categories;
|
||||
- update/delete triggers do not exist in MVP because payment facts are append-only.
|
||||
|
||||
This is deliberate schema-owned logic, named in migrations and covered by integration tests. Bulk promotion remains efficient because a multi-row `INSERT ... SELECT` advances each affected revision once, not once per imported row. Trigger failure aborts the fact insert; analytics can never observe a new fact under an old revision.
|
||||
Dataset replacement and future ZarinPal live writes are outside the MVP. Either change requires a separately approved recovery, namespace-selection, and invalidation design before payment-fact mutation is allowed.
|
||||
|
||||
## Staging boundary
|
||||
|
||||
@@ -266,7 +249,7 @@ Importer staging tables are run-scoped, migration-owned technical structures rat
|
||||
|
||||
## Deletion, mutation, and retention
|
||||
|
||||
- Imported and live payment facts are append-only in MVP. Corrections require a new specification/ADR and matching invalidation behavior.
|
||||
- Imported payment facts are immutable in MVP. Corrections or live writes require a new specification/ADR and matching invalidation behavior.
|
||||
- Merchant/category/terminal/import provenance referenced by facts cannot be deleted. Synthetic display names and password hashes may be updated through approved identity workflows.
|
||||
- Session rows are revoked/expired, not hard-deleted during their security lifetime. Authentication audit events expire after 30 days; analytics-access and CSV-export audit events expire after 90 days. Cleanup is allowed only after `expires_at` and must not remove payment/import facts.
|
||||
- Redis analytics entries are disposable and expire after 30 days; no calculated analytics table exists in PostgreSQL.
|
||||
@@ -275,7 +258,7 @@ Importer staging tables are run-scoped, migration-owned technical structures rat
|
||||
|
||||
- Atlas-generated versioned SQL is reviewed and committed; production startup never auto-migrates.
|
||||
- Migration tests create the schema from zero and upgrade from every supported release state.
|
||||
- Constraint tests cover terminal ownership, user/audit tenant ownership, no-attempt exclusion, attempt uniqueness, bootstrap/live provenance, paired card/bank nullability, session token digest length, audit fingerprint length, and revision-trigger atomicity.
|
||||
- Constraint tests cover terminal ownership, user/audit tenant ownership, no-attempt exclusion, attempt uniqueness, bootstrap provenance, paired card/bank nullability, session token digest length, and audit fingerprint length.
|
||||
- Query-plan verification uses production-shaped volumes and `EXPLAIN (ANALYZE, BUFFERS)` for approved analytics, detail pagination, CSV export, and session-authentication paths before adding further indexes.
|
||||
- Initial fact tables are not partitioned. Partitioning requires measured operational need and a separate ADR.
|
||||
|
||||
@@ -287,20 +270,20 @@ Importer staging tables are run-scoped, migration-owned technical structures rat
|
||||
- Session-level amount, fee, merchant, terminal, status, creation, expiry, origin, and provenance are stored once.
|
||||
- `verify_type` is stored once per terminal and reconciles to every source record for that terminal.
|
||||
- Payer tokens remain unchanged and restricted; they are absent from uniqueness constraints, logs, exports, peer results, and AI inputs.
|
||||
- Direct session/attempt inserts atomically select new merchant/cohort revision namespaces.
|
||||
- The sole completed import run provides an immutable dataset cache namespace without database triggers.
|
||||
- Approved period, authentication, drill-down, and export access paths have an explicit initial index or a documented measure-first rule.
|
||||
- Schema creation, FK/check constraints, trigger behavior, 30/90-day retention cleanup, and query plans are integration-tested before implementation approval can be considered complete.
|
||||
- Schema creation, FK/check constraints, snapshot selection, 30/90-day retention cleanup, and query plans are integration-tested before implementation approval can be considered complete.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Always: preserve source keys and nulls, enforce tenant ownership with FKs, store money at session grain, use UTC instants, version migrations, and test revision changes atomically.
|
||||
- Always: preserve source keys and nulls, enforce tenant ownership with FKs, store money at session grain, use UTC instants, version migrations, and treat imported payment facts as immutable.
|
||||
- Ask first: add a table/column/index, denormalize tenant/category data, introduce partitioning, classify a new lifecycle status, or permit fact mutation/deletion.
|
||||
- Never: create one wide CSV table, create a fake no-attempt row, duplicate session money on attempts, trust file order, store raw session tokens, or hide production migrations in startup.
|
||||
|
||||
## Decision batch for approval
|
||||
## Resolved decisions
|
||||
|
||||
1. Store terminal-invariant `verify_type` on `terminals`, not every payment attempt.
|
||||
2. Preserve future non-empty lifecycle status values as text and classify them through the metric catalogue instead of using rigid PostgreSQL enums.
|
||||
3. Use explicit statement-level insert triggers for merchant/cohort analytics revisions because direct ZarinPal writes can bypass Go.
|
||||
3. Omit cache-revision tables and database triggers because the MVP dataset is immutable; use the sole completed import run as the Redis cache namespace.
|
||||
4. Persist minimal 30-day authentication audit events and 90-day analytics/export audit events because the approved Identity and API specs require queryable operator evidence without raw payloads, cursors, credentials, or sensitive data.
|
||||
5. Approve the table, constraint, index, and restrictive-deletion contract above as the migration target.
|
||||
|
||||
Reference in New Issue
Block a user