# 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.