@hyperframes/gcp-cloud-run 0.8.231 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,20 +1,9 @@
1
- # HyperFrames distributed render orchestration on Cloud Workflows.
1
+ # HyperFrames Plan v2 distributed render orchestration on Cloud Workflows.
2
2
  #
3
- # Plan → BuildChunkList → AssertChunkCount → RenderChunks (parallel) → Assemble
3
+ # Plan → BuildChunkList → RenderChunks (parallel) → Assemble
4
4
  #
5
- # Mirrors the Step Functions state machine in
6
- # `examples/aws-lambda/template.yaml`. Every step POSTs to the same Cloud Run
7
- # service URL (passed in as `args.ServiceUrl`) and varies only the body's
8
- # `Action`. The service returns the step's small result body on 2xx; on a
9
- # non-retryable failure it returns HTTP 400, on a retryable failure HTTP 5xx —
10
- # the `retryable` predicate below keys off exactly that split.
11
- #
12
- # The final returned object accumulates every step's result body so
13
- # `getRenderProgress` can read frame totals + per-step durations on success:
14
- # { Plan: {...}, Chunks: [{...}, ...], Assemble: {...} }
15
- #
16
- # Deploy with `gcloud workflows deploy` (the Terraform module / the
17
- # `hyperframes cloudrun deploy` command do this for you).
5
+ # Every step POSTs to the same Cloud Run service URL and varies only the
6
+ # body's Action. Plan v2 is the only supported transport.
18
7
 
19
8
  main:
20
9
  params: [args]
@@ -26,51 +15,20 @@ main:
26
15
  - planOutputGcsPrefix: ${args.PlanOutputGcsPrefix}
27
16
  - outputGcsUri: ${args.OutputGcsUri}
28
17
  - config: ${args.Config}
29
- # Plan v2 is the default. Explicit v1 remains available during the
30
- # deprecated monolithic-plan compatibility window.
31
18
  - planProtocol: ${default(map.get(args, "PlanProtocol"), "v2")}
32
19
 
33
- # ── Plan (Activity A) ────────────────────────────────────────────────────
34
- - selectPlanProtocol:
20
+ - checkPlanProtocol:
35
21
  switch:
36
- - condition: ${planProtocol == "v1"}
37
- next: planV1
38
22
  - condition: ${planProtocol == "v2"}
39
- next: planV2
23
+ next: plan
40
24
  next: unsupportedPlanProtocol
25
+
41
26
  - unsupportedPlanProtocol:
42
27
  raise:
43
28
  code: PLAN_PROTOCOL_UNSUPPORTED
44
- message: ${"PlanProtocol must be v1 or v2; got " + string(planProtocol)}
45
- - planV1:
46
- try:
47
- call: http.post
48
- args:
49
- url: ${serviceUrl}
50
- timeout: 1800
51
- auth:
52
- type: OIDC
53
- audience: ${serviceUrl}
54
- body:
55
- Action: plan
56
- PlanProtocol: v1
57
- ProjectGcsUri: ${projectGcsUri}
58
- PlanOutputGcsPrefix: ${planOutputGcsPrefix}
59
- Config: ${config}
60
- result: planRespV1
61
- retry:
62
- predicate: ${retryable}
63
- max_retries: 6
64
- backoff:
65
- initial_delay: 2
66
- max_delay: 60
67
- multiplier: 2
68
- next: capturePlanV1
69
- - capturePlanV1:
70
- assign:
71
- - planResult: ${planRespV1.body}
72
- next: validatePlanResult
73
- - planV2:
29
+ message: ${"PlanProtocol must be v2 or absent; got " + string(planProtocol)}
30
+
31
+ - plan:
74
32
  try:
75
33
  call: http.post
76
34
  args:
@@ -85,7 +43,7 @@ main:
85
43
  ProjectGcsUri: ${projectGcsUri}
86
44
  PlanOutputGcsPrefix: ${planOutputGcsPrefix}
87
45
  Config: ${config}
88
- result: planRespV2
46
+ result: planResp
89
47
  retry:
90
48
  predicate: ${retryable}
91
49
  max_retries: 6
@@ -93,46 +51,29 @@ main:
93
51
  initial_delay: 2
94
52
  max_delay: 60
95
53
  multiplier: 2
96
- next: capturePlanV2
97
- - capturePlanV2:
98
- assign:
99
- - planResult: ${planRespV2.body}
100
- next: validatePlanResult
101
- - validatePlanResult:
102
- # Fail closed before fan-out. A v2 render may never fall back to a
103
- # v1 PlanGcsUri, and a v1 render may never consume v2 locators.
104
- switch:
105
- - condition: ${planProtocol == "v1" and (not("PlanProtocol" in planResult) or planResult.PlanProtocol == "v1") and ("PlanGcsUri" in planResult) and not("PlanV2ManifestGcsUri" in planResult) and not("PlanV2ArtifactGcsPrefix" in planResult)}
106
- next: captureChunkCount
107
- - condition: ${planProtocol == "v2" and ("PlanProtocol" in planResult) and planResult.PlanProtocol == "v2" and ("PlanV2ManifestGcsUri" in planResult) and ("PlanV2ArtifactGcsPrefix" in planResult) and not("PlanGcsUri" in planResult)}
108
- next: captureChunkCount
109
- next: planProtocolLocatorMismatch
110
- - planProtocolLocatorMismatch:
111
- raise:
112
- code: PLAN_PROTOCOL_LOCATOR_MISMATCH
113
- message: "Plan response did not match the selected protocol's disjoint locator contract."
114
- - captureChunkCount:
54
+
55
+ - capturePlan:
115
56
  assign:
116
- - chunkCount: ${planResult.ChunkCount}
57
+ - planResult: ${planResp.body}
58
+ - chunkCount: ${planResp.body.ChunkCount}
117
59
 
118
- # ── BuildChunkList + AssertChunkCount ──────────────────────────────────────
119
- - assertChunkCount:
60
+ - validatePlanResult:
120
61
  switch:
121
- - condition: ${chunkCount > 0}
62
+ - condition: ${planResult.PlanProtocol == "v2" and ("PlanV2ManifestGcsUri" in planResult) and ("PlanV2ArtifactGcsPrefix" in planResult) and not("PlanGcsUri" in planResult) and chunkCount > 0}
122
63
  next: buildChunkList
123
- next: planProducedZeroChunks
124
- - planProducedZeroChunks:
64
+ next: invalidPlanResult
65
+
66
+ - invalidPlanResult:
125
67
  raise:
126
- code: PLAN_PRODUCED_ZERO_CHUNKS
127
- message: "Plan returned ChunkCount=0 — the composition produced no frames. Non-retryable producer-side invariant violation."
68
+ code: PLAN_RESULT_INVALID
69
+ message: "Plan response did not satisfy the Plan v2 locator contract or produced no chunks."
70
+
128
71
  - buildChunkList:
129
- # Pre-size the ordered chunk-URI + per-chunk result lists so the
130
- # parallel branches below assign by index (distinct indices, no
131
- # read-modify-write race on a shared accumulator).
132
72
  assign:
133
73
  - chunkIndexes: []
134
74
  - chunkUris: []
135
75
  - chunkResults: []
76
+
136
77
  - fillLists:
137
78
  for:
138
79
  value: i
@@ -144,53 +85,15 @@ main:
144
85
  - chunkUris: ${list.concat(chunkUris, "")}
145
86
  - chunkResults: ${list.concat(chunkResults, "")}
146
87
 
147
- # ── RenderChunks (Activity B, fanned out) ──────────────────────────────────
148
88
  - renderChunks:
149
89
  parallel:
150
90
  shared: [chunkUris, chunkResults]
151
- # Run up to chunkCount chunks at once, clamped to 20 — Cloud
152
- # Workflows hard-caps concurrent branches/iterations per execution
153
- # at 20 (https://cloud.google.com/workflows/quotas). Above that,
154
- # iterations queue regardless of concurrency_limit, so a config with
155
- # maxParallelChunks > 20 still renders correctly; the extra chunks
156
- # just wait. All chunkCount iterations always run.
157
91
  concurrency_limit: ${math.min(chunkCount, 20)}
158
92
  for:
159
93
  value: idx
160
94
  in: ${chunkIndexes}
161
95
  steps:
162
- - selectChunkProtocol:
163
- switch:
164
- - condition: ${planProtocol == "v2"}
165
- next: renderOneChunkV2
166
- next: renderOneChunkV1
167
- - renderOneChunkV1:
168
- try:
169
- call: http.post
170
- args:
171
- url: ${serviceUrl}
172
- timeout: 1800
173
- auth:
174
- type: OIDC
175
- audience: ${serviceUrl}
176
- body:
177
- Action: renderChunk
178
- PlanProtocol: v1
179
- ChunkIndex: ${idx}
180
- PlanGcsUri: ${planResult.PlanGcsUri}
181
- PlanHash: ${planResult.PlanHash}
182
- ChunkOutputGcsPrefix: ${planOutputGcsPrefix}
183
- Format: ${planResult.Format}
184
- result: chunkResp
185
- retry:
186
- predicate: ${retryable}
187
- max_retries: 4
188
- backoff:
189
- initial_delay: 2
190
- max_delay: 60
191
- multiplier: 2
192
- next: storeChunk
193
- - renderOneChunkV2:
96
+ - renderOneChunk:
194
97
  try:
195
98
  call: http.post
196
99
  args:
@@ -216,49 +119,12 @@ main:
216
119
  initial_delay: 2
217
120
  max_delay: 60
218
121
  multiplier: 2
219
- next: storeChunk
220
122
  - storeChunk:
221
123
  assign:
222
124
  - chunkUris[idx]: ${chunkResp.body.ChunkGcsUri}
223
125
  - chunkResults[idx]: ${chunkResp.body}
224
126
 
225
- # ── Assemble (Activity C) ──────────────────────────────────────────────────
226
- - selectAssembleProtocol:
227
- switch:
228
- - condition: ${planProtocol == "v2"}
229
- next: assembleV2
230
- next: assembleV1
231
- - assembleV1:
232
- try:
233
- call: http.post
234
- args:
235
- url: ${serviceUrl}
236
- timeout: 1800
237
- auth:
238
- type: OIDC
239
- audience: ${serviceUrl}
240
- body:
241
- Action: assemble
242
- PlanProtocol: v1
243
- PlanGcsUri: ${planResult.PlanGcsUri}
244
- ChunkGcsUris: ${chunkUris}
245
- AudioGcsUri: ${planResult.AudioGcsUri}
246
- OutputGcsUri: ${outputGcsUri}
247
- Format: ${planResult.Format}
248
- # Forward the caller's exact-CFR request (Config.cfr) to assemble.
249
- # `"cfr" in config` guards the optional key; when unset this is
250
- # false, which the handler reads as the default -c copy path.
251
- Cfr: ${("cfr" in config) and config.cfr}
252
- result: assembleResp
253
- retry:
254
- predicate: ${retryable}
255
- max_retries: 4
256
- backoff:
257
- initial_delay: 2
258
- max_delay: 60
259
- multiplier: 2
260
- next: done
261
- - assembleV2:
127
+ - assemble:
262
128
  try:
263
129
  call: http.post
264
130
  args:
@@ -274,8 +140,6 @@ main:
274
140
  PlanV2ArtifactGcsPrefix: ${planResult.PlanV2ArtifactGcsPrefix}
275
141
  PlanHash: ${planResult.PlanHash}
276
142
  ChunkGcsUris: ${chunkUris}
277
- # Audio is an assembler-scoped v2 artifact and is materialized
278
- # from the manifest, never carried through a v1 AudioGcsUri.
279
143
  AudioGcsUri: null
280
144
  OutputGcsUri: ${outputGcsUri}
281
145
  Format: ${planResult.Format}
@@ -288,7 +152,6 @@ main:
288
152
  initial_delay: 2
289
153
  max_delay: 60
290
154
  multiplier: 2
291
- next: done
292
155
 
293
156
  - done:
294
157
  return:
@@ -296,11 +159,7 @@ main:
296
159
  Chunks: ${chunkResults}
297
160
  Assemble: ${assembleResp.body}
298
161
 
299
- # Retry predicate: retry transient/server failures (403 from Cloud Run IAM
300
- # propagation, 429, and 5xx), never the handler's non-retryable 400s (bad
301
- # input, plan-hash mismatch, unsupported format, …). The handler does not emit
302
- # 403, so that status is always from Cloud Run's authentication edge.
303
- # Connection / timeout errors carry no `.code`; retry those too.
162
+ # Retry transient/server failures, never handler-classified HTTP 400s.
304
163
  retryable:
305
164
  params: [e]
306
165
  steps: