From 1e853980e4b52719086845872057dfd72878d91b Mon Sep 17 00:00:00 2001 From: jordan Date: Thu, 5 Feb 2026 00:09:15 -0700 Subject: [PATCH] feat: inject provisioned credentials into component deployments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Components now automatically receive DATABASE_URL, REDIS_URL, and other infrastructure credentials when deployed. Previously, credentials were provisioned and stored but never injected into K8s deployments. Changes: - Add fetchProjectCredentials() to component_deploy.go - Populate spec.Secrets before calling deployer.Deploy() - Fix slackpath-4 to provision postgres + redis before services - Add terminology docs to clarify platform vs skeleton code This completes the infrastructure provisioning flow: 1. add-db → provisions CockroachDB, stores DATABASE_URL 2. add-service → deploys with DATABASE_URL in environment Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 10 ++++ ai-lookup/features/composable-monorepo.md | 4 +- ai-lookup/index.md | 2 + ai-lookup/terminology.md | 35 +++++++++++ ...lackpath-4-microservice-constellation.yaml | 23 ++++++++ internal/service/component_deploy.go | 59 +++++++++++++++++++ 6 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 ai-lookup/terminology.md diff --git a/CLAUDE.md b/CLAUDE.md index fa78a50..46d9b4e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,6 +4,16 @@ Run Claude Code instances in isolated Kubernetes pods with REST API control. Ena **Platform:** threesix.ai - Agent-driven development at scale with shared worker pools. +## Terminology + +| Term | Meaning | Location | +|------|---------|----------| +| **platform** | rdev itself (orchestrator API, handlers, workers) | `cmd/rdev-api/`, `internal/`, `pkg/api/` | +| **skeleton** | Code that ships in generated projects | `internal/adapter/templates/templates/skeleton/` | +| **component templates** | Service/worker/app/cli templates added to skeleton | `templates/components/{service,worker,cli,app-*}/` | + +When discussing code: "add to **platform**" = edit rdev; "add to **skeleton**" = edit project templates. + ## Find Your Guide | If you need to... | Read this | diff --git a/ai-lookup/features/composable-monorepo.md b/ai-lookup/features/composable-monorepo.md index 005c371..5be1682 100644 --- a/ai-lookup/features/composable-monorepo.md +++ b/ai-lookup/features/composable-monorepo.md @@ -5,7 +5,9 @@ ## Summary -Composable Monorepo Templates evolve rdev's project scaffolding from single templates to full monorepo architecture. Every project starts as a monorepo skeleton, with components (services, workers, apps, cli) added via API calls. Deployment can target the whole monorepo or individual components. +Composable Monorepo Templates evolve rdev's project scaffolding from single templates to full monorepo architecture. Every project starts with the **skeleton** (monorepo base), with **component templates** (services, workers, apps, cli) added via API calls. Deployment can target the whole monorepo or individual components. + +**Terminology:** "skeleton" = monorepo base code; "platform" = rdev itself. See [terminology.md](../terminology.md). **Key Facts:** - `POST /projects` creates monorepo skeleton (not single template) diff --git a/ai-lookup/index.md b/ai-lookup/index.md index 1ae2157..cc53c21 100644 --- a/ai-lookup/index.md +++ b/ai-lookup/index.md @@ -4,6 +4,8 @@ Quick reference for rdev concepts and facts. | Topic | File | Confidence | Updated | Summary | |-------|------|------------|---------|---------| +| **Terminology** | +| Platform vs Skeleton | [terminology.md](./terminology.md) | High | 2026-02 | Distinguishing rdev code from generated project code | | **Architecture** | | Hexagonal Architecture | [patterns/hexagonal.md](./patterns/hexagonal.md) | High | 2025-01 | Ports/adapters pattern, layer separation | | **Core Services** | diff --git a/ai-lookup/terminology.md b/ai-lookup/terminology.md new file mode 100644 index 0000000..4d88317 --- /dev/null +++ b/ai-lookup/terminology.md @@ -0,0 +1,35 @@ +# rdev Terminology + +**Last Updated:** 2026-02-05 +**Confidence:** High + +## Summary + +Distinguishing between rdev code (the platform) and generated project code (skeleton) prevents confusion when discussing implementations. + +## Core Terms + +| Term | Definition | Code Location | +|------|------------|---------------| +| **platform** | rdev API itself—handlers, services, adapters, workers | `cmd/rdev-api/`, `internal/`, `pkg/api/` | +| **skeleton** | Monorepo base that ships in every generated project | `templates/skeleton/` | +| **component templates** | Templates for services, workers, apps, CLIs added to skeleton | `templates/components/` | +| **generated project** | A project created by rdev (contains skeleton + components) | Lives in Gitea repos | + +## Usage Examples + +- "Add auth middleware to **platform**" → edit `internal/auth/` +- "Add auth middleware to **skeleton**" → edit `templates/skeleton/pkg/auth/` +- "Platform's work queue" → `internal/adapter/postgres/work_queue.go` +- "Skeleton's job queue" → `templates/skeleton/pkg/queue/` (planned) + +## File Pointers + +- Platform entry: `cmd/rdev-api/main.go` +- Skeleton root: `internal/adapter/templates/templates/skeleton/` +- Component registry: `internal/adapter/templates/registry.go` + +## Related + +- [Composable Monorepo](./features/composable-monorepo.md) - Skeleton + component architecture +- [Template Provider](./services/template-provider.md) - How templates are seeded diff --git a/cookbooks/trees/slackpath-4-microservice-constellation.yaml b/cookbooks/trees/slackpath-4-microservice-constellation.yaml index 828beab..472e941 100644 --- a/cookbooks/trees/slackpath-4-microservice-constellation.yaml +++ b/cookbooks/trees/slackpath-4-microservice-constellation.yaml @@ -19,19 +19,42 @@ steps: - project_id: .data.name - domain: .data.domain + add-db: + description: Add CockroachDB for user/auth storage + depends_on: [create-project] + action: api + method: POST + endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" + body: + type: postgres + name: "main-db" + + add-redis: + description: Add Redis for job queue and pub/sub + depends_on: [create-project] + action: api + method: POST + endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" + body: + type: redis + name: "job-queue" + add-auth: + depends_on: [add-db] action: api method: POST endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" body: { type: service, name: "auth-svc" } add-chat: + depends_on: [add-redis] action: api method: POST endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" body: { type: service, name: "chat-svc" } add-worker: + depends_on: [add-redis] action: api method: POST endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" diff --git a/internal/service/component_deploy.go b/internal/service/component_deploy.go index c274005..6382130 100644 --- a/internal/service/component_deploy.go +++ b/internal/service/component_deploy.go @@ -32,6 +32,9 @@ func (s *ComponentService) createInitialComponentDeployment( // Build sibling service URLs for service discovery siblingServices := s.buildSiblingServiceURLs(ctx, projectID, component.Name) + // Fetch project credentials (DATABASE_URL, REDIS_URL, etc.) from credential store + secrets := s.fetchProjectCredentials(ctx, projectID) + spec := domain.DeploySpec{ ProjectName: projectID, ComponentPath: component.Path, @@ -41,6 +44,7 @@ func (s *ComponentService) createInitialComponentDeployment( Replicas: 1, BasePath: basePath, SiblingServices: siblingServices, + Secrets: secrets, } log := logging.FromContext(ctx).WithService("component") @@ -152,3 +156,58 @@ func (s *ComponentService) buildSiblingServiceURLs(ctx context.Context, projectI func toUpperSnake(s string) string { return strings.ToUpper(strings.ReplaceAll(s, "-", "_")) } + +// fetchProjectCredentials retrieves stored infrastructure credentials for a project. +// Returns a map of env var names to values (e.g., {"DATABASE_URL": "postgres://...", "REDIS_URL": "redis://..."}). +// Missing credentials are silently skipped - not all projects have all infrastructure. +func (s *ComponentService) fetchProjectCredentials(ctx context.Context, projectID string) map[string]string { + if s.credentialStore == nil { + return nil + } + + // List of infrastructure credentials to fetch + credentialKeys := []string{ + "DATABASE_URL", + "DATABASE_URL_STAGING", + "REDIS_URL", + "REDIS_URL_STAGING", + "REDIS_PREFIX", + } + + // Build scoped keys: "{projectID}:{key}" + scopedKeys := make([]string, len(credentialKeys)) + for i, key := range credentialKeys { + scopedKeys[i] = projectID + ":" + key + } + + // Fetch all credentials in one call + creds, err := s.credentialStore.GetMultiple(ctx, scopedKeys) + if err != nil { + log := logging.FromContext(ctx).WithService("component") + log.Warn("failed to fetch project credentials", + logging.FieldProjectID, projectID, + logging.FieldError, err, + ) + return nil + } + + // Convert scoped keys back to env var names + secrets := make(map[string]string) + for scopedKey, value := range creds { + // Extract env var name from scoped key: "myproject:DATABASE_URL" -> "DATABASE_URL" + parts := strings.SplitN(scopedKey, ":", 2) + if len(parts) == 2 && value != "" { + secrets[parts[1]] = value + } + } + + if len(secrets) > 0 { + log := logging.FromContext(ctx).WithService("component") + log.Debug("fetched project credentials for deployment", + logging.FieldProjectID, projectID, + "credential_count", len(secrets), + ) + } + + return secrets +}