Adding a New Chain
No code changes required. HoodCloud is chain-agnostic — adding a chain requires only YAML configuration.Required Files
| File | Purpose |
|---|---|
hoodcloud-chain-configs/chains/{chain}/profile.yaml | Chain identity, node types, resources, providers, sync data, binaries |
hoodcloud-chain-configs/chains/{chain}/runtime.yaml | Node status provider, ports, key paths |
hoodcloud-chain-configs/recipes/{chain}/{nodeType}.yaml | Installation and sync recipe |
hoodcloud-chain-configs/chains/{chain}/observation.yaml | (Optional) Metric collectors and CEL health policies |
hoodcloud-chain-configs/upgrades/{chain}/ | Upgrade manifests — one YAML per binary × node_type, defines target version, artifact URL, checksum, state compatibility |
profile.yaml
Defines chain identity, node types with resource requirements, provider mappings, sync data, and binary versions.runtime.yaml
Declares health check expressions, port mappings, and key paths.observation.yaml (Optional)
Defines metric collectors and CEL health policies. Enables the observation system for this chain.Recipe
Recipes define installation and sync logic. Sync data fromprofile.yaml available via {{.SyncData.xxx}}. Use when conditionals to enable/disable steps.
Key concepts:
- Data in Profile, Logic in Recipe:
profile.yamlholds sync URLs/config, recipes define what to do with them whenconditionals: Steps execute only when condition is truthy (non-empty string)- One recipe per node type: A single
full.yamlhandles multiple sync methods via conditionals - Progress monitoring:
metadata.progress_monitortracks long-running steps (snapshot downloads)
- Systemd-based: Binary download + systemd service (e.g., Cosmos chains)
- Docker-based:
docker_containerstep type with auto-HOME, restart policy, volume/port mapping
hoodcloud-chain-configs/recipes/ for reference implementations.
Adding a New Collector Type
Package:internal/observation/collector/
-
Implement
Collectorinterface: -
Register in
collector/registry.goinsideNewRegistry(): -
Use in chain
observation.yaml:
prometheus, http, script, otlp
Adding a Health Event Handler
Use case: React to node state transitions or dimension changes (custom alerting, audit logging, external webhook).-
Implement the handler interface:
-
Register in bootstrap (
internal/app/bootstrap/):
See also: Health and Incidents — Health Event Outbox for the outbox processing model.
Adding a Runtime Adapter
Package:internal/opsagent/upgrade/adapters/
Runtime adapters implement HOW upgrade actions execute on a specific runtime. Adding a new runtime (e.g., LXC, Podman, Kubernetes) requires only a new adapter — zero changes to actions, executor, or workflows.
-
Implement
RuntimeAdapterinterface ininternal/opsagent/upgrade/adapters/{runtime}.go: -
Register in the adapter factory based on
runtime.typefrom chain config. -
Configure chain profiles with
runtime: { type: lxc }.
adapters/systemd.go), Docker (adapters/docker.go).
Key design rules:
- The adapter handles ALL runtime-specific logic — actions never branch on runtime type
AcquireArtifactreturns an opaqueArtifactHandle(file path for systemd, image ref for Docker)BackupStatewritesmanifest.jsonlast (atomic marker forIsBackedUp())RestoreStatemust be safe to call during compensation (reverse-order rollback)
See also: Workflows — Agent Upgrade Execution for the three-layer architecture.
Adding an Upgrade Action Primitive
Package:internal/opsagent/upgrade/actions/
v1 uses compiled action composition (8 built-in actions). Adding a new action primitive:
-
Implement
UpgradeActioninterface: -
Add to the action sequence in
internal/opsagent/upgrade/sequences.go.
BackupCurrentState, AcquireArtifact, VerifyArtifact, StopNode, InstallArtifact, WriteConfigs, ReloadDaemon, StartNode.
Key rules:
IsAlreadyDone()must check actual system state via theRuntimeAdapter— no shell commandsCompensate()must be safe to call in reverse orderTimeout()returns a per-action time budget enforced by the executor
Adding a New Workflow
Package:internal/workflows/
-
Define workflow function:
-
Register in orchestrator (
cmd/orchestrator/main.go): -
Trigger from API or background job via
temporalClient.ExecuteWorkflow().
See also: Workflows for existing workflow patterns and compensation.
Adding a New Cloud Provider
No code changes required. The Terraform module registry is self-describing.-
Create module directory:
infrastructure/terraform/modules/node-host-{provider}/ -
Create
interface.yaml(self-describing module): -
Create Terraform files:
main.tf,variables.tf,outputs.tf,cloud-init.yaml -
Store credentials in Vault:
vault kv put secret/hoodcloud/providers/{provider} ... -
Update chain profiles to include the new provider in
node_types.{type}.providers.
interface.yaml. Paths derived from naming convention: {modules_dir}/node-host-{provider}. Credentials loaded from registry’s required_env_vars.
Adding a Notification Channel
Package:internal/incident/notifier/
-
Implement
contracts.Notifier: -
Register in bootstrap (
internal/app/bootstrap/incident.go) alongside existing channels.
X-Webhook-Secret).
Notification types: created, escalated, resolved, auto_resolved, flapping, correlated
Built-in features: Rate limiting (per-node, per-user, global sliding window) and persistent retry via notification_outbox apply to all channels automatically.
See also: Health and Incidents — Notification Pipeline for the full dispatch architecture.
Adding a Payment Provider
Package:payment-service/internal/adapters/
- Create adapter package at
payment-service/internal/adapters/<provider>/adapter.go - Implement the
Providerinterface (seepayment-service/internal/adapters/provider.go) - Add provider config in
payment-service/internal/config/config.go - Register in provider factory in
payment-service/cmd/payment-service/main.go - Add webhook/event handler if the provider uses callbacks
payment-service/internal/adapters/tempo/) — blockchain event watcher pattern. Stripe adapter (payment-service/internal/adapters/stripe/) — webhook-based pattern.
See also: Payment Service — Multi-Provider Architecture for the provider interface and existing adapters.
Adding Wallet Types
Use case: Support a new blockchain’s wallet signatures (e.g., Cosmos, Polkadot).-
Add
WalletTypeconstant tointernal/models/wallet.go: -
Implement
contracts.SignatureVerifierininternal/wallet/<chain>/verifier.go: -
Register in bootstrap (
internal/app/bootstrap/services.go): -
Update address detection in
internal/wallet/detect.goif auto-detection is needed.
secp256k1), Solana (Ed25519).
See also: CLAUDE.md — Chain-Agnostic Wallet Operations for the SignatureVerifierRegistry pattern.
Adding a Leader-Gated Component
Package:internal/health/leader.go (generic, reusable)
The leader election implementation is not health-evaluator-specific. Any future singleton component can use it:
-
Create a dedicated advisory lock connection via
internal/app/bootstrap/leader_election.go: -
Create leader elector with a unique lock ID:
-
Gate goroutine loops using the leader context:
- The advisory lock connection MUST be a dedicated
pgx.Conn, not from the pool - Each component needs a unique lock ID to avoid conflicting with the health evaluator’s lock
- Subsystems that are already multi-instance safe (e.g.,
FOR UPDATE SKIP LOCKED) should NOT be leader-gated
See also: Health and Incidents — Leader Election for the health evaluator’s implementation.
Adding New API Endpoints
- Add handler method in
internal/api/(e.g.,handler_node.go) - Register route in
internal/api/router.go - Add service layer logic in
internal/service/ - Apply scope enforcement via
RequireScope()middleware
DualAuthMiddleware), Rate Limiting, Scope Enforcement, CORS.
Scope enforcement (internal/api/scope.go): Both JWT and API key requests are subject to scope checks via RequireScope(). Missing scope returns 403 Forbidden. The admin scope is backend-granted only and required for rollout endpoints and future admin-only operations.
See also: CLAUDE.md — Server Separation for endpoint lists and auth details. For authentication provider setup, see Clerk Setup.
Adding Tracing to New Components
Distributed tracing is automatic for HTTP, gRPC, database, and Temporal activities whenOTEL_ENABLED=true.
For custom spans:
otel.GetTextMapPropagator().Inject(ctx, ...).
RPC Adapters
Package:internal/adapters/rpc/
Generic JSON-RPC types and chain-specific response structures for health data collection.
| File | Purpose |
|---|---|
types.go | JSONRPCResponse[T] — generic JSON-RPC 2.0 response with typed Result |
ethereum.go | EthSyncStatus, EthBlockNumberResponse — Ethereum eth_syncing/eth_blockNumber responses |
cosmos.go | CosmosSyncStatus, CosmosStatusResponse — Cosmos SDK /status response |
Related Documents
- Overview — System overview, service descriptions
- Domain Model — State machines, business rules
- Workflows — Temporal workflows, provisioning inputs
- Health and Incidents — Health pipeline, incidents, notifications, cleanup
- Payment Service — Payment provider architecture
- Developer Guide — Error handling patterns, debugging