feat: inject provisioned credentials into component deployments

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 <noreply@anthropic.com>
This commit is contained in:
jordan 2026-02-05 00:09:15 -07:00
parent 34e12ff3d5
commit 1e853980e4
6 changed files with 132 additions and 1 deletions

View File

@ -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. **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 ## Find Your Guide
| If you need to... | Read this | | If you need to... | Read this |

View File

@ -5,7 +5,9 @@
## Summary ## 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:** **Key Facts:**
- `POST /projects` creates monorepo skeleton (not single template) - `POST /projects` creates monorepo skeleton (not single template)

View File

@ -4,6 +4,8 @@ Quick reference for rdev concepts and facts.
| Topic | File | Confidence | Updated | Summary | | Topic | File | Confidence | Updated | Summary |
|-------|------|------------|---------|---------| |-------|------|------------|---------|---------|
| **Terminology** |
| Platform vs Skeleton | [terminology.md](./terminology.md) | High | 2026-02 | Distinguishing rdev code from generated project code |
| **Architecture** | | **Architecture** |
| Hexagonal Architecture | [patterns/hexagonal.md](./patterns/hexagonal.md) | High | 2025-01 | Ports/adapters pattern, layer separation | | Hexagonal Architecture | [patterns/hexagonal.md](./patterns/hexagonal.md) | High | 2025-01 | Ports/adapters pattern, layer separation |
| **Core Services** | | **Core Services** |

35
ai-lookup/terminology.md Normal file
View File

@ -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

View File

@ -19,19 +19,42 @@ steps:
- project_id: .data.name - project_id: .data.name
- domain: .data.domain - 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: add-auth:
depends_on: [add-db]
action: api action: api
method: POST method: POST
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
body: { type: service, name: "auth-svc" } body: { type: service, name: "auth-svc" }
add-chat: add-chat:
depends_on: [add-redis]
action: api action: api
method: POST method: POST
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
body: { type: service, name: "chat-svc" } body: { type: service, name: "chat-svc" }
add-worker: add-worker:
depends_on: [add-redis]
action: api action: api
method: POST method: POST
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"

View File

@ -32,6 +32,9 @@ func (s *ComponentService) createInitialComponentDeployment(
// Build sibling service URLs for service discovery // Build sibling service URLs for service discovery
siblingServices := s.buildSiblingServiceURLs(ctx, projectID, component.Name) 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{ spec := domain.DeploySpec{
ProjectName: projectID, ProjectName: projectID,
ComponentPath: component.Path, ComponentPath: component.Path,
@ -41,6 +44,7 @@ func (s *ComponentService) createInitialComponentDeployment(
Replicas: 1, Replicas: 1,
BasePath: basePath, BasePath: basePath,
SiblingServices: siblingServices, SiblingServices: siblingServices,
Secrets: secrets,
} }
log := logging.FromContext(ctx).WithService("component") log := logging.FromContext(ctx).WithService("component")
@ -152,3 +156,58 @@ func (s *ComponentService) buildSiblingServiceURLs(ctx context.Context, projectI
func toUpperSnake(s string) string { func toUpperSnake(s string) string {
return strings.ToUpper(strings.ReplaceAll(s, "-", "_")) 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
}