Skip to main content

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

RevisionPurposeMin instancesMaxConcurrencyCPU/RAMEgress
lock-integration-apiREST API, sync handlers2100802 vCPU / 1 GiBCloud NAT, static egress IP
lock-integration-webhookVendor webhook ingress2502001 vCPU / 512 MiBnone
lock-integration-saga-runnerPub/Sub push subscribers (sagas + webhook processor)280202 vCPU / 1 GiBCloud 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

JobSchedulePurpose
lock-merkle-anchordaily 02:00 UTCCompute Merkle root of prior-day lock_audit partition; submit to audit-service
lock-battery-predict-batchdaily 02:30 UTCPer-tenant battery predictions via ai-orchestrator-service
lock-attempt-anomaly-batchhourly :05Per-tenant anomaly batch scoring
lock-vendor-cred-rotationdaily 03:00 UTCIdentify aging vendor creds, rotate per policy
lock-sandbox-conformancenightly 04:00 UTCRun vendor sandbox conformance against TTLock/Salto/Vostio
lock-offline-cert-cleanupdaily 05:00 UTCMove 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

ComponentService AccountKey roles
lock-integration-apisa-lock-integration-api@…roles/cloudsql.client, roles/pubsub.publisher, roles/pubsub.subscriber, roles/secretmanager.secretAccessor (per-tenant scoped)
lock-integration-webhooksa-lock-integration-webhook@…roles/secretmanager.secretAccessor for signing-secret refs only
lock-integration-saga-runnersa-lock-integration-saga@…superset of api SA + roles/cloudtasks.enqueuer if scheduled retries used
Jobssa-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-signing KMS asymmetric key.
  • Pub/Sub topics use ordered delivery with orderingKey = key_credential_id (or master_key_id, or lock_device_id depending on topic) to preserve per-aggregate causality.
  • BigQuery sinks for lock_audit and selected operational events via analytics-service BQ 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 stagingprod via Cloud Deploy.
  • Each environment runs db-migrate Cloud 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

MetricPer 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 connections40 from api, 20 from webhook, 60 from saga-runner via PgBouncer

10. Cross-references