diff --git a/.ai/ai-root-instructions.md b/.ai/ai-root-instructions.md index 1e39212..ad9fe95 100644 --- a/.ai/ai-root-instructions.md +++ b/.ai/ai-root-instructions.md @@ -1,6 +1,6 @@ # AI Assistant Guidelines -**Updated**: 2026-03-02 +**Updated**: 2026-03-04 --- @@ -57,6 +57,10 @@ This main file references specialized instruction files. **Load relevant files b - [Mermaid](instructions/skills/mermaid.instructions.md) - Diagram types, color contrast rules, sizing - [Analysis](instructions/skills/analysis.instructions.md) - Where to write analysis, tmp/ convention, full-pass writing allowed +- [Helm](instructions/skills/helm.instructions.md) - Resource ownership, values hygiene, required vs defaults, dependency caching +- [Clean Code](instructions/skills/clean-code.instructions.md) - Naming, functions, classes, error handling, formatting, DRY/KISS/SOLID, Helm YAML +- [Clean Architecture](instructions/skills/clean-architecture.instructions.md) - Dependency rule, layers, boundaries, SOLID, Helm as outermost layer + ### constraints/ — Load When Needed - [Agent Capabilities](instructions/constraints/agent-capabilities.instructions.md) - AI limitations, user responsibilities, debugging workflows - [Kubernetes Access](instructions/constraints/kubernetes-access.instructions.md) - kubectl/helm restrictions, port-forwarding patterns @@ -77,11 +81,15 @@ User asks for analysis/comparison → instructions/skills/analysis.instructio User asks about project → instructions/behavior/project-context.instructions.md → docs/ai-context.md User needs to debug cluster → instructions/constraints/agent-capabilities.instructions.md + instructions/constraints/kubernetes-access.instructions.md User reports pod issues → instructions/constraints/container-limitations.instructions.md +User works with Helm charts → instructions/skills/helm.instructions.md +User writes or reviews code → instructions/skills/clean-code.instructions.md +User designs a system or service → instructions/skills/clean-architecture.instructions.md +User creates Helm charts or YAML → instructions/skills/clean-code.instructions.md + instructions/skills/helm.instructions.md + instructions/skills/clean-architecture.instructions.md Always active → instructions/behavior/core-principles.instructions.md ``` --- -**Last Updated**: 2026-03-02 +**Last Updated**: 2026-03-04 **Maintained By**: Project Owner **AI Assistants**: Follow these guidelines strictly - no exceptions diff --git a/.ai/instructions/skills/clean-architecture.instructions.md b/.ai/instructions/skills/clean-architecture.instructions.md new file mode 100644 index 0000000..5f8c3cc --- /dev/null +++ b/.ai/instructions/skills/clean-architecture.instructions.md @@ -0,0 +1,84 @@ +# Clean Architecture + +Based on Robert C. Martin's *Clean Architecture*. Apply these principles when designing systems, services, and infrastructure — including Helm chart structure and Kubernetes deployments. + +--- + +## The Dependency Rule + +**Source code dependencies point only inward.** Inner layers define interfaces; outer layers implement them. + +``` +[ Frameworks & Drivers ] ← outermost: UI, DB, APIs, Helm, K8s + [ Interface Adapters ] ← controllers, presenters, gateways + [ Use Cases ] ← application business rules + [ Entities ] ← enterprise business rules (innermost) +``` + +- Inner layers know nothing about outer layers +- Data crossing a boundary is always in the form the inner layer prefers — never a raw DB row or HTTP request object +- If an outer layer changes (swap MySQL for Postgres, REST for gRPC), the inner layers are untouched + +## Layers + +### Entities — Core Business Rules +- Enterprise-wide business objects with their validation and invariants +- Pure domain models: no framework imports, no DB annotations, no HTTP concepts +- Stable — change only if fundamental business rules change + +### Use Cases — Application Business Rules +- Orchestrate entities to fulfil one actor's goal: `TransferMoneyUseCase`, `ProvisionClusterUseCase` +- Define input/output via interfaces owned by this layer — no knowledge of controllers or DB implementations +- One use case = one reason to exist + +### Interface Adapters +- Convert data between use cases and the outside world +- Controllers translate HTTP/CLI input → use case input models +- Gateways implement repository interfaces defined by use cases +- Presenters format use case output → UI/API response format + +### Frameworks & Drivers +- The outermost ring: web frameworks, ORMs, message brokers, Kubernetes, Helm +- These are details — they plug into the system via the adapters; the core doesn't know they exist + +## SOLID as the Foundation + +- **SRP** — one module, one actor, one reason to change +- **OCP** — extend behaviour through new implementations, not modification of existing code +- **LSP** — subtypes must honour the contracts of their interfaces +- **ISP** — prefer narrow, focused interfaces over fat ones +- **DIP** — high-level policy depends on abstractions; low-level detail implements them + +## Boundaries + +- Boundaries are where the power lives — they isolate components so that one side can change without affecting the other +- Define boundaries at the most volatile points: DB, UI, external services +- Cross a boundary only via an interface; never pass a concrete type across + +## What This Means in Practice + +- Business rules have no imports from frameworks, ORMs, or infrastructure libraries +- Tests of use cases and entities need no running database, no HTTP server, no cluster +- Swapping the database or the delivery mechanism (REST → gRPC, monolith → microservices) touches only the outer rings +- "Screaming architecture" — the top-level structure should reflect the use cases, not the framework: `invoice/`, `user/`, `payment/` not `controllers/`, `models/`, `views/` + +--- + +## In Helm and Kubernetes + +Clean Architecture applies to how infrastructure is organised, not just application code. + +**Helm charts are the outermost layer — they are the delivery mechanism, not the system.** + +- A chart should deploy exactly one service or one cohesive unit — not a bundle of unrelated things +- Business rules belong in application config (`values.yaml` keys mapped to env vars); they must not be hardcoded in templates +- Separate concerns across charts: one chart for the app, separate charts (or dependencies) for databases, queues, and shared infrastructure +- A chart change (resource limits, replica count, ingress rules) must never require touching application business logic +- Use `_helpers.tpl` as the adapter layer — it translates chart-level abstractions into Kubernetes primitives; templates themselves stay thin +- Environments (`values-dev.yaml`, `values-prod.yaml`) are driver-layer configuration — they override defaults without touching templates +- Test charts with `helm template` + schema validation; business logic is tested independently of the chart + +**Dependency Rule for Helm:** +- App charts depend on library charts, not the reverse +- Library charts define interfaces (helpers, common labels); app charts implement them +- Avoid circular chart dependencies — they indicate a missing layer or a misplaced responsibility diff --git a/.ai/instructions/skills/clean-code.instructions.md b/.ai/instructions/skills/clean-code.instructions.md new file mode 100644 index 0000000..3c27e66 --- /dev/null +++ b/.ai/instructions/skills/clean-code.instructions.md @@ -0,0 +1,78 @@ +# Clean Code + +Based on Robert C. Martin's *Clean Code*. Apply these principles to all code and configuration, including Helm charts, YAML, and infrastructure-as-code. + +--- + +## Naming + +- Use intention-revealing names: `daysSinceLastPayment` not `d` +- Don't lie with names: `accounts` not `accountList` if it isn't a list +- Pronounceable, searchable names — avoid encodings like `a2dp`, `strName` +- One word per concept across the codebase: pick `fetch`, `get`, or `retrieve` — not all three +- Names should read like prose: `if user.isEligibleForRefund()` not `if user.check()` + +## Functions + +- **Do one thing** — if you can extract a meaningful function from it, it's doing two things +- **One level of abstraction per function** — don't mix high-level orchestration with low-level detail in the same function +- Keep short — aim for under 20 lines; use extract method aggressively +- 0–2 arguments preferred; use a parameter object when more are needed +- **No side effects** — a function named `checkPassword` must not also start a session +- No flag arguments: `delete(file, true)` means the function already does two things — split it + +## Classes + +- **Single Responsibility Principle** — one reason to change +- Small and cohesive — methods should use the class's fields, not act as utilities +- Hide internals: public API simple, private implementation complex +- No god classes — prefer `InvoiceCalculator` over `BusinessLogic` + +## Error Handling + +- Throw exceptions, don't return error codes — let callers handle what they understand +- Descriptive messages: `throw new UnauthorizedAccess("Admin role required for this action")` +- Define custom exceptions close to where they're used +- Clean up resources: use try/finally or RAII patterns; never leak + +## Comments + +- Good code needs no comments — if you need to explain *what*, rename instead +- Comments explain *why*, not *what*: acceptable for non-obvious decisions, legal notices +- TODOs are temporary; remove before merging +- Never commit commented-out code + +## Formatting + +- Related code stays close — declarations near first use +- Consistent indentation throughout the codebase — agree once, automate with linting +- No deep nesting — if you're 4 levels deep, extract a function +- Team standard beats personal preference every time + +## General Principles + +- **DRY** — every piece of knowledge has one representation; duplication is a maintenance liability +- **KISS** — the simplest solution that works; complexity must earn its place +- **Boy Scout Rule** — leave the code cleaner than you found it; small improvements compound +- **SOLID** — especially SRP (above) and Open-Closed: open for extension, closed for modification +- **Boundaries** — wrap third-party code in adapters; don't let external APIs bleed through the codebase + +## Testing + +- Tests must be **FAST, Independent, Repeatable, Self-Validating** +- One concept per test — a test that asserts many things is a test that hides failures +- Keep test code as clean as production code — it will be maintained just as long +- Write code testable from day one; retrofitting testability is expensive + +--- + +## In Helm Charts and YAML + +The same principles apply to configuration: + +- **Naming**: value keys should be intention-revealing — `replicaCount` not `rc`, `service.port` not `p` +- **Single responsibility**: one chart does one thing; don't bundle unrelated services +- **No magic values**: named values in `values.yaml`, never hardcoded in templates +- **DRY**: use template helpers (`_helpers.tpl`) to avoid repeating label blocks and name patterns +- **Comments**: explain *why* a value is set a certain way, not what it does — the YAML speaks for itself +- **Defaults that make sense**: a default of `false` or `""` that always needs overriding is not a useful default diff --git a/.ai/instructions/skills/helm.instructions.md b/.ai/instructions/skills/helm.instructions.md new file mode 100644 index 0000000..2edc568 --- /dev/null +++ b/.ai/instructions/skills/helm.instructions.md @@ -0,0 +1,218 @@ +# Helm Chart Development Guidelines + +## 🔍 Before Creating Anything New + +**ALWAYS search the workspace for existing implementations first.** + +Before writing a new template, config, or resource: +1. Search the workspace for similar existing files +2. If found, migrate/adapt it — don't invent a new structure +3. Only create from scratch if nothing exists + +--- + +## 🏠 Resource Ownership + +**Every Kubernetes resource must have exactly one owner. Never duplicate.** + +- If a chart creates a resource (e.g. Traefik creates IngressClass), don't create it elsewhere +- Before adding a template for a resource, verify no other chart/subchart already manages it + +``` +❌ Wrong: subchart A creates IngressClass AND subchart B creates IngressClass +✅ Right: one chart owns IngressClass, others do not touch it +``` + +--- + +## 🧹 values.yaml Hygiene + +**values.yaml must only contain values that templates actually use.** + +Before adding a value: +- Verify at least one template references `.Values.` +- If no template uses it → don't add it +- Empty string defaults (`key: ""`) that only serve as documentation are forbidden — use README instead + +**Umbrella chart values.yaml:** +- Contains only subchart defaults that override upstream chart defaults +- All environment-specific values belong in the deployment repo +- Must NOT contain empty placeholders + +**Subchart values.yaml:** +- Contains only values the subchart's own templates reference +- Required values (validated with `required`) must NOT have empty defaults — absence should trigger the error + +--- + +## ✅ required vs defaults + +Use `required` for values that MUST come from the deployer. Do NOT put empty defaults in values.yaml for these. + +```yaml +# Good: fails loudly at install if not provided +name: {{ required "ingress.className is required" .Values.ingress.className }} + +# Bad: silently passes empty string, fails later in obscure way +name: {{ .Values.ingress.className | default "" }} +``` + +--- + +## 📦 Subchart Dependency Conditions + +Use `condition:` in `Chart.yaml` to enable/disable components. Use flat booleans, not nested `.enabled`: + +```yaml +# Chart.yaml +dependencies: + - name: keycloak + repository: "file://../components/keycloak" + version: "0.2.0" + condition: components.keycloak + +# values (Good) +components: + keycloak: true + traefik: false + +# values (Avoid) +keycloak: + enabled: true +``` + +--- + +## ⏱️ Hook Ordering + +Resources that depend on other resources must use `helm.sh/hook` with `hook-weight` to ensure correct install order. + +Established ordering convention: +- `hook-weight: "10"` — infrastructure prerequisites (e.g. ClusterIssuer, IngressClass) +- `hook-weight: "15"` — resources that depend on prerequisites (e.g. Certificate) + +```yaml +metadata: + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "10" +``` + +--- + +## 📄 Configuration Files over values.yaml + +**Prefer `files/` + ConfigMap over defining configuration in values.yaml.** + +When a component needs configuration (CASC, realm config, ini files, etc.): +1. Put the config file in `files/` directory +2. Create a ConfigMap that reads it with `.AsConfig` + `tpl` +3. Use `{{ .Values.global.xxx }}` expressions inside the config file for dynamic values + +```yaml +# templates/configmap.yaml +kind: ConfigMap +apiVersion: v1 +metadata: + name: my-component-config +data: {{ (tpl (.Files.Glob "files/*").AsConfig . ) | nindent 2 }} +``` + +```yaml +# files/config.yaml — contains Helm template expressions +server: + url: https://{{ .Values.global.domain }} + env: {{ .Values.global.environment }} +``` + +**Why:** Config files are readable, diffable, and version-controlled as real files. +Large configs in values.yaml become unmaintainable and hard to review. + +**When `.AsConfig` + `tpl` is required:** +- Config file contains `{{ }}` template expressions → always use `.AsConfig` + `tpl` +- Config file contains special characters (`*`, `{`, `}`) → `.AsConfig` handles escaping safely + +```yaml +# Never use plain Files.Get on files with template expressions or special chars: +{{ tpl (.Files.Get "files/config.json") . }} # ❌ breaks on * characters + +# Always use AsConfig: +{{ tpl (.Files.Glob "files/config.json").AsConfig . }} # ✅ +``` + +--- + +## 🔄 Dependency Caching + +After editing any subchart template or values, cached charts in `charts/` will be stale: + +```bash +rm -rf charts/ Chart.lock +helm dependency update . +``` + +Always rebuild before `helm template` testing when subchart files have changed. + +--- + +## 🎂 Umbrella Chart Pattern + +The standard structure is an umbrella chart that integrates multiple component charts. + +``` +my-umbrella/ +├── Chart.yaml ← declares component charts as dependencies +├── values.yaml ← static baseline: shared across all installations +├── templates/ ← only cross-cutting resources that no subchart owns +└── charts/ ← built by helm dependency update +``` + +**Two-file values layering:** + +| File | Where it lives | Purpose | +|---|---|---| +| `values.yaml` | umbrella chart repo | static baseline, same across all IAC installations | +| `values..yaml` | IAC / deployment repo | installation-specific overrides and required parameters | + +- `values.yaml` contains defaults valid for every installation; it never contains installation-specific data +- `values..yaml` provides what that specific installation requires — credentials, hostnames, sizing, feature flags +- Required values (no sensible default) use `required` in templates and have **no entry** in `values.yaml` — their absence triggers a clear error at install time + +--- + +## ⚡ KISS — Start Minimal, Add Only When Asked + +**Do not add knobs, flags, or options that aren’t needed right now.** + +- Build the minimum that meets the stated functional requirement +- Do not anticipate future needs with extra values, conditions, or template logic +- Complexity is easy to add; it is hard to remove once deployed +- AI may **suggest** what might be needed later — but must not implement it without explicit instruction + +```yaml +# ❌ Wrong: adding a flag “just in case” +features: + enableFutureThing: false # not needed yet, adding anyway + +# ✅ Right: don’t add it. If it’s needed, the developer will ask. +``` + +**When in doubt, leave it out.** A value that exists but is never used is noise. A template condition that is never toggled is dead code. Raise the question with the developer instead of silently implementing it. + +--- + +## 🧪 Testing Templates + +Standard verification command: + +```bash +helm template . \ + --set global.key=value \ + ... \ + 2>&1 | grep "^kind:" | sort | uniq -c +``` + +Verify: +- Expected resource kinds appear +- No `Error:` lines +- `required` validation fires when values are missing diff --git a/.gitignore b/.gitignore index de3a5f5..f3a6cf9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ .ai-superpower.version tmp/ -.ai