---
title: "Submit a targeted node failure injection"
method: POST
path: "/simulations/{simulationId}/inject-failure"
tags: ["Simulations"]
---

# Submit a targeted node failure injection

`POST /simulations/{simulationId}/inject-failure`

## Request

- Base URL: `https://www.cloudworldmodel.ai/api`
- URL: `https://www.cloudworldmodel.ai/api/simulations/{simulationId}/inject-failure`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-PAYMENT`
  - API key in header `PAYMENT-SIGNATURE`

Fails one resource in the simulation (marks it critical) and records an
error event.

Quick `inject-failure` uses the resource's bounded `failureParkSteps`
window (or `criticalSteps` when that field is omitted): the targeted
compute/Kubernetes node is unavailable and receives no routed traffic
during that window, then rejoins the routing pool automatically when
organic cooldown can resume. A targeted database is also unavailable
during its bounded park window. With no serving database and positive
offered traffic, the next step reports 100% errors, zero throughput,
and `errorBreakdown.dbFailure: 100`. If an AWS Aurora writer explicitly
declares a healthy related replica (`replicaOf` the writer) via
`characteristics.auroraStandbyResourceId`, the modeled first step
remains interrupted; on the second step the standby takes over and
`metrics.databases[].auroraFailover` reports the failed and standby
resource IDs, phase, and success. Promotion removes the writer-outage
penalty while the writer remains parked. `multiAz` or a second,
unrelated database alone does not prove a write-capable standby.
Each quick step is one modeled simulation second: the second-step
promotion is one second after injection, not 30 seconds. Chaos
`database_crash` instead samples at 10-second intervals with a default
30-second promotion and a 1800-second sole-writer restart after the
injection duration. Compare equivalent outage/serving phases, not
equal step indices or wall-clock times. These are deterministic
assumptions, not observed AWS failover timing.
Its original connection configuration is
restored after the window, followed by organic cooldown, or earlier
via `recover-resource`. Random selection remains compute/Kubernetes-only.
Network and other non-node resources cannot be quick-failed.
Authenticated callers can instead use `failure.create` with
`database_overload` for a typed database failure; this typed tool is
not available anonymously. This is different from the persistent
reversible `instance_down` failure created through `/failures`, which
remains unavailable until deactivated or recovered with
`recover-resource`; `instance_kill` permanently removes the resource.
Poll the step response or `GET /simulations/{simulationId}` for
`availabilityState`, `isRoutable`, `routedRps`, and `recoveryProgress`.
`failureParkSteps` must be a positive integer; an explicit zero is
rejected rather than treated as an ambiguous no-park mode.

**Targeting (recommended)** — pass `resourceId` or `resourceName`
(human-readable name; exact match preferred, an unambiguous prefix is
accepted) in the request body to fail a specific, named resource (e.g.
`{ "resourceName": "web-server-01" }`). This path is deterministic and
reproducible — use it for controlled scenarios, CI fault-tree runs, and
any case where a predictable outcome matters. If `resourceName` matches
more than one resource, a 400 is returned with a `candidates` list
naming every match so the caller can retry unambiguously. If the
resolved resource is not in a faileable state (already critical/warning),
a 400 describing its current status is returned.

**Random mode (opt-in)** — pass `{"random": true}` to explicitly select
a random healthy node via the simulation's RNG. This path is
**non-deterministic** and not suitable for controlled scenarios or CI
replay. Use it only when intentional random selection is explicitly
desired. An empty body `{}` returns 400 with a hint naming all three
valid patterns.

The response always echoes the applied outcome via
`resolvedResourceId`, `resolvedResourceName`, and `previousHealth` —
on the random path these are populated from whichever resource was
selected.

Returns 400 if no healthy nodes are available to fail.
For fine-grained control over failure type and duration, use
`POST /simulations/{simulationId}/failures` instead.

**Resource name discovery (agents: do this first)** — before calling this
endpoint, call `GET /simulations/{simulationId}` and read the top-level
`resources` array. Each element has an `id` and a `name` field. Use one of
those values as `resourceId` or `resourceName` in your request body. The
response also returns a `healthyNodes` array `[{id, name}, ...]` listing
every currently healthy resource, so you can update your inventory on each
call without a separate lookup.

**Unknown fields are rejected before payment settlement** — if the request body
contains any field other than `resourceId`, `resourceName`, or `random` (for
example the common agent mistake `targetResourceId`, `target`, or `resource`),
the endpoint returns a structured 400 with a "Did you mean" hint before any
USDC charge is settled. An empty body `{}` also returns 400 with a hint naming
all three valid patterns.

Requires `write` scope and ownership.

## Path parameters

- `simulationId` string, required

## Request body

- object: Specify `resourceId` or `resourceName` to target a specific node (deterministic, reproducible). Pass `random: true` to explicitly opt into random node selection (non-deterministic).
  - `resourceId` string: **Recommended** — ID of the resource to fail. Takes precedence over resourceName. Use this for reproducible, deterministic fault injection.
  - `resourceName` string: **Recommended** — Human-readable name of the resource to fail (exact match preferred; unambiguous prefix accepted). Ambiguous names return 400 with a candidates list.
  - `random` true: Set to `true` to explicitly opt into random node selection. Mutually exclusive with resourceId and resourceName.

## Response `200`

Node failure injected

- object
  - `simulation` Simulation
    - `appWeight` 'lean' | 'typical' | 'heavy': Immutable root-level simulation.create input. Omission selects typical and appWeightDefaulted=true; explicit typical has identical numerical predictions. In the generic fallback at 100 total RPS across two m5.large apps (50 per server), typical targets 20% app-host CPU, P50 18.5 ms and P95 45 ms, not the deliberately lean benchmark's 1.9% CPU. These are placeholder guidance, not AWS measurements; the separately eligible owned typical Node CRUD workload uses its measured 20/100/200 RPS fit instead. Heavy doubles typical load/latency multipliers as an unsupported product assumption.
    - `appWeightDefaulted` boolean: True only when the create request omitted appWeight. Immutable intake provenance.
    - `predictionEvidence` PredictionEvidence: Versioned provenance of prediction basis, not a claim simulated outputs were observed. CPU is mean serving app-host CPU (not aggregate cpuUsage); no apps means appCpu=null and an empty per-resource array. `latencyP99Basis` independently identifies the P99 basis: owned in-VPC internal-ALB fit, scaled from that fit (not directly measured), or uncalibrated generic P99. `predictionEvidence.latencyP99`, when present, carries P99's evidenceLevel and low/central/high assumption interval; it is prediction provenance, not observed simulated output, and not a statistical confidence interval. When `latencyPercentileStatus` is unavailable, public latency percentiles are null; a serving resource without a modeled latency distribution does not support P50/P95/P99. Fargate allocation/billing/capacity facts alone do not establish workload latency. Resilience `timeoutMs` is a per-edge, per-attempt deadline over local service time, target base latency, injected latency, and queued wait, not a deadline against aggregate chain percentiles. Historical evidence may omit or null this optional quantity. `latencyBasis` names the general modeled latency path; use `latencyP99Basis` for P99. Ranges are assumption intervals, not confidence intervals. Measured lean uses 0.65–1.35 times the center; lean proportional AWS M5 scaling uses CPU 0.40–1.60 and latency 0.50–1.50 times the final reported P50/P95 centers (after applicable penalties). The lean scaled baseline uses offered application RPS T: P50 = max(1.45, 2.4833 - 0.0010466*T) ms, P95 = max(P50, 3.10, 4.3697 - 0.0012973*T) ms (full-precision fit). On the exact healthy lean owned ALB → 2× m5.large → MySQL db.r5.large graph at 10–1,000 offered target RPS, P99 uses max(final P95, 7.021919127633514 + 0.00027013891327780484*T) ms, fitted by ordinary least squares on the owned campaign's 10/100/500 RPS P99 measurements. The 1,000-RPS point is a holdout, not a fit point. The owned fit is a prediction, not a new observed sample or a provider measurement. Exact eligible P99 uses 0.65–1.35× assumption bounds; analogous lean M5 sizes use the fit as scaling (0.50–1.50× within range), not direct measurement. Beyond 1,000 RPS scaling/extrapolation uses 0.25–2.00×. Generic typical/heavy and ineligible paths retain the generic, unverified P99 formula, with 0.50–2.00× bounds in-range and 0.25–3.00× beyond. Active failures disqualify a healthy measured P99 claim. The exact owned typical Node CRUD path instead fits P99 from 20/100/200 RPS measurements; this is a prediction, not an observed output or a general typical-app reference. Its 300 RPS holdout is not fitted. Matching one- and three-app-server topologies are explicitly labeled owned-scaled and derive app CPU/latency/goodput from equivalent two-server fit load (2 × total RPS ÷ app-server count); their server counts were not measured. Database CPU and connection demand still use total offered traffic. One server at 300 total RPS requires a 600 RPS equivalent fit extrapolation beyond the holdout, with P50 held at its 300 RPS projection. Pool-capacity comparisons are warnings only and do not independently imply errors or throughput loss. The final P99 is floored at final P95; P99 is not scored in Accuracy. No generic 5 ms floor applies to scaled lean latency; 0.05 ms is only a physical/non-finite safety floor. Reference estimates use 0.50–2. Beyond 1000 offered RPS, scaled uses 0.25–2 and reference 0.25–3. Unknown-vCPU legacy fallback uses the widest reference range and has no external reference. `legacyGeneric` explicitly labels use of that unverified generic branch: true at the top level if any reported app CPU or latency quantity used it, and true on each affected appCpuByResource item. The evidence note names each affected resource's shape and reason (missing vCPU, flexible shape without resource.characteristics.ocpus, or unknown/ unrecognized shape). CPU ranges are physically capped at 100%. Prediction-only capacity metadata covers GCP E2 standard e2-standard-2/4/8/16/32, N1 standard n1-standard-1/2/4/8/16/32/64/96, and N2 standard n2-standard-2/4/8/16/32/48/64/80/96/128. Google-published vCPU and memory specifications establish hardware dimensions only, not CWM performance, throughput, or price. Historical rows missing evidence are returned non-mutating as unavailable/legacy with null weight/load/quantities and empty IDs; they are never relabeled measured by today's model.
      - `version` 1, required
      - `legacyGeneric` boolean, nullable: True when any reported app CPU or latency quantity used the unverified generic fallback; false for newly produced non-generic evidence; null on explicitly unavailable/legacy historical evidence. Predating saved evidence can omit this field entirely; it is never reconstructed.
      - `evidenceLevel` 'measured' | 'scaled from measured' | 'reference estimate' | 'unavailable/legacy', required
      - `appWeight` 'lean' | 'typical' | 'heavy' | 'null', nullable, required
      - `appWeightDefaulted` boolean, nullable, required
      - `loadScope` 'within measured load' | 'beyond measured load' | 'below measured load' | 'not calibrated' | 'null', nullable, required
      - `sourceIds` string[], required
      - `formulaIds` string[], required
      - `appCpu` PredictionRange, required
        - `low` number, required
        - `central` number, required
        - `high` number, required
      - `appCpuByResource` object[], required
        - `low` number, required
        - `central` number, required
        - `high` number, required
        - `evidenceLevel` 'measured' | 'scaled from measured' | 'reference estimate', required
        - `resourceId` string, required
        - `legacyGeneric` boolean: True when this resource's app CPU used the unverified generic fallback. Required for newly produced evidence; absent on predating saved evidence.
      - `requestServingCapacity` object[]: Per-Fargate-service configured task count, per-task modeled rate, aggregate capacity, and explicit evidence basis. Assumptions are not measured throughput.
        - `resourceId` string, required
        - `resourceName` string, required
        - `taskCount` integer, required: Effective configured desired task count.
        - `perTaskCapacityRps` number, required
        - `aggregateCapacityRps` number, required
        - `maxTaskCount` integer, required
        - `maxAggregateCapacityRps` number, required
        - `capacityEvidence` object, required
          - `basis` 'assumption' | 'documented' | 'measured' | 'catalog', required
          - `origin` 'caller-provided' | 'fargate-size-heuristic' | 'policy-default' | 'provider-catalog', required
          - `source` string
      - `latencyAvailability` PredictionLatencyAvailability
        - `status` 'unavailable', required
        - `reason` string, required: Names the unresolved Fargate shape, missing workload-specific latency calibration, and applicable per-edge timeout boundary.
        - `resourceIds` string[], required
      - `latencyP50` PredictionQuantityEvidence, required
        - `low` number, required
        - `central` number, required
        - `high` number, required
        - `evidenceLevel` 'measured' | 'scaled from measured' | 'reference estimate', required
      - `latencyPercentileStatus` 'modeled' | 'unavailable': Whether percentile evidence is available from a modeled serving-tier distribution.
      - `latencyP95` PredictionQuantityEvidence, required
        - `low` number, required
        - `central` number, required
        - `high` number, required
        - `evidenceLevel` 'measured' | 'scaled from measured' | 'reference estimate', required
      - `latencyP99` PredictionQuantityEvidence
        - `low` number, required
        - `central` number, required
        - `high` number, required
        - `evidenceLevel` 'measured' | 'scaled from measured' | 'reference estimate', required
      - `note` string, required
    - `id` string, uuid: Unique simulation identifier
    - `name` string
    - `description` string
    - `resources` Resource[]
      - `metadata` object: Resource metadata; configured Route 53 nodes persist modeled lifecycle telemetry under route53Failover.
        - `route53Failover` Route53FailoverTelemetry: Persisted on the configured Route 53 resource's metadata.route53Failover. This is modeled DNS health/cache state, not measured AWS telemetry. Traffic shares are fractions of offered load (0–1), not percentages. replicationLagSeconds accumulates modeled source-unavailability time until promotion and freezes at promotion; MODELED_NOT_MEASURED is not observed replication lag or proof of data loss/RPO. Regional recovery does not rewrite a lag value frozen at promotion.
          - `evaluatedAtStep` integer: Engine input-step index at evaluation, not elapsed seconds.
          - `primaryHealthy` boolean: Health-check verdict after consecutive-probe thresholds.
          - `primaryAvailable` boolean: Whether the primary region currently has available modeled compute.
          - `consecutiveFailures` integer
          - `consecutiveSuccesses` integer
          - `lastCheckSeconds` number: Simulation-clock seconds of the last health probe.
          - `primaryTrafficShare` number
          - `secondaryTrafficShare` number
          - `staleDnsTrafficShare` number: Offered-load fraction still routed to unavailable regional compute.
          - `staleDnsRps` number: Modeled offered RPS times staleDnsTrafficShare; not measured requests.
          - `transitionStartedSeconds` number
          - `transitionFromShare` number
          - `transitionTargetShare` number
          - `phase` 'primary' | 'detecting_failure' | 'draining' | 'secondary' | 'detecting_recovery' | 'recovering': Probe detection and TTL drain/recovery are distinct phases; later healthy state does not erase earlier stale routing.
          - `replicationLagSeconds` number
          - `replicationLagBasis` 'MODELED_NOT_MEASURED'
          - `replicationLagFrozenAtPromotion` boolean
      - `id` string, required: Unique resource identifier
      - `dataState` 'intact' | 'temporarily_unavailable' | 'permanently_lost': Data durability, separate from service health; permanently_lost is irreversible.
      - `recoverySource` 'none' | 'surviving_backup' | 'surviving_replica'
      - `recoverySourceResourceId` string
      - `databaseRebuilt` boolean: Explicitly rebuilt empty database; does not restore original lost data.
      - `type` 'compute' | 'network' | 'database' | 'storage' | 'kubernetes' | 'cache' | 'queue', required: Resource type. `kubernetes` models a managed container node pool (EKS / GKE / AKS / OKE / DOKS): its `characteristics.nodeCount` worker nodes autoscale between `characteristics.minNodes` and `characteristics.maxNodes`, growing both serving capacity (`maxThroughput`) and hourly cost proportionally.
      - `name` string, required: Human-readable resource name
      - `provider` 'aws' | 'gcp' | 'azure' | 'oci' | 'digitalocean', required: Cloud provider
      - `status` 'healthy' | 'warning' | 'critical' | 'offline', required: Current resource status
      - `location` object: Resource location for create and read. Owned typical requires regionKey us-east-2 on all four nodes; the server stores canonical use2. Do not set region under characteristics.
        - `regionKey` string, required
        - `zoneKey` string
        - `localityType` 'az' | 'zone' | 'availability_domain' | 'fault_domain'
        - `providerLabel` string
        - `faultDomainKey` string
      - `characteristics` object: Provider-specific resource characteristics. For AWS RDS resources, lifecycle/support and version inputs such as `extendedSupport`, `engineVersion`, `databaseEngine`, `databaseEngineVersion`, and `dbEngineVersion` are rejected with `INVALID_FIELD`: CWM models base resource rates and behavior but does not price RDS lifecycle/support surcharges. Direct `engine` is also rejected except for literal `engine: "mysql"` as workload-only identity on the exact AWS `db.r5.large` database in the canonical owned CRUD graph; prefer `workloadDatabaseEngine: "mysql"`. This alias does not declare engine version, Extended Support, lifecycle, or billing. The bounded SQL Server Express contract instead uses `rdsSqlServerBilling.engine: "sqlserver-ex"` inside its nested, strict configuration. Arbitrary data remains supported under resource-level `metadata`. For Aurora Serverless, set ACU limits with `config.minCapacity` and `config.maxCapacity` (or flat `minCapacity` and `maxCapacity`). For AWS aurora-serverless databases with size `db.serverless`, flat `minAcu` and `maxAcu` are aliases when the corresponding canonical field is absent. Top-level and nested config aliases are rejected. A modeled two-instance pair cannot change its topology or ACU bounds through PATCH. Owned AWS CRUD calibration eligibility is an exact predicate: exactly four healthy, routable AWS resources and exactly four edges, one internal ALB → each of two compute apps → one database, with no extra resources or edges. Both apps are `m5.large`; each absent `serviceFamily` is inferred as `ec2` for fit eligibility only, explicit `ec2` is accepted, and any other explicit family fails. The database is `db.r5.large`; absent `serviceFamily` is inferred as `rds` for eligibility only, explicit `rds` is accepted, and any conflicting explicit family fails. MySQL workload identity is required on that database: use `workloadDatabaseEngine: "mysql"` or the narrow literal `engine: "mysql"` alias described above. The alias is accepted on that database shape even in an incomplete graph but does not by itself make the graph fit-eligible. The sole network node must have explicit `serviceFamily: "alb"`, or, only when serviceFamily is absent, an unambiguous name containing `ALB` or `Application Load Balancer`; any explicit size/type discriminator must not identify NLB or another network service. A generic unnamed network/load-balancer node, explicit NLB, or contradictory discriminator is ambiguous and fails eligibility. Each app may explicitly set `workload: "crud"` or omit the flag only for this completely matched canonical graph with MySQL identity; this is a canonical CRUD modeling assumption, not proof of actual customer workload behavior, and the inference is identified in `calibrationEvidence.note`. Explicit conflicting workload values fail. No CRUD inference applies to similar-but-not-exact graphs, another instance type/provider, or an incomplete graph. Each app must have autoscaling disabled and fixed `minInstances=maxInstances=2`; traffic must be in the inclusive 10–1,000 RPS range. There must be no active failure and no failure, warmup, or snapshot metadata. App `capacityRps`, `requestsPerSecond`, non-default `maxThroughput`, and non-default `baseLatency` overrides disqualify the fit. If supplied, database `maxConnections` must be 500 and `effectiveMaxConnections` must be absent; if supplied, ALB `maxThroughput` must be 50,000. The canonical `us-east-2` / seed `20240601` pair is the parity fixture, not evidence that another region has been measured. The separate owned typical fit requires appWeight=typical and the same four-node topology, explicit workload="crud-typical", appRuntime="node", appWorkerCount=2 and appDbPoolSize=250 per compute node, workloadDatabaseVersion="8.0" and explicit maxConnections=500 on the MySQL database, and internal loadBalancerScheme on the ALB. Every node must declare location.regionKey="us-east-2" (stored as canonical "use2"), bounds must be fixed 2→2, and load must be 20–300 RPS. Missing identity is never inferred. Only measured 20/100/200 RPS rungs from campaign typical-v1-20260927c are fitted; 300 RPS is a holdout and 500 RPS is saturation context. Generic typical apps and the public lean benchmark do not receive this fit. For ineligible graphs, `calibrationEvidence.kind` is `modeled` and `calibrationEvidence.note` names every actual failed eligibility check in stable order, with resource identifiers and safe expected / actual values; it must not report only a generic “not applied”. Examples include missing or ambiguous ALB identity, wrong app size or family, non-fixed instance bounds, traffic outside the range, active failure, and conflicting workload identity. For eligible graphs, evidence identifies the owned calibration and states that latency is modeled on the in-VPC ALB path, not end-to-end. No other hardware has been validated. Lean owned evidence requires root appWeight=lean; the separate typical fit requires typical. Known-vCPU serving compute uses the workload model: proportional lean AWS M5 scaling is scaled from measured. At offered application RPS T its full-precision fitted baseline is P50 = max(1.45, 2.4833 - 0.0010466*T) ms and P95 = max(P50, 3.10, 4.3697 - 0.0012973*T) ms. Topology, congestion, storage, memory, routing and failure penalties follow this baseline. There is no generic 5 ms floor on lean scaled latency; 0.05 ms is only a physical/non-finite safety floor. Other providers and generic typical/heavy are reference estimates. An explicitly identified owned typical Node CRUD setup uses its own separate three-rung fit; it does not change the lean benchmark. Unknown-vCPU fallback retains explicitly unverified generic output with no external reference. See PredictionEvidence for assumption intervals.
        - `route53Failover` Route53FailoverConfig: Reusable opt-in DNS failover model on characteristics.route53Failover. Probe detection (consecutive health checks) and resolver cache expiry (DNS TTL) are separate clocks, expressed in simulated seconds. simulationSecondsPerStep converts the engine input-step index to seconds; currentTime and metric timestamp remain step indices, not seconds. scenario.get returns the configured model for graph-copy creation.
          - `primaryRegion` string, required
          - `secondaryRegion` string, required
          - `healthCheckIntervalSeconds` number
          - `failureThreshold` integer
          - `recoveryThreshold` integer
          - `dnsTtlSeconds` number
          - `primaryTrafficShare` number
          - `simulationSecondsPerStep` number
        - `workload` 'crud' | 'crud-typical': Explicit application workload identity. Set to "crud-typical" only for the owned typical Node CRUD setup (see the required fields below). For lean, set "crud" on each app; each may omit its flag only when the complete exact canonical graph and MySQL database identity match; this is a modeling assumption, not evidence of actual customer workload behavior. Explicit conflicting workload values fail. See the characteristics contract for the complete gate.
        - `appRuntime` string: Explicit runtime identity for owned typical eligibility; must be node. Not inferred from EC2.
        - `appWorkerCount` integer: Explicit workers per app host; owned typical requires two on each m5.large.
        - `appDbPoolSize` integer: Explicit database connection pool size per app host; owned typical requires 250 on each host.
        - `loadBalancerScheme` 'internal' | 'internet-facing': Explicit ALB scheme; owned typical requires internal.
        - `workloadDatabaseVersion` string: Workload-only MySQL version identity; owned typical requires 8.0. Does not enable RDS lifecycle or support billing inputs.
        - `workloadDatabaseEngine` 'mysql': Preferred MySQL workload identity for the exact healthy AWS db.r5.large node in the owned CRUD graph. Literal characteristics.engine="mysql" is a workload-only alias on this exact shape; it does not declare engine version, Extended Support, lifecycle, or billing. Those inputs remain rejected. See the characteristics contract for the complete gate.
        - `engine` string: Narrow AWS RDS workload-only alias: only literal "mysql" is accepted on the exact db.r5.large canonical owned-CRUD database; prefer workloadDatabaseEngine. Does not declare engine version, Extended Support, lifecycle, or billing capability; other engine values and lifecycle/billing inputs remain rejected.
        - `rdsSqlServerBilling` RdsSqlServerBillingConfig: Bounded caller-provided billing assumptions for AWS RDS SQL Server Express. Attach this object only to an `aws` `database` resource whose `serviceFamily` is `rds-sqlserver-express`. CPU is a constant interval assumption, not inferred from traffic, database load, generic simulated CPU, or a managed-service process. The initial earned-credit and surplus-credit ledgers are separate and cannot both be positive.
          - `engine` 'sqlserver-ex': SQL Server Express, license included. Other engines and editions are outside this bounded model.
          - `region` 'us-east-1': Pricing region supported by this model. It must agree with `resource.location.regionKey` when that field is supplied.
          - `deployment` 'single-az': Single-AZ deployment. Multi-AZ pricing is not included.
          - `instanceClass` 'db.t3.micro' | 'db.t3.small' | 'db.t3.medium' | 'db.t3.large' | 'db.t3.xlarge', required: Supported SQL Server Express T3 instance class. If `characteristics.size` or `instanceType` is also present, it must equal this value. Non-burstable classes are intentionally unsupported because this bounded regional Express catalog has no verified non-burstable SKU/rate; they fail validation rather than receiving a fabricated or generic fallback price.
          - `storageType` 'gp2' | 'gp3', required: General Purpose SSD storage price to apply. Additional gp3 IOPS and throughput are not modeled.
          - `allocatedStorageGiB` integer, required: Caller-declared allocated database storage in GiB.
          - `durationHours` number, required: Billing interval in hours. Storage is prorated over a documented 730-hour reference month.
          - `sustainedCpuPercent` number, required: Caller-provided constant average CPU utilization over the interval. This is neither an application-load attribution nor evidence that RdsAdminService, SQL Server, the OS, or any other process caused it.
          - `creditMode` 'unlimited': T3 Unlimited credit accounting. Disabling Unlimited is not modeled and would not remove the underlying CPU demand.
          - `initialCreditBalance` number: Earned CPU credits available at interval start, in vCPU-minutes. Bounded by the selected class's 24-hour earning limit.
          - `initialSurplusCreditBalance` number: Uncharged Unlimited surplus-credit debt at interval start, in vCPU-minutes. It is repaid before new credits are banked and is bounded by the selected class's 24-hour earning limit.
          - `settleSurplusAtEnd` boolean: Whether to charge and clear otherwise uncharged surplus debt at interval end, representing an explicit stop/delete settlement assumption. False keeps debt up to the 24-hour cap in the ending surplus ledger.
        - `minCapacity` number: Aurora Serverless minimum ACU (flat form; nested config takes precedence). Default 0.5. Explicit reader bounds must match the writer's cluster range; omitted reader bounds inherit it.
        - `maxCapacity` number: Aurora Serverless maximum ACU (flat form; nested config takes precedence). Default 16. Explicit reader bounds must match the writer's cluster range; omitted reader bounds inherit it.
        - `minAcu` number: Exact AWS Aurora Serverless v2 db.serverless shape only; alias for flat minCapacity.
        - `maxAcu` number: Exact AWS Aurora Serverless v2 db.serverless shape only; alias for flat maxCapacity.
        - `auroraReaderScalingMode` 'writer-mirror' | 'independent': AWS Aurora Serverless v2 reader scaling: writer-mirror (default, promotion tiers 0/1) follows the writer's capacity; CPU is a modeled mirror, not independently routed load. independent (promotion tiers 2–15) uses only routed work and the ACU floor. Set on an explicit reader or on the two-instance writer to configure its generated reader. Each instance bills its own ACU-hours. https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.how-it-works.html (reviewed 2026-09-25).
        - `config` object: Aurora Serverless ACU configuration. Use minCapacity/maxCapacity, not minAcu/maxAcu. The range is cluster-wide for all Serverless v2 instances; explicit reader bounds conflicting with the writer are rejected, while omitted reader bounds inherit the writer range. https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.setting-capacity.html#aurora-serverless-v2-examples-setting-capacity-range-for-cluster (reviewed 2026-09-25).
          - `minCapacity` number
          - `maxCapacity` number
        - `serviceFamily` string: Provider-specific service family identifier. AWS values include: `ec2`, `ecsFargate`, `rds`, `rds-sqlserver-express`, `aurora-postgresql`, `aurora-dsql`, `aurora-serverless`, `dynamodb`, `s3`, `elb`, `cloudfront`, `lambda`. GCP values include: `gce`, `cloud-sql`, `cloud-spanner`, `bigtable-ssd`, `bigtable-hdd`, `cloudRun`, `cloudFunctions`, `gcs`, `cloud-load-balancing`. Azure values include: `azure_vm`, `azure-sql`, `azureFunctions`, `azureContainerApps`, `blob-storage`, `azure-lb`, `eventhub`. OCI values include: `oci_vm`, `ociFunctions`, `autonomous-db`, `oci-container`, `oci-lb`, `oci-streaming`. DigitalOcean values include: `droplets`, `managed_postgresql`, `spaces`, `load_balancer`. Only for owned AWS CRUD fit eligibility, absent serviceFamily is inferred as `ec2` for exact AWS compute `m5.large` apps and `rds` for exact AWS database `db.r5.large`; explicit matching values remain valid and conflicting values fail. The sole AWS network node accepts explicit `alb`, or, only when family is absent, a resource name unambiguously containing `ALB` or `Application Load Balancer` and no contradictory size/type discriminator. These inferences are eligibility-only and do not rewrite the caller's persisted input. Idle / residual-billing families (billed a flat rate regardless of traffic — see `characteristics.billingState`): AWS: `nat-gateway`, `vpc-endpoint`, `eip-detached`, `ebs-snapshot`, `alb-idle`. GCP: `cloud-nat`, `psc-endpoint`, `static-ip`, `pd-snapshot`, `clb-idle`. Azure: `azure-nat-gw`, `private-endpoint`, `azure-static-ip`, `disk-snapshot`, `azure-lb-idle`. OCI: `oci-reserved-ip`, `block-backup`, `oci-lb-idle`. DigitalOcean: `do-reserved-ip`, `do-vol-snapshot`, `do-lb-idle`. Snapshot/backup families bill per GB-month × `capacityGB` ÷ 730.
        - `containerCpu` number: Request-serving compute only — runtime CPU allocation in vCPUs. For ECS/Fargate this must be supplied together with `containerMemoryGiB` and match a supported task allocation. It is canonicalized to the same allocation represented by `size`.
        - `containerMemoryGiB` number: Request-serving compute only — runtime memory allocation in GiB. For ECS/Fargate this must be supplied together with `containerCpu` and match a supported task allocation.
        - `vcpu` number: ECS/Fargate task vCPU allocation; equivalent to containerCpu when both are equal.
        - `memoryGiB` number: ECS/Fargate task memory allocation; equivalent to containerMemoryGiB when both are equal.
        - `cpuArchitecture` 'X86_64' | 'ARM64': ECS/Fargate Linux architecture; ARM64 uses the separate Graviton vCPU and memory rates.
        - `runtimeMemoryProfile` object: Optional explicit per-instance application working-set assumption. CWM never infers it from configured memory, CPU, RPS, aggregate memory telemetry, or synthetic latency. It does not alter allocated memory billing.
          - `workingSetGiB` number, required
          - `source` 'user-provided' | 'calibrated' | 'external', required: Provenance category for the declared working-set profile.
          - `confidence` 'low' | 'medium' | 'high', required: Confidence level assigned to the declared working-set profile.
          - `limitExceededProbability` number: Only calibrated/external profiles may provide a probability.
          - `onLimitExceeded` object: Optional bounded consequences; omitted means CWM reports only the threshold condition.
            - `termination` 'terminate' | 'restart'
            - `failedWorkFraction` number: Required when termination is declared; the explicit bounded fraction of work stranded by that termination.
            - `retryMultiplier` number
            - `latencyPenaltyMs` number
            - `retryCostPerMillionRequests` number
        - `kubernetesCpuHpa` KubernetesCpuHpaConfig: Opt-in bounded CPU-based workload replica model. It changes workload replica counts only; it does not scale Kubernetes node pools, call a live cluster, or claim Kubernetes HPA controller parity. CPU request and demand are labeled ASSUMED unless their corresponding evidence object is supplied. An optional reviewed CPU/RPS measurement series can derive demand through a least-squares fit constrained through zero; its declared demand must match the fit and its source and reference must be retained in cpuDemandEvidence. Omitted CPU requests are never inferred; Utilization targets then return an explicit error. Utilization is measured as a percentage of the declared request; AverageValue is absolute millicores per pod. Each recommendation is ceil(current replicas × current metric ÷ target), then clamped to replica bounds.
          - `cpuRequestMilliCores` number: Explicit CPU request per pod in millicores. Required only for Utilization targets; never defaulted.
          - `cpuRequestEvidence` KubernetesCpuHpaEvidence
            - `source` 'calibrated' | 'external', required
            - `reference` string, required
          - `cpuDemandMilliCoresPerRps` number, required: Explicit assumed or evidenced CPU demand in millicores per offered RPS.
          - `cpuDemandEvidence` KubernetesCpuHpaEvidence
            - `source` 'calibrated' | 'external', required
            - `reference` string, required
          - `cpuDemandCalibration` object: Caller-reviewed CPU-demand calibration. The fitted value is aggregate workload CPU millicores divided by offered RPS, using least squares constrained through zero. cpuDemandEvidence must retain the source and reference. Each point is measured at workload level, not inferred by CWM.
            - `method` 'least-squares-through-origin', required
            - `reviewed` true, required: Explicit caller confirmation that the displayed fit was reviewed.
            - `measurements` object[], required
              - …
          - `targets` union[], required: Configure at most one target of each type. If both are configured, the larger bounded replica recommendation is applied.
            - union
              - …
          - `replicas` integer, required: Current workload replica count.
          - `minReplicas` integer, required
          - `maxReplicas` integer, required
          - `scaleDownStabilizationSteps` integer: Optional scale-down hold window measured in simulation steps.
        - `kubernetesMemoryProfile` object: Opt-in per-replica Kubernetes memory-pressure/OOM contract. CWM never infers per-pod memory, OOM likelihood, or working-set demand from the aggregate `memoryUsage` metric, CPU, RPS, or the configured limit alone. Absent by default — kubernetes resources without a profile explicitly report `evaluationStatus: "not_evaluated"` in `metrics.kubernetesMemory`. Single-pool clusters emit one row; a cluster with `nodePools` emits one row per pool and evaluates only pools that provide their own profile. Node/replica billing is unaffected.
          - `limitGiB` number, required: Configured `resources.limits.memory` per replica, in GiB.
          - `requestGiB` number: Configured `resources.requests.memory` per replica, in GiB.
          - `baselineWorkingSetGiB` number, required: Declared idle/floor per-replica working set.
          - `burstWorkingSetGiB` number: Declared transient/burst working set applied only while the replica is still warming up. Must be at least `baselineWorkingSetGiB`.
          - `headroomGiB` number: Additional declared safety margin folded into the demand/limit comparison.
          - `loadBreakpoints` object[], required: Declared working-set curve keyed on per-replica RPS, in strictly ascending `perReplicaRps` order. CWM interpolates linearly between breakpoints, and below the first breakpoint ramps linearly from the idle `baselineWorkingSetGiB` anchor up to the first breakpoint's declared value. Above the last breakpoint, behavior is governed by `extrapolation` — it never assumes a linear RPS-to-memory relationship beyond what is declared or implied by that policy.
            - `perReplicaRps` number, required: Per-replica request rate at which the declared working-set value applies.
            - `workingSetGiB` number, required: Declared per-replica working-set demand at this load, in GiB.
          - `extrapolation` 'clamp' | 'linear': Behavior above the highest declared loadBreakpoint. "linear" (default, preserves pre-existing behavior) extrapolates the final declared segment's slope beyond the last breakpoint. "clamp" holds demand flat at the last breakpoint's `workingSetGiB` value for any RPS beyond it — for curves known to plateau (e.g. a connection-pool- or cache-bounded working set). Only governs the region above the last breakpoint; the below-first-breakpoint ramp is unaffected either way.
          - `source` 'user-provided' | 'calibrated' | 'external', required: Provenance category for the declared working-set profile.
          - `confidence` 'low' | 'medium' | 'high', required: Confidence level assigned to the declared working-set profile.
          - `restartDelaySteps` number: Bounded restart/readiness delay measured in simulation steps before an OOMKilled replica rejoins serving capacity.
          - `restartDelaySeconds` number: Deprecated compatibility input. This historical spelling is normalized as simulation steps, not wall-clock seconds; use restartDelaySteps.
          - `maxConcurrentRestartFraction` number: Upper bound on the fraction of replicas that may be mid-restart at once, modeling k8s's staggered pod eviction/restart behavior.
          - `memoryAwareAutoscaling` boolean: Opt-in: when true, the modeled memory-pressure ratio can also trigger HPA scale-out alongside the existing CPU-only default. Leaving this unset/false keeps CPU-only scale-out decisions byte-identical.
          - `memoryScaleOutThresholdRatio` number: Demand/limit ratio at or above which memory-aware scale-out engages. Only consulted when `memoryAwareAutoscaling` is true.
        - `kubernetesMemoryEvidence` object: Observed configuration from a standard Kubernetes Deployment or StatefulSet. These source-labelled request/limit facts are never treated as runtime working-set demand and do not activate OOM, restart, capacity, latency, error, autoscaling, or billing effects.
          - `source` 'kubernetes-manifest', required: Manifest source category for the observed configuration.
          - `sourceFile` string, required: Repository-relative path of the manifest containing this configuration.
          - `kind` 'Deployment' | 'StatefulSet', required: Kubernetes workload kind that declares the memory configuration.
          - `workload` string, required: Name of the Deployment or StatefulSet that supplied the configuration.
          - `namespace` string, required: Kubernetes namespace containing the workload.
          - `replicas` integer: Literal replica count declared by the workload, when available.
          - `status` 'complete' | 'partial' | 'ambiguous' | 'missing' | 'non_literal', required: Whether the manifest memory configuration was fully and unambiguously extracted.
          - `requestGiB` number: Observed per-replica memory request from the manifest, in GiB.
          - `limitGiB` number: Observed per-replica memory limit from the manifest, in GiB.
          - `reason` 'multiple_containers' | 'non_literal_memory' | 'memory_not_declared': Specific reason extraction was incomplete or unsupported.
        - `eksSpotInterruption` object: Opt-in deterministic AWS EKS Spot interruption lifecycle. Ordinary Spot pricing and risk behavior is unchanged when this object is absent. `triggerAtStep` is a simulation step; the interruption notice is always a bounded 120 simulated seconds.
          - `enabled` true, required
          - `triggerAtStep` integer
          - `noticeWindowSeconds` 120
          - `simulationSecondsPerStep` number
          - `workloadReplicas` integer
          - `imageSizeMiB` number
          - `imageCacheState` 'warm' | 'cold'
          - `imageCacheHit` boolean
          - `pullBandwidthMiBPerSecond` number
          - `schedulingCapacity` integer
          - `startupSeconds` number
          - `interruptionHandling` 'reschedule' | 'drain-only' | 'fail-fast'
        - `healthChecks` object: Opt-in dependency-aware load-balancer and Kubernetes endpoint probes. Omission preserves existing simulation behavior. Dependency resource IDs are explicit graph references; livenessOnly checks do not depend on downstream resources.
          - `loadBalancer` EndpointHealthCheck
            - `path` string
            - `intervalSeconds` integer
            - `timeoutSeconds` integer
            - `unhealthyThreshold` integer
            - `healthyThreshold` integer
            - `dependencyResourceIds` string[]
            - `livenessOnly` boolean: If true, dependencyResourceIds must be empty and downstream outages do not fail this check.
          - `livenessProbe` EndpointHealthCheck
            - `path` string
            - `intervalSeconds` integer
            - `timeoutSeconds` integer
            - `unhealthyThreshold` integer
            - `healthyThreshold` integer
            - `dependencyResourceIds` string[]
            - `livenessOnly` boolean: If true, dependencyResourceIds must be empty and downstream outages do not fail this check.
          - `readinessProbe` EndpointHealthCheck
            - `path` string
            - `intervalSeconds` integer
            - `timeoutSeconds` integer
            - `unhealthyThreshold` integer
            - `healthyThreshold` integer
            - `dependencyResourceIds` string[]
            - `livenessOnly` boolean: If true, dependencyResourceIds must be empty and downstream outages do not fail this check.
          - `deployment` object
            - `maximumPercent` integer
            - `minimumHealthyPercent` integer
          - `maxConcurrentRestartFraction` number
          - `restartDelaySeconds` number
        - `desiredTaskCount` integer: ECS/Fargate only — initial desired running task count. With ECS CPU target tracking, the declared minimum is used when omitted. Fargate never uses generic compute cloning.
        - `minTaskCount` integer: ECS/Fargate only — smallest running task fleet, including zero for scale-to-zero services.
        - `maxTaskCount` integer: ECS/Fargate only — hard task-fleet ceiling. Offered traffic above total task capacity queues and can produce latency/errors.
        - `perTaskCapacityRps` number: ECS/Fargate only — modeled serving capacity of one running task in requests per second; it may differ by service/tier and is not a provider performance guarantee.
        - `perTaskCapacityEvidence` object: Caller-supplied evidence. Without a documented/measured basis and supporting source, the rate is labeled an assumption. Guesses must not be called measured.
          - `basis` 'assumption' | 'documented' | 'measured', required: documented/measured are caller assertions and require a supporting source; assumption is unverified.
          - `source` string: Supporting public document URL, report, or measurement reference. Required for documented or measured basis.
        - `applicationAutoscalingPolicy` object: Effective ECS CPU-only target-tracking policy when ecsCpuTargetTracking is enabled on create, or an explicitly supplied ECS policy.
          - `policyType` 'TargetTrackingScaling' | 'StepScaling'
          - `metricType` 'ECSServiceAverageCPUUtilization'
          - `targetValue` number
          - `scaleOutCooldownSeconds` integer
          - `scaleInCooldownSeconds` integer
          - `simulationSecondsPerStep` number
        - `taskStartupSeconds` number: ECS/Fargate only — modeled task startup wait in simulation steps (approximately seconds).
        - `fargateApplicationModel` object: Explicit application assumptions, not native ECS or ALB behavior. The FIFO counts pending requests separately from goodput. Idle/wake requires an explicit timed target-tracking policy and minimum task count zero.
          - `queueCapacityRequests` integer
          - `scaleToZeroOnIdle` boolean
        - `appReadySteps` integer: Request-serving compute only — modeled application-readiness ramp after a task or instance becomes active. Zero keeps the default binary-ready behavior.
        - `taskScaleDownSeconds` number: ECS/Fargate only — modeled quiet-period wait before a Fargate task is stopped.
        - `concurrency` integer: Request-serving compute only — maximum simultaneous requests per active instance or replica. Capacity is derived from this value and requestDurationSeconds; Lambda is constrained to 1.
        - `requestDurationSeconds` number: Request-serving compute only — modeled request duration in seconds. Each provider policy applies its documented billing granularity separately from capacity calculations.
        - `requestServingTargetUtilizationPct` number: AWS Lambda only — modeled scale-out target as a percentage of active-environment capacity. A value below 100% reserves request headroom; when omitted, scaling targets full modeled capacity.
        - `provisionedInstances` integer: Explicit ready capacity. It raises the warm floor and accrues the provider's warm/provisioned capacity charge.
        - `idleTimeoutSeconds` number: Idle period before a scale-to-zero eligible service releases non-required warm capacity.
        - `requestServingKind` 'function' | 'container': Declares whether this request-serving resource follows function or HTTP container lifecycle semantics.
        - `billingState` 'active' | 'idle' | 'stopped' | 'detached' | 'deleted': Residual billing state for idle-infrastructure cost simulation. `active` (default) — normal billing. `idle` — resource exists but serves no traffic; idle-family resources bill their flat residual rate. `stopped` — stopped compute/database: instance cost is $0 but any `capacityGB` storage keeps billing. `detached` — unattached IP / orphaned network artifact; bills the unattached rate for its `serviceFamily`. `deleted` — removed via idle-resource remediation; skipped entirely by the cost engine.
        - `size` string: Provider size/SKU label. For `serviceFamily: ecsFargate`, use an exact canonical allocation label such as `0.5 vCPU / 1 GB` or `1 vCPU / 2 GB`. A label and explicit `containerCpu`/`containerMemoryGiB` pair are equivalent; if both are supplied they must match. Unsupported, malformed, incomplete, or conflicting Fargate shapes return `INVALID_FARGATE_TASK_SIZE`. The default `0.25 vCPU / 0.5 GB` is used only when neither sizing form is supplied.
        - `instanceType` string: Compatibility alias for `size`, commonly used for AWS EC2 instance types such as `t3.medium`. When `size` is omitted, this value is canonicalized into `size` and resolved through the provider catalog. If both fields are supplied they must be identical; `size` is the canonical field. Unknown values remain visible as unrecognized SKUs and use the normal estimated fallback coverage.
        - `ocpus` integer: OCI flexible compute capacity input, with family-specific documented limits validated against the selected shape. VM.Standard.E4.Flex accepts an explicit positive integer from 1 through 64 for catalog-backed capacity resolution. OCI x86 maps 1 OCPU to 2 vCPUs; this hardware specification does not establish provider-specific performance, throughput, latency, or price. Memory is independently selected and is not inferred as a fixed amount per OCPU. Invalid counts are rejected; omitting the count leaves a flexible shape explicitly generic.
        - `maxThroughput` number
        - `capacityRps` number: Compute only — the literal per-node RPS ceiling: the request rate at which this node reaches ~95% CPU (critical territory). When supplied, the engine converts it to an internal `effectiveMaxThroughput = capacityRps / criticalUtilThreshold` using the provider's calibrated saturation coefficient, so the CPU curve peaks at the stated capacity without changing any accuracy calibration. Agents that know their workload's RPS capacity can set this field instead of tuning `maxThroughput` directly. Simulations that omit `capacityRps` fall back to `maxThroughput` with byte-identical behaviour. Kubernetes create requests containing `capacityRps` are rejected; use `maxThroughput` for the total Kubernetes cluster RPS ceiling, or `nodePools[].maxThroughput` for per-node pool capacity.
        - `baseLatency` number
        - `maxConnections` integer: Database only — the connection-pool capacity (maximum concurrent connections) for a database resource. The simulation models connection-pool pressure as `activeConnections / maxConnections` (surfaced as `metrics.connection_pressure`), so raising this value gives the database more headroom before the pool saturates. Aurora Serverless v2 retains an explicit limit at every ACU; when omitted, the PostgreSQL estimate derives from maximum configured ACU (16 ACU with minimum 2 gives 3360). Other databases retain their existing defaults.
        - `sessionAffinity` SessionAffinity: Modeled sticky session cohorts for generic compute or a Kubernetes workload. Compute peers require identical config/fleetId. Existing sessions never migrate on scale-out. Owner loss disconnects them. Omission preserves ordinary routing; min=max is not affinity. These are explicit assumptions, not measured OpenShell metrics.
          - `mode` 'sticky', required: Enables sticky routing of modeled sessions to their initial owner.
          - `fleetId` string, required: Shared identifier grouping compute peers or Kubernetes replicas in the same session-affinity fleet.
          - `initialSessions` number: Number of active sessions present at the start of the simulation.
          - `arrivalsPerStep` number: New sessions arriving during each simulation step.
          - `meanLifetimeSteps` number: Mean session lifetime in simulation steps.
          - `requestsPerSessionPerSecond` number: Request rate generated by each active session.
          - `maxSessionsPerOwner` number: Maximum active sessions assigned to one compute member or Kubernetes replica.
          - `reconnect` 'none' | 'next-step': Whether sessions disconnected by owner loss may reconnect on the next step.
          - `workload` object: Required only on Kubernetes. CPU-only replica HPA independent of node scaling. Assumes scheduling capacity is available. minReplicas <= replicas <= maxReplicas.
            - `replicas` integer, required: Starting number of workload replicas in the Kubernetes fleet.
            - `minReplicas` integer, required: Minimum workload replicas allowed by replica autoscaling.
            - `maxReplicas` integer, required: Maximum workload replicas allowed by replica autoscaling.
            - `targetCpuPercent` number
            - `capacityRpsPerReplica` number, required: Modeled request-per-second serving capacity of each workload replica.
            - `autoscaling` boolean
        - `connectionDemand` object: Database only. Omit to retain load-derived connection demand. declaredConnections and idlePoolFloor are ASSUMPTION / plan-time budgets (for example, replicas × per-pod pool size), not observed live DB connections. `declared` ignores instantaneous RPS; `max` takes the greatest of load-derived demand and the declared/floor values. Connection-pressure metrics report both load-derived and selected demand, plus the mode that drove pressure. This setting does not change cost or modeled database CPU/latency. Above the usable limit, declared demand adds a rule-based pool-saturation error signal (up to the existing 5% component cap); below the limit, provider-fit baseline errors remain load-derived unless saturationErrorThreshold is configured. That optional threshold suppresses pool errors below the configured aggregate utilization and applies the provider error curve to utilization beyond it; set the same threshold on every database in a multi-database graph. It does not change capacity, and this thresholded curve is not provider-calibrated.
          - `mode` 'load-derived' | 'declared' | 'max', required
          - `saturationErrorThreshold` number: Optional utilization threshold for the load-derived pool error curve. Below it, pool saturation adds no errors; above it, the curve uses utilization beyond the threshold. In graphs with multiple databases, set the same value on all databases. This does not change connection capacity.
          - `declaredConnections` integer
          - `idlePoolFloor` integer
        - `multiAz` boolean: For AWS Aurora Serverless v2, true with instanceCount 2 creates a separately billed reader in another AZ. Legacy true without instanceCount has no inferred reader and emits a normalized-config warning.
        - `instanceCount` integer: For generic compute, this fixes the number of represented VMs (integer 1–100); capacity is aggregated and CPU is reported per VM. It cannot be combined with autoscaling:true, minInstances or maxInstances. For AWS Aurora Serverless v2, supported values remain 1 with multiAz false or 2 with multiAz true; two instances have separate ACU charges. Conflicting explicit replicas, unknown AZs and unsupported families are rejected. A writer's derived reader has id <writer-id>-reader, characteristics.replicaOf and a distinct location.zoneKey.
        - `replicaOf` string: Database only — explicit ID of the writer this independently modeled healthy database can replace during a chaos database_crash. Promotion defaults to 30 simulation seconds; otherwise the sole writer remains down for the injection duration plus a modeled 1800-second restart delay. Chaos custom injections can override these delays. These are assumptions, not AWS SLAs. A supported Aurora Serverless v2 multiAz:true and instanceCount:2 pair creates this explicit relationship during simulation creation; multiAz alone does not.
        - `auroraStandbyResourceId` string: Opt-in modeled Aurora writer failover. On an AWS Aurora database writer, reference the ID of a distinct healthy AWS Aurora database standby in this simulation. A supported Aurora Serverless v2 multiAz:true and instanceCount:2 pair populates this reference. multiAz alone does not imply standby capacity. Quick inject-failure models a one-step promotion interruption, then reduced DB failure impact while the writer remains parked. No AWS calls or observed failover timing.
        - `capacityGB` number: Storage only — provisioned storage capacity in gigabytes for a storage/volume resource. Used together with `maxIops` to model block-storage throughput and IOPS utilization.
        - `maxIops` number: Storage only — the provisioned maximum IOPS (I/O operations per second) for a block-storage volume. The simulation models storage IOPS utilization against this ceiling (surfaced as `metrics.storageIopsUtilization`). Falls back to the provider's default volume profile IOPS when omitted.
        - `inferenceMode` boolean: Kubernetes only — marks a GPU/inference cluster. When `true` the engine bills each worker node at the provider's GPU node rate and emits the inference economics metrics (`metrics.gpuUtilization`, `metrics.tokensPerSecond`, `metrics.costPerMillionTokens`). GPU clusters also participate in idle-GPU detection (sustained utilization below 15% fires an idle-candidate event with the monthly waste estimate) and the GPU right-sizing hints (`gpu-underutilized`, `gpu-saturated`, `gpu-api-breakeven`).
        - `accelerator` string: Kubernetes + `inferenceMode` only — the GPU/accelerator type of the node pool. Known values: `a10g` (AWS g5), `t4` (GCP), `a10-fractional` (Azure NC8ads_A10_v4 — 1/3 A10 partition at one-third of full-A10 throughput), `a10` (OCI VM.GPU.A10.1 — full A10), `h100` (DigitalOcean GPU Droplet). Determines the per-node token throughput used to derive `metrics.tokensPerSecond` and `metrics.costPerMillionTokens`.
        - `managedComputePlatform` 'nvidia-dgx-cloud' | 'nvidia-dgx-cloud-lepton': Kubernetes + `inferenceMode` only — managed AI platform overlay running on top of the underlying cloud provider. When set to `nvidia-dgx-cloud`, the resource is an NVIDIA DGX Cloud node (e.g. `dgx-h200`, `dgx-b200`, `gb200-nvl72`) hosted on the provider named in `provider` (azure/gcp/oci) — the provider drives region and base pricing while the platform drives billing semantics (bundled control plane and fabric networking). `nvidia-dgx-cloud-lepton` is reserved for future multi-provider placement.
        - `openshiftOffering` 'rosa-hcp' | 'rosa-classic' | 'aro' | 'openshift-dedicated' | 'self-managed': Kubernetes-only distribution overlay. The resource `provider` remains the backing substrate for regions, network behavior, worker-node pricing, and provider latency. `rosa-hcp` and `rosa-classic` require AWS; `aro` requires Azure; `openshift-dedicated` supports AWS or GCP; and `self-managed` supports any existing CWM backing provider. Platform/licensing charges are additive to worker-node charges, and cost breakdowns identify worker, platform, and control-plane rows separately. AWS publishes ROSA's $0.171 per 4-vCPU worker service fee uniformly across supported standard regions and its ROSA HCP $0.25 per-cluster-hour fee. Azure publishes the ARO D4s v3 (4 vCPU) East US OpenShift line at $124.830/month, separate from the $160.60/month Linux VM line ($0.171/hour using 730 hours/month). Verified 2026-08-27 at https://azure.microsoft.com/en-us/pricing/details/openshift/. A three-worker, 2-vCPU-per-worker topology derives a valid $0.2565/hour platform total from that unit rate ($0.171 × 6 ÷ 4); it is not an independent published rate. The published ARO platform unit rate is marked `official` only when the resource location is Azure East US; resources in another region, or without a region, retain the East US amount as an estimate/reference assumption. OpenShift Dedicated and self-managed subscription allocations remain estimated where no comparable universal hourly line is published. Omit this field for byte-compatible generic Kubernetes.
        - `topology` object: Kubernetes + `inferenceMode` only — categorical GPU interconnect topology of the node. Two independent fields govern two independent efficiency coefficients. `intraNode` (inference): bounds tensor-parallel serving efficiency within a single node (PCIe 0.70, NVLink 0.85, NVLink+NVSwitch 0.92, InfiniBand 0.80). Omitting `intraNode` yields exactly 1.0, preserving pre-topology behavior. `interNode` (distributed training, `trainingMode: true` + more than one node): bounds collective-communication (all-reduce) efficiency across N DGX nodes (InfiniBand 0.90, NVLink+NVSwitch 0.88, NVLink 0.82, PCIe/Ethernet 0.65). Ignored for inference resources and for single-node training (where there is no inter-node all-reduce); absent `interNode` yields exactly 1.0. Flat-form aliases: `characteristics.intraNode` and `characteristics.interNode` (top-level, same enum) are also accepted and normalized into this nested `topology` object at the API boundary. The nested value wins when both shapes are present; only the nested form is stored and echoed back.
          - `intraNode` 'pcie' | 'nvlink' | 'nvlink-nvswitch' | 'infiniband': GPU-to-GPU interconnect within a single node. Applied to inference resources (default); ignored when `trainingMode: true`.
          - `interNode` 'pcie' | 'nvlink' | 'nvlink-nvswitch' | 'infiniband': Node-to-node fabric for distributed training. Applied only when `trainingMode: true` and node count > 1; ignored for inference resources and single-node training.
        - `intraNode` 'pcie' | 'nvlink' | 'nvlink-nvswitch' | 'infiniband': Flat-form alias for `topology.intraNode`. Normalized into the nested `topology` object at the API boundary before storage; the nested value wins when both are present. Only the nested form is stored and echoed back.
        - `interNode` 'pcie' | 'nvlink' | 'nvlink-nvswitch' | 'infiniband': Flat-form alias for `topology.interNode`. Normalized into the nested `topology` object at the API boundary before storage; the nested value wins when both are present. Only the nested form is stored and echoed back.
        - `includesControlPlane` boolean: Kubernetes only — when `true`, the managed platform's node rate already bundles the Kubernetes control plane, so the per-hour managed-K8s cluster fee is skipped at every billing surface. Set by DGX Cloud resources; absent/false preserves standard provider billing.
        - `bundledNetworking` boolean: Kubernetes only — when `true`, high-performance fabric networking (e.g. InfiniBand) is bundled into the platform node rate rather than billed separately. Declarative flag set by DGX Cloud resources; informational in Phase 1.
        - `trainingMode` boolean: Kubernetes + `inferenceMode` only — when `true`, the resource models a distributed-training workload across multiple DGX nodes. The simulation engine applies the inter-node topology coefficient (`topology.interNode`) to bound aggregate throughput by the collective-communication (all-reduce) efficiency of the cross-node fabric: InfiniBand 0.90, NVLink-Switch 0.88, NVLink 0.82, PCIe/Ethernet 0.65. When `false` or absent (default), inference mode is used and `topology.interNode` is ignored — behaviour is byte-identical to resources created before this field existed.
        - `tokensPerRequest` number: Kubernetes + `inferenceMode` only — average output tokens generated per inference request. Bounds token throughput by request demand (`throughputRps × tokensPerRequest`) and drives the decode component of the inference latency model (`latencyP50/P95/P99`). Defaults to 500 when omitted.
        - `inputTokensPerRequest` number: Kubernetes + `inferenceMode` only — average prompt (prefill) tokens per inference request. Drives the time-to-first-token component of the inference latency model. Defaults to `2 × tokensPerRequest` (a typical ⅔-input / ⅓-output chat mix) when omitted.
        - `cacheHitRate` number: Cache / CDN only — the expected cache hit rate as a fraction between 0 and 1 (e.g. `0.85` = 85%). Higher values reduce the load that reaches downstream origin/database resources and lower effective latency. Defaults to 0.8 when omitted for cache resources. Declared request-flow caches must specify it explicitly.
        - `cacheableRequestShare` number: Optional request-flow assumption for an explicitly modeled cache: the fraction of offered requests that are eligible for cache lookup. Cache hits reduce downstream work only within this share; remaining traffic bypasses the cache. This is a scenario assumption, not a measured production request distribution.
        - `asynchronousRequestShare` number: Optional request-flow assumption for an explicitly modeled queue: the fraction of offered requests enqueued for asynchronous processing. Queue consumers pass work downstream at their configured drain rate; queued messages do not reach databases until consumed. Cacheable and asynchronous shares must sum to at most 1.
        - `queueConsumerCount` integer: Number of modeled consumers for an explicitly configured queue. Total drain capacity is this count multiplied by `queueDrainRatePerConsumerRps`.
        - `queueDrainRatePerConsumerRps` number: Modeled messages consumed per second by each queue consumer. This is a disclosed scenario assumption, not a provider-measured throughput guarantee.
        - `queueCapacityMessages` integer: Explicit queue backlog capacity in messages. Queue fill is backlog divided by this capacity, not offered RPS divided by maximum throughput.
        - `queueSimulationStepSeconds` number: Simulation window in seconds used to convert per-second queue enqueue and drain rates into backlog messages.
        - `queueVisibilityTimeoutSeconds` number: SQS-style visibility timeout for unacknowledged messages. Messages return to the visible queue only after an actual consumer delivery remains unacknowledged for this duration.
        - `queueRetentionSeconds` number: Maximum message age. Expired messages are deleted; retention expiry does not transfer them to the dead-letter queue.
        - `queueMaxReceiveCount` integer: Maximum delivery attempts before an unacknowledged message is moved to the dead-letter queue.
        - `queueDeadLetterQueueResourceId` string: ID of a separate queue resource connected from this source queue.
        - `queueConsumerAckFailureRatePct` number: Optional assumed fraction of attempted consumer deliveries that are not acknowledged. A nonzero value requires complete queue lifecycle settings; the default is zero.
        - `queueConsumerFor` string: Compute-resource role binding to the ID of its directly connected queue. Producer scaling does not alter these consumer resources.
        - `cdnTraffic` CdnTraffic: Optional network-CDN request mix, an assumption rather than observed traffic. cacheHitRate applies only to the cacheable fraction. Critical CDN status bypasses edge caching. Omission preserves legacy aggregate-cache semantics.
          - `cacheableFraction` number, required
          - `cacheableMissDatabaseFraction` number, required
          - `dynamicDatabaseFraction` number, required
        - `autoscaling` boolean: Compute only — marks a compute resource as the autoscaling primary. When multiple compute resources exist, the engine uses the one flagged `autoscaling: true` as the autoscaling target; otherwise it falls back to the first compute resource.
        - `maxInstances` integer: Compute only — hard per-resource ceiling for RL `scale_out`. For request-serving services, this is the maximum active instances, replicas, or Lambda concurrent execution environments; it caps capacity and sheds excess demand rather than creating VM clones. For regular compute, it constrains the effective maximum below the simulation-wide autoscaling limit.
        - `minInstances` integer: Compute only — hard per-resource floor for RL `scale_in`. For request-serving services, this is the minimum warm instance or replica floor (raised by `provisionedInstances` when larger). For regular compute, it prevents scale-in below the declared floor.
        - `scaleOutCpuThreshold` number: Compute or Kubernetes — overrides the simulation-wide CPU autoscaling scale-out target (`autoscalingConfig.scaleOutCpuThreshold`, also settable via the top-level `autoscalingTargetCpu` / `scaleOutCpuThreshold` / `scaleOutCpuPercent` / `autoscaleTargetCpuPercent` aliases) for THIS resource's scale decisions only — every other resource in the simulation keeps using the simulation-wide default. Lets, for example, one GKE cluster scale out at 60% while an EC2 fleet in the same simulation scales out at 80%. Absent → this resource follows the simulation-wide default (byte-identical to before this field existed).
        - `scaleInCpuThreshold` number: Compute or Kubernetes — overrides the simulation-wide CPU autoscaling scale-in target (`autoscalingConfig.scaleInCpuThreshold`) for THIS resource's scale decisions only. Absent → this resource follows the simulation-wide default.
        - `nodeCount` number: Kubernetes only — the current number of worker nodes in the managed node pool. The cluster's `maxThroughput` and hourly cost scale proportionally with this count as autoscaling adds/removes nodes.
        - `workerVcpu` number: Kubernetes OpenShift only — vCPU capacity of each worker node used to prorate published platform or license fees billed per 4 vCPU-hours. Defaults to the canonical 2-vCPU worker when no size is recognized; set explicitly for custom shapes.
        - `minNodes` number: Kubernetes only — the minimum number of worker nodes the node pool will scale in to. Defaults to the provider autoscaling profile minimum when omitted.
        - `maxNodes` number: Kubernetes only — the maximum number of worker nodes the node pool will scale out to. Defaults to the provider autoscaling profile maximum when omitted.
        - `perNodeRate` number: inferenceMode Kubernetes only — caller-supplied GPU node hourly rate (USD/hr per worker node). Use this when the cluster size is not in the CWM GPU catalog (GPU_LARGE_NODE_HOURLY_RATES) to restore cost modeling for the unknown SKU. Must be strictly positive; zero and negative values are rejected (a zero rate looks "free" to any consumer that skips the costFidelity field, which recreates the false-precision problem this field was designed to solve). When absent and the SKU is also unrecognised, the cluster cost is excluded from the aggregate total and costComplete is set to false in the step response.
        - `pricingModel` 'on-demand' | 'spot': Compute and Kubernetes only — the pricing model for worker instances. `on-demand` (default) uses standard pay-as-you-go rates. `spot` applies the provider's spot/preemptible discount to reduce instance cost significantly (AWS/GCP/Azure: ~60% off on-demand; OCI: exactly 50% off) at the cost of potential interruption when the cloud provider needs capacity back. Non-compute resource types (database, storage, network, etc.) and DigitalOcean resources (which have no spot offering) ignore this field.
        - `eksSpotMigration` object: AWS EKS Kubernetes Spot interruption model. Opt-in only. When enabled, CWM emits a bounded 120-second interruption notice, workload migration/readiness progress, and a separate deadline hit/miss result. Migration evidence is not the same as sustained service-health recovery.
          - `enabled` boolean
          - `deadlineSeconds` integer
          - `workloadCount` integer
          - `imageSizeMiB` number
          - `imageCached` boolean
          - `pullBandwidthMiBPerSecond` number
          - `schedulingCapacity` number
          - `startupSeconds` number
          - `migrationDurationSeconds` number: Optional deterministic scenario override. When omitted, CWM derives migration duration from image pull, scheduling, and startup inputs.
          - `triggerAtSeconds` number
          - `handling` 'reschedule' | 'no_reschedule'
        - `nodePools` object[]: Kubernetes only — multiple node pools per cluster. When present, each pool scales independently within its own min/max bounds and is billed at its own per-node hourly rate. The legacy single-pool fields (`nodeCount`, `minNodes`, `maxNodes`) are used only when `nodePools` is absent.
          - `name` string: Human-readable node pool name.
          - `nodeCount` number: Current number of worker nodes in this pool. Scales proportionally with the pool's serving capacity and cost as autoscaling adds/removes nodes.
          - `workerVcpu` number: Kubernetes OpenShift only — vCPU capacity of each worker node in this pool for published per-4-vCPU platform or license fee proration. Falls back to the cluster-level workerVcpu, recognized size, or 2 vCPU.
          - `minNodes` number: Minimum number of worker nodes this pool will scale in to. Defaults to the provider autoscaling profile minimum when omitted.
          - `maxNodes` number: Maximum number of worker nodes this pool will scale out to. Defaults to the provider autoscaling profile maximum when omitted.
          - `perNodeRate` number: Hourly cost per node for this pool, USD/hr. Must be strictly positive (zero and negative values are rejected). Falls back to the provider default rate when omitted.
          - `maxThroughput` number: Per-node serving capacity (requests/second) for this pool. When pools carry different instance sizes, the engine uses these values to compute the correct CPU credit on scale events — a pool of large nodes relieves more load per node added or removed than a pool of small nodes. Falls back to a uniform distribution of the cluster's total `maxThroughput` across all nodes when omitted (preserving backwards-compatible behaviour for homogeneous fleets).
        - `streamShardsCount` number: DynamoDB only — number of shards provisioned for the DynamoDB Stream attached to this table. Each shard processes up to 1 000 records/s. When write throughput (see `streamWriteRps`) exceeds the total shard capacity, the simulation engine accumulates iterator age (seconds) and emits a `stream_lag` warning event. The iterator age is drained proportionally when traffic falls back within shard capacity. Ignored on non-DynamoDB resources.
        - `streamWriteRps` number: DynamoDB only — explicit write throughput (records/s) for the DynamoDB Stream. When omitted the engine estimates write load as 10 % of the current simulation traffic. Providing an explicit value is useful when the stream write pattern is decoupled from the simulated request traffic (e.g. bulk imports or batch pipelines that run independently). Ignored when `streamShardsCount` is 0 or absent.
        - `eventNotificationsEnabled` boolean: Storage event notifications — when true the engine models event notification delivery latency: baseline 1 500 ms, rising by 1 ms per 10 RPS above 5 000 RPS, capped at 30 000 ms. The latency penalty is propagated to downstream serverless compute resources (Lambda for S3, Cloud Functions for GCS, Azure Functions for Azure Blob via Event Grid, OCI Functions for OCI Object Storage). A `notification_delayed` warning event is emitted once when simulation traffic exceeds 20 000 RPS and cleared when traffic drops back below that threshold. Supported providers: `aws` (S3), `gcp` (GCS), `azure` (Blob Storage), `oci` (Object Storage). Ignored on non-storage resources and OCI block-volume resources.
        - `partitionCount` number: Azure Event Hubs only — number of partitions provisioned on the event hub. Each partition processes up to `partitionWriteRps` write records/s (defaults to 1 000 records/s per partition when omitted). When write throughput exceeds total partition capacity the engine accumulates `partitionLag` (seconds) in resource metadata and emits a `partition_lag` warning event when lag exceeds 30 s. Ignored on non-eventhub resources.
        - `streamPartitions` number: OCI Streaming only — number of stream partitions. Uses the same Kafka-style offset lag model as Azure Event Hubs `partitionCount`. When absent the engine falls back to `partitionCount`. A `stream_lag` warning event fires when lag exceeds 30 s. Ignored on non-oci-streaming resources.
        - `partitionWriteRps` number: Azure Event Hubs / OCI Streaming only — total write records/second produced into the event stream by all upstream writers combined. When omitted the engine estimates write load as 10 % of the current simulation traffic. Each partition handles 1 000 records/s; lag accumulates when `partitionWriteRps` exceeds `partitionCount × 1 000` (Event Hubs) or `streamPartitions × 1 000` (OCI Streaming).
        - `warmupSteps` integer: Compute and Kubernetes only — length of the JIT/CPU warm-up window in simulation steps. The capacity penalty is steepest on the first step (counter 0) and decays linearly to zero once the counter reaches this value, after which the resource operates at full capacity. Falls back to the engine default of 3 when omitted. A longer window models runtimes that take many steps to reach full throughput (e.g. JVM + JIT compilation, Python ML model loading); a value of 1 produces a single-step burst followed immediately by full capacity (useful for static binaries or pre-warmed containers). Ignored on non-compute resource types.
        - `warmupSeverity` number: Compute and Kubernetes only — peak fraction by which effective capacity is reduced on the first warm-up step. For example, `0.8` reduces capacity to 20 % of its normal value on step 0. The penalty decays linearly to 0 across the `warmupSteps` window. Falls back to the engine default of `0.5` (capacity halved) when omitted. Use higher values (closer to 1) to model cold-start-heavy stacks such as JVM applications or services that load large ML models at start; use lower values to model near-instant start-ups; set to `0` to disable the warm-up capacity penalty entirely. Ignored on non-compute resource types.
      - `routedRps` number: Compute and Kubernetes only — effective requests per second routed to this resource during the most recent simulation step. Only present on compute and kubernetes resources in step responses (`POST /simulations/{id}/step`); absent on GET and on non-compute, non-kubernetes resource types. The value is a float (one decimal place) so per-node values sum exactly to the simulation-level traffic without integer rounding error. For an ALB topology with three nodes at 45 RPS total all three nodes report `routedRps: 15.0`; if one node is killed the two surviving nodes each report `routedRps: 22.5` (the killed node's share is redistributed to the survivors). A node that is failed or parked reports `routedRps: 0.0`. Kubernetes clusters in an ALB topology report the per-cluster share of the routed traffic.
      - `availabilityState` 'available' | 'degraded' | 'unavailable' | 'scaled_to_zero' | 'cold_start': Compute, Kubernetes, and Database only — explicit availability classification set by the step pipeline after traffic redistribution is finalised. Absent on resource types where availability is not tracked (network, storage, cache, queue, security). `"available"` — the resource is healthy and serving traffic normally. `"degraded"` — the resource is under stress (`status: "critical"` or `"warning"`) but still serving positive traffic. `isRoutable` is `true`. A saturated DB with many open connections that is still handling requests correctly shows `"degraded"`, NOT `"unavailable"` — `status: "critical"` alone does not imply unavailability. `"unavailable"` — the resource is not serving any traffic: it is parked-critical (cpuUsage=0 and/or maxThroughput=0 while status=critical), downed by an AZ outage, or removed by an instance_kill failure. `isRoutable` is `false`. `"scaled_to_zero"` — an ECS Fargate service has no running tasks after scale-in. `"cold_start"` — an ECS Fargate service has no task ready to serve while tasks start. Both states have `isRoutable: false`; neither means a failed or parked resource. The field is optional for backward compatibility with stored snapshots that predate this field; it is always present on resources returned by step endpoints.
      - `isRoutable` boolean: Compute, Kubernetes, and Database only — whether this resource is receiving routed traffic during the current simulation step. `true` for resources with `availabilityState` of `"available"` or `"degraded"`. `false` for resources with `availabilityState` of `"unavailable"`, `"scaled_to_zero"`, or `"cold_start"`. Absent on resource types where availability is not tracked (network, storage, cache, queue, security), and absent on stored snapshots that predate this field.
      - `recoveryBlockedReason` string: When the recovery cooldown is not advancing because an engine guard blocked the demotion step, this field names the active guard. Absent when no guard is active and the resource is progressing normally through its cooldown arc. `"failure_park_window"` — the node was explicitly failed via injectNodeFailure and the minimum park-window has not yet elapsed; cooldownSteps is counting up toward the window boundary, not down toward healthy. `"idle_cpu_floor"` — the resource is parked-critical but the estimated idle CPU is still above the recovery threshold, so it cannot heal yet. The field is cleared (absent) on every step where neither guard fires.
      - `failureParkStepsRemaining` integer: Compute and Kubernetes only — number of simulation steps remaining in the forced unavailability window after an `inject-failure` call. Present only on step responses (`POST /simulations/{id}/step`); absent on GET responses, on resources that were not explicitly failed via `inject-failure`, and when the forced window has expired (value would be 0, so the field is omitted instead). The value counts down from `parkSteps - 1` on the first step after failure injection toward 0 on the last step of the park window, where `parkSteps` is `recoveryPolicy.failureParkSteps` when supplied and otherwise `recoveryPolicy.criticalSteps` (default 4, so the initial value is typically 3). Once the field is absent the node enters its organic cooldown-demotion recovery arc: it transitions critical → warning → healthy over subsequent steps.
      - `recoveryProgress` RecoveryProgress: Read-only recovery progress derived from the engine's current resource state. Poll `GET /simulations/{simulationId}` or the next step response until `state` is `healthy`; `stepsToHealthy` is only a lower-bound estimate.
        - `state` 'parked' | 'blocked' | 'cooling_down' | 'healthy', required: Current recovery stage; blocked means an active failure prevents cooldown.
        - `parkWindow` object, required: Progress through the forced-unavailability window before cooldown recovery can advance.
          - `totalSteps` integer, required: Total forced-unavailability steps for an explicitly failed compute/Kubernetes resource; zero when not applicable.
          - `completedSteps` integer, required: Park-window steps completed so far.
          - `remainingSteps` integer, required: Park-window steps remaining before cooldown can advance.
        - `cooldown` object, required: Progress through the current status-demotion cooldown window.
          - `target` 'warning' | 'healthy' | 'null', nullable, required: Status targeted by the current cooldown, or null when already healthy.
          - `completedSteps` integer, required: Consecutive qualifying simulation steps already completed toward the cooldown target.
          - `requiredSteps` integer, required: Consecutive qualifying steps required to reach target.
          - `remainingSteps` integer, required: Qualifying cooldown steps still needed.
      - `recoveryPolicy` object: Per-resource recovery thresholds that control how quickly the simulation transitions a resource from critical → warning → healthy. The four cooldown fields default to the global values (criticalCpuThreshold: 80, criticalSteps: 4, warningCpuThreshold: 70, warningSteps: 3) when omitted. `failureParkSteps`, when supplied, controls only the bounded unavailability window for the quick `inject-failure` flow; after that window the node can rejoin routing and continues through organic critical → warning → healthy cooldown. It must be at least 1. Use the persistent `instance_down` failure plus `recover-resource` when the node must remain unavailable until an explicit recovery action. Stateless microservices can use lower thresholds and fewer steps to heal faster; databases can use higher thresholds and more steps for a stricter recovery window.
        - `criticalCpuThreshold` number: CPU must drop to or below this percentage before the critical → warning cooldown clock starts.
        - `criticalSteps` integer: Number of consecutive simulation steps the CPU must stay below criticalCpuThreshold before transitioning from critical to warning.
        - `warningCpuThreshold` number: CPU must drop to or below this percentage before the warning → healthy cooldown clock starts.
        - `warningSteps` integer: Number of consecutive simulation steps the CPU must stay below warningCpuThreshold before transitioning from warning to healthy.
        - `failureParkSteps` integer: Number of simulation steps that a node failed through the quick inject-failure flow remains unavailable before it may rejoin routing. When omitted, criticalSteps is used. Zero is rejected.
    - `connections` Connection[]
      - `sourceId` string, required: ID of the source resource
      - `targetId` string, required: ID of the target resource
    - `traffic` number
    - `runtimeTrafficOverrideActive` boolean: Server-managed runtime control. When true on the SQS Queue Backlog Saturation scenario, steps ignore finalized traffic patterns without changing replay identity. Callers cannot set this field.
    - `scenarioAttribution` object: Trusted server-side attribution copied from the live scenario catalog; absent for explicit resource-graph creates.
      - `id` string, required: Live scenario catalog ID that supplied the simulation graph
      - `version` string, nullable, required: Catalog version, when provided
      - `revision` string, nullable, required: Catalog revision, when provided
      - `source` 'scenario-catalog', required: Trusted server-side attribution source
    - `currentTime` integer: Current simulation time step
    - `scenarioHash` string: Canonical SHA-256 of the persisted scenario graph and attached traffic-pattern order.
    - `effectiveConfigHash` string: Versioned prediction SHA-256 over the replay startup-input hash, engine version, and calibration identity. The original replay-only hash remains at replayIdentity.effectiveConfigHash.
    - `predictionEffectiveConfigHash` string: Engine-versioned prediction identity, separate from the replay-input identity.
    - `engineVersion` string: Simulation engine version used to produce the prediction.
    - `calibrationEvidence` CalibrationEvidence
      - `kind` 'owned' | 'owned-scaled' | 'modeled', required: Whether the applicable workload calibration is owned, explicitly scaled from an owned fit (not measured at that topology), or remains modeled output. The owned CRUD fit uses calibrationId aws-crud/v1:473f1339-f712-4096-96d6-3d4fc07cb427:7416cb63ace3a7ab2e3486bb6f132a2dcb574c34.
      - `latencyBasis` string, required: General modeled latency path/boundary; use latencyP99Basis for percentile-specific P99 provenance.
      - `latencyP99Basis` string: P99-specific basis: owned in-VPC internal-ALB fit, scaled from that fit (not directly measured), or uncalibrated generic model. Optional on older persisted calibration evidence.
      - `calibrationId` string: Versioned identifier of the owned calibration, present only when it applies.
      - `note` string, required: Human-readable calibration scope and limitations. For eligible canonical CRUD graphs, identify omitted workload inference as a modeling assumption when used. For modeled fallbacks, name all actual failed eligibility checks in stable order with resource IDs and safe expected/actual values; do not return only a generic "not applied" message. The owned-scaled typical fit identifies its two-server source, equivalent-load derivation, unmeasured server count, pool-to-database ceiling warnings, and any extrapolation.
    - `replayIdentity` ReplayIdentity
      - `scenarioHash` string, required: Canonical SHA-256 of the persisted scenario graph and attached traffic-pattern order.
      - `effectiveConfigHash` string, required: Canonical SHA-256 of the effective six-control startup configuration.
    - `normalizedConfig` NormalizedConfig: Read-only introspection of what CWM actually modelled from the creation payload. Returned on `POST /api/simulations` (201) and `GET /api/simulations/{simulationId}` so a consumer can verify the resolved billing and capacity values per resource — e.g. why two payloads that look identical produce different costs (different resolved `size`, GPU SKU, or cost multiplier). Every value is derived with the same resolution functions the billing/autoscale engine uses.
      - `resources` NormalizedResourceConfig[]
        - `id` string, required: Resource id (matches the entry in `resources`)
        - `name` string, required
        - `type` string, required
        - `provider` string, required
        - `requestFlowAssumptions` object: Per-resource opt-in cache or queue configuration. Drain rates, request shares, hit rate, and queue capacity are declared model assumptions rather than production observations.
          - `role` 'cache' | 'queue', required
          - `cacheableRequestShare` number
          - `cacheHitRatePct` number
          - `asynchronousRequestShare` number
          - `consumerCount` integer
          - `drainRatePerConsumerRps` number
          - `totalDrainCapacityRps` number
          - `queueCapacityMessages` integer
          - `queueVisibilityTimeoutSeconds` number
          - `queueRetentionSeconds` number
          - `queueMaxReceiveCount` integer
          - `deadLetterQueueResourceId` string
          - `queueConsumerAckFailureRatePct` number
          - `simulationStepSeconds` number
          - `note` string, required
        - `resolvedSku` string: Effective resource SKU after applying the size/instanceType compatibility alias. When both are supplied they must match; an unknown explicit SKU remains visible and is reported through the normal estimated/unsupported coverage path.
        - `openshiftOffering` 'rosa-hcp' | 'rosa-classic' | 'aro' | 'openshift-dedicated' | 'self-managed': Resolved OpenShift distribution overlay, when present.
        - `openshiftPlatformFeePerHour` number: Additive OpenShift platform or license fee in USD/hr. For per-4-vCPU offerings this is prorated across the resolved worker vCPU capacity.
        - `openshiftPricingStatus` 'official' | 'estimated': Provenance of the resolved OpenShift platform/license amount. ARO is official only for a resource explicitly located in Azure East US; other or missing regions are estimated reference use of the East US published line.
        - `openshiftControlPlanePricingStatus` 'official' | 'estimated': Provenance of the backing-provider or OpenShift-specific control-plane charge. For ARO this is estimated because the control-plane charge is modeled from Azure Kubernetes pricing and is included in the aggregate rather than represented as an excluded aro/managed resource.
        - `resolvedCostMultiplier` number: Effective cost multiplier the engine applies (explicit characteristics.costMultiplier, size-tier backfill, or pricing benchmark backfill — whichever the billing path resolves).
        - `resolvedHourlyRate` number: Resolved generic instance hourly rate (base rate × multiplier), USD/hr. Applies to compute and fixed-rate relational database resources; request-priced DynamoDB is described by dynamoDbBilling instead.
        - `rdsSqlServerBilling` RdsSqlServerBillingResult: Deterministic interval estimate for one bounded RDS SQL Server Express configuration. This is not an AWS invoice reconstruction. It excludes free tier, discounts, taxes, backup overage, transfer, additional gp3 IOPS/throughput, and account-specific adjustments; actual CloudWatch sampling and AWS hourly settlement can also differ.
          - `input` RdsSqlServerBillingConfig, required: Bounded caller-provided billing assumptions for AWS RDS SQL Server Express. Attach this object only to an `aws` `database` resource whose `serviceFamily` is `rds-sqlserver-express`. CPU is a constant interval assumption, not inferred from traffic, database load, generic simulated CPU, or a managed-service process. The initial earned-credit and surplus-credit ledgers are separate and cannot both be positive.
            - `engine` 'sqlserver-ex': SQL Server Express, license included. Other engines and editions are outside this bounded model.
            - `region` 'us-east-1': Pricing region supported by this model. It must agree with `resource.location.regionKey` when that field is supplied.
            - `deployment` 'single-az': Single-AZ deployment. Multi-AZ pricing is not included.
            - `instanceClass` 'db.t3.micro' | 'db.t3.small' | 'db.t3.medium' | 'db.t3.large' | 'db.t3.xlarge', required: Supported SQL Server Express T3 instance class. If `characteristics.size` or `instanceType` is also present, it must equal this value. Non-burstable classes are intentionally unsupported because this bounded regional Express catalog has no verified non-burstable SKU/rate; they fail validation rather than receiving a fabricated or generic fallback price.
            - `storageType` 'gp2' | 'gp3', required: General Purpose SSD storage price to apply. Additional gp3 IOPS and throughput are not modeled.
            - `allocatedStorageGiB` integer, required: Caller-declared allocated database storage in GiB.
            - `durationHours` number, required: Billing interval in hours. Storage is prorated over a documented 730-hour reference month.
            - `sustainedCpuPercent` number, required: Caller-provided constant average CPU utilization over the interval. This is neither an application-load attribution nor evidence that RdsAdminService, SQL Server, the OS, or any other process caused it.
            - `creditMode` 'unlimited': T3 Unlimited credit accounting. Disabling Unlimited is not modeled and would not remove the underlying CPU demand.
            - `initialCreditBalance` number: Earned CPU credits available at interval start, in vCPU-minutes. Bounded by the selected class's 24-hour earning limit.
            - `initialSurplusCreditBalance` number: Uncharged Unlimited surplus-credit debt at interval start, in vCPU-minutes. It is repaid before new credits are banked and is bounded by the selected class's 24-hour earning limit.
            - `settleSurplusAtEnd` boolean: Whether to charge and clear otherwise uncharged surplus debt at interval end, representing an explicit stop/delete settlement assumption. False keeps debt up to the 24-hour cap in the ending surplus ledger.
          - `computeCost` number, required: License-included Single-AZ instance cost over `durationHours`, USD.
          - `licensingCost` number, required: Separate SQL Server license surcharge over the interval, USD. Zero for this bounded Express license-included catalog because licensing is already included in computeCost.
          - `storageCost` number, required: Selected gp2/gp3 storage cost over `durationHours`, USD, using a 730-hour reference month.
          - `cpuCreditCost` number, required: Charged Unlimited surplus-credit cost over the interval, USD.
          - `totalCost` number, required: Sum of modeled instance, storage, and CPU-credit components, USD.
          - `effectiveHourlyCost` number, required: Effective interval total divided by durationHours, USD/hr.
          - `workloadVcpus` number, required: Fixed absolute CPU demand used for size comparisons, equal to class vCPUs times sustained CPU fraction.
          - `credits` RdsSqlServerCreditLedger, required: CPU-credit ledger for the caller-defined interval. One CPU credit is one vCPU-minute. Earned and consumed credits are totals; ending earned and surplus balances remain distinct.
            - `earnedCredits` number, required: Baseline credits earned during the interval.
            - `consumedCredits` number, required: Credits consumed by the declared vCPU utilization during the interval.
            - `creditCap` number, required: Maximum banked earned credits or uncharged surplus debt, equal to 24 hours of baseline earnings.
            - `endingCreditBalance` number, required: Earned credits remaining after consumption and repayment of any initial surplus.
            - `endingSurplusCreditBalance` number, required: Uncharged Unlimited surplus debt retained at interval end; zero when end settlement is requested.
            - `chargedCredits` number, required: Total credits billed as surplus, equal to overflow plus explicit end-settlement credits.
            - `overflowChargedCredits` number, required: Surplus credits beyond the 24-hour uncharged debt cap.
            - `settlementChargedCredits` number, required: Surplus debt charged because `settleSurplusAtEnd` was true.
          - `assumptions` string[], required: Human-readable pricing, interval, provenance, and exclusion assumptions that qualify this estimate.
        - `rateProvenance` object: Provenance of the resolved rate — present whenever `resolvedHourlyRate` or `resolvedGpuRatePerNode` is emitted. Tells consumers what pricing basis the constant reflects, which lookup path resolved it, and when it was last cross-checked against the provider's published pricing page (static metadata from the pricing constants, not fetched live). AWS M7, M8, and M9 entries such as `m7g.large`, `m8g.large`, and `m9g.large` use `basis: on-demand` and `resolution: exact-catalog` when their resolved hourly value matches the canonical AWS EC2 Price List catalog. This certifies price provenance only; EC2 performance characteristics remain CWM-modeled unless separately marked by a performance source.
          - `basis` 'on-demand' | 'spot' | 'estimated', required: Pricing basis — `on-demand` for published list-price constants, `estimated` for fallback/approximation constants.
          - `resolution` 'exact-catalog' | 'large-sku' | 'entry-level-fallback' | 'base-rate-multiplier' | 'fargate-task-vcpu-memory' | 'aurora-acu-hour' | 'cloud-run-request-based' | 'request-serving-usage-based', required: Lookup path taken — exact first-party catalog SKU (for example AWS M7, M8, or M9), exact premium GPU SKU, provider entry-level GPU fallback, or the general base-rate × multiplier path.
          - `lastVerified` string, date, nullable, required: ISO-8601 date the constant was last verified against the provider's public pricing page; null when unknown.
          - `region` string, nullable, required: Region the verified rate applies to, from the constant's pricing source (e.g. `us-east-1`, `us-central1`, `eastus`, `us-ashburn-1`); null only for genuinely region-uniform pricing (e.g. DigitalOcean).
          - `sku` string: Exact provider catalog SKU when resolution is exact-catalog (for example `m7g.large`); absent for generic and GPU fallback paths.
          - `architecture` string: Fargate Linux task architecture (X86_64 or ARM64).
          - `source` string: Official pricing source URL for verified billing-unit rates, or price-list source for an exact catalog rate; absent for generic and fallback paths.
        - `resolvedMaxThroughputRps` number: Effective aggregate throughput (RPS) for this resource. For fixed compute fleets, per-VM resolved throughput is multiplied by effectiveVmInstanceCount; independently materialized autoscaling members each count as one VM. When compute capacity is not declared, the resolved CPU/capacity model default is reported rather than an enforced serving ceiling. For Kubernetes, this is the enforced cluster serving ceiling; 0 means no serving ceiling is configured (the autoscaler's separate 10,000-RPS heuristic is not a ceiling).
        - `effectiveVmInstanceCount` integer: Effective physical VM count represented by this compute resource during simulation. A fixed logical fleet can represent multiple VMs; materialized autoscaling members each represent one. Absent for non-compute resources.
        - `effectiveCpuCapacityRps` number: Aggregate CPU-model capacity denominator (RPS) for this compute resource, calculated as the resolved per-VM CPU denominator times effectiveVmInstanceCount. This model denominator can differ from the declared serving capacity and is not itself a throughput ceiling.
        - `nodeCount` integer: Resolved worker node count (kubernetes).
        - `minNodes` integer: Effective autoscale floor (explicit minNodes or the provider autoscaling profile default the engine applies). Single-pool kubernetes.
        - `maxNodes` integer: Effective autoscale ceiling (explicit maxNodes or the provider profile default, floored to the current node count). Single-pool kubernetes.
        - `perNodeRatePerHour` number: Per-worker-node hourly rate the cluster bills at, USD/hr (single-pool kubernetes).
        - `controlPlaneFeePerHour` number: Cost-only cluster control-plane management fee, USD/hr. It is not control-plane CPU, API-throttling, or cooldown telemetry.
        - `spotFactor` number: Spot-pricing discount factor applied to node billing (present only when pricingModel is spot).
        - `currentNodesCostPerHour` number: Hourly cost at the CURRENT node count — controlPlaneFee + Σ(rate × nodes), spot factor applied, USD/hr.
        - `nodePools` object[]: Per-pool resolved billing/bounds for multi-pool clusters. When present, nodeCount/billingFloor values aggregate across pools and the pool entries carry the authoritative per-pool rates.
          - `name` string
          - `nodeCount` integer
          - `minNodes` integer: Effective per-pool scale-in floor (explicit or profile default).
          - `maxNodes` integer: Effective per-pool scale-out ceiling (explicit or profile default).
          - `perNodeRatePerHour` number: The rate this pool bills each node at (explicit perNodeRate or the cluster default), USD/hr.
          - `perNodeMaxThroughputRps` number: Per-node serving capacity for this pool, when explicitly configured.
        - `autoscaleThreshold` object: Effective CPU autoscale thresholds (provider profile merged with any simulation-level override; GPU inference clusters include the provider GPU overlay).
          - `scaleOutCpuPercent` number
          - `scaleInCpuPercent` number
        - `resolvedGpuRatePerNode` number: Resolved GPU node hourly rate (inference-mode kubernetes clusters), USD/hr.
        - `resolvedSkuLabel` string: The exact GPU SKU (`size`) that matched the premium GPU catalogue, or a fallback indicator naming the provider-level default GPU rate used instead.
        - `perNodeTokensPerSec` number: Per-node token serving capacity (tokens/s) resolved from the accelerator type.
        - `billingFloorNodes` integer: Smallest node count autoscaling can ever shrink the cluster to — min(nodeCount, effective minNodes), summed across pools for multi-pool clusters.
        - `billingFloorCostPerHour` number: Minimum billable hourly cost — controlPlaneFee + rate × billingFloorNodes (per-pool rates for multi-pool clusters), spot factor applied, USD/hr.
        - `resolvedStorageTier` string: Resolved storage/instance tier (databases; the `size` characteristic).
        - `resolvedConnectionLimit` integer: Effective connection limit — the exact value the engine's connection-pressure model uses (serverless ACU-based limit, else maxConnections, connections, or the engine default of 100).
        - `acuBilling` object: Aurora Standard Serverless v2 billing units. Hourly compute is currentAcu multiplied by ratePerAcuHour per billable DB instance; not a flat instance-hour rate.
          - `currentAcu` number
          - `minAcu` number
          - `maxAcu` number
          - `ratePerAcuHour` number
        - `dynamoDbBilling` object: DynamoDB's modeled cost is request-volume based, not a fixed instance-hour price. For each DynamoDB resource, the CWM modeled hourly cost is referenceModeledCostPerHour multiplied by that resource's allocated request rate divided by referenceRequestRateRps. Simulation traffic is split evenly across DynamoDB resources. The reference cost is derived from CWM's generic database base-rate anchor and effective cost multiplier; it is not AWS-published DynamoDB pricing or a provider quote.
          - `billingModel` 'request-volume', required: DynamoDB's modeled hourly cost scales linearly with allocated request rate.
          - `referenceRequestRateRps` number, required: Per-resource request-rate reference at which the CWM modeled hourly cost anchor applies.
          - `referenceModeledCostPerHour` number, required: CWM-modeled USD/hour anchor at referenceRequestRateRps; not an AWS DynamoDB price.
          - `referenceCostBasis` 'cwm-modeled-estimate', required: Identifies the reference cost as an internal CWM model estimate rather than provider-published pricing.
          - `providerPublishedPrice` false, required: Always false; the CWM reference anchor is not a provider-published price or quote.
        - `auroraInstanceRole` 'writer' | 'reader'
        - `auroraReaderScalingMode` 'writer-mirror' | 'independent'
        - `auroraScalingNote` string
        - `requestServing` object: Provider request-serving configuration (Cloud Run, functions, Azure Container Apps, or ECS/Fargate) derived from the same characteristics used by capacity and billing. Usage-based cost excludes provider free tiers, networking, logging, and discounts unless noted by the service policy.
          - `serviceFamily` string
          - `serviceKind` 'function' | 'container'
          - `containerCpu` number
          - `containerMemoryGiB` number
          - `cpuArchitecture` 'X86_64' | 'ARM64'
          - `concurrency` integer
          - `requestDurationSeconds` number
          - `minInstances` integer
          - `maxInstances` integer
          - `provisionedInstances` integer
          - `perInstanceCapacityRps` number
          - `idleMinimumCostPerHour` number
          - `desiredTaskCount` integer
          - `minTaskCount` integer
          - `maxTaskCount` integer
          - `perTaskCapacityRps` number
          - `perTaskCapacityEvidence` object: Resolved trust label. CWM size heuristics and unverified caller values are assumptions, never measured throughput or provider catalog facts.
            - `basis` 'assumption' | 'documented' | 'measured' | 'catalog', required
            - `origin` 'caller-provided' | 'fargate-size-heuristic' | 'policy-default' | 'provider-catalog', required
            - `source` string
          - `aggregateCapacityRps` number: Desired task count multiplied by this service's resolved per-task rate.
          - `maxAggregateCapacityRps` number: Maximum task count multiplied by this service's resolved per-task rate.
          - `taskStartupSeconds` number
          - `taskScaleDownSeconds` number
          - `taskVcpuHourRate` number: ECS/Fargate Linux on-demand us-east-1 vCPU-hour rate.
          - `taskMemoryGibHourRate` number: ECS/Fargate Linux on-demand us-east-1 GB-hour rate.
          - `billingGranularitySeconds` number: ECS/Fargate per-second billing granularity.
          - `minimumBillableSeconds` number: ECS/Fargate minimum billable task start duration.
        - `behaviorModel` object: Machine-readable identification of the latency simulation path applied to this resource at step time. Present on all resource types. Use `latencyPath` to programmatically determine which model will run — e.g. to verify a GPU inference cluster is using `gpu-inference/ttft-decode` rather than the generic K8s path.
          - `latencyPath` string, required: Canonical latency-path identifier. One of: `gpu-inference/ttft-decode` (K8s cluster with inferenceMode: true — token-economics TTFT+decode model), `generic-kubernetes/load-saturation` (K8s cluster without inferenceMode — generic saturation model), `generic-compute/load-saturation` (compute/VM resources), `database/connection-saturation` (database resources — connection-pool pressure model), `database/request-capacity` (DynamoDB — request/capacity model), `storage/iops-saturation` (OCI Block Volume only — IOPS ceiling tracked; penalties propagated to co-located DBs), `generic/flat-rate` (all other storage, network, cache, queue, security — cost-only, no latency simulation).
          - `description` string, required: Human-readable description of the latency model.
          - `modelingNote` string: Optional provenance note. Present on GPU inference resources (`latencyPath: gpu-inference/ttft-decode`) to remind API consumers that TTFT and decode-throughput figures are produced by the CWM simulation model, not by externally measured hardware benchmarks. When topology is omitted, includes an explicit legacy-baseline note stating that factor 1.0 reflects the pre-topology assumption, not a measurement of a specific interconnect fabric. Absent on non-inference resources.
          - `topologyThroughputFactor` number: The exact throughput scaling coefficient that `resolveTopologyScalingFactor` applied to this resource's token capacity (`nodes × perNodeTokens × topoFactor × gpuUtil/100`). 1.0 when `topology.intraNode` is absent or unrecognised (legacy baseline). Present only on inferenceMode Kubernetes resources. Entirely separate from `topologyTtftFactor` and `topologyDecodeFactor`, which describe the TTFT+decode latency model.
          - `topologyThroughputCalibrationStatus` 'calibrated' | 'estimated' | 'not_applicable': Calibration status of the throughput scaling factor (`resolveTopologyScalingFactor`) that bounds effective token capacity — entirely separate from the TTFT and decode latency factors (`resolveTopologyLatencyFactors`) described by `topologyCalibrationStatus`. "estimated" for all current inference-mode intraNode coefficients (0.70 pcie / 0.85 nvlink / 0.92 nvlink-nvswitch), which are engineering assumptions based on bandwidth arithmetic with no directly measured controlled per-request token-throughput ratio attributable to intraNode fabric type alone. "not_applicable" on non-inferenceMode resources. Reserve "calibrated" for when a directly measured source is added.
          - `topologyIsLegacyBaseline` boolean: `true` when `topology.intraNode` was absent or unrecognised, meaning `topologyThroughputFactor: 1.0` reflects the pre-topology legacy baseline — not a measurement of a specific interconnect fabric. `false` when an explicit, known `intraNode` value was supplied and the throughput factor is topology-modeled. Present only on inferenceMode Kubernetes resources. Agents should check this field before interpreting a factor of 1.0 as a best-fabric result.
          - `ibNetworkingCostPerNodePerHour` number: Per-node InfiniBand networking cost premium (USD/hr) added on top of the base GPU node rate when `topologyInterNode === "infiniband"` and `topologyNodeCount > 1`. Sourced from `IB_NETWORKING_COST_PER_NODE_PER_HOUR` in `shared/provider-pricing.ts` (calibration status: estimated — derived from the Azure NC24rs_v3 vs NC24s_v3 IB delta lower bound and NVIDIA DGX Cloud networking pricing upper bound; will be updated to on-demand status when a provider publishes a discrete IB fabric line-item at this granularity). Total cluster IB cost = `ibNetworkingCostPerNodePerHour × nodeCount`, surfaced as a separate `"infiniband_networking"` entry in the simulation-level `costBreakdown`. Absent when IB networking is not active (no `interNode` topology, single-node cluster, or non-IB fabric type).
        - `inferenceWarnings` string[]: Non-fatal configuration warnings. Populated when a Kubernetes resource carries inference/GPU characteristics (`accelerator`, `tokensPerRequest`, `inputTokensPerRequest`) but `inferenceMode` is absent or false — meaning the `generic-kubernetes/load-saturation` model will be used instead of the GPU inference model. Empty or absent when no warnings apply. To silence these warnings and activate the GPU inference path, set `characteristics.inferenceMode: true` on the resource.
        - `auroraTopologyWarnings` string[]: Non-fatal warning on a legacy AWS Aurora database with multiAz:true but no instanceCount and no explicit standby. No reader is inferred from that flag alone; specify instanceCount:2 on Aurora Serverless v2 to create a separately billed reader in another AZ.
      - `requestFlow` object: Graph-level assumptions for the explicitly configured cache and queue flow. Values remain assumptions; this summary does not claim measured production behavior, partition lag, or error absorption.
        - `cacheResourceId` string
        - `cacheableRequestShare` number
        - `configuredCacheHitRatePct` number
        - `queueResourceId` string
        - `asynchronousRequestShare` number
        - `consumerCount` integer
        - `totalDrainCapacityRps` number
        - `queueCapacityMessages` integer
        - `queueVisibilityTimeoutSeconds` number
        - `queueRetentionSeconds` number
        - `queueMaxReceiveCount` integer
        - `deadLetterQueueResourceId` string
        - `queueConsumerAckFailureRatePct` number
        - `simulationStepSeconds` number
        - `databaseResourceIds` string[]
        - `note` string
    - `normalizationReceipt` NormalizationReceipt: Immutable, versioned receipt stored at creation and returned unchanged by create, get, and step responses. It records each material assumption's source: human/API input, MCP/agent input, platform default, catalog resolution, engine inference, or unsupported fallback. It makes normalized Kubernetes inputs comparable without requiring equivalent natural-language prompts.
      - `version` 'gke-normalization-receipt/v2', required
      - `creationPolicy` object, required
        - `canonicalOrdering` string
        - `warmup` object
          - `applied` boolean
          - `steps` integer
          - `reason` string
      - `modelingScope` object, required: Explicit generic-Kubernetes limitations. CWM models worker-node capacity, CPU/utilization, autoscaling, and recovery only. The control-plane management fee is cost-only; CWM does not simulate or report control-plane CPU, master/API throttling, or control-plane cooldown. memoryUsage is synthetic aggregate utilization and cannot establish per-pod GiB, working-set demand, OOM timing, cgroup limits, eviction, or OOMKill behavior.
        - `kubernetesWorkerCapacity` string
        - `controlPlane` string
        - `memoryUsage` string
      - `kubernetes` object[], required
        - `resourceId` string, required
        - `requested` object, required
        - `effective` object, required
          - `capacity` object: Resolved Kubernetes worker serving capacity. For a multi-pool cluster, sum `pools[].capacityRps` to reconstruct `clusterMaxThroughputRps`; each `pools[].capacityShare` is the pool's share of that cluster ceiling and sums to approximately 1.0.
            - `perNodeRps` number, required: Uniform-equivalent per-node capacity in requests per second. For pool-aware capacity, use the pool-level `perNodeRps` values instead.
            - `clusterMaxThroughputRps` number, required: Immutable pre-effect Kubernetes cluster serving ceiling in requests per second. This is the reconstruction target for the sum of `pools[].capacityRps`.
            - `representation` 'pool-aware' | 'uniform-equivalent', required: Whether the capacity is broken down using heterogeneous node-pool weights or represented as a uniform per-node equivalent.
            - `source` string: How the cluster ceiling was resolved.
            - `pools` object[]: Per-pool capacity contributions. Present only for multi-pool Kubernetes resources. `capacityWeight` is the pool's node-count-weighted capacity, `capacityShare` is `capacityWeight` divided by the sum of all pool weights, and `capacityRps` is `clusterMaxThroughputRps × capacityShare`.
              - …
        - `fields` object, required
        - `pricing` object, required
          - `fidelity` 'catalog' | 'estimated' | 'excluded', required
          - `costExcludedFromTotals` boolean, required
          - `warning` string
    - `resilienceConfig` ResilienceConfig: Dependency-resilience model for a simulation. When attached (via `simulation.resilienceConfig`), the step engine traverses declared dependency edges, applies retry policies and circuit-breaker state, and emits `retryAmplificationFactor` + full `resilience` telemetry in every step response. **Limits** (shared with the engine to prevent unbounded work): - `dependencies`: max 64 edges - `scheduledFaults`: max 32 faults - `maxRetries` per dependency: max 8 - `maxCascadeDepth`: max 8 - `maxGeneratedRps`: max 500,000 RPS - `maxStepWork`: max 2,048 work units per step **Version:** only `version: 1` is accepted.
      - `enabled` boolean: Master switch — set false to disable the resilience model without removing the config.
      - `version` 1: Config schema version (must be 1).
      - `dependencies` ResilienceDependency[]: Directed dependency edges to model. Duplicate ids are rejected.
        - `id` string, required: Unique identifier for this dependency within the simulation.
        - `sourceId` string, required: Resource ID of the upstream (calling) service.
        - `targetId` string, required: Resource ID of the downstream (called) service.
        - `requestRatio` number: Requests sent to the target per original client request (fan-out multiplier).
        - `authRequestsPerAttempt` number: Auth/token requests added per attempt to an auth service. Models token-refresh overhead.
        - `authDependencyId` string: Optional dependency edge that receives the auth/token traffic generated by this edge. Use it to model gateway retry amplification causally saturating a downstream token service. The referenced dependency must exist and cannot be this dependency itself.
        - `retryPolicy` ResilienceRetryPolicy: Per-dependency retry policy. Controls how many times a failed request is retried, the back-off shape, jitter, and per-call timeout. Retry budgets (`retryBudgetRatio` / `retryBudgetRps`) cap the total retry RPS produced by this dependency so cascading retry storms are bounded.
          - `maxRetries` integer: Maximum retry attempts per original request (0 = no retries).
          - `backoffMs` integer: Initial back-off delay in milliseconds before the first retry.
          - `backoffMultiplier` number: Exponential multiplier applied to backoffMs on each retry attempt.
          - `jitterRatio` number: Fraction of the computed delay added as random jitter (0 = no jitter, 1 = full jitter).
          - `timeoutMs` integer: Per-request timeout in milliseconds. Requests that exceed this are counted as failures.
          - `retryBudgetRatio` number: Retry budget as a multiple of the original RPS. Retry RPS is capped at `retryBudgetRatio × originalRps`. Set to 0 to disable retries entirely even when maxRetries > 0.
          - `retryBudgetRps` number: Absolute retry RPS cap (takes precedence over retryBudgetRatio when both are set and the ratio-derived cap would be higher). Use to impose a hard ceiling on retry amplification regardless of traffic scale.
          - `retryActorId` string: Optional stable identifier for the actor producing this edge's retries (for example, a gateway or client).
        - `capacity` ResilienceCapacityConstraint: Capacity model for a dependency's target resource endpoint.
          - `maxRps` number: Peak RPS the target can sustain before requests overflow.
          - `maxConcurrent` integer: Maximum concurrent in-flight requests the target can hold.
          - `meanServiceTimeMs` number: Average time (ms) the target takes to process one request. Drives Little's Law concurrency pressure.
        - `protection` ResilienceProtection: Load-protection mechanisms for a dependency edge.
          - `circuitBreaker` ResilienceCircuitBreaker: Circuit-breaker configuration for one dependency edge.
            - `enabled` boolean: Whether the circuit breaker is active for this dependency.
            - `failureRateThreshold` number: Fraction of requests that must fail before the breaker opens (0.5 = 50%).
            - `minimumRequests` integer: Minimum requests in the measurement window before the failure rate is evaluated.
            - `openSteps` integer: Number of simulation steps the breaker stays open before transitioning to half-open.
            - `halfOpenMaxRequests` integer: Max probe requests allowed through while the breaker is half-open.
          - `rateLimitRps` number: Hard RPS ceiling applied to this dependency. Requests above the limit are shed immediately (counted in `shedRps`). Absent means no rate limit.
          - `loadShedding` boolean: When true, requests beyond the dependency's capacity are shed rather than queued. Enables graceful degradation under overload.
      - `scheduledFaults` ResilienceScheduledFault[]: Step-ranged faults to inject during the run. Duplicate ids are rejected.
        - `id` string, required: Unique fault identifier within the resilience config.
        - `type` 'capacity_limit' | 'concurrency_limit' | 'latency' | 'error_rate' | 'traffic_surge', required: Fault category: - `capacity_limit` — reduce the target's RPS ceiling to `capacityPercent`% of normal. - `concurrency_limit` — cap concurrent in-flight requests at `maxConcurrent`. - `latency` — add `addedLatencyMs` to every request on this path. - `error_rate` — inject synthetic failures at `errorRate` fraction (0–1). - `traffic_surge` — multiply original client demand on the targeted root dependency.
        - `targetResourceId` string: Resource to fault (mutually exclusive with dependencyId; one is required).
        - `dependencyId` string: Dependency edge to fault (mutually exclusive with targetResourceId; one is required).
        - `startStep` integer, required: Simulation step at which the fault activates (inclusive).
        - `endStep` integer: Simulation step at which the fault deactivates (exclusive). Omit for a permanent fault.
        - `capacityPercent` number: Used with type=capacity_limit — target capacity reduced to this percentage of normal.
        - `maxConcurrent` integer: Used with type=concurrency_limit — max concurrent requests while the fault is active.
        - `addedLatencyMs` number: Used with type=latency — milliseconds added to every request.
        - `errorRate` number: Used with type=error_rate — fraction of requests that fail synthetically.
        - `trafficMultiplier` number: Used with type=traffic_surge — multiplier applied to original client demand.
      - `scalingPolicies` ResilienceScalingPolicy[]: Capacity/autoscaling policies, including intentionally mismatched observed and constrained metrics.
        - `id` string, required
        - `dependencyId` string, required
        - `constrainedMetric` 'rps' | 'concurrency', required
        - `observationMetric` 'source_cpu' | 'capacity_utilization', required
        - `observedResourceId` string: Optional resource supplying source_cpu; defaults to the dependency source.
        - `scaleOutThresholdPercent` number, required
        - `scaleOutCapacityMultiplier` number
      - `externalMetrics` ExternalMetricsConfig: Opt-in deterministic external-metric recommendation model, not KEDA controller parity. No network calls, fallback algorithm, cooldowns, provisioning, billing changes, or provider autoscaler changes. Capacity is a separate modeled recommendation track. Each trigger recommends ceil(value / targetValue), clamped to the capacity bounds. Successful triggers combine by maximum, so zero cannot cancel demand. Samples start at step 0, have strictly increasing steps, and hold until replaced. Evaluation uses simulation.currentTime before step advancement. Normal creation warms up five steps; read returned currentTime before choosing sample steps. A supplied positive currentTime restores that clock and skips creation warm-up. The entire declared timeline is replayable: PATCH replaces the timeline and resets its derived history; reset/clone require no hidden controller state. drop_failed_trigger excludes errors but blocks scale-in whenever any trigger errors; all errors produce no recommendation and retain current modeled capacity. freeze_combined_decision retains the last all-success combined recommendation on any error, or returns null/frozen_without_prior if none exists. initialCapacity is not a successful recommendation. Only no_ready_endpoints may opt into a successful zero.
        - `errorMode` 'drop_failed_trigger' | 'freeze_combined_decision', required
        - `initialCapacity` integer, required: Must lie between minCapacity and maxCapacity.
        - `minCapacity` integer
        - `maxCapacity` integer
        - `triggers` object[], required: Independently identified triggers; IDs must be unique.
          - `id` string, required
          - `targetValue` number, required
          - `noReadyEndpointsAsZero` boolean: Converts only no_ready_endpoints to successful zero, never discovery_error or fetch_error.
          - `samples` ExternalMetricSample[], required
            - union
              - …
      - `podServices` PodService[]: Opt-in generic pod lifecycle, keyed by unique compute or Kubernetes resourceId. Omission preserves legacy behavior. This models workload pods, not billed VM nodes. Ordinary steps are one simulated second. Full step/metrics output is in resilience.podServices; compact MCP output is in resilienceDiagnostics.podServices. Initial pods are Ready by default; scale-out pods pass startup and probes. No controller, probe HTTP, or vendor-proxy parity is claimed.
        - `resourceId` string, required
        - `replicas` integer, required: Initial workload pod count; must match kubernetesCpuHpa.replicas when present.
        - `perPodCapacityRps` number, required: Declared modeled capacity of one serving pod; not measured capacity.
        - `initialReady` boolean
        - `readiness` object
          - `startupDelaySeconds` number
          - `readinessProbePeriodSeconds` integer
          - `failureThreshold` integer
          - `successThreshold` integer
        - `endpointPropagation` object
          - `addDelaySeconds` number
          - `removeDelaySeconds` number
        - `termination` object: New requests are accepted during preStop, bounded by the grace period. Afterwards a stale registered endpoint produces connection errors until removal. preStopSeconds cannot exceed terminationGracePeriodSeconds. In-flight work is modeled from prior per-endpoint RPS times declared duration. finish allows already accepted work to finish; drop-at-sigkill drops unfinished work at the grace deadline. Drops are reported separately as prior-request counts, not falsely attributed to the current step's offered requests.
          - `preStopSeconds` number
          - `terminationGracePeriodSeconds` number
          - `inflightDrainBehavior` 'finish' | 'drop-at-sigkill'
          - `requestDurationSeconds` number
        - `hpaUnreadyPodPolicy` 'excluded' | 'zero' | 'target': CPU-request HPA averages Ready-pod demand only (excluded), includes unready pods at zero (zero), or includes them at each metric target (target). Recommendations use the existing nominal-replica formula and stabilization. With no Ready pods, excluded holds the current count. This is an exploration model, not controller parity.
        - `replicaChanges` object[]: Unique ascending absolute engine steps; overrides HPA desired counts when supplied.
          - `step` integer, required
          - `replicas` integer, required
        - `readinessChanges` object[]: Unique ascending steps providing a synthetic service-wide probe signal; default passing.
          - `step` integer, required
          - `passing` boolean, required
        - `rollingUpdate` object: One deterministic rollout; absolute pod-count surge/unavailable bounds cannot both be zero.
          - `startStep` integer, required
          - `maxSurge` integer
          - `maxUnavailable` integer
      - `maxCascadeDepth` integer: Maximum dependency depth the engine traverses per step (prevents O(n) blow-up on deep graphs).
      - `maxGeneratedRps` number: Hard ceiling on combined retry-generated and auth/token RPS across all dependency edges per step.
      - `maxStepWork` integer: Maximum work-unit budget consumed per step across all dependency paths.
      - `retryGeneratedTrafficAffectsCost` boolean: When true, retry-amplified RPS contributes to the cost model (more compute utilization = higher cost).
  - `event` object: Event recording the node failure
  - `resolvedResourceId` string: ID of the resource that was failed (targeted or randomly selected)
  - `resolvedResourceName` string: Name of the resource that was failed
  - `previousHealth` string: The resource's health status immediately before the failure was applied (e.g. healthy)
  - `healthyNodes` object[]: Every resource still in healthy status after this failure was applied, as `{id, name}` pairs. Agents can use this list to pick valid targets for subsequent inject-failure calls without an extra `GET /simulations/{simulationId}` round-trip.
    - `id` string: Resource ID
    - `name` string: Resource name

## Other responses

- `400`: No healthy nodes available to fail, ambiguous resourceName (body includes a candidates array listing every matching resource), the targeted resource is not in a faileable state (body describes its current status), or an empty body `{}` (must supply at least one of `resourceName`, `resourceId`, or `random: true`). The response body contains a structured `FieldError` (or array of `FieldError` objects) so agents can programmatically identify the rejected field and self-correct.
- `401`: No credentials provided to an API-key-protected endpoint. The response body includes a machine-readable `apiKeyAuth` block pointing to the key creation endpoint so agents can self-serve an API key without consulting the spec.
- `403`: Access denied
- `404`: Resource not found
- `500`: Internal server error

---

[API](https://skmtc.dev/cloudworldmodel/apis/cloud-world-model-agent-api.md) · [All operations](https://skmtc.dev/cloudworldmodel/apis/cloud-world-model-agent-api/llms.txt) · [OpenAPI document](https://skmtc.dev/cloudworldmodel/apis/cloud-world-model-agent-api/revisions/ecc3ad1ff07f?raw)
