Skip to content

CONV · Conventions

The rules every table follows: how money, phones and dates are stored, what is never deleted, and how the database refuses. Code CONV.

  • DOC-Q1 (search_path): a new rule, R-CONV-15, below.
  • DOC-Q5 (which phone columns): R-CONV-07 covers every phone column, the four that V13 left unchecked included. They are listed under the rule.
  • DOC-Q7 (dates defaulting to the server's day): registered_on, started_on and issued_on are business dates, so R-CONV-10 covers them. Their default is the pool's date, not the server's.
  • DOC-Q8 (unknown setting name, bad counter period): neither becomes a rule. They catch a caller's mistake, not a business rule, so their refusals stay under R-CONV-13.

R-CONV-01 · Money is stored in whole rupiah as bigint, never as a fraction or a float

Section titled “R-CONV-01 · Money is stored in whole rupiah as bigint, never as a fraction or a float”
  • Status: planned
  • Example: given a rent charge of Rp 150.000, when it is saved, then amount holds 150000 as a bigint, and a money column declared as numeric, real or double precision is a defect.
  • Refusal: none: a fraction can't be stored in a bigint column.
  • Who: every writer.
  • Source: [Ref] Database rules and conventions, Money.

R-CONV-02 · A money amount is never negative

Section titled “R-CONV-02 · A money amount is never negative”
  • Status: planned
  • Example: given a pricing group, when its admin_fee is saved as -50000, then the row is refused. The same holds for rates, costs, fees, shares and totals.
  • Refusal: none: check_violation (23514).
  • Who: every writer.
  • Source: [Ref] Data model V13, the ≥ 0 checks on money columns.

R-CONV-03 · A period never ends before it starts

Section titled “R-CONV-03 · A period never ends before it starts”
  • Status: planned
  • Example: given an insurance policy starting on 2026-11-01, when ends_on is saved as 2026-10-31, then the row is refused. The same holds for rentals, maintenance records and payouts.
  • Refusal: none: check_violation (23514).
  • Who: every writer.
  • Source: [Ref] Data model V13: rentals, insurance_policies, maintenance_records, payouts.

R-CONV-04 · A due date is never before the date the charge or letter was raised

Section titled “R-CONV-04 · A due date is never before the date the charge or letter was raised”
  • Status: planned
  • Example: given a charge dated 2026-10-07, when its due_date is saved as 2026-10-06, then the row is refused. The same holds for a debt letter's due_on against its issued_on.
  • Refusal: none: check_violation (23514).
  • Who: every writer.
  • Source: [Ref] Data model V13: charges, debt_letters.

R-CONV-05 · A percentage reading (fuel level, battery) is between 0 and 100

Section titled “R-CONV-05 · A percentage reading (fuel level, battery) is between 0 and 100”
  • Status: planned
  • Example: given an inspection, when fuel_level is saved as 120, then the row is refused.
  • Refusal: none: check_violation (23514).
  • Who: every writer.
  • Source: [Ref] Data model V13: inspections.

R-CONV-06 · An odometer or kilometre reading is never negative

Section titled “R-CONV-06 · An odometer or kilometre reading is never negative”
  • Status: planned
  • Example: given a car, when odometer_km is saved as -10, then the row is refused. The same holds for inspections and maintenance records.
  • Refusal: none: check_violation (23514).
  • Who: every writer.
  • Source: [Ref] Data model V13: vehicles, inspections, maintenance_records.

R-CONV-07 · A phone number is stored in E.164 form, such as +6281234567890

Section titled “R-CONV-07 · A phone number is stored in E.164 form, such as +6281234567890”
  • Status: planned
  • Example: given a garage, when its phone is saved as 0812-3456-7890, then the row is refused; +6281234567890 is accepted.
  • Refusal: none: check_violation (23514).
  • Who: every writer. The data phase normalizes v1's numbers before they are loaded.
  • Source: [Ref] Database rules and conventions, Normalized input. The columns: drivers.phone, driver_emergency_contacts.phone, queue_tickets.phone, profiles.phone, rental_companies.contact_phone, garages.phone and insurers.phone. V13 checked only the first three; the other four are added (DOC-Q5). Any phone column added later follows the rule.

R-CONV-08 · Every mutable table has created_at and updated_at

Section titled “R-CONV-08 · Every mutable table has created_at and updated_at”
  • Status: planned
  • Example: given any table whose rows can be updated, when its columns are listed, then created_at and updated_at are both there, as timestamptz.
  • Refusal: none: this describes the schema.
  • Who: every table. Append-only tables (GPS positions, events, the audit log) carry their own time column instead.
  • Source: D9 (P14).

R-CONV-09 · The database sets updated_at on every update, whatever the client sends

Section titled “R-CONV-09 · The database sets updated_at on every update, whatever the client sends”
  • Status: planned
  • Example: given a driver updated at 10:00, when a client updates the row at 10:05 and sends updated_at = 2020-01-01, then updated_at reads 10:05.
  • Refusal: none: the value is overwritten, not refused.
  • Who: every writer.
  • Source: D9 (P14); [Ref] Database rules and conventions, Timestamps.

R-CONV-10 · Business dates (charge, queue and due dates, day counts) are computed in the pool's time zone

Section titled “R-CONV-10 · Business dates (charge, queue and due dates, day counts) are computed in the pool's time zone”
  • Status: planned
  • Example: given a pool in Asia/Makassar (WITA), when a charge is raised at 23:30 WITA on 7 October, which is 15:30 UTC, then its charge_date is 2026-10-07, and at 00:30 WITA on 8 October it is 2026-10-08.
  • Refusal: none.
  • Who: every writer; app.pool_today(pool) gives the date.
  • Source: D9 (P11). Also covered (DOC-Q7): drivers.registered_on (the driver's pool), maintenance_records.started_on (the car's home pool) and debt_letters.issued_on (the driver's pool). In V13 they default to the server's date, which is UTC on Supabase; they default to the pool's date instead.

R-CONV-11 · A row can't be deleted while business rows reference it

Section titled “R-CONV-11 · A row can't be deleted while business rows reference it”
  • Status: planned
  • Example: given a car with a rental, when someone deletes the car, then the delete is refused. Photos, items and other pure children go with their parent.
  • Refusal: none: foreign_key_violation (23503).
  • Who: every writer.
  • Source: D9 (P13); [Ref] Database rules and conventions, No hard deletes.

R-CONV-12 · Every single-column foreign key has an index

Section titled “R-CONV-12 · Every single-column foreign key has an index”
  • Status: planned
  • Example: given rentals.vehicle_id points at vehicles, when the indexes on rentals are listed, then one starts with vehicle_id.
  • Refusal: none: this describes the schema.
  • Who: every table.
  • Source: [Ref] Database rules and conventions, Indexes.

R-CONV-13 · Every refusal from a database function or trigger raises a stable key named domain.reason, in lower case

Section titled “R-CONV-13 · Every refusal from a database function or trigger raises a stable key named domain.reason, in lower case”
  • Status: planned
  • Example: given a car at pool PML and a driver at another pool, when the rental is booked, then the error message is exactly rental.cross_pool. A key that isn't registered raises internal.unknown_error_key instead.
  • Refusal: internal.unknown_error_key, when a function tries to raise an unregistered key.
  • Who: every database function and trigger. Argument checks that catch a caller's mistake (settings.unknown_key, settings.pool_not_found, document.bad_period, document.wrong_organization, vehicle.not_found, driver.not_found, profile.not_found) follow this rule and have no rule of their own (DOC-Q8).
  • Source: D10.

R-CONV-14 · A refusal's detail is a JSON object holding the values its message needs

Section titled “R-CONV-14 · A refusal's detail is a JSON object holding the values its message needs”
  • Status: planned
  • Example: given an allocation of Rp 200.000 against a charge with Rp 150.000 open, when it is saved, then the detail is {"charge_id": "…", "balance": 150000, "amount": 200000}.
  • Refusal: none: this describes every refusal.
  • Who: every database function and trigger.
  • Source: D10.

R-CONV-15 · Every function that runs as its owner sets a fixed search_path

Section titled “R-CONV-15 · Every function that runs as its owner sets a fixed search_path”
  • Status: planned
  • Example: given app.set_vehicle_status, which is security definer, when its definition is read, then it sets search_path to a fixed list, so a caller's own schema can't swap a table or function it uses.
  • Refusal: none: this describes the schema.
  • Who: every security definer function.
  • Source: D3 (rules run in security definer functions); Supabase's database linter, lint 0011 function_search_path_mutable. Added for DOC-Q1.