makaron-persona-look-cli 0.4.1 → 0.5.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
@@ -1,10 +1,10 @@
1
1
  # Makaron Persona + Look CLI
2
2
 
3
- `makaron-persona-look-cli` keeps stable character identity (`Persona`) separate from styling (`Look`). It provides owner-local administration plus a Worker-backed library that an Agent can search and download as a selected image reference pack. It never generates an image or submits a paid render.
3
+ `makaron-persona-look-cli` keeps stable character identity (`Persona`) separate from styling (`Look`). It provides owner-local administration plus a Worker-backed library that an Agent can search, and uses Makaron as the default render engine for one customer-ready three-view turnaround image.
4
4
 
5
5
  ## What an Agent gets
6
6
 
7
- `preview` downloads the selected Persona source/approved auxiliary views and the Look source, plus a safe `selection.json` render brief. It is intentionally a **source-reference pack**, not a synthesized image: the Persona is the only identity reference and the Look image's face must be excluded from any later render.
7
+ `preview` downloads the selected Persona source/approved auxiliary views and the Look source, plus a safe `selection.json` render brief. It is intentionally a **source-reference pack**, not a synthesized image. `render` is the delivery route: it sends those private inputs to the caller's authenticated Makaron CLI and writes exactly one full-body front/right-side/back turnaround sheet. The Persona is the only identity reference; the Look image's face is excluded.
8
8
 
9
9
  The raw `private-library/` is ignored by Git and is never included in the npm package. D1 holds catalog metadata, private R2 holds approved source bytes, and a Worker issues an individual credential to each enrolling Agent. The individual credential is stored only in the Agent's local mode-600 config file and is never printed by the CLI.
10
10
 
@@ -26,7 +26,7 @@ personlib remote doctor --json
26
26
  personlib remote preview --brief '为一个20秒 K-pop 女团舞台视频找冷感、银黑未来街头造型人物参考图' --output-dir ./personlib-selection --json
27
27
  ```
28
28
 
29
- The Worker is deployed at `https://personlib-agent.bzz0309.workers.dev` and its HTTPS URL is embedded in this release's `package.json`. `prepublishOnly` blocks an accidental npm release if that endpoint is removed. npm publication is a separate final step.
29
+ The Worker is deployed at `https://personlib-agent.bzz0309.workers.dev` and its HTTPS URL is embedded in the package. `prepublishOnly` blocks a future npm release if that endpoint is removed.
30
30
 
31
31
  ## Owner deployment
32
32
 
@@ -44,10 +44,16 @@ personlib remote sync --library ./private-library --json
44
44
  Once setup and `remote doctor` succeed, an Agent can fulfill a request such as “按这段文案获取对应图” with:
45
45
 
46
46
  ```bash
47
- personlib remote preview --brief '20 秒嘻哈说唱女 rapper 舞台视频,红黑街头风、宽松工装、强节奏' --output-dir ./personlib-selection --json
47
+ personlib remote render --brief '20 秒嘻哈说唱女 rapper 舞台视频,红黑街头风、宽松工装、强节奏' --output-dir ./personlib-turnaround --json
48
48
  ```
49
49
 
50
- The result contains the selected original reference images. If the intended result is a brand-new generated image, connect a separate Makaron rendering adapter after an explicit credit/submit authorization; this CLI deliberately stops before that paid/external action.
50
+ This is a paid external generation action: the Agent must first have working Makaron authentication and receive explicit approval to spend credits. The result is `turnaround.png` (or `.jpg`/`.webp`), plus `turnaround-plan.json`, `prompt_used.md`, and `qc_report.md`. The CLI never automatically retries a paid job. Use `--dry-run` to inspect the exact prompt and matching IDs without downloading inputs or submitting anything, or `--no-wait` to keep the returned Makaron run ID for later retrieval.
51
+
52
+ For a free reference-only check, retain the explicit preview command:
53
+
54
+ ```bash
55
+ personlib remote preview --brief '20 秒嘻哈说唱女 rapper 舞台视频,红黑街头风、宽松工装、强节奏' --output-dir ./personlib-selection --json
56
+ ```
51
57
 
52
58
  ## Local owner-admin commands
53
59
 
@@ -90,8 +90,9 @@ function scoreLook(record, brief) {
90
90
 
91
91
  function buildRenderBrief(persona, look) {
92
92
  const identity = persona.identity || {};
93
+ const direction = look.look || look;
93
94
  const lock = [identity.face_shape, identity.brows, identity.eyes, identity.nose, identity.mouth, identity.skin, identity.stable_marks, identity.body_evidence].filter(Boolean).join("; ");
94
- return `Original ${identity.presentation || "adult"} character. Identity lock: ${lock}. Keep this Persona facial structure and stable marks; do not use the Look reference face. Look direction: ${look.family}. ${look.silhouette}. Garments: ${look.garments}. Palette: ${look.palette}. Materials: ${look.materials}. Accessories: ${look.accessories}. Scene: ${look.scene}. Replace source logos, watermarks, wordmarks, celebrity likenesses, and recognizable brand elements with original generic design.`;
95
+ return `Original ${identity.presentation || "adult"} character. Identity lock: ${lock}. Keep this Persona facial structure and stable marks; do not use the Look reference face. Look direction: ${direction.family}. ${direction.silhouette}. Garments: ${direction.garments}. Palette: ${direction.palette}. Materials: ${direction.materials}. Accessories: ${direction.accessories}. Scene: ${direction.scene}. Replace source logos, watermarks, wordmarks, celebrity likenesses, and recognizable brand elements with original generic design.`;
95
96
  }
96
97
 
97
98
  async function records(env, table) {
@@ -99,6 +99,8 @@ test("owner sync, self-registration, recommendation, compose, and private previe
99
99
  assert.equal(recommendation.persona.id, "P-001");
100
100
  assert.equal(recommendation.look.id, "L-132");
101
101
  assert.match(recommendation.render_brief, /do not use the Look reference face/i);
102
+ assert.match(recommendation.render_brief, /urban transit/);
103
+ assert.doesNotMatch(recommendation.render_brief, /undefined/);
102
104
  response = await worker.fetch(request("/v1/compose", { method: "POST", headers, body: JSON.stringify({ persona_id: "P-001", look_id: "L-132" }) }), env);
103
105
  assert.equal((await response.json()).composition.policies.look_source_face, "excluded");
104
106
  response = await worker.fetch(request("/v1/preview", { method: "POST", headers, body: JSON.stringify({ persona_id: "P-001", look_id: "L-132" }) }), env);
@@ -179,33 +179,162 @@ async function authenticated(args, output, path, json) {
179
179
  jsonOutput({ ok: true, remote: payload }, output, args);
180
180
  }
181
181
 
182
- async function preview(args, output) {
182
+ async function requestedReferencePack(args) {
183
183
  const remote = agentConnection(args);
184
184
  const brief = getOption(args, "--brief");
185
185
  const personaId = getOption(args, "--persona");
186
186
  const lookId = getOption(args, "--look");
187
- if (!brief && !(personaId && lookId)) throw new Error("usage: personlib remote preview --brief TEXT | --persona P-001 --look L-001 [--output-dir PATH]");
187
+ if (!brief && !(personaId && lookId)) throw new Error("usage: personlib remote <preview|render> --brief TEXT | --persona P-001 --look L-001 [--output-dir PATH]");
188
188
  const { payload } = await request(remote.endpoint, "/v1/preview", { token: remote.token, method: "POST", json: brief ? { brief } : { persona_id: personaId, look_id: lookId } });
189
- const destination = getOption(args, "--output-dir");
190
- if (!destination) {
191
- jsonOutput({ ok: true, remote: payload }, output, args);
192
- return;
193
- }
194
- const absoluteDestination = resolve(destination);
195
- mkdirSync(absoluteDestination, { recursive: true });
189
+ return { remote, payload };
190
+ }
191
+
192
+ async function downloadReferencePack(remote, payload, destination, args) {
193
+ mkdirSync(destination, { recursive: true });
196
194
  const downloaded = [];
197
195
  for (const asset of payload.assets || []) {
198
196
  const response = await fetch(`${remote.endpoint}${asset.url}`, { headers: { authorization: `Bearer ${remote.token}` } });
199
197
  if (!response.ok) throw new Error(`remote asset download failed (${response.status}): ${asset.asset_id}`);
200
198
  const extension = { "image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp" }[response.headers.get("content-type")] || ".bin";
201
199
  const name = `${asset.role}${asset.index ? `-${asset.index}` : ""}${extension}`;
202
- const target = join(absoluteDestination, name);
200
+ const target = join(destination, name);
203
201
  if (existsSync(target) && !has(args, "--overwrite")) throw new Error(`refusing to overwrite ${target}; pass --overwrite to replace it`);
204
202
  writeFileSync(target, Buffer.from(await response.arrayBuffer()));
205
- downloaded.push({ role: asset.role, filename: name });
203
+ downloaded.push({ role: asset.role, filename: name, path: target });
204
+ }
205
+ writeFileSync(join(destination, "selection.json"), `${JSON.stringify({ ...payload, assets: downloaded.map(({ path, ...asset }) => asset) }, null, 2)}\n`, "utf8");
206
+ return downloaded;
207
+ }
208
+
209
+ async function preview(args, output) {
210
+ const { remote, payload } = await requestedReferencePack(args);
211
+ const destination = getOption(args, "--output-dir");
212
+ if (!destination) {
213
+ jsonOutput({ ok: true, remote: payload }, output, args);
214
+ return;
215
+ }
216
+ const absoluteDestination = resolve(destination);
217
+ const downloaded = await downloadReferencePack(remote, payload, absoluteDestination, args);
218
+ jsonOutput({ ok: true, retrieval: { kind: "persona-look-reference-pack", image_status: "source-reference-pack-not-a-generated-composite", output_dir: absoluteDestination, files: [...downloaded.map(({ path, ...asset }) => asset), { role: "selection", filename: "selection.json" }] } }, output, args);
219
+ }
220
+
221
+ function makaronCommand() {
222
+ return (process.env.MAKARON_CLI_COMMAND || `npx -y makaron-cli@${process.env.MAKARON_CLI_VERSION || "0.13.0"}`).trim().split(/\s+/);
223
+ }
224
+
225
+ function redactProviderText(value) {
226
+ return String(value || "").replace(/mk_(?:live|test)?_?[A-Za-z0-9_-]{8,}/g, "[REDACTED_MAKARON_KEY]");
227
+ }
228
+
229
+ function providerJson(args, timeout = 900000) {
230
+ const command = makaronCommand();
231
+ const result = spawnSync(command[0], [...command.slice(1), ...args], {
232
+ encoding: "utf8",
233
+ timeout,
234
+ maxBuffer: 20 * 1024 * 1024,
235
+ env: { ...process.env, MAKARON_DISABLE_UPDATE_CHECK: "1" }
236
+ });
237
+ if (result.error) throw new Error(`Makaron CLI could not start: ${result.error.message}`);
238
+ if (result.status !== 0) throw new Error(`Makaron CLI failed (exit ${result.status}): ${redactProviderText(result.stderr || result.stdout).slice(-800)}`);
239
+ const stdout = String(result.stdout || "").trim();
240
+ for (const candidate of [stdout, ...stdout.split(/\r?\n/).reverse()]) {
241
+ try { return JSON.parse(candidate); } catch {}
242
+ }
243
+ throw new Error("Makaron CLI did not return machine-readable JSON");
244
+ }
245
+
246
+ function providerBody(payload) {
247
+ return payload?.result && typeof payload.result === "object" ? payload.result : payload || {};
248
+ }
249
+
250
+ function providerRunId(payload) {
251
+ const body = providerBody(payload);
252
+ const id = body.runId || body.run_id || body.id;
253
+ if (!id) throw new Error("Makaron submission did not return a run ID");
254
+ return id;
255
+ }
256
+
257
+ function providerImages(payload) {
258
+ const body = providerBody(payload);
259
+ const images = [];
260
+ for (const item of Array.isArray(body.output) ? body.output : []) if (item?.type === "image" && item.url) images.push({ url: item.url, id: item.id, task_id: item.task_id, status: item.status || body.status });
261
+ for (const item of body.images || []) if (item?.imageUrl) images.push({ url: item.imageUrl, id: item.id, status: body.status || "completed" });
262
+ return [...new Map(images.map((item) => [item.url, item])).values()];
263
+ }
264
+
265
+ function turnaroundPrompt(payload) {
266
+ if (!payload?.render_brief) throw new Error("remote selection did not include a render brief");
267
+ return [
268
+ payload.render_brief,
269
+ "Asset instruction: create exactly one photorealistic full-body three-view character turnaround sheet, not a collage of different people.",
270
+ "Layout: three equal vertical panels at identical scale: left is straight-on front view, center is exact right-side profile, right is back view. Show head-to-toe including both shoes in every panel.",
271
+ "Identity rule: the Persona reference is the only identity reference. Preserve the same adult facial structure, skin traits, stable marks, apparent age, hairline, build, and proportions in all three panels. Never use the Look-reference face.",
272
+ "Look rule: use the Look reference only for the selected outfit, hairstyle, makeup, accessories, palette, and material direction. Keep the exact same clothing, footwear, hairstyle, and accessories in every panel. Ignore any scene, person, text, logo, or watermark in the Look reference.",
273
+ "Use a seamless neutral light-grey studio background, even lighting, fixed eye-level camera, natural relaxed standing pose, clean anatomy, and no props.",
274
+ "No text, labels, watermark, source logos, team marks, celebrity likenesses, or recognizable brand elements."
275
+ ].join(" ");
276
+ }
277
+
278
+ function outputExtension(url) {
279
+ try {
280
+ const extension = extname(new URL(url).pathname).toLowerCase();
281
+ return [".png", ".jpg", ".jpeg", ".webp"].includes(extension) ? extension : ".png";
282
+ } catch {
283
+ return ".png";
284
+ }
285
+ }
286
+
287
+ async function renderTurnaround(args, output) {
288
+ const { remote, payload } = await requestedReferencePack(args);
289
+ const outputDir = resolve(getOption(args, "--output-dir") || "./personlib-turnaround");
290
+ const prompt = turnaroundPrompt(payload);
291
+ const plan = {
292
+ kind: "persona-look-turnaround",
293
+ engine: "makaron",
294
+ view: "front-side-back",
295
+ persona_id: payload.persona?.id,
296
+ look_id: payload.look?.id,
297
+ image_model: getOption(args, "--image-model") || "openai",
298
+ project: getOption(args, "--project") || "auto",
299
+ output_dir: outputDir,
300
+ source_policy: "persona-is-the-only-identity-reference",
301
+ prompt
302
+ };
303
+ if (has(args, "--dry-run")) {
304
+ jsonOutput({ ok: true, dry_run: true, render: plan }, output, args);
305
+ return;
306
+ }
307
+ const finalName = "turnaround";
308
+ const existingOutput = [".png", ".jpg", ".jpeg", ".webp"].map((extension) => join(outputDir, `${finalName}${extension}`)).find(existsSync);
309
+ if (existingOutput && !has(args, "--overwrite")) throw new Error(`refusing to overwrite ${existingOutput}; pass --overwrite to replace it`);
310
+ const referenceDir = join(outputDir, "_internal_references");
311
+ const references = await downloadReferencePack(remote, payload, referenceDir, args);
312
+ const persona = references.find((asset) => asset.role === "persona");
313
+ const look = references.find((asset) => asset.role === "look");
314
+ if (!persona || !look) throw new Error("reference pack must contain both a Persona and Look image");
315
+ mkdirSync(outputDir, { recursive: true });
316
+ writeFileSync(join(outputDir, "turnaround-plan.json"), `${JSON.stringify(plan, null, 2)}\n`, "utf8");
317
+ writeFileSync(join(outputDir, "prompt_used.md"), `# Makaron turnaround prompt\n\n${prompt}\n`, "utf8");
318
+ const command = ["chat", "--project", plan.project, "--json", "--background", "--image", persona.path, "--image", look.path, "--image-model", plan.image_model, prompt];
319
+ const submitted = providerJson(command, 180000);
320
+ const runId = providerRunId(submitted);
321
+ const submission = { run_id: runId, project_id: providerBody(submitted).projectId || providerBody(submitted).project_id, project_url: providerBody(submitted).projectUrl || providerBody(submitted).project_url };
322
+ if (has(args, "--no-wait")) {
323
+ writeFileSync(join(outputDir, "qc_report.md"), `# QC report\n\n- State: PENDING_VISUAL_QC\n- Run ID: ${runId}\n- Requested asset: front-side-back turnaround sheet\n`, "utf8");
324
+ jsonOutput({ ok: true, submitted: true, render: { ...plan, ...submission, qc_state: "PENDING_VISUAL_QC" } }, output, args);
325
+ return;
206
326
  }
207
- writeFileSync(join(absoluteDestination, "selection.json"), `${JSON.stringify({ ...payload, assets: downloaded }, null, 2)}\n`, "utf8");
208
- jsonOutput({ ok: true, retrieval: { kind: "persona-look-reference-pack", image_status: "source-reference-pack-not-a-generated-composite", output_dir: absoluteDestination, files: [...downloaded, { role: "selection", filename: "selection.json" }] } }, output, args);
327
+ const completed = providerJson(["responses", "get", runId, "--wait", "--json"]);
328
+ const body = providerBody(completed);
329
+ if (["failed", "aborted"].includes(body.status)) throw new Error(`Makaron run ${runId} ended with status ${body.status}`);
330
+ const images = providerImages(completed);
331
+ if (images.length !== 1) throw new Error(`Makaron run ${runId} returned ${images.length} images; expected exactly one turnaround sheet. No automatic reroll was submitted.`);
332
+ const finalPath = join(outputDir, `${finalName}${outputExtension(images[0].url)}`);
333
+ const media = await fetch(images[0].url);
334
+ if (!media.ok) throw new Error(`Makaron image download failed (${media.status})`);
335
+ writeFileSync(finalPath, Buffer.from(await media.arrayBuffer()));
336
+ writeFileSync(join(outputDir, "qc_report.md"), `# QC report\n\n- State: PENDING_VISUAL_QC\n- Run ID: ${runId}\n- Asset: ${basename(finalPath)}\n- Required check: one adult Persona identity across front, exact side, and back views; one unchanged Look; no source Look face, logos, or text.\n- Retry policy: no automatic reroll.\n`, "utf8");
337
+ jsonOutput({ ok: true, render: { ...plan, ...submission, status: body.status || "completed", qc_state: "PENDING_VISUAL_QC", files: { turnaround: finalPath, plan: join(outputDir, "turnaround-plan.json"), prompt: join(outputDir, "prompt_used.md"), qc_report: join(outputDir, "qc_report.md") } } }, output, args);
209
338
  }
210
339
 
211
340
  async function sync(args, output, { readManifest }) {
@@ -249,6 +378,7 @@ export async function runRemote(args, { output, readManifest }) {
249
378
  return authenticated(childArgs, output, "/v1/compose", { persona_id: personaId, look_id: lookId });
250
379
  }
251
380
  if (subcommand === "preview") return preview(args.slice(1), output);
381
+ if (subcommand === "render") return renderTurnaround(args.slice(1), output);
252
382
  if (subcommand === "sync") return sync(args.slice(1), output, { readManifest });
253
- throw new Error("usage: personlib remote <setup|doctor|recommend|compose|preview|sync>");
383
+ throw new Error("usage: personlib remote <setup|doctor|recommend|compose|preview|render|sync>");
254
384
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-persona-look-cli",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Portable Persona and Look library for Makaron agents",
5
5
  "private": false,
6
6
  "type": "module",
@@ -15,19 +15,20 @@ Persona is the stable identity reference: face structure, natural skin traits, s
15
15
  - “这张图进 Look 库” is an owner-only intake request. Use `personlib intake --into look --rights authorized`; extract only clothing, hair, makeup, accessory, palette, material, and scene information. Never use the source face as a new Persona.
16
16
  - “找一个人物” is a remote search request: use `personlib remote recommend --brief TEXT` after `personlib remote doctor` succeeds.
17
17
  - “为广告找一个人物” should use `personlib recommend --brief TEXT`. If there is no appropriate Look asset, use the returned temporary Look only for this request; do not save it unless the owner asks.
18
- - “按这段文案获取对应图” should use `personlib remote preview --brief TEXT --output-dir PATH` on 小龙虾 (or local `personlib fetch` in owner mode). It returns a Persona + Look reference pack and a render brief, not a synthesized image.
18
+ - “按这段文案获取对应图” should use `personlib remote render --brief TEXT --output-dir PATH --json` on 小龙虾. The default engine is Makaron and the requested output is exactly one full-body front/right-side/back turnaround sheet of one adult Persona in one unchanged Look. Before that command, confirm a successful `personlib remote doctor`, local Makaron authentication, and the user's explicit approval to spend generation credits. Use `--dry-run` to show the chosen Persona/Look IDs and exact prompt without submitting.
19
+ - “只看看匹配的参考” should use `personlib remote preview --brief TEXT --output-dir PATH --json`. This is free retrieval of the private Persona + Look reference pack, not a synthesized image.
19
20
  - “都市通勤 / 职场 / 杂志感” should use `personlib recommend --brief TEXT`; prefer catalog office, commute, workwear, and magazine-editorial tags before creating a temporary Look.
20
21
  - “晚宴 / 红毯 / 高级派对” should use `personlib recommend --brief TEXT`; prefer adult gala, red-carpet, black-tie, cocktail, and luxury-party Looks without retaining a source event or person identity.
21
22
  - “运动休闲时尚” should use `personlib recommend --brief TEXT`; prefer adult athleisure, retro sport, track, court, and sport-luxe Looks without retaining team, school, or brand identity.
22
23
  - “给这个人物换妆造” means keep the Persona and select or create a Look.
23
- - “用这个人物生成” means compose Persona first, Look second, then render through the caller's Makaron authentication.
24
+ - “用这个人物生成” means compose Persona first, Look second, then use `personlib remote render --persona P-xxx --look L-xxx --output-dir PATH --json` through the caller's Makaron authentication.
24
25
 
25
26
  ## Access boundary
26
27
 
27
- Any enrolled OpenClaw Agent may use credentialed `remote doctor`, `remote recommend`, `remote compose`, and `remote preview`. It may not call `remote sync`, upload source images, modify tags, delete assets, or publish the bundle. Open enrollment is an owner-selected access rule; never request or print API keys, Agent tokens, or owner tokens. `preview` returns source references only; the selected Persona remains the sole identity reference and the Look-source face stays excluded.
28
+ Any enrolled OpenClaw Agent may use credentialed `remote doctor`, `remote recommend`, `remote compose`, `remote preview`, and `remote render`. It may not call `remote sync`, upload source images, modify tags, delete assets, or publish the bundle. `remote render` additionally needs the Agent's own already-authenticated Makaron CLI and explicit credit-spend approval. Open enrollment is an owner-selected access rule; never request or print API keys, Agent tokens, owner tokens, or Makaron credentials. `preview` returns source references only; `render` returns the turnaround and preserves its plan, prompt, run ID, and QC record.
28
29
 
29
30
  ## Current local-admin milestone
30
31
 
31
- The current package supports local Persona and Look intake, list, show, integrity validation, `recommend --brief TEXT`, `fetch --brief TEXT --output-dir PATH`, and `compose --persona P-xxx --look L-xxx`; it also ships the remote Worker/client implementation. The remote commands need a deployed private Worker and a successful top-level `setup` before use; do not claim that the current host is live until `remote doctor` succeeds against its Worker URL. Catalog-matching requests return an existing reference pack only. Both local and remote paths produce a render brief only: the Persona is the sole identity reference, the Look source face is excluded, and logos/watermarks/wordmarks are replaced. An authorized image renderer may produce one preview from that brief only after its own explicit deployment and credit/submit authorization.
32
+ The current package supports local Persona and Look intake, list, show, integrity validation, `recommend --brief TEXT`, `fetch --brief TEXT --output-dir PATH`, and `compose --persona P-xxx --look L-xxx`; it also ships the remote Worker/client implementation. The remote commands need a deployed private Worker and a successful top-level `setup` before use; do not claim that the current host is live until `remote doctor` succeeds against its Worker URL. `remote render` obtains the selected private inputs just in time, calls Makaron with the Persona as the sole identity reference, and asks for one neutral-studio front/right-side/back turnaround. It keeps internal inputs in `_internal_references/` for traceability but only the finished turnaround is customer-ready. Reject an output that is not one three-view adult turnaround, mixes identities or Looks, uses the Look source face, or includes source text/logos. Record `PASS`, `REROLL`, or `BLOCKED`; do not submit a hidden retry.
32
33
 
33
34
  When a source has preppy, uniform-adjacent, or youth-coded styling, store only an adult office-core/editorial interpretation. Do not use a student identity, school context, or sexualized youth styling.