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_idis 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_idin the signedAuthContexttoken fromcore-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 signing —
signing-svcholds 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.