DEPLOYMENT_TOPOLOGY — lock-integration-service
Bundle: SERVICE_OVERVIEW · SECURITY_MODEL · OBSERVABILITY · SYNC_CONTRACT
Cross-cutting: docs/02 §6 Runtime topology, ADR-0001 — Core architecture and tech stack, ADR-0003 — Electron offline-first desktop.
The service runs on GCP as Cloud Run revisions plus auxiliary jobs. The Electron desktop, where on-prem encoder access lives, is part of the deployment topology even though it is not a server — it is the only environment that hosts the Generic Wiegand adapter (USB/serial via serialport/node-hid).
1. Components
┌────────────────────────── GCP project: melmastoon-prod ──────────────────────────┐
│ │
│ API Gateway (Cloud Endpoints) │
│ │ │
│ ▼ │
│ ┌──────────────────────────┐ ┌──────────────────────────┐ │
│ │ lock-integration │ │ lock-integration │ │
│ │ (cloud-run "api") │ │ (cloud-run "webhook") │ │
│ │ - REST API │ │ - vendor webhooks │ │
│ │ - serves /sync handlers │ │ - mTLS for Vostio │ │
│ └────────┬─────────────────┘ └────────┬─────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ Cloud SQL Postgres 15 (regional HA, CMEK) │ │
│ │ schema: lock; RLS on tenant_id; pgvector? no │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ │ ▲ │
│ ▼ │ │
│ ┌──────────────────────────┐ │ │
│ │ Pub/Sub │ │ │
│ │ topics: melmastoon.lock.*│ ──────────────────┘ outbox publisher │
│ │ subs: lock-integration- │ │
│ │ {saga|webhook|...}│ │
│ └────────┬─────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────┐ ┌──────────────────────────┐ │
│ │ Cloud Run "saga-runner" │ │ Cloud Run Jobs │ │
│ │ push subs: │ │ - daily merkle-anchor │ │
│ │ reservation.* │ │ - daily battery-predict │ │
│ │ staff.shift.* │ │ - vendor-cred rotation │ │
│ │ iam.user.deactivated.v1│ │ - sandbox-conformance │ │
│ │ lock.vendor_webhook.* │ │ (nightly) │ │
│ └────────┬─────────────────┘ └──────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ Vendor adapters │ ───► TTLock cloud (HTTPS) │
│ │ (in-process libs) │ ───► Salto SVN cloud (HTTPS) + on-prem connector│
│ │ routed by VendorAdapter │ via Cloud VPN tunnel │
│ │ registry │ ───► Vostio cloud (mTLS) │
│ └──────────────────────────┘ │
│ │ │
│ ▼ (audit anchors) │
│ ┌──────────────────────────┐ │
│ │ audit-service │ │
│ └──────────────────────────┘ │
│ │
│ ┌─────────────────────────────────┐ │
│ │ Secret Manager │ │
│ │ per-tenant CMEK │ │
│ │ vendor-cred secrets │ │
│ │ offline-issuance signing key │ │
│ └─────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────────┘
│
▼ /sync/v1/* (TLS, optional mTLS)
┌─────────────────── Property site (on-prem) ───────────────────┐
│ │
│ ┌───────────────────────────────────────┐ │
│ │ Electron desktop (Node 20 main) │ │
│ │ - Generic Wiegand adapter │ │
│ │ - serialport / node-hid │ │
│ │ - SQLite mirror (encrypted) │ │
│ │ - offline-issuance cert (Ed25519) │ │
│ │ - device key (Ed25519, OS keychain) │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ USB Wiegand │ │ TTLock BLE │ │ Salto on-prem │ │
│ │ encoder │ │ via Web BT? │ │ XS4 connector │ │
│ │ (HID/serial) │ │ (no — main only)│ │ (separate svc) │ │
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
└────────────────────────────────────────────────────────────────┘
The Electron renderer never holds vendor SDKs or USB handles. All hardware I/O happens in the main process, exposed to the renderer via a typed IPC channel. See ADR-0003 §Process model.
2. Cloud Run revisions
| Revision | Purpose | Min instances | Max | Concurrency | CPU/RAM | Egress |
|---|---|---|---|---|---|---|
lock-integration-api | REST API, sync handlers | 2 | 100 | 80 | 2 vCPU / 1 GiB | Cloud NAT, static egress IP |
lock-integration-webhook | Vendor webhook ingress | 2 | 50 | 200 | 1 vCPU / 512 MiB | none |
lock-integration-saga-runner | Pub/Sub push subscribers (sagas + webhook processor) | 2 | 80 | 20 | 2 vCPU / 1 GiB | Cloud NAT |
All run in region us-central1 for global tenants, europe-west1 for EU-residency tenants. Per-region instance is fully isolated (DB, Pub/Sub, secrets); routing by tenant residency happens at API Gateway via header → backend mapping.
3. Cloud Run Jobs
| Job | Schedule | Purpose |
|---|---|---|
lock-merkle-anchor | daily 02:00 UTC | Compute Merkle root of prior-day lock_audit partition; submit to audit-service |
lock-battery-predict-batch | daily 02:30 UTC | Per-tenant battery predictions via ai-orchestrator-service |
lock-attempt-anomaly-batch | hourly :05 | Per-tenant anomaly batch scoring |
lock-vendor-cred-rotation | daily 03:00 UTC | Identify aging vendor creds, rotate per policy |
lock-sandbox-conformance | nightly 04:00 UTC | Run vendor sandbox conformance against TTLock/Salto/Vostio |
lock-offline-cert-cleanup | daily 05:00 UTC | Move expired offline certs to CRL, archive |
All Jobs use the same container image as the API revision; entrypoint switches via env JOB_NAME.
4. Identity and IAM
| Component | Service Account | Key roles |
|---|---|---|
lock-integration-api | sa-lock-integration-api@… | roles/cloudsql.client, roles/pubsub.publisher, roles/pubsub.subscriber, roles/secretmanager.secretAccessor (per-tenant scoped) |
lock-integration-webhook | sa-lock-integration-webhook@… | roles/secretmanager.secretAccessor for signing-secret refs only |
lock-integration-saga-runner | sa-lock-integration-saga@… | superset of api SA + roles/cloudtasks.enqueuer if scheduled retries used |
| Jobs | sa-lock-integration-job@… | minimal per-job grants; merkle anchor job needs audit-service invoker |
Cross-service calls (e.g., to iam-service or audit-service) use Workload Identity-issued JWTs with aud="svc://<target>".
5. Networking
- All Cloud Run services are internal + cloud load balancing (private), reached only by API Gateway and Pub/Sub push.
- Webhook revision is exposed publicly via a separate Cloud Load Balancer behind Cloud Armor (vendor IP allowlists for TTLock & Salto where stable; mTLS for Vostio).
- Outbound to vendors uses Cloud NAT with a stable egress IP per region — vendors allowlist us.
- Salto on-prem connector reachable via Cloud VPN tunnel to property site; private VPC route.
6. Storage
- Cloud SQL Postgres 15, regional HA, CMEK with
melmastoon-kms/lock-data-{tenant-residency}keys, automatic backups (35d retention, 7d PITR). - Secret Manager per-tenant secrets for vendor creds and per-cloud
lock-offline-issuance-signingKMS asymmetric key. - Pub/Sub topics use ordered delivery with
orderingKey = key_credential_id(ormaster_key_id, orlock_device_iddepending on topic) to preserve per-aggregate causality. - BigQuery sinks for
lock_auditand selected operational events viaanalytics-serviceBQ exporter.
7. Configuration
All non-secret config via environment variables, sourced from Secret Manager refs at boot:
DATABASE_URL_REF=projects/.../secrets/lock-db-dsn/versions/latest
PUBSUB_PROJECT=melmastoon-prod
PUBSUB_TOPIC_PREFIX=melmastoon.lock
TENANT_RESIDENCY_REGION=us-central1
VENDOR_TIMEOUT_MS_DEFAULT=4000
CIRCUIT_BREAKER_OPEN_AFTER_FAILURES=20
CIRCUIT_BREAKER_RESET_AFTER_SECONDS=30
OFFLINE_ISSUANCE_SIGNING_KEY=projects/.../cryptoKeys/lock-offline-issuance-signing/cryptoKeyVersions/1
Per-tenant overrides (e.g., adapter timeout) live in vendor_adapters.config_jsonb.
8. Release process
- Container image built on PR via Cloud Build; tagged
lock-integration:<git-sha>. - Promoted through
staging→prodvia Cloud Deploy. - Each environment runs
db-migrateCloud Run Job before traffic shift. - Traffic shift: 5% → 25% → 100% with 10-min soak at each step; auto-rollback on saga error budget burn alert.
- Webhook revision deploys independently from api revision; saga-runner deploys last (consumes possibly-newer event schemas).
9. Capacity baseline
| Metric | Per cloud baseline (10 tenants × 5 properties × 60 rooms avg) |
|---|---|
RPS to api | ~30 sustained, 200 peak |
| Webhook RPS | ~50 sustained, 600 peak |
| Saga executions/min | ~80 |
| Outbox row rate | ~150/min |
| DB connections | 40 from api, 20 from webhook, 60 from saga-runner via PgBouncer |