---
title: "Run multiple RL actions in a single request"
method: POST
path: "/rl/environments/{environmentId}/batch-step"
tags: ["RL Environments"]
---

# Run multiple RL actions in a single request

`POST /rl/environments/{environmentId}/batch-step`

## Request

- Base URL: `https://www.cloudworldmodel.ai/api`
- URL: `https://www.cloudworldmodel.ai/api/rl/environments/{environmentId}/batch-step`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-PAYMENT`
  - API key in header `PAYMENT-SIGNATURE`

Executes up to 30 actions sequentially in a single HTTP round-trip, advancing the
simulation by one step per action. Stops early and returns partial results if the
episode ends (`done: true`) before all actions are processed.

**Rate limiting:** Each action in the batch counts as one call against the
5 000 req/hr RL training quota. A batch of 30 actions consumes 30 quota units.
If the quota is exhausted mid-batch, the endpoint returns 429 with a `Retry-After`
header indicating how many seconds remain until the window resets.

**Idle TTL:** The last successful batch-step call resets the environment's 2-hour
idle timer, the same as a single `step` call.

## Path parameters

- `environmentId` string, uuid, required

## Request body

- object
  - `steps` object[], required: Ordered list of step actions to execute (max 30)
    - `action` Action, required: Action selected by the RL agent for one simulation step.
      - `type` 'adjust_threshold' | 'scale_out' | 'scale_in' | 'add_resource' | 'remove_resource' | 'no_op' | 'set_recovery_policy', required: Type of action to execute
      - `parameters` union, required: Type-specific values for the selected action; pass an empty object when the action has no parameters.
        - AdjustThresholdParams: Parameters for adjust_threshold action
          - `cpuThreshold` number: CPU utilization threshold for scaling (%)
          - `throughputThreshold` number: Throughput utilization threshold (%)
          - `latencyThreshold` number: Latency threshold for scaling (ms)
        - ScaleParams: Parameters for scaling the compute fleet in or out.
          - `instanceCount` integer, required: Number of instances to add (`scale_out`) or remove (`scale_in`) — a delta, not a target count. The effective change is clamped to the per-resource instance bounds: the ceiling is `min(autoscalingConfig.maxInstances, characteristics.maxInstances)` for `scale_out`, and the floor is `max(autoscalingConfig.minInstances, characteristics.minInstances)` for `scale_in`. When clamping occurs the step response `info` object contains the flat keys `scale_clamped: true`, `requested` (this parameter's value), `actual` (delta applied, 0 if blocked), and `limit` (effective bound).
        - ResourceParams: Resource details for adding a resource, or its identifier when removing one.
          - `resource` Resource
            - `metadata` object: Resource metadata; configured Route 53 nodes persist modeled lifecycle telemetry under route53Failover.
              - …
            - `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.
              - …
            - `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.
              - …
            - `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.
              - …
            - `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.
              - …
          - `resourceId` string: For remove_resource action, ID of resource to remove
          - `resourceType` 'compute' | 'database' | 'storage' | 'network' | 'security': For add_resource action, type of resource to add
          - `provider` 'aws' | 'gcp' | 'azure' | 'oci' | 'digitalocean': For add_resource action, cloud provider for the new resource
          - `targetSize` string: For add_resource action, size label for the new resource (e.g. "t3.micro" for aws)
          - `regionKey` string: For add_resource action, region key to assign to the new resource (e.g. "us-east-1" for aws). Must be a valid region for the specified provider. Returns UNKNOWN_REGION if invalid.
        - SetRecoveryPolicyParams: Parameters for set_recovery_policy action. Supports two call forms: **Per-resource form** — provide `resourceId` and the nested `recoveryPolicy` object to override healing thresholds on one specific resource. **Global flat form** — omit `resourceId` and pass the four threshold fields (`criticalCpuThreshold`, `criticalSteps`, `warningCpuThreshold`, `warningSteps`) directly at the top level to apply the same policy to every resource in the simulation at once. The nested `recoveryPolicy` object takes precedence over the flat fields if both are supplied.
          - `resourceId` string: ID of the resource to update. Omit to apply the policy globally to all resources (global flat form).
          - `recoveryPolicy` object: Recovery policy thresholds to apply (per-resource form). Takes precedence over flat threshold fields when both are provided.
            - `criticalCpuThreshold` number, required: CPU % above which a resource is considered critical
            - `criticalSteps` integer, required: Steps the resource must stay at critical CPU before recovery triggers
            - `warningCpuThreshold` number, required: CPU % above which a resource is considered in warning state
            - `warningSteps` integer, required: Steps the resource must stay at warning CPU before recovery triggers
          - `criticalCpuThreshold` number: CPU % above which a resource is considered critical (global flat form — applies to all resources when resourceId is omitted)
          - `criticalSteps` integer: Steps a resource must stay at critical CPU before recovery triggers (global flat form)
          - `warningCpuThreshold` number: CPU % above which a resource is considered in warning state (global flat form)
          - `warningSteps` integer: Steps a resource must stay at warning CPU before recovery triggers (global flat form)
    - `tick_seconds` integer: Simulated seconds to advance per step (overrides environment default)

## Response `200`

Batch steps executed successfully

- object
  - `results` object[], required: Step results in the same order as the request `steps` array. May be shorter than the request array if the episode ended early (`done: true` in the last result).
    - `t` integer, required: Current step index
    - `obs` RLObs, required: Agent-facing decision state returned by the step and observation endpoints. Contains only the signals an RL agent needs to choose its next action. Fractions (cpu_util, error_rate, uptime) are in the range [0, 1].
      - `rps` number, required: Throughput in requests per second
      - `cpu_util` number, required: CPU utilization as a fraction (0–1)
      - `instances` integer, required: Total number of active compute instances
      - `traffic` number, required: Current traffic load (requests per second)
      - `currentTime` integer, required: Current simulation time step
      - `tick_seconds` integer: Number of simulated seconds this step advanced the clock. Reflects the per-step `tick_seconds` override supplied in the step request body, or the episode-level `episodeConfig.tick_seconds` when no per-step override was given.
      - `unmodeled_dimensions_active` boolean: True when at least one cost dimension is active but not modeled in the reward function (e.g. egress or cross-AZ traffic). See `unmodeled_cost_warning` at the step-response top level for the list of active dimensions. Absent (falsy) when all dimensions are modeled or no unmodeled dimensions are detected.
      - `warmup_factor` number: Minimum warmup capacity factor across all compute/Kubernetes resources that carry a warm-up counter, in (0, 1]. Value is 1.0 when all instances are fully warmed; less than 1.0 while any instance is still in its warm-up window. **Mid-episode transition:** This field is absent at episode start when no compute resource carries a warm-up counter. It appears dynamically once a cold compute instance is provisioned — either via an `add_resource` action or via canvas autoscaling triggered by high CPU or traffic. After the warm-up window completes (typically 3 steps), the field returns to 1.0 and may become absent again if no further cold instances exist. **Safe access pattern:** Agents building a fixed-width observation vector must guard against its absence by defaulting to 1.0 when the field is not present. In Python: `wf = obs.get("warmup_factor", 1.0)`. In JavaScript/TypeScript: `const wf = obs.warmup_factor ?? 1.0`. Never assume the field is present across all steps of an episode. Use this as a direct observation feature in your reward function — low values indicate recently-provisioned instances are not yet at full throughput. For per-resource detail see metrics.compute[].warmup_factor.
      - `routedRps_per_node` number: Average requests per second routed to each active compute node this step. Computed as the instance-count-weighted mean of per-node `routedRps` across all compute resources: `sum(perNodeRps × instances) / totalInstances`. Unlike `rps` (the simulation-wide throughput total), this field expresses per-node load directly so agents can make scaling decisions without dividing by instance count themselves. When nodes fail their share of traffic is redistributed to surviving nodes — `routedRps_per_node` captures this redistribution effect accurately, eliminating the approximation error that arises when computing `rps / instances` after a partial failure. **Availability:** Present on step responses whenever at least one compute resource exists and the simulation engine has produced a routedRps map for the step. Absent on reset observations (which are pre-step snapshots) and on simulations that contain no compute resources. **Safe access pattern:** Default to `obs.get("rps", 0) / max(obs.get("instances", 1), 1)` when the field is absent for backward compatibility.
      - `unavailable_node_count` integer: Number of compute, Kubernetes, and database resources whose `availabilityState` is `"unavailable"` at this step. A value of zero means every tracked resource is serving traffic normally. Use this as a compact policy signal — when greater than zero, at least one node has failed (parked-critical, AZ-outage, or instance_kill) and surviving nodes are absorbing its redirected traffic share. Pair with `observation.resources[].availabilityState` for per-node detail when you need to identify which specific resources are unavailable. **Zero on reset observations** (all resources default to `"available"` before the first step, so this value is always 0 on fresh episode start). **Safe access pattern:** `unavailable = obs.get("unavailable_node_count", 0)` — treat absence as zero.
    - `metrics` RLMetrics, required: Evaluation-oriented metrics returned alongside obs by the step and observation endpoints. These are the outputs your reward function and monitoring dashboards should read. Fractions (error_rate, uptime) are in the range [0, 1]. The `connection_pressure` field is optional — it is only present when the simulation contains at least one database resource (e.g. RDS, Cloud SQL, Azure SQL, Autonomous DB, Managed PostgreSQL). Agents should guard against its absence when no database resource is configured.
      - `cost_usd_hr` number, required: Current hourly cost in USD
      - `latency_p95` number, required: P95 latency in milliseconds
      - `error_rate` number, required: Error rate as a fraction (0–1)
      - `uptime` number, required: Uptime fraction (0–1); equals 1 − error_rate
      - `sla_violations` integer, required: Number of SLA dimension violations at this step (0 = fully compliant)
      - `connection_pressure` number: DB connection-pool pressure ratio (activeConnections / maxConnections), capped at 3.0. Only present when the simulation contains database resources. Values > 1.0 indicate pool exhaustion; values > 1.5 indicate severe saturation. Use this field to build reward functions that penalise connection-pool exhaustion independently of the aggregate error_rate signal.
      - `compute` object[]: Per-resource warm-up state for compute and Kubernetes resources that carry a warm-up counter. Only present when at least one such resource exists in the simulation. Absent when no resources have a warm-up counter. **Mid-episode transition:** Like `obs.warmup_factor`, this array is absent at episode start when no compute resource carries a warm-up counter. It appears dynamically once a cold compute instance is provisioned mid-episode (via `add_resource` or canvas autoscaling) and disappears again once all warm-up windows complete. Guard against its absence with `metrics.compute ?? []` or `metrics.get("compute", [])`. Agents should read `obs.warmup_factor` as the primary scalar observation feature — do not re-derive it from step counters or this array.
        - `resource_id` string: Resource id of this compute or Kubernetes resource
        - `name` string: Human-readable resource name
        - `warmup_factor` number: Normalized capacity factor in (0, 1]. Value is 1.0 when fully warmed; less than 1.0 while warming (steepest at step 0). Use this directly as an RL observation feature instead of inferring it from step counters.
        - `warming` boolean: True while the resource is still in its warm-up window
        - `warmup_steps_remaining` integer: Steps remaining until the resource is fully warmed (0 when warmed)
      - `error_breakdown` object: Per-contributor breakdown of the error rate. Each field is the pre-clamp additive contribution in percentage-point units (same scale as `error_rate × 100`). `queue_absorption` is the amount removed by queue buffering (positive value = reduction). `dependency_failure` is the client-level contribution from failed downstream dependency paths. Only present when the simulation engine produces an errorBreakdown for the step. Absent on early steps or simulations without failure sources. Use these fields to build reward functions that penalise specific failure modes — for example, penalise `pool_saturation > 0` for modeled connection demand beyond a usable database limit, or reward reducing `capacity_overload` after a scale-out. `database_pressure` reports the generic contribution once a database's connection or CPU utilization exceeds its effective threshold (80% by default); benchmark-compatibility runs retain their provider reference fit. It shares a combined 5 percentage-point database-error cap with `pool_saturation`. The accompanying details name the database and whether connection use, CPU, or both drove the pressure. Pool-saturation details identify databases whose modeled demand exceeds their own usable connection limits. `cpu_overload` is serving-CPU telemetry, not a compute-failure signal. Only active, request-serving compute and Kubernetes resources that are actually serving traffic contribute; parked or unavailable nodes are excluded from the numerator and denominator. Database and network resource CPU is separate and never contributes to `cpu_overload`. For each eligible serving resource, the engine computes `max(0, (cpuUsagePercent - 80) / 20)`, averages those per-resource excesses, and multiplies by the provider slope. Ordinary/custom AWS uses a 10 percentage-point slope: one eligible AWS node at 100% CPU contributes 10 points, while eligible nodes at 100% and 70% contribute 5 points. Adding a parked 100% node, a database at 100%, or a network resource at 100% leaves that value unchanged. Other providers currently use slope 0. The owned AWS CRUD benchmark may intentionally report zero here as an internal calibration policy; that is not a generic API rule and should not be applied to ordinary AWS responses.
        - `database_pressure` number: Generic database pressure contribution after per-database connection utilization or CPU utilization exceeds its effective threshold (80% by default); benchmark-compatibility runs retain their provider reference fit.
        - `database_pressure_details` object[]: Per-database attribution for the under-limit pressure contribution.
          - `resource_id` string, required
          - `name` string, required
          - `cause` 'connection_pressure' | 'cpu_pressure' | 'connection_and_cpu_pressure' | 'provider_reference_fit', required
          - `modeled_connections` number, required
          - `usable_connection_limit` number, required
          - `connection_utilization` number, required
          - `cpu_utilization` number, required
          - `contribution_pct` number, required
        - `pool_saturation` number: Error contribution from connection-pool exhaustion (percentage points)
        - `pool_saturation_details` object[]: Databases whose modeled connection demand exceeds their own usable pool limits.
          - `resource_id` string, required
          - `name` string, required
          - `cause` 'connection_limit_exceeded', required
          - `modeled_connections` number, required
          - `usable_connection_limit` number, required
          - `demand_basis` 'modeled connection demand; not observed live sessions', required
        - `db_failure` number: Error contribution from failed or overloaded database resources (percentage points)
        - `compute_failure` number: Error contribution from failed compute nodes (percentage points)
        - `capacity_overload` number: Error contribution from traffic exceeding compute capacity (percentage points)
        - `cpu_overload` number: Serving CPU overload contribution in percentage points. Uses the mean per-resource CPU excess above 80% among active serving compute/Kubernetes resources, excluding parked or unavailable nodes and excluding database/network CPU. Ordinary/custom AWS uses a 10-point slope, so 100% on one eligible node contributes 10 points.
        - `oci_storage` number: Error contribution from OCI storage I/O issues (percentage points)
        - `queue_absorption` number: Amount of error rate absorbed (buffered) by queue resources (percentage points, positive = reduction in effective error rate). Subtract this from the sum of the other contributors to get the net error rate before clamping.
        - `dependency_failure` number: Client-level error contribution from unavailable or failing downstream dependency paths (percentage points)
      - `ttft_p50_ms` number: Weighted-average time-to-first-token (TTFT) in milliseconds across all `inferenceMode` GPU Kubernetes clusters in the simulation. Accounts for the accelerator catalog TTFT base, the prompt-token count (`inputTokensPerRequest`), the topology TTFT factor, and batch pressure at the current GPU utilization. Only present when the simulation contains at least one active `inferenceMode` cluster with positive throughput weight. Absent on non-inference simulations. Use this field to reward inference-serving agents that minimize time-to-first-token.
      - `decode_tokens_per_sec` number: Weighted-average effective decode throughput (tokens/second) across all `inferenceMode` GPU Kubernetes clusters. Derived from the accelerator catalog decode rate, the topology decode factor, and batch pressure at the current GPU utilization. Higher values indicate the cluster is decoding tokens faster. Only present when at least one active `inferenceMode` cluster exists with positive throughput weight. Absent on non-inference simulations. Convert to per-request decode latency: `decodeMs = (outputTokensPerRequest / decode_tokens_per_sec) * 1000`.
    - `resources` Resource[]: Full resource list with per-resource recoveryPolicy
      - `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.
    - `reward` number, required: Scalar total reward for this step
    - `reward_components` object, required
      - `performance` number
      - `cost` number
      - `stability` number
      - `sla` number
      - `connection_pressure` number: DB connection-pool saturation penalty. Only present when the simulation contains database resources. 0 when healthy; negative (floor −1.0) when pool is exhausted. When a DB is added mid-episode the penalty is ramped in linearly over 5 steps so it does not cause a sudden reward jump. See step response for full formula.
    - `done` boolean, required: True when the episode has ended
    - `sim_time_human` string, required: Human-readable simulation time
    - `info` object, required: Additional episode metadata. When a `scale_out` or `scale_in` action is trimmed to honour per-resource bounds (`characteristics.maxInstances` / `characteristics.minInstances`), the following flat keys are added: `scale_clamped` (boolean, always `true`), `requested` (integer — instances requested), `actual` (integer — instances applied, `0` if fully blocked), `limit` (integer — effective bound). Absent when no clamping occurred.

## Other responses

- `400`: Invalid request data — structured field-level error
- `404`: Resource not found
- `429`: RL rate limit exceeded (5 000 req/hr)

---

[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)
