# KeluargaMart Master System Reference, Operational Guidelines & Audit Ledger
> **Platform:** KeluargaMart Omnichannel Enterprise (Backend, ERP, POS, B2B, NextReport)
> **Target Environment:** Hostinger KVM 2 VPS (2 vCPU, 8 GB RAM), Podman Rootless (`deployer`), Cloudflare Zero Trust Tunnels
> **Last Comprehensive Audit:** 30 Oktober 2026 (Live Audit & Health Verification Passed)
> **Active Release Milestone:** v1.4.1 (PWA Resilience, Logistical Scoping & COD Settlement)
> **Authoritative Compliance:** AGENTS.md Anti-Hallucination & Zero-Drift Protocol
---
## 1. Architectural Boundaries & Core Invariants
### 1.1 The Single Source of Truth
- **Authoritative Core:** The Go Chi backend (`apps/backend`) is the **sole source of truth** for database schemas, financial logic, inventory valuation, discounts, transaction lifecycles, and RBAC permission catalogs.
- **Client Passivity:** Frontends (`apps/erp`, `apps/pos`, `apps/b2b`) **must never invent business rules**, fabricate API return envelopes, or bypass backend validation. All frontend validations are purely user experience affordances.
### 1.2 Zero-Drift API & Permission Governance
- Canonical routes are mirrored between `apps/backend/docs/openapi.yaml` and `packages/contracts`.
- Canonical RBAC permissions are defined in `apps/backend/internal/modules/rbac/catalog.go` and mirrored into `packages/permissions`.
- **Enforcement:** Every API route or permission key change must pass `python3 scripts/check-drift.py`. Failure to pass blocks deployment. Frontend apps must import from `@keluargamart/contracts/routes` and `@keluargamart/permissions`.
### 1.3 Security & Multi-Tenancy Invariants
1. **Fail-Closed Authorization:** Every route requires authentication by default unless explicitly tagged public.
2. **Tenant & Warehouse Scoping:** All operations touching inventory, stock transfers, orders, and shifts MUST filter strictly by the authenticated session's tenant/warehouse/store context.
3. **Financial Immutability:** Finalized transactions, journal entries, payments, and stock balance ledgers are strictly immutable. Adjustments require reverse compensating entries.
4. **Secret Protection:** Never commit `.env`, private keys, JWT signing keys, or Cloudflare credentials to git.
### 1.4 Production Resource Limits (Hostinger KVM 2)
The production host runs 2 vCPU and 8 GB RAM:
- **Zero-Build on Production:** Prebuilt container images are deployed. Never run compilation or heavy asset bundlers directly on the host VPS.
- **Backend Memory Bounds:** `GOMEMLIMIT=512MiB`, `GOGC=100`. Database pool: `MaxConns=25`, `MinConns=2`.
- **B2B SSR Bounds:** `NODE_OPTIONS="--max-old-space-size=256 --enable-source-maps=false"`.
- **Log Rotation:** Container log drivers must enforce `max-size: 10m` and `max-file: 3`.
---
## 2. Comprehensive Live System Audit (Live Status)
*Audited on: 30 Oktober 2026 14:36 UTC*
### 2.1 Host Infrastructure Health
- **RAM Utilization:** `4.2 GiB used / 7.8 GiB total` (3.6 GiB freely available for cache and burst workloads).
- **Disk Utilization:** `50 GB used / 99 GB total` (53% utilized, 45 GB free).
- **Database Migrations:** Exact match between staging and production: **139 migrations applied** (Latest: `000138_grant_buyer_sales_returns_create.sql` and `000139_push_subscriptions.sql`).
- **Drift Guard:** **PASSED** (389 routes clean, 207 permission keys clean).
### 2.2 Container Stack Status (14 Services Running)
All containers are managed under rootless Podman by user `deployer`:
| Environment | Service Name | Internal Container Port | Host Port Mapping | Health & Runtime State |
|---|---|---|---|---|
| **Production** | `keluargamart-postgres` | 5432/tcp | Internal Pod Network | Up, DB: `erp`, 139 migrations |
| **Production** | `keluargamart-redis` | 6379/tcp | Internal Pod Network | Up, Active caching & pubsub |
| **Production** | `keluargamart-backend` | 8080/tcp | `127.0.0.1:3004` | Up, `/health` 200 OK, `/ready` 200 OK |
| **Production** | `keluargamart-erp` | 80/tcp, 8080/tcp | `127.0.0.1:3001` | Up, Nginx SPA, `km-erp-v2` SW |
| **Production** | `keluargamart-pos` | 80/tcp, 8080/tcp | `127.0.0.1:3003` | Up, Nginx SPA, `km-pos-v2` SW |
| **Production** | `keluargamart-b2b` | 3000/tcp | `127.0.0.1:3002` | Up, Nuxt 4 SSR Nitro engine |
| **Production** | `keluargamart-nextreport` | 5489/tcp | `127.0.0.1:5489` | Up, Chromium worker active |
| **Staging** | `km-stg-postgres` | 5432/tcp | Internal Pod Network | Up, DB: `erp`, 139 migrations |
| **Staging** | `km-stg-redis` | 6379/tcp | Internal Pod Network | Up, Active caching |
| **Staging** | `km-stg-backend` | 8080/tcp | `127.0.0.1:3104` | Up, `/health` 200 OK, `/ready` 200 OK |
| **Staging** | `km-stg-erp` | 80/tcp, 8080/tcp | `127.0.0.1:3101` | Up, Nginx SPA, `km-erp-v2` SW |
| **Staging** | `km-stg-pos` | 80/tcp, 8080/tcp | `127.0.0.1:3103` | Up, Nginx SPA, `km-pos-v2` SW |
| **Staging** | `km-stg-b2b` | 3000/tcp | `127.0.0.1:3102` | Up, Nuxt 4 SSR Nitro engine |
| **Staging** | `km-stg-nextreport` | 5489/tcp | `127.0.0.1:5490` | Up, Chromium worker active |
### 2.3 Cloudflare Ingress & Domain Status
| Domain Endpoint | Target Host Port | HTTP Status | Verification Notes |
|---|---|---|---|
| `https://api-km.trl.my.id` | `127.0.0.1:3004` | `200 OK` | `/health` {"env":"production"}, `/ready` {"db":"ok","redis":"ok"} |
| `https://erp-km.trl.my.id` | `127.0.0.1:3001` | `200 OK` | Vue 3 Back-Office Portal, PWA ready |
| `https://pos-km.trl.my.id` | `127.0.0.1:3003` | `200 OK` | Cashier POS SPA, Offline-first cache |
| `https://b2b-km.trl.my.id` | `127.0.0.1:3002` | `200 OK` | Nuxt SSR Wholesale Storefront |
| `https://stg-api-km.trl.my.id` | `127.0.0.1:3104` | `200 OK` | `/health` {"env":"staging"}, `/ready` {"db":"ok","redis":"ok"} |
| `https://stg-erp-km.trl.my.id` | `127.0.0.1:3101` | `200 OK` | Staging ERP Portal |
| `https://stg-pos-km.trl.my.id` | `127.0.0.1:3103` | `200 OK` | Staging POS SPA |
| `https://stg-b2b-km.trl.my.id` | `127.0.0.1:3102` | `200 OK` | Staging B2B Storefront |
---
## 3. Platform Milestones & Version Timeline
```mermaid
timeline
title KeluargaMart System Milestones
2026-10-17 : v1.0.0 : Turborepo Monorepo Consolidation
: Architecture Decision Records (ADR 001-003)
2026-10-23 : v1.1.0 : Shared Packages Extraction (@keluargamart/*)
: Zero-Drift Python Guard Automation
2026-10-26 : v1.2.0 : Financial & Transaction Integrity Hardening
: POS Cash Shift Reconciliation & Safe Stock Locks
2026-10-28 : v1.3.0 : Omnichannel B2B Warehouse Order Packing
: Monthly Transaction Counter Reset System
2026-10-29 : v1.4.0 : Background Push Notifications (Web Push API)
: Transfer Stock Destination Warehouse Authorization Guard
: B2B Return Submission Multi-Tenant Scoping Fix
: ERP Sales Order Action Segregation & DO Stepper Sync
2026-10-30 : v1.4.1 : Service Worker Navigation TypeError Elimination
: Modern PWA mobile-web-app-capable Meta Tag Alignment
```
---
## 4. Problem & Fix Register (Historical Logs & Post-Mortems)
### Register Item #1: Stock Transfer Receiving Authorization Bypass
- **Symptom:** Origin warehouse staff (e.g. Marelan Warehouse Manager) were able to click "Terima" and complete goods receipts for items departing their own warehouse to destination stores (e.g. Kmart Tembung).
- **Root Cause:** Frontend checked only `status === 'IN_TRANSIT'`. Backend handler checked user permissions generically without asserting `authUser.WarehouseID === transfer.DestinationWarehouseID`.
- **Remediation:**
- Backend: Added strict warehouse matching in `internal/inventory/service.go`. Unauthorized non-Superadmins receive `403 Forbidden` (`"Hanya gudang tujuan yang berhak menyelesaikan penerimaan barang"`).
- Frontend (`apps/erp`): Action button "Terima" is conditionally rendered only when `currentUser.warehouse_id === transfer.destination_warehouse_id` (or user is superadmin).
### Register Item #2: B2B Return Submission "Forbidden" (HTTP 403)
- **Symptom:** Customer stores submitting return requests on `b2b-km.trl.my.id` for valid sales orders received HTTP 403 Forbidden.
- **Root Cause:**
1. RBAC permission `sales_returns:create` was not granted to the `buyer` role in the catalog and database.
2. Data ownership validation compared JWT `user.ID` directly to `sales_order.customer_id` instead of fetching the linked customer account.
- **Remediation:**
- Applied migration `000138_grant_buyer_sales_returns_create.sql`.
- Added resolver linking authenticated B2B user to `customer_id` and verified tenant ownership.
### Register Item #3: B2B Cash on Delivery (COD) Payment Gateway Popup
- **Symptom:** Completed COD orders (e.g. `SO-202610-000024`, `SO-202610-000030`) displayed "Bayar Sekarang" and triggered Midtrans/e-wallet popups despite already being delivered.
- **Root Cause:** The order list card component only checked `payment_status !== 'PAID'` without checking `payment_method` or `order_status`. Backend allowed initiating gateway tokens on completed COD orders.
- **Remediation:**
- Frontend (`apps/b2b`): Suppressed "Bayar Sekarang" button if `payment_method === 'COD' || 'CASH'`, or `order_status === 'COMPLETED'`, or `payment_status === 'PAID'`.
- Backend: Added auto-settlement upon delivery in `AdvanceFulfillment` and `DeliverDeliveryOrder`. When a COD order reaches `DELIVERED`, backend sets payment status to `PAID` and registers the cash settlement automatically.
### Register Item #4: Sales Order vs Delivery Order Action Segregation & DO Stepper Sync
- **Symptom:**
1. The Sales Order detail page (`/sales/orders/:id`) displayed warehouse operational buttons (`Mulai Packing`, `Selesaikan Pesanan`).
2. Delivery Order modal (`/distribution/delivery-orders`) was stuck at step 1 (`Tgl Order`) despite fulfillment progress, and the complete button was clickable when `shipped_quantity === 0`.
- **Remediation:**
- Sales Order detail page cleaned of warehouse mutation actions (now read-only tracking).
- DO Stepper mapped linearly to 5 stages:
1. Order Dibuat
2. Packing / Sedang Dikemas
3. Siap Diantar / Assigned to Driver
4. Dalam Pengiriman (In Transit)
5. Selesai / Diterima
- Completion action disabled unless `shipped_quantity > 0` and DO status is in transit.
### Register Item #5: Background Push Notifications
- **Symptom:** Notifications only sounded while the browser dashboard was actively open in the foreground; tab closing resulted in missed order alerts.
- **Remediation:**
- Created migration `000139_push_subscriptions.sql` storing Web Push endpoint and VAPID keys.
- Implemented background `push` and `notificationclick` listeners in `sw.js`.
- Added subscription triggers in frontend clients.
### Register Item #6: Service Worker Navigation TypeError (`Failed to convert value to 'Response'`)
- **Symptom:** Console logged `sw.js:1 Uncaught (in promise) TypeError: Failed to convert value to 'Response'` and `The FetchEvent for "..." resulted in a network error response: the promise was rejected.`
- **Root Cause:**
In `sw.js`, lines 40-44:
`fetch(event.request).catch(() => caches.match('/index.html') || caches.match('/'))`
`caches.match()` returns a Promise. When network fails and `/index.html` is not in CacheStorage, the Promise resolves to `undefined`. `event.respondWith()` requires a valid `Response` object and rejects `undefined` with a TypeError, breaking page navigation completely. Also, cross-origin requests were not ignored, causing third-party adblocker blocks to trigger fetch failures.
- **Remediation:**
- Added `if (url.origin !== self.location.origin) return;` to ignore cross-origin requests immediately.
- Converted navigation handler to async IIFE returning a valid offline 503 HTML `Response` if both network and cache miss.
- Converted static asset handler to fallback to `new Response(null, { status: 404 })` instead of `undefined`.
- Bumped cache names to `km-erp-v2`, `km-pos-v2`, `km-b2b-v2`.
### Register Item #7: Deprecated Meta Tags
- **Symptom:** Browser logged ` is deprecated. Please include `.
- **Remediation:** Added `` in `apps/erp/index.html`, `apps/pos/index.html`, and `apps/b2b/nuxt.config.ts`.
---
## 5. Operational Runbooks & Standard Commands
### 5.1 Container & Service Management (Podman Rootless)
All container operations must be executed as user `deployer`:
```bash
# List all running KeluargaMart containers
su - deployer -c "podman ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'" | grep -E "keluargamart|km-stg"
# Restart a specific service (example: ERP)
su - deployer -c "podman restart keluargamart-erp"
su - deployer -c "podman restart km-stg-erp"
# View live container logs (example: Backend)
su - deployer -c "podman logs -f --tail 50 keluargamart-backend"
```
### 5.2 Database Migration Verification
```bash
# Check migration count in production and staging
su - deployer -c "podman exec keluargamart-postgres psql -U erp -d erp -c 'SELECT count(*) FROM schema_migrations;'"
su - deployer -c "podman exec km-stg-postgres psql -U erp -d erp -c 'SELECT count(*) FROM schema_migrations;'"
# View latest 5 applied migrations
su - deployer -c "podman exec keluargamart-postgres psql -U erp -d erp -c 'SELECT version, applied_at FROM schema_migrations ORDER BY version DESC LIMIT 5;'"
```
### 5.3 Contract & Permission Drift Guard Check
```bash
# Must be executed before every merge or deployment
python3 scripts/check-drift.py
```
### 5.4 Public Endpoint Health Verification
```bash
# Production probes
curl -s https://api-km.trl.my.id/health
curl -s https://api-km.trl.my.id/ready
# Staging probes
curl -s https://stg-api-km.trl.my.id/health
curl -s https://stg-api-km.trl.my.id/ready
# Service Worker version verification
curl -s https://erp-km.trl.my.id/sw.js | head -n 5
curl -s https://stg-erp-km.trl.my.id/sw.js | head -n 5
```
---
## 6. Known Quirks & Agent Troubleshooting Guide
1. **Podman `ps` returns empty when run as root:**
Containers are owned by the `deployer` user via rootless Podman. Always run `su - deployer -c "podman ..."` to interact with containers.
2. **Postgres Role and DB Name:**
The PostgreSQL user and database name are both `erp` (not `keluargamart` or `postgres`). Connecting with `-U erp -d erp` is required.
3. **Bind-mounted `sw.js` in Deployment Data:**
Production and staging containers mount `sw.js` from `data/deploy/{erp,pos,b2b}/sw.js`. When editing Service Worker code in `apps/*/public/sw.js`, remember to copy updates to `data/deploy/*/sw.js` and `/opt/stacks/keluargamart-monorepo-staging/data/deploy/*/sw.js` so running containers receive the file without needing a full rebuild.
4. **Third-Party Browser Extensions in Console Logs:**
Errors mentioning `Shortcut-*.js`, `ContentScript-*.js`, or `runtime.lastError` originate from the user's browser extensions (e.g. keyboard shortcuts, translation plugins). They are not application bugs.
5. **Adblockers and Cloudflare Beacon:**
`static.cloudflareinsights.com/beacon.min.js net::ERR_FAILED` is caused by browser adblockers (uBlock Origin, Brave Shield) blocking Cloudflare analytics. Our Service Worker now bypasses all cross-origin requests so these blocks do not interfere with app performance.