@civitai/blocks-react 0.65.2 → 0.67.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.
@@ -40,14 +40,17 @@
40
40
  import { type BlockContext, type BlockResourceInfo, type BlockResourcePickerType, type BlockUploadedImageInfo, type BlockGenerationSourceImageInfo, type BuzzAccountType, type BlockBuzzTransaction, type BlockBuzzAccount, type BlockDailyCompensationResource, type BlockViewer, type BlockWildcardPack, type BlockWildcardPackErrorCode, type AppWorkflow, type BlockPublishedModel, type BlockGatedImage, type BlockCollectionFollowErrorCode, type BlockCreatePostHostError, type BlockCreatePostResult, type BlockPrepareTrainingDatasetHostError, type BlockRunTrainingHostError, type BlockTrainingRejectionReason, type ColorDomain, type SharedStorageValue, type Theme, type ViewerInfo, type WorkflowBody } from '@civitai/app-sdk/blocks';
41
41
  /**
42
42
  * How submits behave. `'none'` = everything succeeds; `'all'` / `'insufficient'`
43
- * = every submit RESOLVES an insufficient-Buzz `failed` snapshot, priced
44
- * (exercises the per-cell Top-Up CTA); `'some'` = ~1 in 3 submits fail.
43
+ * = every submit fails as if the viewer were OUT OF BUZZ, until an
44
+ * `OPEN_BUZZ_PURCHASE` resets the mode to `'none'`; `'some'` = ~1 in 3
45
+ * submits fail with a generic error.
45
46
  *
46
- * 🔴 THE TWO OUTCOMES ARE NO LONGER THE SAME SHAPE (civitai/civitai-app-starters#251).
47
- * `'all'`/`'insufficient'` RESOLVE — they carry a `cost`, so they are a budget
48
- * OUTCOME. `'some'` **REJECTS** with `code: 'exception'` — it emits the host's
49
- * cost-less `failureSnapshot(err)`. A mixed grid therefore needs both a
50
- * `snap.status` branch and a `catch`.
47
+ * 🔴 EVERY FAILING MODE **REJECTS** with `WorkflowSubmitError` code
48
+ * `'exception'` — each emits the host's cost-less `failureSnapshot(err)`. That
49
+ * includes `'all'`/`'insufficient'`, which until this was fixed RESOLVED a
50
+ * priced `failed` snapshot. Production never does that for an empty wallet: the
51
+ * orchestrator refuses the debit, the server throws, and the host replies with
52
+ * `failureSnapshot(err)` (no `cost`). See {@link MockBuzzScenario}. A priced,
53
+ * RESOLVING refusal is a spend CAP — {@link MockGenerationScenario.submitCapRefusal}.
51
54
  */
52
55
  export type MockHostFailMode = 'none' | 'some' | 'all' | 'insufficient';
53
56
  /**
@@ -152,15 +155,15 @@ export interface MockGenerationScenario {
152
155
  * error }` with **no `cost`**. `useBuzzWorkflow().submit()` rejects with a
153
156
  * `WorkflowSubmitError` whose `code` is `'exception'`.
154
157
  *
155
- * 🔴 THIS IS THE PRODUCER THE OTHER SUBMIT KNOBS DO NOT SIMULATE, and that gap
156
- * is how civitai/civitai-app-starters#251 stayed invisible: the balance /
157
- * `insufficient` knobs model a budget REJECTION (a priced outcome the block
158
- * recovers from with a top-up), so a block author testing "what if submit goes
159
- * wrong" only ever saw the arm that resolves. The `failEstimate` knob is the
160
- * estimate-side twin of this one.
158
+ * This knob was added for civitai/civitai-app-starters#251, when no other submit
159
+ * knob produced a rejection at all. The out-of-Buzz knobs
160
+ * ({@link MockBuzzScenario}) now emit this same shape, because production does;
161
+ * this one stays as the way to inject an arbitrary server message with the
162
+ * wallet untouched. The `failEstimate` knob is the estimate-side twin of this
163
+ * one.
161
164
  *
162
- * Checked FIRST, before the disallowed-account / insufficient-Buzz / generic
163
- * paths: a host-side throw pre-empts every server-side decision.
165
+ * Checked FIRST, before the disallowed-account / spend-cap / out-of-Buzz /
166
+ * generic paths: a host-side throw pre-empts every server-side decision.
164
167
  *
165
168
  * Default: unset (submits behave normally).
166
169
  */
@@ -171,6 +174,28 @@ export interface MockGenerationScenario {
171
174
  * check how your block's developer-facing error surface renders one.
172
175
  */
173
176
  failSubmitExceptionMessage?: string;
177
+ /**
178
+ * Make every SUBMIT come back as a server SPEND-CAP refusal — the priced,
179
+ * RESOLVING shape `{ workflowId:'failed', status:'failed', cost:{ total }, error }`,
180
+ * where `total` is this generation's cost. `useBuzzWorkflow().submit()`
181
+ * RESOLVES it (no rejection); the block reads `status === 'failed'`.
182
+ *
183
+ * This is what the real submit returns when a cap stops the run before
184
+ * anything is spent: the app's per-generation budget, the viewer's daily (or
185
+ * private-run) cap, the per-app consent budget, the app's spend or rate limit,
186
+ * or a dev-session cap. 🔴 **Buying Buzz fixes none of them**, so a block must
187
+ * not offer a top-up for this reply. Running OUT of Buzz is a different shape —
188
+ * a rejection; see {@link MockBuzzScenario}.
189
+ *
190
+ * Pass the server's text (e.g. `'insufficient buzz budget: estimate 600 exceeds
191
+ * budget 200'`), or `true` for a default message. Checked after the
192
+ * disallowed-account path and before the out-of-Buzz path, matching the real
193
+ * ordering: the caps are checked before the orchestrator is asked to debit.
194
+ * Live-tunable via `setScenario({ generation: { submitCapRefusal } })`; set it
195
+ * to `undefined` there to clear it. URL: `?capRefusal=1` (or `=<text>`).
196
+ * Default: unset.
197
+ */
198
+ submitCapRefusal?: string | true;
174
199
  /** A single result image url (or `(body) => url`). */
175
200
  image?: ImageSpec;
176
201
  /**
@@ -248,24 +273,43 @@ export declare const MOCK_TRAINING_DATASET_GONE_ERROR = "training dataset not fo
248
273
  export declare const MOCK_TRAINING_NONE_ELIGIBLE_ERROR = "none of the requested images can be used for training";
249
274
  export declare const MOCK_TRAINING_NONE_IMPORTED_ERROR = "none of the requested images could be prepared for training";
250
275
  /**
251
- * BUZZ scenario controls — simulate a balance so the insufficient-Buzz / top-up
252
- * UX is exercisable. The mock host treats `balance` as a spendable wallet:
253
- * each succeeding generation DEBITS its cost; a submit whose cost would exceed
254
- * the remaining balance resolves to an insufficient-Buzz `failed` snapshot
255
- * (exercising the Top-Up CTA), and `OPEN_BUZZ_PURCHASE` REFILLS the balance.
276
+ * BUZZ scenario controls — simulate a viewer who runs out of Buzz. The mock host
277
+ * treats `balance` as a spendable wallet: each succeeding generation DEBITS its
278
+ * cost, a submit whose cost would exceed the remaining balance fails as
279
+ * OUT OF BUZZ, and `OPEN_BUZZ_PURCHASE` REFILLS the balance.
280
+ *
281
+ * 🔴 OUT OF BUZZ **REJECTS** — `useBuzzWorkflow().submit()` throws a
282
+ * `WorkflowSubmitError` with code `'exception'`, the reason on
283
+ * `err.snapshot.error`. That is what production does. The orchestrator refuses
284
+ * the debit (HTTP 403), the server turns that into a thrown `BAD_REQUEST`, and
285
+ * the host replies with its cost-less `failureSnapshot(err)`. These knobs used to
286
+ * RESOLVE a priced `failed` snapshot instead, so a block tested against the mock
287
+ * could build a "resolved refusal → top-up" flow that production never reaches.
288
+ *
289
+ * Nothing structural tells this rejection apart from other `'exception'`s (the
290
+ * server message is upstream text, not a contract). To decide whether to offer
291
+ * a top-up, compare the viewer's spendable balance (`useBuzzBalance()`) with the
292
+ * quoted cost — never the error text. In the mock, that balance is
293
+ * {@link MockHostOptions.buzzBalance}, which is separate from `balance` below.
294
+ *
295
+ * A priced, RESOLVING refusal is a spend CAP, which a top-up cannot fix — see
296
+ * {@link MockGenerationScenario.submitCapRefusal}.
256
297
  */
257
298
  export interface MockBuzzScenario {
258
299
  /**
259
300
  * Simulated spendable balance. When set, generations debit against it and a
260
- * gen that would exceed it returns an insufficient-Buzz outcome. When
261
- * `undefined`, balance is NOT simulated (back-compat: only the legacy
262
- * `failMode` drives insufficiency).
301
+ * gen that would exceed it is refused as out of Buzz (a REJECTION, see above).
302
+ * When `undefined`, balance is NOT simulated (back-compat: only the legacy
303
+ * `failMode` and {@link insufficient} drive the out-of-Buzz path).
263
304
  */
264
305
  balance?: number;
265
306
  /**
266
- * Force every submit down the insufficient-Buzz path regardless of balance.
267
- * Equivalent to the legacy `failMode: 'insufficient'`; provided here so the
268
- * insufficient UX is reachable from the `buzz` group alone.
307
+ * Force every submit down the out-of-Buzz path regardless of balance — it
308
+ * REJECTS with code `'exception'` (see above). Equivalent to the legacy
309
+ * `failMode: 'insufficient'`; provided here so the path is reachable from the
310
+ * `buzz` group alone. `OPEN_BUZZ_PURCHASE` clears it — and clears
311
+ * `failMode: 'insufficient' | 'all'` too (back to `'none'`), so a top-up
312
+ * followed by a retry succeeds whichever knob forced the out-of-Buzz state.
269
313
  */
270
314
  insufficient?: boolean;
271
315
  }
@@ -423,8 +467,8 @@ export interface MockHostOptions {
423
467
  consentGrantable?: boolean;
424
468
  /**
425
469
  * How submits behave. Default `'none'` (all succeed). See
426
- * {@link MockHostFailMode} — `'some'` REJECTS while `'all'`/`'insufficient'`
427
- * RESOLVE, since #251.
470
+ * {@link MockHostFailMode} — every failing mode REJECTS with code
471
+ * `'exception'`; `'all'`/`'insufficient'` model an out-of-Buzz viewer.
428
472
  */
429
473
  failMode?: MockHostFailMode;
430
474
  /**
@@ -476,8 +520,8 @@ export interface MockHostOptions {
476
520
  */
477
521
  generation?: MockGenerationScenario;
478
522
  /**
479
- * BUZZ scenario: simulated balance + force-insufficient. See
480
- * {@link MockBuzzScenario}.
523
+ * BUZZ scenario: simulated balance + force-out-of-Buzz. Both REJECT the
524
+ * submit, as production does. See {@link MockBuzzScenario}.
481
525
  */
482
526
  buzz?: MockBuzzScenario;
483
527
  /**
@@ -485,8 +529,9 @@ export interface MockHostOptions {
485
529
  * block via the host-mediated `GET_BUZZ_BALANCE` → `BUZZ_BALANCE_RESULT`
486
530
  * bridge (what the `useBuzzBalance` hook reads). Distinct from the
487
531
  * {@link MockBuzzScenario.balance} spendable-wallet knob, which only drives
488
- * the insufficient-Buzz / top-up SUBMIT path — this is the displayable
489
- * per-pool balance. Absent → {@link DEFAULT_BUZZ_BALANCE} (a plausible
532
+ * the out-of-Buzz SUBMIT rejection — this is the displayable per-pool balance,
533
+ * and the number a block should compare with the quoted cost to decide on a
534
+ * top-up. Set both when exercising that flow. Absent → {@link DEFAULT_BUZZ_BALANCE} (a plausible
490
535
  * non-zero wallet, so a block shows a balance out of the box).
491
536
  */
492
537
  buzzBalance?: MockBuzzBalance;
@@ -574,8 +619,9 @@ export interface MockHostOptions {
574
619
  /**
575
620
  * The bare (post-less) scanned `Image` row ids returned from
576
621
  * `PUBLISH_GENERATION_OUTPUTS` (what `usePublishGenerationOutputs().publish()`
577
- * resolves with). Absent → {@link DEFAULT_PUBLISH_IMAGE_IDS}. Live-tunable via
578
- * {@link MockHost.setScenario}.
622
+ * resolves with). Absent → {@link DEFAULT_PUBLISH_IMAGE_IDS}. Each reply makes
623
+ * these ids postable once in a `CREATE_POST_FROM_APP` `published` source (see
624
+ * {@link createPostResult}). Live-tunable via {@link MockHost.setScenario}.
579
625
  */
580
626
  publishImageIds?: number[];
581
627
  /**
@@ -636,6 +682,21 @@ export interface MockHostOptions {
636
682
  * `useCreatePostFromApp().createPost()` resolves with). Absent →
637
683
  * {@link DEFAULT_CREATE_POST_RESULT}. Ignored when {@link createPostError} is
638
684
  * set. Live-tunable via {@link MockHost.setScenario}.
685
+ *
686
+ * A `{ kind: 'published', imageIds }` source is checked as the server's
687
+ * `resolveAppPublishedImages` checks it (civitai/civitai#5639). Production
688
+ * accepts the viewer's own images that this app stamped and that are not in a
689
+ * post yet, from this session or an earlier one. The mock has no earlier
690
+ * sessions, so it accepts ONLY ids issued as postable on this mock instance
691
+ * (an accepted `OPEN_IMAGE_UPLOAD { bytes }` reply,
692
+ * {@link uploadImageBytesResult}, or a `PUBLISH_GENERATION_OUTPUTS` reply,
693
+ * {@link publishImageIds}) plus the ids seeded with {@link postableImageIds}.
694
+ * Any other id, a picked `useImageUpload()` id (production leaves it
695
+ * unstamped) or an id a previous post already adopted is refused with
696
+ * `an image is not available to post`, and a source with no positive integer
697
+ * id with `no valid image ids in a published source`. A refused post adopts
698
+ * nothing. `workflow` sources are not checked. The mock does not model the
699
+ * scan wait, the viewer's ceiling or the server's input schema.
639
700
  */
640
701
  createPostResult?: BlockCreatePostResult;
641
702
  /**
@@ -731,6 +792,104 @@ export interface MockHostOptions {
731
792
  * `setScenario({ runTrainingCapRefusal: undefined })` clears it.
732
793
  */
733
794
  runTrainingCapRefusal?: string;
795
+ /**
796
+ * Force `SAVE_IMAGE` to reply with this `error` instead of saving — any of
797
+ * the host's free-text refusals (`'image url is not allowed'`,
798
+ * `'image is not available'`, `'busy'`, …), or
799
+ * `'invalid save-image request'` to model a host that PREDATES the `bytes`
800
+ * variant (what production replies to `saveImage({ bytes })` until the host
801
+ * ships it). Applies to every variant; checked after the request-shape gate,
802
+ * so an invalid request still gets `invalid save-image request`. Absent →
803
+ * the save succeeds (subject to the `bytes` classification below).
804
+ * Live-tunable via {@link MockHost.setScenario}; `undefined` clears it.
805
+ *
806
+ * The `bytes` variant is classified exactly as the host's contract says
807
+ * (civitai/civitai-app-starters#583): PNG / WebP / JPEG by magic bytes, else
808
+ * UTF-8 text with no NUL (JSON when it parses and the CLEANED `filename` —
809
+ * `?`/`#` replaced with `_`, path dropped — ends `.json`, case-insensitive),
810
+ * else `file type is not allowed`; an empty buffer,
811
+ * `invalid save-image request`; over the host's byte cap,
812
+ * `file exceeds the maximum save size`. 🔴 What the mock cannot model: that
813
+ * the variant is PAGE-ONLY (it does not know which surface the block renders
814
+ * on; a slot host has no `SAVE_IMAGE` handler and rejects it with
815
+ * `unsupported on this host`),
816
+ * the host's concurrency and per-window rate limits (`busy` — force it
817
+ * here), and the real browser
818
+ * download. {@link onSaveBytes} reports what would have been saved.
819
+ */
820
+ saveImageError?: string;
821
+ /**
822
+ * Called with each `bytes` save the mock ACCEPTS — the classified `mimeType`
823
+ * (one of `image/png`, `image/webp`, `image/jpeg`, `application/json`,
824
+ * `text/plain`), the download `filename` exactly as the host cleans it (the
825
+ * same character rules, then the extension forced from that type), and the
826
+ * `bytes` themselves.
827
+ * The mock cannot trigger a browser download, so this is the observable a
828
+ * test or harness asserts on.
829
+ */
830
+ onSaveBytes?: (file: {
831
+ bytes: ArrayBuffer;
832
+ mimeType: string;
833
+ filename: string;
834
+ }) => void;
835
+ /**
836
+ * The moderated image an ACCEPTED `OPEN_IMAGE_UPLOAD { bytes }` replies with
837
+ * (what `useUploadImageBytes().upload()` resolves with). Absent →
838
+ * {@link DEFAULT_IMAGE_BYTES_UPLOAD}, whose `imageId` differs from
839
+ * {@link DEFAULT_IMAGE_UPLOAD}'s so a block that confuses a picked upload
840
+ * with an uploaded one is visible. Each accepted upload makes its `imageId`
841
+ * postable ONCE in a `CREATE_POST_FROM_APP` `{ kind: 'published', imageIds }`
842
+ * source on this same mock instance, as the real host allows for the app that
843
+ * uploaded it; see {@link createPostResult} for what the mock refuses.
844
+ * Live-tunable via {@link MockHost.setScenario}.
845
+ *
846
+ * The `bytes` variant runs the host's admission rules
847
+ * (civitai/civitai#5639, `imageUploadBytes.ts`) in the host's order: not a
848
+ * non-empty `ArrayBuffer`, or `purpose: 'generationSource'` →
849
+ * `invalid image-upload request`; over 40 MiB →
850
+ * `file exceeds the maximum upload size` (checked FIRST); a full rolling
851
+ * window (3 uploads or 80 MiB per 60 s, per install) → `busy`; not PNG /
852
+ * WebP / JPEG by magic bytes → `file type is not allowed`; then
853
+ * {@link uploadImageBytesError}, standing in for the server persist and the
854
+ * scan. 🔴 What the mock cannot model: that the variant is PAGE-ONLY, the
855
+ * `posts:write:self` scope check (force `block lacks posts:write:self scope`
856
+ * with {@link uploadImageBytesError}), the real scan, the no-reply for a
857
+ * `requestId` reused while in flight (the mock replies at once), and that a
858
+ * host OLDER than the variant opens its picker instead.
859
+ */
860
+ uploadImageBytesResult?: BlockUploadedImageInfo;
861
+ /**
862
+ * Force an `OPEN_IMAGE_UPLOAD { bytes }` that passed the admission rules
863
+ * above to reply with this `error`: a server refusal the real host forwards
864
+ * (`block lacks posts:write:self scope`,
865
+ * `Rate limit exceeded, please retry shortly.`, a scan refusal, …), or
866
+ * `busy` / `no block token`. The upload still counts against the window, as
867
+ * on the host. Absent → it succeeds. Live-tunable via
868
+ * {@link MockHost.setScenario}; `undefined` clears it.
869
+ */
870
+ uploadImageBytesError?: string;
871
+ /**
872
+ * Called with each `bytes` upload the mock ACCEPTS: the sniffed `mimeType`
873
+ * (`image/png`, `image/webp` or `image/jpeg`), the `filename` as the host
874
+ * stores it (the `useSaveImage()` bytes cleaning, the sniffed extension, at
875
+ * most 255 characters), and a copy of the `bytes`.
876
+ */
877
+ onUploadImageBytes?: (file: {
878
+ bytes: ArrayBuffer;
879
+ mimeType: string;
880
+ filename: string;
881
+ }) => void;
882
+ /**
883
+ * Image ids the mock treats as already issued and postable when the host is
884
+ * created, as production treats an image this app stamped for the viewer in
885
+ * an EARLIER session that is not in a post yet. Each seeded id may be posted
886
+ * ONCE in a `CREATE_POST_FROM_APP` `{ kind: 'published', imageIds }` source;
887
+ * see {@link createPostResult} for what the mock refuses. Use it for a test
888
+ * that posts an image the block uploaded before the test started, instead of
889
+ * driving an upload or `PUBLISH_GENERATION_OUTPUTS` first. Absent → none.
890
+ * Read once at creation; not live-tunable.
891
+ */
892
+ postableImageIds?: number[];
734
893
  /**
735
894
  * Buzz pools a `SUBMIT_WORKFLOW` must REJECT when named in `body.accountType`
736
895
  * — simulates the real backend's content-rating clamp. The real host throws a
@@ -895,7 +1054,7 @@ export interface MockHostOptions {
895
1054
  * {@link MockHost.setTheme}, which pushes a real `THEME_CHANGE` — the mock of
896
1055
  * the viewer toggling dark mode with the block already mounted.)
897
1056
  */
898
- export type MockHostScenarioPatch = Pick<MockHostOptions, 'consentGrantable' | 'failMode' | 'cost' | 'pollsUntilDone' | 'cannedPicks' | 'cannedImageUpload' | 'cannedGenerationSourceUpload' | 'cannedImageScan' | 'generation' | 'buzz' | 'storage' | 'shared' | 'buzzBalanceError' | 'viewerResult' | 'viewerError' | 'buzzTransactions' | 'buzzAccounts' | 'dailyCompensation' | 'buzzReadError' | 'wildcardPack' | 'wildcardPackError' | 'collectionFollowError' | 'createPostResult' | 'createPostError' | 'trainingDatasetRejected' | 'trainingDatasetError' | 'trainingQuoteTotal' | 'runTrainingError' | 'runTrainingCapRefusal' | 'appWorkflows' | 'appWorkflowsError' | 'publishImageIds' | 'publishError' | 'gatedImages' | 'gatedImagesError' | 'disallowedAccountTypes'>;
1057
+ export type MockHostScenarioPatch = Pick<MockHostOptions, 'consentGrantable' | 'failMode' | 'cost' | 'pollsUntilDone' | 'cannedPicks' | 'cannedImageUpload' | 'cannedGenerationSourceUpload' | 'cannedImageScan' | 'generation' | 'buzz' | 'storage' | 'shared' | 'buzzBalanceError' | 'viewerResult' | 'viewerError' | 'buzzTransactions' | 'buzzAccounts' | 'dailyCompensation' | 'buzzReadError' | 'wildcardPack' | 'wildcardPackError' | 'collectionFollowError' | 'createPostResult' | 'createPostError' | 'trainingDatasetRejected' | 'trainingDatasetError' | 'trainingQuoteTotal' | 'runTrainingError' | 'runTrainingCapRefusal' | 'appWorkflows' | 'appWorkflowsError' | 'publishImageIds' | 'publishError' | 'gatedImages' | 'gatedImagesError' | 'disallowedAccountTypes' | 'saveImageError' | 'uploadImageBytesResult' | 'uploadImageBytesError'>;
899
1058
  /** Runtime Buzz-balance handle exposed on {@link MockHost.buzz}. */
900
1059
  export interface MockBuzzHandle {
901
1060
  /** Current simulated balance, or `undefined` when balance isn't simulated. */
@@ -941,8 +1100,9 @@ export declare function disallowedAccountError(accountType: BuzzAccountType): st
941
1100
  * Reads the URL query toggles the gen-matrix dev harness uses, so a starter's
942
1101
  * dev harness keeps working with `?viewer/?consent/?fail/?theme/?pick/?pickCkpt`.
943
1102
  * Layer-1 additions: `?balance/?latency/?costPerGen/?failNext/?failRate/?seed`
944
- * map onto the new scenario groups so a dev can flip insufficient-buzz /
945
- * failures / latency without editing code.
1103
+ * map onto the new scenario groups so a dev can flip out-of-Buzz /
1104
+ * failures / latency without editing code. `?capRefusal=1` (or `=<text>`) sets
1105
+ * {@link MockGenerationScenario.submitCapRefusal}.
946
1106
  *
947
1107
  * Returns a partial overlay applied ON TOP of explicit {@link MockHostOptions}
948
1108
  * (URL wins — it's the interactive dev knob). No-op outside a browser.
@@ -987,7 +1147,7 @@ export declare function readMockHostUrlOptions(win?: (Window & typeof globalThis
987
1147
  * const host = createMockHost({ generation: { failNext: 1, latencyMs: 1500 }, buzz: { balance: 5 } });
988
1148
  * const uninstall = host.install();
989
1149
  * // … drive the block / assertions …
990
- * host.buzz.setBalance(0); // flip to insufficient mid-session
1150
+ * host.buzz.setBalance(0); // out of Buzz mid-session: submit() now rejects ('exception')
991
1151
  * host.setScenario({ generation: { failRate: 1 } });
992
1152
  * uninstall();
993
1153
  */