This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Setup and build
make setup # Download dependencies and setup environment
make build-local # Build for local development
make run # Run service locally with environment setup
# Code quality (run before committing)
make lint # Run golangci-lint (install if needed)
make test # Run all tests with race detection and coverage
make quality # Run fmt, vet, lint, and test together
# Layer-specific testing
make test-domain # Test business logic only
make test-infrastructure # Test external integrations
make test-application # Test workflow coordination
make test-presentation # Test message handlers
# Production builds
make build # Build for Linux deployment (Docker)
make docker-build # Build container image
make helm-install # Deploy using Helm chart# Configuration validation
./bin/lfx-indexer -check-config
# Health endpoints
curl http://localhost:8080/livez # Kubernetes liveness probe
curl http://localhost:8080/readyz # Kubernetes readiness probe
curl http://localhost:8080/health # General health status
# Debug logging
LOG_LEVEL=debug make runThis is a Clean Architecture implementation processing NATS messages into OpenSearch documents. The service handles both modern V2 messages (lfx.index.*) and legacy V1 messages (lfx.v1.index.*) with queue group load balancing.
-
Domain Layer (
internal/domain/): Pure business logic and interfacescontracts/: Core entities (LFXTransaction,TransactionBody) and repository interfacesservices/indexer_service.go: Central business logic (1100+ lines)
-
Application Layer (
internal/application/): Use case orchestrationmessage_processor.go: Coordinates V2/V1 message processing workflows
-
Infrastructure Layer (
internal/infrastructure/): External service adaptersmessaging/: NATS client with queue group supportstorage/: OpenSearch document indexingauth/: JWT validation against Heimdall servicecleanup/: Background conflict resolution
-
Presentation Layer (
internal/presentation/handlers/): Protocol concernsindexing_message_handler.go: NATS message handlinghealth_handler.go: Kubernetes health endpoints
NATS → MessagingRepository → IndexingMessageHandler → MessageProcessor → IndexerService → [Enricher] → OpenSearch
↓
NATS Event Published
lfx.{object_type}.{action}
After every successful OpenSearch write, the service publishes a NATS event. The subject is dynamic based on the object type and action — every object type the indexer handles (project, committee, meeting, etc.) automatically gets its own set of event subjects.
Subject format: lfx.{object_type}.{action}
Examples for project (same pattern applies to all object types):
lfx.project.createdlfx.project.updatedlfx.project.deleted
Useful wildcard subscriptions:
lfx.project.*— all actions for a specific typelfx.*.created— created events across all types
Payload (internal/domain/contracts/events.go — IndexingEvent):
{
"document_id": "project:abc-123",
"object_id": "abc-123",
"object_type": "project",
"action": "created",
"timestamp": "2026-03-05T19:57:25.679Z",
"body": { ... }
}body is the full TransactionBody written to OpenSearch. Publish failures are non-blocking — the OpenSearch write is unaffected and the error is logged.
Message Routing: Subject prefix determines version (lfx.index.* = V2, lfx.v1.index.* = V1)
Enricher System: Extensible data processing based on object type
- Current:
ProjectEnricher,ProjectSettingsEnricher - Register new enrichers in
IndexerService.NewIndexerService()
Authentication:
- V2: JWT tokens via
Authorizationheader (validated against Heimdall) - V1: Simple
X-Username/X-Emailheaders - Delegation:
X-On-Behalf-Ofsupport
Configuration: CLI flags > Environment variables > Defaults
# Core services (required)
NATS_URL=nats://nats:4222
OPENSEARCH_URL=http://localhost:9200
JWKS_URL=http://localhost:4457/.well-known/jwks
# Message processing
NATS_QUEUE=lfx.indexer.queue
OPENSEARCH_INDEX=resources
NATS_INDEXING_SUBJECT=lfx.index.>
NATS_V1_INDEXING_SUBJECT=lfx.v1.index.>
# Optional configuration
PORT=8080
LOG_LEVEL=info
JANITOR_ENABLED=true- Domain layer cannot import from infrastructure/presentation layers
- All external dependencies must use repository interfaces
- Business logic stays in domain services
- Dependency injection only in
cmd/lfx-indexer/main.go
- Add constant to
pkg/constants/messaging.go - Create enricher in
internal/enrichers/implementingEnricherinterface - Register enricher in
IndexerService.NewIndexerService() - Add tests for the new enricher
Domain events (lfx.{object_type}.created/updated/deleted) are emitted automatically for all object types — no additional work required.
- Always reply to NATS messages with "OK" or "ERROR: details"
- Log comprehensive context for debugging (action, object_type, message_id)
- Handle both V2 (past-tense actions) and V1 (present-tense actions) formats
- Support base64-encoded data fields
- Use layer-specific make targets for focused testing
- Mock external dependencies using interfaces in
internal/mocks/ - Test both V2 and V1 message formats where applicable
- Include race detection in all test runs
internal/domain/services/indexer_service.go: Core business logicinternal/application/message_processor.go: Message workflow orchestrationinternal/domain/contracts/transaction.go: Core business entitiesinternal/domain/contracts/events.go:IndexingEvent— the outbound domain event payloadinternal/enrichers/registry.go: Enricher registration and lookupinternal/infrastructure/config/app_config.go: Configuration managementcmd/lfx-indexer/main.go: Dependency injection and service startup