chore: import upstream snapshot with attribution
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# Local Terraform working files — never commit these.
|
||||
.terraform/
|
||||
.terraform.lock.hcl
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
*.tfvars
|
||||
crash.log
|
||||
@@ -0,0 +1,197 @@
|
||||
# HyperFrames distributed render stack on Google Cloud.
|
||||
#
|
||||
# Topology (the GCP twin of the AWS Lambda adapter's SAM template):
|
||||
#
|
||||
# GCS bucket ←→ Cloud Run service (plan / renderChunk / assemble)
|
||||
# ▲
|
||||
# │ OIDC-authenticated http.post per step
|
||||
# │
|
||||
# Cloud Workflows (Plan → parallel RenderChunk → Assemble)
|
||||
#
|
||||
# Two service accounts keep least-privilege boundaries:
|
||||
# - run_sa : the render service's identity; read/write the bucket only.
|
||||
# - workflow_sa: the workflow's identity; invoke the render service only.
|
||||
|
||||
locals {
|
||||
workflow_source = var.workflow_source_path != "" ? var.workflow_source_path : "${path.module}/workflow.yaml"
|
||||
name = var.project_name
|
||||
}
|
||||
|
||||
# ── Storage: plan tarballs, chunk outputs, final renders ─────────────────────
|
||||
resource "google_storage_bucket" "render" {
|
||||
name = "${local.name}-render-${var.project_id}"
|
||||
project = var.project_id
|
||||
location = var.region
|
||||
uniform_bucket_level_access = true
|
||||
force_destroy = var.bucket_force_destroy
|
||||
|
||||
# Render artifacts (plan tarballs, chunk files) are disposable scratch.
|
||||
# Sweep them after 7 days so the bucket doesn't accumulate cost; final
|
||||
# outputs that adopters want to keep should be copied elsewhere.
|
||||
lifecycle_rule {
|
||||
condition {
|
||||
age = 7
|
||||
}
|
||||
action {
|
||||
type = "Delete"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ── Service accounts ─────────────────────────────────────────────────────────
|
||||
resource "google_service_account" "run_sa" {
|
||||
account_id = "${local.name}-run"
|
||||
project = var.project_id
|
||||
display_name = "HyperFrames render service (Cloud Run)"
|
||||
}
|
||||
|
||||
resource "google_service_account" "workflow_sa" {
|
||||
account_id = "${local.name}-wf"
|
||||
project = var.project_id
|
||||
display_name = "HyperFrames render orchestration (Workflows)"
|
||||
}
|
||||
|
||||
# Render service reads inputs + writes outputs in the render bucket only.
|
||||
resource "google_storage_bucket_iam_member" "run_sa_bucket" {
|
||||
bucket = google_storage_bucket.render.name
|
||||
role = "roles/storage.objectAdmin"
|
||||
member = "serviceAccount:${google_service_account.run_sa.email}"
|
||||
}
|
||||
|
||||
# ── Cloud Run render service ─────────────────────────────────────────────────
|
||||
resource "google_cloud_run_v2_service" "render" {
|
||||
name = "${local.name}-render"
|
||||
project = var.project_id
|
||||
location = var.region
|
||||
# Authenticated only — no public invoker binding. Only the workflow SA can
|
||||
# call it. Ingress stays "all" because Workflows reaches the service over
|
||||
# Google's front door, not the VPC.
|
||||
ingress = "INGRESS_TRAFFIC_ALL"
|
||||
# Let `terraform destroy` (and replacement on image bumps) remove the
|
||||
# service without a manual console step. The render service is stateless —
|
||||
# all durable artifacts live in GCS.
|
||||
deletion_protection = false
|
||||
|
||||
template {
|
||||
service_account = google_service_account.run_sa.email
|
||||
timeout = "${var.request_timeout_seconds}s"
|
||||
# One render (chunk / plan / assemble) per instance — each uses the whole
|
||||
# box's CPU + memory + /tmp. The workflow's concurrency_limit governs how
|
||||
# many instances run at once.
|
||||
max_instance_request_concurrency = 1
|
||||
|
||||
scaling {
|
||||
min_instance_count = var.min_instances
|
||||
max_instance_count = var.max_instances
|
||||
}
|
||||
|
||||
containers {
|
||||
image = var.image
|
||||
|
||||
resources {
|
||||
limits = {
|
||||
cpu = var.cpu
|
||||
memory = var.memory
|
||||
}
|
||||
# Keep CPU allocated only during request processing (request-based
|
||||
# billing). Renders are entirely request-scoped.
|
||||
cpu_idle = true
|
||||
}
|
||||
|
||||
env {
|
||||
# Scopes every event's GCS URIs to this bucket (the handler's
|
||||
# GCS_URI_NOT_ALLOWED guard). Defense against request injection.
|
||||
name = "HYPERFRAMES_RENDER_BUCKET"
|
||||
value = google_storage_bucket.render.name
|
||||
}
|
||||
|
||||
startup_probe {
|
||||
http_get {
|
||||
path = "/healthz"
|
||||
}
|
||||
timeout_seconds = 5
|
||||
period_seconds = 10
|
||||
failure_threshold = 6
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Only the workflow's identity may invoke the render service.
|
||||
resource "google_cloud_run_v2_service_iam_member" "workflow_invokes_run" {
|
||||
name = google_cloud_run_v2_service.render.name
|
||||
project = var.project_id
|
||||
location = var.region
|
||||
role = "roles/run.invoker"
|
||||
member = "serviceAccount:${google_service_account.workflow_sa.email}"
|
||||
}
|
||||
|
||||
# ── Cloud Workflows orchestration ────────────────────────────────────────────
|
||||
resource "google_workflows_workflow" "render" {
|
||||
name = "${local.name}-render"
|
||||
project = var.project_id
|
||||
region = var.region
|
||||
service_account = google_service_account.workflow_sa.id
|
||||
source_contents = file(local.workflow_source)
|
||||
# Allow `terraform destroy` to remove the workflow without a manual step;
|
||||
# the definition is reproducible from this module.
|
||||
deletion_protection = false
|
||||
}
|
||||
|
||||
# ── Runaway-request alert (backstop against a fan-out bug) ────────────────────
|
||||
resource "google_monitoring_alert_policy" "runaway_requests" {
|
||||
project = var.project_id
|
||||
display_name = "${local.name}-render runaway request count"
|
||||
combiner = "OR"
|
||||
|
||||
conditions {
|
||||
display_name = "Render service request count > threshold (1h)"
|
||||
condition_threshold {
|
||||
filter = join(" AND ", [
|
||||
"resource.type = \"cloud_run_revision\"",
|
||||
"resource.labels.service_name = \"${google_cloud_run_v2_service.render.name}\"",
|
||||
"metric.type = \"run.googleapis.com/request_count\"",
|
||||
])
|
||||
comparison = "COMPARISON_GT"
|
||||
threshold_value = var.render_request_alarm_threshold
|
||||
duration = "0s"
|
||||
aggregations {
|
||||
alignment_period = "3600s"
|
||||
per_series_aligner = "ALIGN_SUM"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notification_channels = var.notification_channels
|
||||
}
|
||||
|
||||
# ── Workflow-failure alert ───────────────────────────────────────────────────
|
||||
# Request-count alone misses a render that fails 100% of the time at low
|
||||
# volume. Alert on any FAILED workflow execution so a broken render path is
|
||||
# visible even when traffic is light.
|
||||
resource "google_monitoring_alert_policy" "workflow_failures" {
|
||||
project = var.project_id
|
||||
display_name = "${local.name}-render workflow execution failures"
|
||||
combiner = "OR"
|
||||
|
||||
conditions {
|
||||
display_name = "Failed workflow executions (5m)"
|
||||
condition_threshold {
|
||||
filter = join(" AND ", [
|
||||
"resource.type = \"workflows.googleapis.com/Workflow\"",
|
||||
"resource.labels.workflow_id = \"${google_workflows_workflow.render.name}\"",
|
||||
"metric.type = \"workflows.googleapis.com/finished_execution_count\"",
|
||||
"metric.labels.status = \"FAILED\"",
|
||||
])
|
||||
comparison = "COMPARISON_GT"
|
||||
threshold_value = 0
|
||||
duration = "0s"
|
||||
aggregations {
|
||||
alignment_period = "300s"
|
||||
per_series_aligner = "ALIGN_SUM"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notification_channels = var.notification_channels
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
output "render_bucket_name" {
|
||||
description = "GCS bucket holding plan tarballs, chunk outputs, and final renders. Pass as renderToCloudRun({ bucketName })."
|
||||
value = google_storage_bucket.render.name
|
||||
}
|
||||
|
||||
output "service_url" {
|
||||
description = "HTTPS URL of the Cloud Run render service. Pass as renderToCloudRun({ serviceUrl })."
|
||||
value = google_cloud_run_v2_service.render.uri
|
||||
}
|
||||
|
||||
output "workflow_name" {
|
||||
description = "Workflow id. Pass as renderToCloudRun({ workflowId })."
|
||||
value = google_workflows_workflow.render.name
|
||||
}
|
||||
|
||||
output "workflow_id_full" {
|
||||
description = "Fully-qualified workflow resource name."
|
||||
value = google_workflows_workflow.render.id
|
||||
}
|
||||
|
||||
output "run_service_account_email" {
|
||||
description = "Render service identity (read/write the render bucket)."
|
||||
value = google_service_account.run_sa.email
|
||||
}
|
||||
|
||||
output "workflow_service_account_email" {
|
||||
description = "Workflow identity (invokes the render service)."
|
||||
value = google_service_account.workflow_sa.email
|
||||
}
|
||||
|
||||
output "region" {
|
||||
description = "Region everything was deployed into. Pass as renderToCloudRun({ location })."
|
||||
value = var.region
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
# This module is applied directly (by `examples/gcp-cloud-run/scripts/smoke.sh`
|
||||
# and `hyperframes cloudrun deploy`), so it configures the google provider
|
||||
# from its own variables. Credentials come from the environment — either
|
||||
# Application Default Credentials (`gcloud auth application-default login`)
|
||||
# or a `GOOGLE_OAUTH_ACCESS_TOKEN` env var.
|
||||
#
|
||||
# If you instead embed this as a CHILD module, delete this block and pass a
|
||||
# configured provider from your root module.
|
||||
provider "google" {
|
||||
project = var.project_id
|
||||
region = var.region
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
variable "project_id" {
|
||||
type = string
|
||||
description = "GCP project id to deploy the render stack into."
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
type = string
|
||||
description = "Region for the Cloud Run service, Workflow, and bucket."
|
||||
default = "us-central1"
|
||||
}
|
||||
|
||||
variable "project_name" {
|
||||
type = string
|
||||
description = "Name prefix applied to the service / workflow / bucket / service accounts."
|
||||
default = "hyperframes"
|
||||
}
|
||||
|
||||
variable "image" {
|
||||
type = string
|
||||
description = "Fully-qualified container image for the render service (e.g. us-central1-docker.pkg.dev/PROJECT/REPO/hyperframes-render:TAG), built from packages/gcp-cloud-run/Dockerfile."
|
||||
}
|
||||
|
||||
variable "cpu" {
|
||||
type = string
|
||||
description = "vCPU per Cloud Run instance. Allowed: 1, 2, 4, 8. Renders are CPU-bound; 4 is a good default."
|
||||
default = "4"
|
||||
}
|
||||
|
||||
variable "memory" {
|
||||
type = string
|
||||
description = "Memory per Cloud Run instance. Must be ≥ 2Gi per vCPU at cpu=4. Headroom for Chrome + ffmpeg + the chunk's frames in /tmp."
|
||||
default = "16Gi"
|
||||
}
|
||||
|
||||
variable "request_timeout_seconds" {
|
||||
type = number
|
||||
description = "Per-request timeout. Cloud Run hard cap is 3600s; a single chunk should finish well inside this."
|
||||
default = 3600
|
||||
}
|
||||
|
||||
variable "min_instances" {
|
||||
type = number
|
||||
description = "Min Cloud Run instances. Default 0 (scale-to-zero) is cheapest but means the first render after idle pays a cold start (image pull + Chrome + bun boot, ~20-30s). Set to 1 to keep one warm if first-render latency matters."
|
||||
default = 0
|
||||
}
|
||||
|
||||
variable "max_instances" {
|
||||
type = number
|
||||
description = "Max Cloud Run instances. Each chunk pins one instance (request concurrency = 1), and the workflow fans out up to the plan's chunk count (which never exceeds Config.maxParallelChunks). Keep this >= the largest maxParallelChunks you render with, or excess chunks queue behind 429s + retry backoff. Also a runaway-cost backstop."
|
||||
default = 100
|
||||
}
|
||||
|
||||
variable "workflow_source_path" {
|
||||
type = string
|
||||
description = "Path to the Cloud Workflows YAML. Defaults to the copy shipped with this module."
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "render_request_alarm_threshold" {
|
||||
type = number
|
||||
description = "Cloud Monitoring alert fires when render-service request count exceeds this in a 1-hour window. Backstop against a runaway fan-out."
|
||||
default = 1000
|
||||
}
|
||||
|
||||
variable "notification_channels" {
|
||||
type = list(string)
|
||||
description = "Cloud Monitoring notification channel ids for the runaway-request alert. Empty disables notifications (the policy still records)."
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "bucket_force_destroy" {
|
||||
type = bool
|
||||
description = "Allow `terraform destroy` to delete the render bucket even when it still holds objects. Off by default to match the AWS adapter's RETAIN policy."
|
||||
default = false
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = ">= 5.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
# HyperFrames distributed render orchestration on Cloud Workflows.
|
||||
#
|
||||
# Plan → BuildChunkList → AssertChunkCount → RenderChunks (parallel) → Assemble
|
||||
#
|
||||
# Mirrors the Step Functions state machine in
|
||||
# `examples/aws-lambda/template.yaml`. Every step POSTs to the same Cloud Run
|
||||
# service URL (passed in as `args.ServiceUrl`) and varies only the body's
|
||||
# `Action`. The service returns the step's small result body on 2xx; on a
|
||||
# non-retryable failure it returns HTTP 400, on a retryable failure HTTP 5xx —
|
||||
# the `retryable` predicate below keys off exactly that split.
|
||||
#
|
||||
# The final returned object accumulates every step's result body so
|
||||
# `getRenderProgress` can read frame totals + per-step durations on success:
|
||||
# { Plan: {...}, Chunks: [{...}, ...], Assemble: {...} }
|
||||
#
|
||||
# Deploy with `gcloud workflows deploy` (the Terraform module / the
|
||||
# `hyperframes cloudrun deploy` command do this for you).
|
||||
|
||||
main:
|
||||
params: [args]
|
||||
steps:
|
||||
- init:
|
||||
assign:
|
||||
- serviceUrl: ${args.ServiceUrl}
|
||||
- projectGcsUri: ${args.ProjectGcsUri}
|
||||
- planOutputGcsPrefix: ${args.PlanOutputGcsPrefix}
|
||||
- outputGcsUri: ${args.OutputGcsUri}
|
||||
- config: ${args.Config}
|
||||
|
||||
# ── Plan (Activity A) ────────────────────────────────────────────────────
|
||||
- plan:
|
||||
try:
|
||||
call: http.post
|
||||
args:
|
||||
url: ${serviceUrl}
|
||||
timeout: 1800
|
||||
auth:
|
||||
type: OIDC
|
||||
body:
|
||||
Action: plan
|
||||
ProjectGcsUri: ${projectGcsUri}
|
||||
PlanOutputGcsPrefix: ${planOutputGcsPrefix}
|
||||
Config: ${config}
|
||||
result: planResp
|
||||
retry:
|
||||
predicate: ${retryable}
|
||||
max_retries: 4
|
||||
backoff:
|
||||
initial_delay: 2
|
||||
max_delay: 60
|
||||
multiplier: 2
|
||||
- capturePlan:
|
||||
assign:
|
||||
- planResult: ${planResp.body}
|
||||
- chunkCount: ${planResult.ChunkCount}
|
||||
|
||||
# ── BuildChunkList + AssertChunkCount ──────────────────────────────────────
|
||||
- assertChunkCount:
|
||||
switch:
|
||||
- condition: ${chunkCount > 0}
|
||||
next: buildChunkList
|
||||
next: planProducedZeroChunks
|
||||
- planProducedZeroChunks:
|
||||
raise:
|
||||
code: PLAN_PRODUCED_ZERO_CHUNKS
|
||||
message: "Plan returned ChunkCount=0 — the composition produced no frames. Non-retryable producer-side invariant violation."
|
||||
- buildChunkList:
|
||||
# Pre-size the ordered chunk-URI + per-chunk result lists so the
|
||||
# parallel branches below assign by index (distinct indices, no
|
||||
# read-modify-write race on a shared accumulator).
|
||||
assign:
|
||||
- chunkIndexes: []
|
||||
- chunkUris: []
|
||||
- chunkResults: []
|
||||
- fillLists:
|
||||
for:
|
||||
value: i
|
||||
range: [0, ${chunkCount - 1}]
|
||||
steps:
|
||||
- appendSlots:
|
||||
assign:
|
||||
- chunkIndexes: ${list.concat(chunkIndexes, i)}
|
||||
- chunkUris: ${list.concat(chunkUris, "")}
|
||||
- chunkResults: ${list.concat(chunkResults, "")}
|
||||
|
||||
# ── RenderChunks (Activity B, fanned out) ──────────────────────────────────
|
||||
- renderChunks:
|
||||
parallel:
|
||||
shared: [chunkUris, chunkResults]
|
||||
# Run up to chunkCount chunks at once, clamped to 20 — Cloud
|
||||
# Workflows hard-caps concurrent branches/iterations per execution
|
||||
# at 20 (https://cloud.google.com/workflows/quotas). Above that,
|
||||
# iterations queue regardless of concurrency_limit, so a config with
|
||||
# maxParallelChunks > 20 still renders correctly; the extra chunks
|
||||
# just wait. All chunkCount iterations always run.
|
||||
concurrency_limit: ${math.min(chunkCount, 20)}
|
||||
for:
|
||||
value: idx
|
||||
in: ${chunkIndexes}
|
||||
steps:
|
||||
- renderOneChunk:
|
||||
try:
|
||||
call: http.post
|
||||
args:
|
||||
url: ${serviceUrl}
|
||||
timeout: 1800
|
||||
auth:
|
||||
type: OIDC
|
||||
body:
|
||||
Action: renderChunk
|
||||
ChunkIndex: ${idx}
|
||||
PlanGcsUri: ${planResult.PlanGcsUri}
|
||||
PlanHash: ${planResult.PlanHash}
|
||||
ChunkOutputGcsPrefix: ${planOutputGcsPrefix}
|
||||
Format: ${planResult.Format}
|
||||
result: chunkResp
|
||||
retry:
|
||||
predicate: ${retryable}
|
||||
max_retries: 4
|
||||
backoff:
|
||||
initial_delay: 2
|
||||
max_delay: 60
|
||||
multiplier: 2
|
||||
- storeChunk:
|
||||
assign:
|
||||
- chunkUris[idx]: ${chunkResp.body.ChunkGcsUri}
|
||||
- chunkResults[idx]: ${chunkResp.body}
|
||||
|
||||
# ── Assemble (Activity C) ──────────────────────────────────────────────────
|
||||
- assemble:
|
||||
try:
|
||||
call: http.post
|
||||
args:
|
||||
url: ${serviceUrl}
|
||||
timeout: 1800
|
||||
auth:
|
||||
type: OIDC
|
||||
body:
|
||||
Action: assemble
|
||||
PlanGcsUri: ${planResult.PlanGcsUri}
|
||||
ChunkGcsUris: ${chunkUris}
|
||||
AudioGcsUri: ${planResult.AudioGcsUri}
|
||||
OutputGcsUri: ${outputGcsUri}
|
||||
Format: ${planResult.Format}
|
||||
# Forward the caller's exact-CFR request (Config.cfr) to assemble.
|
||||
# `"cfr" in config` guards the optional key; when unset this is
|
||||
# false, which the handler reads as the default -c copy path.
|
||||
Cfr: ${("cfr" in config) and config.cfr}
|
||||
result: assembleResp
|
||||
retry:
|
||||
predicate: ${retryable}
|
||||
max_retries: 4
|
||||
backoff:
|
||||
initial_delay: 2
|
||||
max_delay: 60
|
||||
multiplier: 2
|
||||
|
||||
- done:
|
||||
return:
|
||||
Plan: ${planResult}
|
||||
Chunks: ${chunkResults}
|
||||
Assemble: ${assembleResp.body}
|
||||
|
||||
# Retry predicate: retry transient/server failures (429 + 5xx), never the
|
||||
# handler's non-retryable 400s (bad input, plan-hash mismatch, unsupported
|
||||
# format, …). Connection / timeout errors carry no `.code`; retry those too.
|
||||
retryable:
|
||||
params: [e]
|
||||
steps:
|
||||
- classify:
|
||||
switch:
|
||||
- condition: ${not("code" in e)}
|
||||
return: true
|
||||
- condition: ${e.code == 429}
|
||||
return: true
|
||||
- condition: ${e.code >= 500 and e.code < 600}
|
||||
return: true
|
||||
- nonRetryable:
|
||||
return: false
|
||||
Reference in New Issue
Block a user