Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 87 additions & 5 deletions .specify/memory/constitution.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
<!--
Sync Impact Report - Constitution Update
Version: 1.0.0
Last Updated: 2025-11-13
Version: 1.0.0 → 1.1.0 (MINOR)
Last Updated: 2026-02-16

Changelog (v1.1.0):
- ADDED: Principle XI: Feature Flag Discipline
* All new features MUST be gated behind a feature flag
* Flag naming conventions and lifecycle management
* Default-off deployment strategy for production safety
* Cleanup requirements for graduated flags
- Version bump: v1.0.0 → v1.1.0 (new principle added)

Changelog (v1.0.0):
- RATIFIED: Constitution officially ratified and adopted
Expand Down Expand Up @@ -40,7 +48,7 @@ Changelog (v0.0.1):
Templates Status:
✅ plan-template.md - References constitution check dynamically
✅ tasks-template.md - Added Phase 3.9 for commit planning/validation (T036-T040)
✅ spec-template.md - No updates needed
✅ spec-template.md - No updates needed (feature flag requirement applies at implementation)

Follow-up TODOs:
- Implement /metrics endpoints in all components
Expand All @@ -49,6 +57,7 @@ Follow-up TODOs:
- Add commit size validation tooling (pre-commit hook or CI check)
- Update PR template to include commit discipline checklist
- Continue vTeam → ACP migration incrementally (docs → UI → code)
- Document feature flag naming conventions in developer guide
-->

# ACP Constitution
Expand Down Expand Up @@ -255,6 +264,54 @@ Each commit MUST be atomic, reviewable, and independently testable:

**Rationale**: Large commits hide bugs, slow reviews, complicate bisecting, and create merge conflicts. Specific thresholds provide objective guidance while exceptions handle legitimate cases. Small, focused commits enable faster feedback, easier debugging (git bisect), and safer reverts.

### XI. Feature Flag Discipline

All new features MUST be gated behind feature flags:

**Mandatory Requirements**:

- **Flag Gating**: Every new user-facing feature MUST be controlled by a feature flag
- **Default Off**: Feature flags MUST default to `false` (disabled) in production
- **Gradual Rollout**: Features MUST support percentage-based or tenant-based rollout
- **Kill Switch**: Every feature flag MUST act as an instant kill switch for its feature

**Flag Naming Convention**:

- Format: `<component>.<feature>.<aspect>` (e.g., `backend.rfe-workflow.enabled`)
- Use lowercase with hyphens for multi-word names
- Include `enabled` suffix for on/off toggles
- Include `percentage` suffix for gradual rollouts

**Implementation Requirements**:

- **Backend**: Check flag before executing feature logic; return 404 or appropriate error when disabled
- **Frontend**: Conditionally render UI components; hide disabled features from navigation
- **Operator**: Skip reconciliation for flagged features when disabled
- **API Contracts**: Flagged endpoints MUST return consistent error responses when feature is disabled

**Flag Lifecycle**:

1. **Development**: Flag created, defaults to `false`
2. **Testing**: Flag enabled in test/staging environments
3. **Rollout**: Gradual enablement (10% → 50% → 100%) in production
4. **Graduation**: Flag removed after feature is stable (minimum 2 weeks at 100%)
5. **Cleanup**: Dead flag code MUST be removed within 30 days of graduation

**Flag Storage**:

- Flags stored in ProjectSettings CR for project-scoped features
- Cluster-wide flags in ConfigMap `ambient-code-feature-flags`
- Frontend flags fetched via `/api/feature-flags` endpoint with caching

**Exceptions** (requires justification in PR):

- **Bug Fixes**: Critical bug fixes do not require flags
- **Internal Refactoring**: Non-user-facing changes do not require flags
- **Infrastructure**: Logging, metrics, observability changes do not require flags
- **Security Patches**: Security fixes MUST NOT be gated (immediate deployment required)

**Rationale**: Feature flags enable safe deployments, instant rollback without redeploy, A/B testing, and gradual rollouts. They decouple deployment from release, reducing blast radius of bugs and enabling faster iteration. Flags that linger become technical debt, hence strict cleanup requirements.

## Development Standards

### Go Code (Backend & Operator)
Expand All @@ -274,6 +331,12 @@ Each commit MUST be atomic, reviewable, and independently testable:
- Status updates: Use `UpdateStatus` subresource
- Watch loops: Reconnect on channel close with backoff

**Feature Flags**: See Principle XI: Feature Flag Discipline

- Import feature flag package: `featureflags`
- Check flags early in handler: `if !featureflags.IsEnabled(ctx, "feature.name") { return }`
- Include flag name in structured logs when feature is disabled

### Frontend Code (NextJS)

**UI Components**:
Expand All @@ -295,6 +358,12 @@ Each commit MUST be atomic, reviewable, and independently testable:
- All routes MUST have `page.tsx`, `loading.tsx`, `error.tsx`
- Components over 200 lines MUST be broken down

**Feature Flags**: See Principle XI: Feature Flag Discipline

- Use `FeatureFlagProvider` context for flag access
- Conditionally render with `<FeatureFlag name="feature.name">` wrapper
- Hide navigation items for disabled features

### Python Code (Runner)

**Environment**:
Expand Down Expand Up @@ -368,6 +437,12 @@ npm run build # Must pass with 0 errors, 0 warnings
- Drop all capabilities by default
- Use non-root users where possible

**Feature Flag Verification**:

- Verify new features are gated behind flags
- Confirm flags default to `false` in production manifests
- Test feature behavior with flag both enabled and disabled

### Production Requirements

**Security**: Apply Principle II security requirements. Additionally: Scan container images for vulnerabilities before deployment.
Expand All @@ -382,6 +457,13 @@ npm run build # Must pass with 0 errors, 0 warnings
- Design for multi-tenancy with shared infrastructure
- Do not use etcd as a database for unbounded objects like CRs. Use an external database like Postgres.

**Feature Flags**:

- All new features deployed with flags defaulting to `false`
- Gradual rollout via flag percentage or tenant targeting
- Monitor error rates and latency during rollout
- Maintain rollback capability via flag disable

## Governance

### Amendment Process
Expand All @@ -404,6 +486,7 @@ npm run build # Must pass with 0 errors, 0 warnings
- Pre-commit checklists MUST be followed for backend, frontend, and operator code
- Complexity violations MUST be justified in implementation plans
- Constitution supersedes all other practices and guidelines
- New features MUST include feature flag implementation per Principle XI

### Development Guidance

Expand All @@ -413,5 +496,4 @@ Runtime development guidance is maintained in:
- Component-specific README files
- MkDocs documentation in `/docs`

**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE]
<!-- Example: Version: 2.1.1 | Ratified: 2025-06-13 | Last Amended: 2025-07-16 -->
**Version**: 1.1.0 | **Ratified**: 2025-11-13 | **Last Amended**: 2026-02-16
42 changes: 42 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
.PHONY: local-test local-test-dev local-test-quick test-all local-url local-troubleshoot local-port-forward local-stop-port-forward
.PHONY: push-all registry-login setup-hooks remove-hooks check-minikube check-kind check-kubectl dev-bootstrap
.PHONY: e2e-test e2e-setup e2e-clean deploy-langfuse-openshift
.PHONY: deploy-unleash deploy-unleash-kind deploy-unleash-openshift unleash-port-forward unleash-status unleash-clean
.PHONY: setup-minio minio-console minio-logs minio-status
.PHONY: validate-makefile lint-makefile check-shell makefile-health
.PHONY: _create-operator-config _auto-port-forward _show-access-info _build-and-load
Expand Down Expand Up @@ -668,6 +669,47 @@ deploy-langfuse-openshift: ## Deploy Langfuse to OpenShift/ROSA cluster
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Deploying Langfuse to OpenShift cluster..."
@cd e2e && ./scripts/deploy-langfuse.sh --openshift

##@ Unleash Feature Flags

deploy-unleash: ## Deploy Unleash (auto-detect platform)
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Deploying Unleash feature flag server..."
@./e2e/scripts/deploy-unleash.sh
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Unleash deployed"

deploy-unleash-kind: check-kind check-kubectl ## Deploy Unleash to kind cluster
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Deploying Unleash to kind cluster..."
@./e2e/scripts/deploy-unleash.sh --kubernetes
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Unleash deployed to kind"
@echo ""
@echo "$(COLOR_BOLD)Next steps:$(COLOR_RESET)"
@echo " 1. Run: $(COLOR_BLUE)make unleash-port-forward$(COLOR_RESET)"
@echo " 2. Access Unleash UI at: http://localhost:4242"
@echo " 3. Login: admin / unleash4all"

deploy-unleash-openshift: ## Deploy Unleash to OpenShift/CRC cluster
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Deploying Unleash to OpenShift cluster..."
@./e2e/scripts/deploy-unleash.sh --openshift
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Unleash deployed to OpenShift"

unleash-port-forward: check-kubectl ## Port-forward Unleash (localhost:4242)
@echo "$(COLOR_BOLD)🔌 Port forwarding Unleash$(COLOR_RESET)"
@echo ""
@echo " Unleash UI: http://localhost:4242"
@echo " Login: admin / unleash4all"
@echo ""
@echo "$(COLOR_YELLOW)Press Ctrl+C to stop$(COLOR_RESET)"
@kubectl port-forward svc/unleash 4242:4242 -n unleash

unleash-status: check-kubectl ## Show Unleash deployment status
@echo "$(COLOR_BOLD)Unleash Status$(COLOR_RESET)"
@kubectl get deployment,pod,svc -l 'app.kubernetes.io/name in (unleash,postgresql)' -n unleash 2>/dev/null || \
echo "$(COLOR_RED)✗$(COLOR_RESET) Unleash not found. Run 'make deploy-unleash' first."

unleash-clean: check-kubectl ## Remove Unleash deployment
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Removing Unleash..."
@kubectl delete namespace unleash --ignore-not-found=true --timeout=60s
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Unleash removed"

##@ Internal Helpers (do not call directly)

check-minikube: ## Check if minikube is installed
Expand Down
6 changes: 6 additions & 0 deletions components/backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ make deps-verify # Verify dependencies
make check-env # Verify Go, kubectl, docker installed
```

### Feature flags (Unleash)

See [docs/feature-flags](../../docs/feature-flags/README.md) for env vars, handler usage, and examples.

## Architecture

See `CLAUDE.md` in project root for:
Expand All @@ -193,6 +197,8 @@ See `CLAUDE.md` in project root for:
- `handlers/sessions.go` - AgenticSession lifecycle, user/SA client usage
- `handlers/middleware.go` - Auth patterns, token extraction, RBAC
- `handlers/helpers.go` - Utility functions (StringPtr, BoolPtr)
- `handlers/featureflags.go` - Feature flag helpers (see docs/feature-flags/)
- `featureflags/featureflags.go` - Unleash client init
- `types/common.go` - Type definitions
- `server/server.go` - Server setup, middleware chain, token redaction
- `routes.go` - HTTP route definitions and registration
63 changes: 63 additions & 0 deletions components/backend/featureflags/featureflags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Package featureflags provides optional Unleash-backed feature flag checks for the backend.
// When UNLEASH_URL and UNLEASH_CLIENT_KEY are not set, all flags are disabled (IsEnabled returns false).
package featureflags

import (
"log"
"net/http"
"os"
"strings"

"github.com/Unleash/unleash-go-sdk/v5"
unleashContext "github.com/Unleash/unleash-go-sdk/v5/context"
)

const appName = "ambient-code-backend"

var initialized bool

// Init initializes the Unleash client when UNLEASH_URL and UNLEASH_CLIENT_KEY are set.
// Safe to call multiple times; only initializes once when config is present.
// Call from main after loading env and before starting the server.
func Init() {
url := strings.TrimSpace(os.Getenv("UNLEASH_URL"))
clientKey := strings.TrimSpace(os.Getenv("UNLEASH_CLIENT_KEY"))
if url == "" || clientKey == "" {
return
}
// Ensure URL has a trailing slash for the SDK
if !strings.HasSuffix(url, "/") {
url += "/"
}
unleash.Initialize(
unleash.WithAppName(appName),
unleash.WithUrl(url),
unleash.WithCustomHeaders(http.Header{"Authorization": {clientKey}}),
)
initialized = true
log.Printf("Unleash feature flags enabled (url=%s)", strings.TrimSuffix(url, "/"))
}

// IsEnabled returns true if the named feature flag is enabled.
// When Unleash is not configured, returns false. Safe to call from any handler.
func IsEnabled(flagName string) bool {
if !initialized {
return false
}
return unleash.IsEnabled(flagName)
}

// IsEnabledWithContext returns true if the flag is enabled for the given user context.
// Use for strategies that depend on userId, sessionId, or remoteAddress.
// When Unleash is not configured, returns false.
func IsEnabledWithContext(flagName string, userID, sessionID, remoteAddress string) bool {
if !initialized {
return false
}
ctx := unleashContext.Context{
UserId: userID,
SessionId: sessionID,
RemoteAddress: remoteAddress,
}
return unleash.IsEnabled(flagName, unleash.WithContext(ctx))
}
4 changes: 4 additions & 0 deletions components/backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.24.0
toolchain go1.24.7

require (
github.com/Unleash/unleash-go-sdk/v5 v5.1.0
github.com/anthropics/anthropic-sdk-go v1.2.0
github.com/gin-contrib/cors v1.7.6
github.com/gin-gonic/gin v1.10.1
Expand Down Expand Up @@ -54,6 +55,7 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/launchdarkly/eventsource v1.10.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
Expand All @@ -64,11 +66,13 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/twmb/murmur3 v1.1.8 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opencensus.io v0.24.0 // indirect
Expand Down
14 changes: 14 additions & 0 deletions components/backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykW
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Unleash/unleash-go-sdk/v5 v5.1.0 h1:W+HHQklU5/H9kjYTn/T4TKvDHE0BxnZ0+MyTk06RdYw=
github.com/Unleash/unleash-go-sdk/v5 v5.1.0/go.mod h1:1u8BfdyjlkV5j43la61n9A9ul4E+YQC2kKQotz8z7BE=
github.com/anthropics/anthropic-sdk-go v1.2.0 h1:RQzJUqaROewrPTl7Rl4hId/TqmjFvfnkmhHJ6pP1yJ8=
github.com/anthropics/anthropic-sdk-go v1.2.0/go.mod h1:AapDW22irxK2PSumZiQXYUFvsdQgkwIWlpESweWZI/c=
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
Expand Down Expand Up @@ -116,6 +118,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
Expand All @@ -137,6 +143,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/launchdarkly/eventsource v1.10.0 h1:H9Tp6AfGu/G2qzBJC26iperrvwhzdbiA/gx7qE2nDFI=
github.com/launchdarkly/eventsource v1.10.0/go.mod h1:J3oa50bPvJesZqNAJtb5btSIo5N6roDWhiAS3IpsKck=
github.com/launchdarkly/go-test-helpers/v3 v3.1.0 h1:E3bxJMzMoA+cJSF3xxtk2/chr1zshl1ZWa0/oR+8bvg=
github.com/launchdarkly/go-test-helpers/v3 v3.1.0/go.mod h1:Ake5+hZFS/DmIGKx/cizhn5W9pGA7pplcR7xCxWiLIo=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
Expand All @@ -155,6 +165,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/onsi/ginkgo/v2 v2.27.3 h1:ICsZJ8JoYafeXFFlFAG75a7CxMsJHwgKwtO+82SE9L8=
github.com/onsi/ginkgo/v2 v2.27.3/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM=
Expand Down Expand Up @@ -194,6 +206,8 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
Expand Down
Loading
Loading