Skip to content
Open
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
1 change: 1 addition & 0 deletions plugins/datum-platform/agents/api-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Based on the task at hand:
| Activity integration (policies) | `capability-activity/SKILL.md`, `capability-activity/implementation.md` |
| Activity integration (events) | `capability-activity/emitting-events.md` |
| Activity integration (consuming) | `capability-activity/consuming-timelines.md` |
| Projecting resources into another service's API | `platform-knowledge/cross-service-patterns.md` |

### 3. Run Scaffold Scripts

Expand Down
5 changes: 3 additions & 2 deletions plugins/datum-platform/agents/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ Before designing, gather context in this order:
4. Read `k8s-apiserver-patterns/SKILL.md` for storage, types, and validation patterns
5. Read `controller-runtime-patterns/SKILL.md` if controllers are involved
6. Read relevant capability skills (`milo-iam`, `capability-quota`, `capability-activity`) if integration is needed
7. Read `.claude/patterns/patterns.json` for high-confidence patterns to follow or avoid
8. Check `.claude/pipeline/designs/` for related existing designs
7. Read `platform-knowledge/cross-service-patterns.md` if the feature touches more than one service
8. Read `.claude/patterns/patterns.json` for high-confidence patterns to follow or avoid
9. Check `.claude/pipeline/designs/` for related existing designs

## Workflow

Expand Down
1 change: 1 addition & 0 deletions plugins/datum-platform/skills/platform-knowledge/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,4 @@ Read the individual capability skills for integration details.
## Related Files

- `services-catalog.md` — Catalog of platform services
- `cross-service-patterns.md` — Directed dependency principle, resource projection, declaration vs. evaluation separation, and technology-neutral API design
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Cross-Service Design Patterns

This document captures the architectural principles that govern how services in the Datum Cloud platform relate to and depend on each other.

## Directed Dependency Principle

Dependencies between services must flow in one direction: **producers push into downstream APIs; downstream services do not watch upstream ones.**

When Service A's resources need to drive behavior in Service B:
- Service A's controller creates or updates resources in Service B's API
- Service B's controller only ever reconciles its own resources
- Service B has no knowledge of Service A's API

**Why**: Cross-watch coupling ties two operators' reconciliation loops together, making them harder to deploy, scale, and reason about independently. A projected resource in the downstream API is an explicit, auditable artifact — it's visible in the cluster and has its own status.

**Counter-pattern to avoid**: Service B watching Service A's CRDs directly. This creates a hidden runtime dependency that isn't expressed in either service's API.

```
✓ Correct
Service A controller ──creates──▶ ServiceB/ResourceX

✗ Avoid
Service B controller ──watches──▶ ServiceA/ResourceY
```

## Resource Projection Pattern

When one service's configuration should drive another service, the producing service projects that configuration as a concrete resource in the consuming service's API.

```
ServiceConfiguration (services.miloapis.com)
└─ services controller reconciles
└─ creates ServiceLevelObjective (telemetry.miloapis.com)
└─ telemetry controller reconciles
└─ backend-specific configuration
```

The projected resource is:
- **Explicit** — visible in the cluster with its own metadata and status
- **Auditable** — creation, updates, and deletion are tracked
- **Decoupled at runtime** — the consuming operator can be unavailable; projected resources queue up and are reconciled when it recovers

The producing service imports the consuming service's Go types as a module dependency. This is an acceptable compile-time coupling; runtime coupling is what to avoid.

## Declaration vs. Evaluation Separation

Configuration resources declare **intent**. Runtime evaluation — whether the system is actually meeting that intent — is a separate concern owned by a different system.

| Concern | Owner | Example |
|---------|-------|---------|
| Declaring an SLO target | `ServiceConfiguration` | `spec.serviceLevelObjectives[].target: "99.9"` |
| Evaluating SLO compliance | Telemetry backend | Recording rules, burn-rate alerts |
| Surfacing violations | Alerting infrastructure | Alert firing, notification routing |

Mixing declaration and evaluation in the same resource creates a resource that reconciles against an external system it has no business querying, and produces confusing status churn on what should be a stable config object.

## Status Conditions Reflect Configuration Health Only

A configuration resource's status conditions should reflect whether **the configuration itself is valid and consistent** — not whether the runtime system is meeting the declared targets.

```
✓ Appropriate status condition
type: SLOReferencesValid
status: "True"
reason: AllIndicatorsResolved
message: "All SLO indicator references resolve to defined SLIs"

✗ Not appropriate
type: SLOCompliant
status: "False"
reason: BurnRateTooHigh
message: "Error budget burn rate exceeds threshold"
```

The second example belongs in a separate resource or alert, not on the config object. Config objects should be stable; runtime compliance state changes continuously.

## Inline Definitions vs. Separate CRDs

When a concept is **inherently per-resource** — it doesn't make sense outside the context of its owner — define it inline as a field, not as a separate CRD.

Promote to a separate CRD only when:
- The concept needs to be shared across multiple resource types
- The concept has its own independent lifecycle (create, update, delete independent of the owner)
- The concept needs its own RBAC surface

**Example**: SLIs on `ServiceConfiguration` are defined inline because they are always per-service and have no value outside of a specific service's configuration.

## Technology-Neutral APIs

API schemas should be **backend-agnostic**. Translation to specific backends (Prometheus, Datadog, OpenTelemetry, etc.) is a deployment and configuration concern owned by the consuming operator — not expressed in the API type.

```yaml
# ✓ Technology-neutral
spec:
serviceLevelIndicators:
- name: request-success-rate
type: Ratio
ratio:
good: { metric: http_requests_total, filters: ['{code!~"5.."}'] }
total: { metric: http_requests_total }

# ✗ Backend-coupled
spec:
prometheus:
recordingRules:
- expr: sum(rate(http_requests_total{code!~"5.."}[5m]))
```

Backend-neutral APIs allow the platform to support multiple telemetry backends without API changes, and keep infrastructure-specific knowledge out of the service contract.