Skip to content

Domain model — reference

A working summary of the domain model. The orientation walks through the tree-and-grid model — ownership is a tree, deployment is a grid — from scratch. The normative schema (every field, type, and default) is platform-domain-api.md.

Noun What it is Authored by Represented as
Team The owner: an SSO group + an envelope that bounds everything below it. Owns no infra itself. Platform (admin) gitops/teams/<team>.yaml → projected Team CR
Product A deployable a team builds; maps to exactly one repo. Team lead (self-service) gitops/products/<team>/<product>.yaml → projected Product CR
Service A single running component (web, api). A Product owns N; a repo sources N (monorepo). Developer repo-native catalog-info.yaml (not a control-plane CR)
Environment A (Product × Stage[, × Customer]) instance = one namespace. The unit of deployment. Team lead / dev (self-service) gitops/environments/**XEnvironment claim
Customer An external (or internal) consumer of a Product; adds a coordinate to per-customer prod environments. Platform / sales ops registry entry (no projected CR yet)
Team ──owns──▶ Product ──owns──▶ Service ◀──sources(1:N)── Repo (one repo : ONE product)
│ └──realized-at-a-Stage-as──▶ Environment = (Product × Stage[, × Customer])
Developer ──member-of──▶ Team (implicit access to its Products)
Developer ──granted──▶ Product (explicit, cross-team — an AccessGrant)
  • Ownership is a tree (Team → Product → Service): exactly one owner each.
  • Access is many-to-many (Developer ↔ Product): a cross-team AccessGrant gives access without ownership. Kept on a separate edge from ownership by design.
  • Identity is per-repo; artifacts are per-service. One repo maps to one Product; the image identity is product-scoped: team-<team>/<product>-<service>.

Agents (XAgent) — a second deployment shape

Section titled “Agents (XAgent) — a second deployment shape”

A Product can deploy as an agent instead of an Environment. XAgent is a sibling claim to XEnvironment — same team / product ownership — but hub-placed and shaped for an AI agent: a model, an autonomy limit (propose-only = read + suggest, never act), a trigger, and awsPermissions, with no stage/namespace grid. The reference agent is triage-copilot (gitops/agents/, owned by team platform). Full treatment belongs to the agentic-platform module (planned).

  • A Stage (dev / test / staging / uat / prod) is a promotion rung a developer reasons about — not a location.
  • A Placement (cloud / region / account / cluster) is where it physically lands — derived by the platform, written to status, never authored. One Stage can map to several Placements (HA/DR). Today every Environment resolves to the single workload cluster.
Derived name Pattern Example
Namespace / Environment name <team>-<product>-<stage> (…-<customer>-<stage> per-customer) alpha-shop-dev
Image / ECR path team-<team>/<product>-<service> team-alpha/shop-storefront
Generated host <product>-<team>-<stage>.<baseDomain> shop-alpha-dev.preprod.aws.refplat.org
Pod-Identity role Pod-<team>-<product>-[<customer>-]<stage>-<service> Pod-alpha-shop-dev-storefront

Any derived identifier that would exceed its length ceiling (namespace 63, IAM role 64) is truncate-and-hashed deterministically — friendly in the common case, always valid in the edge case.

The bound on every Product/Environment a Team may author — enforced at admission (Kyverno on the projected Team CR). Team alpha’s, live:

envelope:
allowedTiers: ["standard"] # an Environment tier outside this set → rejected
allowedStages: ["dev","test","uat","staging","prod"]
quotaCap: { cpu: "8", memory: 16Gi, pods: 40 } # per-Environment ceiling AND aggregate cap
budget: { monthlyUSD: 2000 } # cost guardrail
maxDedicatedIsolation: { cluster: 0, account: 0 } # 0 = pooled only (no dedicated cluster/account)
resources: { allowedEngines: ["s3","sqs","sns","dynamodb"], maxPerEnvironment: 10 }
  • Stateless, hard-enforced at admission: tier ∈ allowedTiers, stage ∈ allowedStages, quota ≤ quotaCap, residency ⊆ allowedLocations, image registry ⊆ the product scope.
  • Aggregate, report-first (designed, not yet built): sum of environment quotas ≤ cap, dedicated-isolation in use ≤ maxDedicatedIsolation — a rollup controller is planned to alert on these (the stateless per-Environment checks above are live; the aggregate tier is not — nothing writes Team.status today), and would hard-enforce only if a team actually pushes a cap.

A tier (standard / elevated / pci / hipaa) names a profile bundling isolation + recovery + availability. It sets a minimum — a regulated tier forces stronger isolation; anyone may dial up. Effective isolation = max(tier-floor, chosen). Recovery/availability are derived from the tier, never re-declared.

  • team is carried on the Environment, not just derived. The XEnvironment claim repeats team (validated == Product.team) because the namespace, the Pod-Identity role, and the envelope policy all need it, and a Crossplane go-template can’t cross-CR-lookup the Product. Denormalized on purpose.
  • One repo : one Product (but one Product : many Services). A monorepo is fine; a repo spanning two Products is not — image identity would be ambiguous.
  • The deployed digest is not in the Environment claim. It lives in a separate Release record, because a digest changes every build and the human-authored claim shouldn’t churn. The claim declares what runs; the Release says which image.
  • Customer only attaches at prod/uat for per-customer products; it’s forbidden on dev/test/staging (those stay internal/pooled).