@hyperframes/gcp-cloud-run 0.8.230 → 0.9.0
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.
- package/README.md +11 -16
- package/dist/events.d.ts +14 -75
- package/dist/index.d.ts +1 -1
- package/dist/index.js +30 -168
- package/dist/sdk/index.js +11 -1
- package/dist/sdk/renderToCloudRun.d.ts +1 -6
- package/dist/server.d.ts +1 -2
- package/dist/server.js +16 -164
- package/package.json +2 -2
- package/terraform/smoke-safety.test.ts +4 -6
- package/terraform/workflow.test.ts +34 -108
- package/terraform/workflow.yaml +27 -168
package/terraform/workflow.yaml
CHANGED
|
@@ -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 →
|
|
3
|
+
# Plan → BuildChunkList → RenderChunks (parallel) → Assemble
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
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
|
-
|
|
34
|
-
- selectPlanProtocol:
|
|
20
|
+
- checkPlanProtocol:
|
|
35
21
|
switch:
|
|
36
|
-
- condition: ${planProtocol == "v1"}
|
|
37
|
-
next: planV1
|
|
38
22
|
- condition: ${planProtocol == "v2"}
|
|
39
|
-
next:
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
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:
|
|
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
|
-
|
|
97
|
-
-
|
|
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
|
-
-
|
|
57
|
+
- planResult: ${planResp.body}
|
|
58
|
+
- chunkCount: ${planResp.body.ChunkCount}
|
|
117
59
|
|
|
118
|
-
|
|
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:
|
|
124
|
-
|
|
64
|
+
next: invalidPlanResult
|
|
65
|
+
|
|
66
|
+
- invalidPlanResult:
|
|
125
67
|
raise:
|
|
126
|
-
code:
|
|
127
|
-
message: "Plan
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
|
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:
|