@civitai/app-sdk 0.58.0 → 0.60.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 CHANGED
@@ -266,7 +266,7 @@ no entry fails the suite.
266
266
  > assigned server-side, so it cannot be enforced locally.
267
267
 
268
268
  **Where it runs.** Every scaffold that ships a `block.manifest.json`
269
- (`starters/civitai-block-starter` and all six `starters/examples/*`) registers
269
+ (`starters/civitai-block-starter` and every `starters/examples/*` app) registers
270
270
  `blockManifestPlugin` from `@civitai/app-sdk/vite` in its `vite.config.ts`. It
271
271
  fires from Vite's `configResolved`, the one hook called on both the dev-server
272
272
  and the build path — so `pnpm dev`, `pnpm dev:harness` and `pnpm build` all fail
@@ -528,7 +528,7 @@ The starters in `civitai/civitai-app-starters` wire this into framework-specific
528
528
 
529
529
  ## Choosing a workflow step type
530
530
 
531
- The orchestrator is a workflow API: each request submits a list of typed steps. `WORKFLOW_STEP_TYPES` is the in-code catalog of every step `$type` it accepts, with a one-line description for each — `textToImage`, `imageGen`, `videoGen`, `comfy`, `customComfy`, `textToSpeech`, `aceStepAudio`, `transcription`, `imageUpscaler` among them (53 in total).
531
+ The orchestrator is a workflow API: each request submits a list of typed steps. `WORKFLOW_STEP_TYPES` is the in-code catalog of every step `$type` it accepts, with a one-line description for each — `textToImage`, `imageGen`, `videoGen`, `comfy`, `customComfy`, `textToSpeech`, `aceStepAudio`, `transcription`, `imageUpscaler` among them (55 in total).
532
532
 
533
533
  The catalog is pinned to the orchestrator's published OpenAPI spec two ways — an offline unit test against a transcribed copy of the spec's `WorkflowStepTemplate` discriminator mapping, and a CI job (`pnpm check:catalogs`) that re-fetches the live spec and diffs it. If a `$type` is listed here, the orchestrator accepts it.
534
534
 
@@ -603,15 +603,15 @@ What it exports:
603
603
 
604
604
  | Export | What |
605
605
  |---|---|
606
- | `WorkflowStepTemplates` | `$type` → template type, for 47 of the catalog's 53 step types. Keyed by the WIRE name, which the generated type names don't always match (`model3DPreview` → `Model3dPreviewStepTemplate`). |
606
+ | `WorkflowStepTemplates` | `$type` → template type, for 47 of the catalog's 55 step types. Keyed by the WIRE name, which the generated type names don't always match (`model3DPreview` → `Model3dPreviewStepTemplate`). |
607
607
  | `WorkflowStepTemplateFor<'videoGen'>` | One step's template. |
608
608
  | `WorkflowStepInputFor<'videoGen'>` | One step's `input` shape, without needing the generated `*Input` name. |
609
609
  | `AnyWorkflowStepTemplate` | Discriminated union of all 47 mapped templates — `Extract<…, { $type: 'comfy' }>` and exhaustive `switch` work. `@civitai/client`'s base `WorkflowStepTemplate` has `$type` as a bare `string`, so it narrows nothing. |
610
610
  | `TypedWorkflowTemplate` | The submit envelope with `steps` narrowed to that union. Pass it straight to `submitWorkflow` / `estimateWorkflow`. |
611
611
 
612
- > 🔴 **The map is not total over the catalog, and that is the expected state.** `WORKFLOW_STEP_TYPES` documents 53 `$type`s; this map covers 47. The 6 with no generated template in the pinned `@civitai/client` are `imageScanning`, `liveTranscription`, `merge`, `preprocessVideo`, `soniloAudioGen`, `yuE2`, and `WorkflowStepTemplateFor<…>` is a compile error for each of them.
612
+ > 🔴 **The map is not total over the catalog, and that is the expected state.** `WORKFLOW_STEP_TYPES` documents 55 `$type`s; this map covers 47. The 8 with no generated template in the pinned `@civitai/client` are `imageScanning`, `liveTextToSpeech`, `liveTranscription`, `merge`, `preprocessVideo`, `promptModeration`, `soniloAudioGen`, `yuE2`, and `WorkflowStepTemplateFor<…>` is a compile error for each of them.
613
613
  >
614
- > The two surfaces move independently on purpose: the catalog tracks the **live** orchestrator spec (a daily job syncs it), while these types track whatever `@civitai/client` was last published from. So the catalog runs ahead and the client catches up. The gap is never silent — `test/orchestrator/step-templates.test-d.ts` carries it as a `never` ledger plus one `@ts-expect-error` per gap `$type`, and `test/orchestrator/step-count-prose.test.ts` derives all four numbers (53, 47, 6, and the names) from `WORKFLOW_STEP_TYPES` and the map's own AST, then fails if this paragraph or its twin in `src/orchestrator/steps.ts` disagrees by one character.
614
+ > The two surfaces move independently on purpose: the catalog tracks the **live** orchestrator spec (a daily job syncs it), while these types track whatever `@civitai/client` was last published from. So the catalog runs ahead and the client catches up. The gap is never silent — `test/orchestrator/step-templates.test-d.ts` carries it as a `never` ledger plus one `@ts-expect-error` per gap `$type`, and `test/orchestrator/step-count-prose.test.ts` derives all four numbers (55, 47, 8, and the names) from `WORKFLOW_STEP_TYPES` and the map's own AST, then fails if this paragraph or its twin in `src/orchestrator/steps.ts` disagrees by one character.
615
615
 
616
616
  The generated `*StepTemplate` and `*Input` types are **not** re-exported individually. Using this subpath already requires `@civitai/client` installed, so if you want one by name, import it straight from there — `import type { TextToImageStepTemplate } from '@civitai/client'`.
617
617
 
@@ -650,7 +650,7 @@ Several of the 47 exist to serve Civitai's own pipelines rather than third-party
650
650
 
651
651
  `training` and `imageResourceTraining` are the exception: an App Block's host explicitly **allows** them on the pass-through arm (see `WorkflowBodyPassThroughStep` under Primitives above for the `maxBuzz` bound, and how a trained epoch is published). For a standalone app, the orchestrator's own authorization still decides.
652
652
 
653
- Note that `WORKFLOW_STEP_TYPES` does **not** mark most of them: of its 53 entries exactly two — `comfyNodepackSnapshot` and `qwenImageBench` — sit under its "Platform internals" heading, and the rest are ordinary documented entries (`webScrape` even carries usage notes). The reason the platform steps are typed anyway is not that the catalog flags them as internal; it's that the catalog *documents* them, so skipping them would make `WorkflowStepTemplateFor<'training'>` a compile error for a step type the SDK documents — which is exactly what is live today for the 3 `$type`s the pinned client cannot type, and is why that gap is spelled out above rather than left to be discovered.
653
+ Note that `WORKFLOW_STEP_TYPES` does **not** mark most of them: of its 55 entries exactly three — `comfyNodepackSnapshot`, `qwenImageBench` and `promptModeration` — sit under its "Platform internals" heading, and the rest are ordinary documented entries (`webScrape` even carries usage notes). The reason the platform steps are typed anyway is not that the catalog flags them as internal; it's that the catalog *documents* them, so skipping them would make `WorkflowStepTemplateFor<'training'>` a compile error for a step type the SDK documents — which is exactly what is live today for each `$type` the pinned client cannot type, and is why that gap is spelled out above rather than left to be discovered.
654
654
 
655
655
  ## Public vs. confidential clients
656
656
 
@@ -213,7 +213,8 @@ export interface BlockInitPayload {
213
213
  * fail-closed defaults for you. `@civitai/blocks-react`'s
214
214
  * `useDomainMaturity()` is the React wrapper over the same thing — it is one
215
215
  * way to get this right, not the only one, since this module is
216
- * runtime-agnostic and two of the starters ship without React.
216
+ * runtime-agnostic and several starters — the Civitai App starter among
217
+ * them — ship without React.
217
218
  */
218
219
  effectiveBrowsingLevel?: number;
219
220
  }
@@ -26,6 +26,7 @@ export declare const BLOCK_SCOPES: {
26
26
  readonly POSTS_WRITE_SELF: "posts:write:self";
27
27
  readonly GOODS_READ_SELF: "goods:read:self";
28
28
  readonly GOODS_PURCHASE_SELF: "goods:purchase:self";
29
+ readonly APPS_STORE_ITEMS_WRITE: "apps:store:items:write";
29
30
  };
30
31
  export type BlockScopeKey = keyof typeof BLOCK_SCOPES;
31
32
  export type BlockScope = (typeof BLOCK_SCOPES)[BlockScopeKey];
@@ -44,8 +45,8 @@ export type BlockScope = (typeof BLOCK_SCOPES)[BlockScopeKey];
44
45
  *
45
46
  * The 4th segment was added to mirror the server's 4-segment
46
47
  * `apps:storage:shared:read` / `apps:storage:shared:write` scopes (the SHARED
47
- * cross-user datastore). The SDK now tracks the server's 4-segment scopes
48
- * directly, so this is no longer a coordinated-major-bump situation — the
48
+ * cross-user datastore); `apps:store:items:write` is the same shape. The SDK
49
+ * now tracks the server's 4-segment scopes directly, so this is no longer a coordinated-major-bump situation — the
49
50
  * pattern just widened to accept the shapes the canonical enum already lists.
50
51
  */
51
52
  export declare const BLOCK_SCOPE_PATTERN: RegExp;
@@ -24,7 +24,7 @@ export const BLOCK_SCOPES = {
24
24
  // apps:storage:shared:* — the SHARED (app-global / cross-user) datastore. Same
25
25
  // no-OAuth-bit posture as apps:storage:*; the server gates them by presence in
26
26
  // the block's approved scope set (plus a min-trust gate + fail-closed flag).
27
- // These are the SDK's only 4-segment scopes — see BLOCK_SCOPE_PATTERN below.
27
+ // 4-segment (as is `apps:store:items:write`) — see BLOCK_SCOPE_PATTERN below.
28
28
  APPS_STORAGE_SHARED_READ: 'apps:storage:shared:read',
29
29
  APPS_STORAGE_SHARED_WRITE: 'apps:storage:shared:write',
30
30
  // collections:* — the App Blocks Collections surface. These have no OAuth bit
@@ -62,6 +62,22 @@ export const BLOCK_SCOPES = {
62
62
  // always needs an explicit grant. Do not collapse the two.
63
63
  GOODS_READ_SELF: 'goods:read:self',
64
64
  GOODS_PURCHASE_SELF: 'goods:purchase:self',
65
+ // apps:store:items:write — publish the VIEWER'S OWN app items into the App
66
+ // Store as sub-listing cards under the calling app (civitai/civitai#5511,
67
+ // `/api/v1/blocks/sub-listings/{upsert,withdraw,mine}`). No OAuth bit.
68
+ //
69
+ // SENSITIVE: it writes store cards every visitor sees, under the viewer's
70
+ // name, so a declaring manifest MUST carry a `scopeJustifications` entry or
71
+ // the server rejects it at submit (not mirrored by `defineBlock` — see
72
+ // KNOWN_GAPS in `../manifest/defineBlock.ts`).
73
+ //
74
+ // CONSENT-EXEMPT on the server, like `apps:storage:shared:write`: the real
75
+ // gates run per call (the parent's enable switch, the shared-write trust
76
+ // check, item authorship, text safety, rate limits, moderator approval), and
77
+ // a consent-gated scope would be dropped from tokens before any could run.
78
+ // Requires a signed-in (non-anonymous) subject. 4-segment, like the shared
79
+ // storage scopes — see BLOCK_SCOPE_PATTERN below.
80
+ APPS_STORE_ITEMS_WRITE: 'apps:store:items:write',
65
81
  };
66
82
  /**
67
83
  * Format helper for the block-scope shape — 3 OR 4 colon-separated lowercase
@@ -78,8 +94,8 @@ export const BLOCK_SCOPES = {
78
94
  *
79
95
  * The 4th segment was added to mirror the server's 4-segment
80
96
  * `apps:storage:shared:read` / `apps:storage:shared:write` scopes (the SHARED
81
- * cross-user datastore). The SDK now tracks the server's 4-segment scopes
82
- * directly, so this is no longer a coordinated-major-bump situation — the
97
+ * cross-user datastore); `apps:store:items:write` is the same shape. The SDK
98
+ * now tracks the server's 4-segment scopes directly, so this is no longer a coordinated-major-bump situation — the
83
99
  * pattern just widened to accept the shapes the canonical enum already lists.
84
100
  */
85
101
  export const BLOCK_SCOPE_PATTERN = /^[a-z]+:[a-z]+:[a-z]+(?::[a-z]+)?$/;
@@ -1933,11 +1933,13 @@ export interface BlockManifestV1 {
1933
1933
  */
1934
1934
  $schema?: 'https://civitai.com/schemas/app-block/v1.json' | (string & {});
1935
1935
  /**
1936
- * NOT a canonical manifest property, and NOT validated. Your app id lives in
1937
- * `civitai.app.json` (`{"appId": "..."}`), which is what the `civitai` CLI
1938
- * reads. The canonical does not forbid extra top-level keys, so the server
1939
- * ignores this one; the scaffolds still carry `"app_REPLACE_ME"` and it is
1940
- * inert.
1936
+ * NOT a canonical manifest property, and NOT validated. Nothing reads it: the
1937
+ * canonical does not forbid extra top-level keys, so the server ignores this
1938
+ * one, and the Go `civitai` CLI identifies an app by the manifest's
1939
+ * `blockId` (its `app submit` keys the submission on it). There is no
1940
+ * separate app-id file either — an earlier version of this comment pointed at
1941
+ * a `civitai.app.json`, which no current tool reads. Scaffolds that still
1942
+ * carry `"app_REPLACE_ME"` here are carrying an inert key.
1941
1943
  */
1942
1944
  appId?: string;
1943
1945
  blockId: string;
@@ -113,6 +113,15 @@ export declare const WORKFLOW_STEP_TYPES: {
113
113
  readonly transcode: "Audio/video transcoding";
114
114
  /** Text-to-speech (multi-voice, multi-language). */
115
115
  readonly textToSpeech: "Text-to-speech synthesis";
116
+ /**
117
+ * Streaming text-to-speech. Inputs mirror `textToSpeech`, restricted to voices
118
+ * that stream with low first-audio latency. The output hands back an
119
+ * `inputUrl` you POST UTF-8 text chunks to (`?seq=0,1,…`, `final=true` on the
120
+ * last) and one continuous `audioUrl` stream (`pcm` s16le 24 kHz mono, or
121
+ * `ogg` Opus). The session ends at `maxCharacters` or after
122
+ * `idleTimeoutSeconds` without input, and is billed on characters spoken.
123
+ */
124
+ readonly liveTextToSpeech: "Streaming text-to-speech — post text in chunks, read back one continuous audio stream";
116
125
  /** Music generation via ACE Step 1.5 (lyrics + style → song). */
117
126
  readonly aceStepAudio: "Music generation (ACE Step 1.5)";
118
127
  /**
@@ -139,6 +148,8 @@ export declare const WORKFLOW_STEP_TYPES: {
139
148
  readonly soniloAudioGen: "Music or a sound effect from a text prompt (Sonilo)";
140
149
  /** Speech-to-text transcription. */
141
150
  readonly transcription: "Speech-to-text transcription";
151
+ /** Speech-to-text over audio streamed in while the user is still talking. */
152
+ readonly liveTranscription: "Live speech-to-text from audio streamed while the user talks";
142
153
  /** Generate captions from audio. */
143
154
  readonly audioCaptioning: "Caption generation from audio";
144
155
  /**
@@ -215,12 +226,14 @@ export declare const WORKFLOW_STEP_TYPES: {
215
226
  readonly echo: "Echo step — round-trip the input for testing";
216
227
  /** Package multiple blobs into a zip archive. */
217
228
  readonly blobArchive: "Zip multiple blobs into an archive";
229
+ /** Combine fields from earlier steps' outputs into a single result. */
230
+ readonly merge: "Merge — combine fields from earlier steps' outputs into one result";
218
231
  /** Snapshot the installed ComfyUI custom-node packs on a worker. */
219
232
  readonly comfyNodepackSnapshot: "Snapshot a worker’s installed ComfyUI node packs (internal)";
220
233
  /** Qwen image benchmarking harness. */
221
234
  readonly qwenImageBench: "Qwen image benchmarking (internal)";
222
- readonly liveTranscription: "Live speech-to-text from audio streamed while the user talks";
223
- readonly merge: "Merge — combine fields from earlier steps' outputs into one result";
235
+ /** Per-label scores and flags for a positive / negative prompt pair, under a versioned policy. */
236
+ readonly promptModeration: "Prompt moderation — per-label scores and flags for a prompt pair (internal)";
224
237
  };
225
238
  export type WorkflowStepType = keyof typeof WORKFLOW_STEP_TYPES;
226
239
  /**
@@ -117,6 +117,15 @@ export const WORKFLOW_STEP_TYPES = {
117
117
  // ----- Audio -------------------------------------------------------------
118
118
  /** Text-to-speech (multi-voice, multi-language). */
119
119
  textToSpeech: 'Text-to-speech synthesis',
120
+ /**
121
+ * Streaming text-to-speech. Inputs mirror `textToSpeech`, restricted to voices
122
+ * that stream with low first-audio latency. The output hands back an
123
+ * `inputUrl` you POST UTF-8 text chunks to (`?seq=0,1,…`, `final=true` on the
124
+ * last) and one continuous `audioUrl` stream (`pcm` s16le 24 kHz mono, or
125
+ * `ogg` Opus). The session ends at `maxCharacters` or after
126
+ * `idleTimeoutSeconds` without input, and is billed on characters spoken.
127
+ */
128
+ liveTextToSpeech: 'Streaming text-to-speech — post text in chunks, read back one continuous audio stream',
120
129
  /** Music generation via ACE Step 1.5 (lyrics + style → song). */
121
130
  aceStepAudio: 'Music generation (ACE Step 1.5)',
122
131
  /**
@@ -143,6 +152,8 @@ export const WORKFLOW_STEP_TYPES = {
143
152
  soniloAudioGen: 'Music or a sound effect from a text prompt (Sonilo)',
144
153
  /** Speech-to-text transcription. */
145
154
  transcription: 'Speech-to-text transcription',
155
+ /** Speech-to-text over audio streamed in while the user is still talking. */
156
+ liveTranscription: 'Live speech-to-text from audio streamed while the user talks',
146
157
  /** Generate captions from audio. */
147
158
  audioCaptioning: 'Caption generation from audio',
148
159
  // ----- Media composition -------------------------------------------------
@@ -226,6 +237,8 @@ export const WORKFLOW_STEP_TYPES = {
226
237
  echo: 'Echo step — round-trip the input for testing',
227
238
  /** Package multiple blobs into a zip archive. */
228
239
  blobArchive: 'Zip multiple blobs into an archive',
240
+ /** Combine fields from earlier steps' outputs into a single result. */
241
+ merge: "Merge — combine fields from earlier steps' outputs into one result",
229
242
  // ----- Platform internals ------------------------------------------------
230
243
  // Present in the consumer spec, so listed here for completeness — but these
231
244
  // exist to serve Civitai's own pipelines. A third-party app has no reason to
@@ -234,15 +247,8 @@ export const WORKFLOW_STEP_TYPES = {
234
247
  comfyNodepackSnapshot: 'Snapshot a worker’s installed ComfyUI node packs (internal)',
235
248
  /** Qwen image benchmarking harness. */
236
249
  qwenImageBench: 'Qwen image benchmarking (internal)',
237
- // ----- Auto-added 2026-10-06 from the orchestrator spec ----------------------
238
- // Added by scripts/sync-orchestrator-catalogs.mjs: the live spec accepts
239
- // these and this catalog did not list them. MOVE each entry into the right
240
- // section above, and replace any TODO(catalog) line with a real one-line
241
- // description — `pnpm check:catalogs` names every placeholder that is left.
242
- // (That check is advisory, so it will not stop this merging. It is a
243
- // reminder, not a gate.)
244
- liveTranscription: 'Live speech-to-text from audio streamed while the user talks',
245
- merge: "Merge — combine fields from earlier steps' outputs into one result",
250
+ /** Per-label scores and flags for a positive / negative prompt pair, under a versioned policy. */
251
+ promptModeration: 'Prompt moderation — per-label scores and flags for a prompt pair (internal)',
246
252
  };
247
253
  /**
248
254
  * Engines that the `imageGen` step accepts. Each one has its own input shape;
@@ -3,7 +3,7 @@
3
3
  * own generated client (`@civitai/client`).
4
4
  *
5
5
  * The sibling `@civitai/app-sdk/orchestrator` module gives you the *catalog*
6
- * (`WORKFLOW_STEP_TYPES` — 53 `$type` names and what each one does) and the
6
+ * (`WORKFLOW_STEP_TYPES` — 55 `$type` names and what each one does) and the
7
7
  * fetch helpers (`submitWorkflow`, `estimateWorkflow`, …), but its body
8
8
  * builders take `input: unknown`. This module is the missing half: the actual
9
9
  * per-step input shapes, tracked against the orchestrator's OpenAPI spec by
@@ -78,9 +78,9 @@
78
78
  * orchestrator's own authorization still decides.
79
79
  *
80
80
  * ⚠️ `WORKFLOW_STEP_TYPES` does NOT mark most of them. Counted at this commit:
81
- * of its 53 entries, exactly TWO sit under its "Platform internals" heading —
82
- * `comfyNodepackSnapshot` and `qwenImageBench`. `training`, `webScrape`,
83
- * `xGuardModeration`, `modelPickleScan` and the `model*` / `media*` steps are
81
+ * of its 55 entries, exactly THREE sit under its "Platform internals" heading —
82
+ * `comfyNodepackSnapshot`, `qwenImageBench` and `promptModeration`. `training`,
83
+ * `webScrape`, `xGuardModeration`, `modelPickleScan` and the `model*` / `media*` steps are
84
84
  * ordinary documented entries under ordinary headings, and `webScrape` carries
85
85
  * consumer-facing usage notes. So "the catalog already flags these as internal"
86
86
  * is not a reason this map types them, and an earlier version of this docblock
@@ -97,9 +97,10 @@
97
97
  * (#315) without anything going red. The measured state, derived rather than
98
98
  * typed:
99
99
  *
100
- * `WORKFLOW_STEP_TYPES` documents 53 `$type`s; this map covers 47. The 6 with
100
+ * `WORKFLOW_STEP_TYPES` documents 55 `$type`s; this map covers 47. The 8 with
101
101
  * no generated template in the pinned `@civitai/client` are `imageScanning`,
102
- * `liveTranscription`, `merge`, `preprocessVideo`, `soniloAudioGen`, `yuE2`, and
102
+ * `liveTextToSpeech`, `liveTranscription`, `merge`, `preprocessVideo`,
103
+ * `promptModeration`, `soniloAudioGen`, `yuE2`, and
103
104
  * `WorkflowStepTemplateFor<…>` is a compile error for each of them.
104
105
  *
105
106
  * That gap is EXPECTED and is not a defect in either surface. The catalog
@@ -110,7 +111,7 @@
110
111
  * stated: it is spelled as a `never` ledger in
111
112
  * `test/orchestrator/step-templates.test-d.ts` (which also pins the compile
112
113
  * error above, one `@ts-expect-error` per gap `$type`), and the sentence naming
113
- * the 3 is pinned character for character — here and in the README — by
114
+ * the gap is pinned character for character — here and in the README — by
114
115
  * `test/orchestrator/step-count-prose.test.ts`, which derives all four numbers
115
116
  * from `WORKFLOW_STEP_TYPES` and this file's own AST. Both go red when the gap
116
117
  * moves in either direction.
@@ -308,7 +309,7 @@ interface StepTemplateMap {
308
309
  xGuardModeration: XGuardModerationStepTemplate;
309
310
  }
310
311
  /**
311
- * `$type` → its step-template type, for 47 of the catalog's 53 step types.
312
+ * `$type` → its step-template type, for 47 of the catalog's 55 step types.
312
313
  *
313
314
  * Keyed by the WIRE name rather than the generated type name, because the wire
314
315
  * name is what you actually have in hand and the generator does not always
@@ -3,7 +3,7 @@
3
3
  * own generated client (`@civitai/client`).
4
4
  *
5
5
  * The sibling `@civitai/app-sdk/orchestrator` module gives you the *catalog*
6
- * (`WORKFLOW_STEP_TYPES` — 53 `$type` names and what each one does) and the
6
+ * (`WORKFLOW_STEP_TYPES` — 55 `$type` names and what each one does) and the
7
7
  * fetch helpers (`submitWorkflow`, `estimateWorkflow`, …), but its body
8
8
  * builders take `input: unknown`. This module is the missing half: the actual
9
9
  * per-step input shapes, tracked against the orchestrator's OpenAPI spec by
@@ -78,9 +78,9 @@
78
78
  * orchestrator's own authorization still decides.
79
79
  *
80
80
  * ⚠️ `WORKFLOW_STEP_TYPES` does NOT mark most of them. Counted at this commit:
81
- * of its 53 entries, exactly TWO sit under its "Platform internals" heading —
82
- * `comfyNodepackSnapshot` and `qwenImageBench`. `training`, `webScrape`,
83
- * `xGuardModeration`, `modelPickleScan` and the `model*` / `media*` steps are
81
+ * of its 55 entries, exactly THREE sit under its "Platform internals" heading —
82
+ * `comfyNodepackSnapshot`, `qwenImageBench` and `promptModeration`. `training`,
83
+ * `webScrape`, `xGuardModeration`, `modelPickleScan` and the `model*` / `media*` steps are
84
84
  * ordinary documented entries under ordinary headings, and `webScrape` carries
85
85
  * consumer-facing usage notes. So "the catalog already flags these as internal"
86
86
  * is not a reason this map types them, and an earlier version of this docblock
@@ -97,9 +97,10 @@
97
97
  * (#315) without anything going red. The measured state, derived rather than
98
98
  * typed:
99
99
  *
100
- * `WORKFLOW_STEP_TYPES` documents 53 `$type`s; this map covers 47. The 6 with
100
+ * `WORKFLOW_STEP_TYPES` documents 55 `$type`s; this map covers 47. The 8 with
101
101
  * no generated template in the pinned `@civitai/client` are `imageScanning`,
102
- * `liveTranscription`, `merge`, `preprocessVideo`, `soniloAudioGen`, `yuE2`, and
102
+ * `liveTextToSpeech`, `liveTranscription`, `merge`, `preprocessVideo`,
103
+ * `promptModeration`, `soniloAudioGen`, `yuE2`, and
103
104
  * `WorkflowStepTemplateFor<…>` is a compile error for each of them.
104
105
  *
105
106
  * That gap is EXPECTED and is not a defect in either surface. The catalog
@@ -110,7 +111,7 @@
110
111
  * stated: it is spelled as a `never` ledger in
111
112
  * `test/orchestrator/step-templates.test-d.ts` (which also pins the compile
112
113
  * error above, one `@ts-expect-error` per gap `$type`), and the sentence naming
113
- * the 3 is pinned character for character — here and in the README — by
114
+ * the gap is pinned character for character — here and in the README — by
114
115
  * `test/orchestrator/step-count-prose.test.ts`, which derives all four numbers
115
116
  * from `WORKFLOW_STEP_TYPES` and this file's own AST. Both go red when the gap
116
117
  * moves in either direction.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@civitai/app-sdk",
3
- "version": "0.58.0",
3
+ "version": "0.60.0",
4
4
  "description": "OAuth + PKCE, encrypted-cookie sessions, scopes, and orchestrator helpers for building third-party Civitai apps.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -101,13 +101,14 @@
101
101
  "collections:read:private",
102
102
  "posts:write:self",
103
103
  "goods:read:self",
104
- "goods:purchase:self"
104
+ "goods:purchase:self",
105
+ "apps:store:items:write"
105
106
  ]
106
107
  }
107
108
  },
108
109
  "scopeJustifications": {
109
110
  "type": "object",
110
- "description": "Per-scope justification: a map of scope-id → free-text rationale explaining WHY the app needs that permission, shown to the moderator during review. REQUIRED for SENSITIVE scopes — any declared scope that can spend or read the viewer's Buzz, read the viewer's private data, or write data other users see (e.g. `ai:write:budgeted`, `social:tip:self`, `goods:purchase:self`, `buzz:read:self`, `collections:read:private`, `apps:storage:shared:write`, `posts:write:self`) MUST carry a non-empty justification here, or the manifest is rejected at submit time. OPTIONAL for non-sensitive scopes — omit those and the manifest stays valid. Every key MUST be a scope also present in `scopes` (justifications for scopes you don't request are rejected). Each value is a non-empty string of at most 500 characters. The requirement is enforced imperatively by the manifest validator (not expressed as JSON-Schema conditionals here). NOTE: the justification captures the developer's STATED rationale only; the platform does not verify the truth of the claims.",
111
+ "description": "Per-scope justification: a map of scope-id → free-text rationale explaining WHY the app needs that permission, shown to the moderator during review. REQUIRED for SENSITIVE scopes — any declared scope that can spend or read the viewer's Buzz, read the viewer's private data, or write data other users see (e.g. `ai:write:budgeted`, `social:tip:self`, `goods:purchase:self`, `buzz:read:self`, `collections:read:private`, `apps:storage:shared:write`, `posts:write:self`, `apps:store:items:write`) MUST carry a non-empty justification here, or the manifest is rejected at submit time. OPTIONAL for non-sensitive scopes — omit those and the manifest stays valid. Every key MUST be a scope also present in `scopes` (justifications for scopes you don't request are rejected). Each value is a non-empty string of at most 500 characters. The requirement is enforced imperatively by the manifest validator (not expressed as JSON-Schema conditionals here). NOTE: the justification captures the developer's STATED rationale only; the platform does not verify the truth of the claims.",
111
112
  "additionalProperties": {
112
113
  "type": "string",
113
114
  "minLength": 1,