Skip to main content

How to Read This Codebase

  1. Domain modelsinternal/models/node.go, internal/models/subscription.go. Understand state machines and business rules.
  2. Request flowcmd/api-server/main.go -> internal/app/bootstrap/api_server.go -> internal/api/router.go -> internal/api/middleware.go -> handlers -> services -> repositories.
  3. Workflowsinternal/workflows/provision.go, internal/activities/. Understand the Temporal orchestration pattern.
  4. Upgrade rolloutinternal/workflows/rollout.go, internal/opsagent/upgrade/. Understand three-level workflow hierarchy and agent-side three-layer architecture.
  5. Agent communicationproto/agent.proto, internal/grpc/server.go, cmd/ops-agent/main.go. Understand bidirectional gRPC communication.
  6. Health monitoringinternal/health/leader.go -> internal/health/machine.go -> internal/health/heartbeat.go -> internal/health/outbox.go -> internal/incident/service.go -> internal/incident/notifier/. Uptime: internal/uptime/state_log_handler.go -> internal/uptime/worker.go.
  7. Chain configurationhoodcloud-chain-configs/chains/. Understand the declarative, chain-agnostic approach.

Key Files

Payment Service Key Files

Common Patterns

Repository Pattern

All database interactions use repositories defined in internal/contracts/repository.go, implemented in internal/database/. Isolates SQL from business logic, testable with mocks.

Service Layer

Services in internal/service/ coordinate between repositories and external systems. Encapsulates business rules, orchestrates operations.

Temporal Workflows

Durable orchestrations in internal/workflows/. Survive process restarts, automatic retries, compensation on failure. Activities in internal/activities/ are idempotent.

Declarative Configuration

Chains defined in YAML (hoodcloud-chain-configs/), not code. Observation collectors, health policies, and recipes are all configuration-driven.

Event-Driven Architecture

All state transitions and dimension changes emit events via health_event_outbox:
Handlers (incident service, migration handler) subscribe without coupling to evaluation logic.

Graceful Degradation

Optional services degrade gracefully — NATS, telemetry, and other optional subsystems fall back to no-op implementations on initialization failure.

Error Handling

Error Types and Handling

Workflow Error Propagation

Compensation code: internal/workflows/provision.go (compensate()), internal/workflows/migrate.go (compensateMigration()).

HTTP Error Responses

Temporal Retry Policies

Tracing Code Paths

”How does a node get marked as DOWN?”

  1. Health evaluator leader fires every 30s (leader-gated via advisory lock) -> ListSnapshots() (3-way JOIN, includes state_version)
  2. EvaluateHeartbeat() per node — pure function, no I/O. If heartbeat timed out: increment ConsecutiveFailures. If >= ConsecutiveFailuresForDown (3): return transition to DOWN.
  3. NodeHealthMachine.ApplyHeartbeatDecisions() — batch update with optimistic locking on both node_health_state.version and nodes.state_version. Inserts HealthTransitionEvent into health_event_outbox.
  4. OutboxWorker (NOT leader-gated, uses FOR UPDATE SKIP LOCKED) dispatches to CompositeTransitionHandler -> incident service creates node_down incident -> migration handler evaluates grace period and cooldown, triggers migration with deterministic workflow ID (migrate-node-{nodeID}-{healthStateVersion}).

”How does a node get upgraded?”

  1. Operator creates rollout via POST /api/v1/rollouts — either with upgrade_id (auto mode: manifest auto-populates binary details, state compatibility, config changes) or with all fields explicit (manual mode)
  2. Operator starts rollout via POST /api/v1/rollouts/{id}/start
  3. RolloutWorkflow launched in Temporal → ResolveRolloutTargets assigns nodes to batches
  4. Per batch: launches UpgradeNodeWorkflow children in parallel
  5. UpgradeNodeWorkflow: PreCheckNodeUpdateNodeState(maintenance)SendUpgradeCommand
  6. Agent receives COMMAND_TYPE_UPGRADE (value 9) in heartbeat → executor runs 8 actions via RuntimeAdapter
  7. WaitForCommandCompletionUpdateNodeBinaryVersionWaitForHealthValidationUpdateNodeState(syncing)
  8. On failure: agent runs per-action compensation (reverse order), workflow marks node failed/rolled_back

”How does uptime get computed?”

  1. State transition emitted by NodeHealthMachine -> health_event_outbox
  2. OutboxWorker dispatches to CompositeTransitionHandler -> StateLogHandler.OnTransition() closes previous node_state_log entry, inserts new entry
  3. UptimeWorker (5min cycle) finds last complete bucket per node, computes hourly buckets from node_state_log entries, upserts into node_uptime_hourly
  4. API request: UptimeService queries node_uptime_hourly with SUM(uptime_seconds) / SUM(total_seconds) over requested window

”How does an agent send a heartbeat?”

  1. Agent heartbeat loop (15s) -> sendHeartbeat()
  2. gRPC ControlPlaneService.Heartbeat() with node_id
  3. Control plane updates agent_registrations.last_seen_at, returns pending commands from Redis queue
  4. Agent executes commands, reports results via ReportEvent()

Debugging Tips

Database Migrations

Migrations are handled by the dedicated cmd/migrate binary, not at service startup. Run as a pre-deploy step:
Services no longer run migrations at startup. If a service starts before migrations are applied, it will fail on schema mismatches.

Leader Election

Logging

Structured logs include request IDs, node IDs, timestamps.

Database Guardrails

Per-service connection pools and statement timeouts are configured to prevent resource exhaustion: Observability: A pgx query tracer emits db_query_duration_seconds histograms per service. Statement timeout errors are counted via db_statement_timeout_total.

Database Queries

Distributed Traces (Tempo)

When OTEL_ENABLED=true:
  1. Open Grafana -> Explore -> Tempo
  2. Search by service name or trace ID
  3. TraceQL queries: {resource.service.name="api-server"}, {status=error}
Tempo is configured with log correlation — click “Logs for this span” to jump to Loki logs.

Temporal Workflows

  1. Open Temporal UI: http://localhost:8233
  2. Search for workflow ID: provision-{uuid}
  3. View execution history (activities, retries, errors)

Redis Command Queue

NATS Streams

Victoria Metrics

Terraform State

Status Page (Gatus)

http://localhost:8081 — health dashboard for all services, infrastructure, and observability stack.

Running Tests

Unit Tests

Integration Tests

Uses testcontainers (Docker required):
Available suites: repository_test.go (NodeRepository CRUD, state transitions), commandqueue_test.go (ProgressStore, stall detection).

E2E Tests

Requires full Docker Compose environment:

Payment Service Tests