Skip to content

Data

Storage layers

Layer Tech Purpose
Transactional AlloyDB (Postgres 15+), one per service OLTP state. Private IP, pgAudit, row-level security. Read replicas on submission-svc and comment-svc for dashboard load.
Files Google Cloud Storage (CMEK) Raw uploads, processed artifacts, signed certificates, PSP exports.
Search / RAG Vertex AI Vector Search SIRP rule corpus, comment library, prior-case retrieval.
Audit / BI BigQuery Append-only, Pub/Sub-sourced, 10-year retention. Auditors read a dedicated restricted dataset.
Cache / sessions Memorystore (Redis) Session state, short-lived caches.
Event bus Pub/Sub Domain events; subscribed to BigQuery sink.
Delayed jobs Cloud Tasks Scheduling not expressible as Temporal timers.

GCS bucket lifecycle

Bucket Contents
siss-uploads Raw PSP uploads — IFC, PDF, DWG, Borang forms. Retained per PDPA rules.
siss-processed Normalized PDFs, extracted artifacts, IFC XKT tiles.
siss-signed Signed Kertas Perakuan and SIGL certificates.
siss-exports PSP-facing downloadable bundles.

All buckets are CMEK-encrypted and accessed through short-lived V4-signed URLs or service-to-service IAM.

Shared identifiers

SISS uses a small set of IDs that trace across every service and every trace span.

ID Format Purpose
tenant_id UUID Silicon Island CMU is the first tenant; multi-tenancy designed-in, not launch feature.
submission_id ULID, prefix sub_ Stable across the whole lifecycle — pre-consult → Kertas Perakuan.
lot_id / plot_id Masterplan / UDP reference Consumed by ai-svc and bim-svc for zoning lookups.
workflow_id Temporal run ID Bound to every state change for audit replay.

Submission aggregate (ERD)

erDiagram
  SUBMISSIONS ||--o{ SUBMISSION_REVISIONS : "has"
  SUBMISSION_REVISIONS ||--o{ SUBMISSION_FILES : "contains"
  SUBMISSIONS ||--o{ PRECONSULT_CHECKLISTS : "produces"
  SUBMISSIONS ||--o{ SUBMISSION_EVENTS : "emits"

  SUBMISSIONS {
    uuid id PK
    uuid tenant_id
    uuid psp_user_id
    string submission_type
    string lot_id
    string status
    timestamp created_at
    timestamp updated_at
  }
  SUBMISSION_REVISIONS {
    uuid id PK
    uuid submission_id FK
    int revision_no
    uuid created_by
    timestamp created_at
  }
  SUBMISSION_FILES {
    uuid id PK
    uuid revision_id FK
    string file_kind
    string gcs_uri
    string content_type
    bigint size_bytes
    string sha256
    timestamp uploaded_at
  }
  PRECONSULT_CHECKLISTS {
    uuid id PK
    uuid submission_id FK
    uuid revision_id FK
    json items
    string officer_decision
    uuid officer_user_id
    timestamp decided_at
  }
  SUBMISSION_EVENTS {
    uuid id PK
    uuid submission_id FK
    string type
    json payload
    timestamp at
  }

Row-level security

Every query in every service runs under a request-scoped AuthContext (tenant_id, user_id, roles, permissions). RLS policies filter rows by tenant_id and, where applicable, by psp_user_id (for PSPs) or department_id (for ATD / ATL officers) before they reach the application layer. See Security & RBAC for the full grain.

Multi-tenancy

Not a launch feature — this section documents the activation path

SISS launches with a single tenant (Silicon Island CMU). The architecture is tenant-aware end-to-end, so onboarding additional local authorities later is an activation problem, not a rewrite.

What's pre-wired today

  • tenant_id is on every table, on every domain event, and on every audit row.
  • AlloyDB row-level security enforces tenant scoping at the query layer — application code can't accidentally leak across tenants.
  • Service-to-service calls carry tenant_id in the signed AuthContext token from core-svc.

What activation would require

  • Tenant provisioning — a workflow that creates the tenant row, seeds roles, registers the SIRP rule corpus, uploads signing certificates, and configures workflow definitions.
  • Per-tenant configuration — SIRP rules, comment templates, workflow shapes, PSP portal branding, notification templates all become tenant-scoped (today they are effectively global because there is only one tenant).
  • Per-tenant identity — each authority federates its own IdP via Identity Platform tenancy (e.g., MyDigital ID for one, ADFS for another).
  • Per-tenant signingsigning-svc holds a separate KMS key set + cert chain per tenant, since each authority has its own legal trust anchor. See Signing.
  • Audit isolation — BigQuery views partitioned by tenant_id; auditors of one tenant must never see another's submissions.
  • Operational isolation — per-tenant SLOs and quotas, noisy-neighbor controls (Pub/Sub flow control, AlloyDB connection pools), per-tenant data-residency where required.

Tradeoff

A single shared instance is cheaper to operate but means one tenant's incident is everyone's incident. Most regulated-vertical SaaS offerings end up providing both shared and dedicated tiers — small authorities share the instance; large ones get their own deployment of the same Helm chart.

Audit retention and PDPA

  • Audit stream — every state-changing domain event plus every auth event, published to Pub/Sub and sunk into BigQuery, append-only, retained 10 years.
  • PDPA subject requests — export and erasure operate on submission_id / user_id. Erasure is tombstone + redact, never physical delete, so audit integrity is preserved.
  • Tamper evidence — daily hash-chain digest of the audit stream is signed by KMS and published to a public transparency log.