mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 10:46:06 +00:00
refactor(producer): make the short-comp band baseline-first and its attribution decisive
Recut after pre-registering the read exposed two flaws in the first cut:
1. Attribution was wrong. de_short_band keyed on frame count + element
ceiling alone, so a webm render, a compile-gated comp, or a forced
screenshot at 400f reported "applied" while its routing was untouched —
poisoning the measurement cohort with unaffected renders and diluting
any effect toward zero. Now the predicate is evaluated twice (900 floor
vs band floor) and the band is DECISIVE only when the calls disagree:
every other eligibility condition passed and only the floor differed.
The cohort contains exactly the renders whose routing the band decides.
2. A same-release flip is unfalsifiable. composition_element_count ships
WITH the routing change, so the before-period cannot be filtered to the
same cohort as the after-period — the comparison would show a speedup
even if the change did nothing (the after-cohort excludes big comps by
construction; the before-cohort includes them). Routing is therefore
gated behind HF_DE_SHORT_BAND_ROUTE, default OFF: this release computes
and emits the full band decision on every render ("applied" is the
counterfactual "would have inverted"), a follow-up flips the default.
Identical cohort selector on both sides of the boundary, and the
skipped/oversize renders in the same frame band form a concurrent
control — a difference-in-differences that absorbs secular drift
(content mix, version-correlated populations, hardware), which a plain
before/after cannot.
Also: countElementTags now counts HTML void elements. Counting only
closers read an image gallery as a tiny comp and opened the band on
exactly the content most likely to lose it (images skew expensive to
paint). Opening tags stay uncounted — inline scripts' `a < b` would
false-positive. Counter semantics are frozen while the baseline is read:
the distribution the baseline release records must be measured by the
same counter that later gates.
Revert-rate baseline for the pre-registered read, measured over 14d
fleet-wide: the 900+ inversion runs 31,756 inverted / 1,705 reverted =
5.1%. At the benched 1.16-1.24x win and ~1.8x revert cost, expected net
for the band is ~12%. Kill criteria for the flip release: DiD <= 0,
in-band revert rate > 5.1% baseline, or band fallback rate > DE baseline.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0749cd9ff8
commit
e9de2fa14f
@@ -81,13 +81,17 @@ export interface RenderCaptureObservability {
|
|||||||
*/
|
*/
|
||||||
compositionElementCount?: number;
|
compositionElementCount?: number;
|
||||||
/**
|
/**
|
||||||
* Why the short-comp band did or did not apply to this render:
|
* Short-comp band decision, emitted only when the band is DECISIVE — every
|
||||||
* "applied" (frames landed in 250-899 and the element count cleared the
|
* other inversion-eligibility condition passed and only the floor (250 vs
|
||||||
* ceiling), "skipped_elements" (band was open by frame count but the comp
|
* 900) differed. "applied": the element count cleared the ceiling too, so
|
||||||
* was too large), or undefined when the frame count made the band
|
* with routing enabled (HF_DE_SHORT_BAND_ROUTE) this render inverts; in the
|
||||||
* irrelevant either way. Distinguishes "the new routing chose this" from
|
* baseline release the same value is the COUNTERFACTUAL "would have
|
||||||
* "the pre-existing 900 floor chose this" — otherwise a fleet perf shift is
|
* inverted". "skipped_elements": the element ceiling was the only blocker.
|
||||||
* unattributable.
|
* Unset: the band could not have affected this render (ineligible for some
|
||||||
|
* other reason, or already inverting at 900+). The selector is computed
|
||||||
|
* identically before and after the routing flip, and the skipped/oversize
|
||||||
|
* renders form the concurrent control for the difference-in-differences
|
||||||
|
* read — that is the entire point of the field.
|
||||||
*/
|
*/
|
||||||
deShortBand?: "applied" | "skipped_elements";
|
deShortBand?: "applied" | "skipped_elements";
|
||||||
/** DE parallel-router outcome: "routed" (fired, held) | "reverted" (fired, self-verify retry rolled back). */
|
/** DE parallel-router outcome: "routed" (fired, held) | "reverted" (fired, self-verify retry rolled back). */
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export interface DrawElementPerfInput {
|
|||||||
preInversionWorkers?: number;
|
preInversionWorkers?: number;
|
||||||
/** Rough compiled-composition element count — gate variable for the short-comp inversion band. */
|
/** Rough compiled-composition element count — gate variable for the short-comp inversion band. */
|
||||||
compositionElementCount?: number;
|
compositionElementCount?: number;
|
||||||
/** Short-comp band attribution: "applied" | "skipped_elements"; unset when the band was irrelevant. */
|
/** Short-comp band decision when the band was DECISIVE: "applied" (inverts once HF_DE_SHORT_BAND_ROUTE is on; counterfactual in the baseline release) | "skipped_elements" (element ceiling was the only blocker); unset when the band could not have affected this render. */
|
||||||
shortBand?: string;
|
shortBand?: string;
|
||||||
parallelRouter?: "routed" | "reverted";
|
parallelRouter?: "routed" | "reverted";
|
||||||
/** Auto-resolved worker count before the router pinned it to 3 (set only when the router fired). */
|
/** Auto-resolved worker count before the router pinned it to 3 (set only when the router fired). */
|
||||||
|
|||||||
@@ -1695,63 +1695,67 @@ describe("shouldPreferSingleWorkerDrawElement (DE priority inversion)", () => {
|
|||||||
expect(shouldPreferSingleWorkerDrawElement(eligible)).toBe(true);
|
expect(shouldPreferSingleWorkerDrawElement(eligible)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Short-comp band ────────────────────────────────────────────────────
|
// ── Short-comp band ──────────────────────────────────────────────────────────────────────
|
||||||
// The band lowers the effective floor from 900 to 250 for SMALL comps only.
|
// The call site evaluates the predicate TWICE — once at the 900 floor, once
|
||||||
// The predicate itself is unchanged — the call site picks the floor — so
|
// at the 250 band floor — and the band is DECISIVE only when the calls
|
||||||
// these pin the arithmetic the call site performs.
|
// disagree. These pin that arithmetic, including the property the design
|
||||||
|
// depends on: the band can only ADD inversions, never remove one.
|
||||||
//
|
//
|
||||||
// Measured basis (400f, single-DE vs parallel-screenshot-W4, 2 reps, ratio
|
// Measured basis (400f, single-DE vs parallel-screenshot-W4, ratio = ss4/de1
|
||||||
// = ss4/de1 so >1 means DE wins):
|
// so >1 means DE wins):
|
||||||
// 24 movers / 0 nodes -> 1.05 DE wins
|
// 24 movers / 0 nodes -> 1.05 | 24 movers / 7000 nodes -> 0.96
|
||||||
// 320 movers / 0 nodes -> 1.24 DE wins
|
// 320 movers / 0 nodes -> 1.24 | 24 movers / 20000 nodes -> 0.71
|
||||||
// 320 movers / 7000 nodes -> 1.09 DE wins
|
// 320 movers / 7000 nodes -> 1.09 | 24 movers / 40000 nodes -> 0.55
|
||||||
// 24 movers / 7000 nodes -> 0.96 DE LOSES
|
// Motion helps DE, DOM size punishes it; the element ceiling is calibrated
|
||||||
// 24 movers / 20000 nodes -> 0.71 DE loses badly
|
// at the lowest-motion case so every higher-motion comp is covered too.
|
||||||
// 24 movers / 40000 nodes -> 0.55 DE loses very badly
|
describe("short-comp band decisiveness (two-floor evaluation)", () => {
|
||||||
// Motion helps DE, DOM size punishes it; the ceiling is calibrated at the
|
const BAND_FLOOR = Math.min(900, 250);
|
||||||
// lowest-motion case so every higher-motion comp is covered too.
|
const atBase = (totalFrames: number, over?: Partial<typeof eligible>) =>
|
||||||
describe("short-comp band floor arithmetic", () => {
|
shouldPreferSingleWorkerDrawElement({ ...eligible, ...over, totalFrames, minFrames: 900 });
|
||||||
const shortBandFloor = (elementCount: number, maxElements = 2500): number =>
|
const atBand = (totalFrames: number, over?: Partial<typeof eligible>) =>
|
||||||
elementCount <= maxElements ? Math.min(900, 250) : 900;
|
shouldPreferSingleWorkerDrawElement({
|
||||||
|
...eligible,
|
||||||
|
...over,
|
||||||
|
totalFrames,
|
||||||
|
minFrames: BAND_FLOOR,
|
||||||
|
});
|
||||||
|
|
||||||
it("opens the 250-frame floor for a small comp", () => {
|
it("is decisive exactly in the 250-899 window for an otherwise-eligible render", () => {
|
||||||
expect(shortBandFloor(800)).toBe(250);
|
expect(atBand(400) && !atBase(400)).toBe(true);
|
||||||
|
expect(atBand(250) && !atBase(250)).toBe(true);
|
||||||
|
expect(atBand(899) && !atBase(899)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is NOT decisive below the band floor — nothing fires either way", () => {
|
||||||
|
expect(atBand(200)).toBe(false);
|
||||||
|
expect(atBase(200)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is NOT decisive at 900+ — the pre-existing floor already inverts, unchanged", () => {
|
||||||
|
expect(atBase(2380)).toBe(true);
|
||||||
|
expect(atBand(2380) && !atBase(2380)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is NOT decisive when the render is ineligible for any other reason — the attribution cohort must exclude renders the band cannot affect", () => {
|
||||||
|
for (const over of [
|
||||||
|
{ requestedWorkers: 3 as const },
|
||||||
|
{ useDrawElement: false },
|
||||||
|
{ deCompileGate: "css_effect:filter" },
|
||||||
|
{ forceScreenshot: true },
|
||||||
|
{ outputFormat: "webm" as const },
|
||||||
|
{ singleWorkerStreamingOk: false },
|
||||||
|
{ probeDeGated: true },
|
||||||
|
]) {
|
||||||
|
expect(atBand(400, over)).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("HF_DE_SHORT_MIN_FRAMES=0 disables via the predicate's own minFrames guard", () => {
|
||||||
expect(
|
expect(
|
||||||
shouldPreferSingleWorkerDrawElement({
|
shouldPreferSingleWorkerDrawElement({
|
||||||
...eligible,
|
...eligible,
|
||||||
totalFrames: 400,
|
totalFrames: 400,
|
||||||
minFrames: shortBandFloor(800),
|
minFrames: Math.min(900, 0),
|
||||||
}),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps the 900-frame floor for a large comp — the measured 1.8x regression case", () => {
|
|
||||||
expect(shortBandFloor(40000)).toBe(900);
|
|
||||||
expect(
|
|
||||||
shouldPreferSingleWorkerDrawElement({
|
|
||||||
...eligible,
|
|
||||||
totalFrames: 400,
|
|
||||||
minFrames: shortBandFloor(40000),
|
|
||||||
}),
|
|
||||||
).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("never RAISES the floor: a large comp at 900+ frames still inverts as it did before", () => {
|
|
||||||
expect(
|
|
||||||
shouldPreferSingleWorkerDrawElement({
|
|
||||||
...eligible,
|
|
||||||
totalFrames: 2380,
|
|
||||||
minFrames: shortBandFloor(40000),
|
|
||||||
}),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("leaves comps below the band floor alone", () => {
|
|
||||||
expect(
|
|
||||||
shouldPreferSingleWorkerDrawElement({
|
|
||||||
...eligible,
|
|
||||||
totalFrames: 200,
|
|
||||||
minFrames: shortBandFloor(800),
|
|
||||||
}),
|
}),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
});
|
});
|
||||||
@@ -1762,8 +1766,18 @@ describe("shouldPreferSingleWorkerDrawElement (DE priority inversion)", () => {
|
|||||||
expect(countElementTags("<div><span>a</span></div>")).toBe(2);
|
expect(countElementTags("<div><span>a</span></div>")).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("undercounts void elements — biases the count DOWN, so the ceiling must stay conservative", () => {
|
it("counts void elements — an image gallery must not read as a tiny comp", () => {
|
||||||
expect(countElementTags("<img><br><hr>")).toBe(0);
|
expect(countElementTags("<img><br><hr>")).toBe(3);
|
||||||
|
expect(countElementTags('<img src="a.png"><IMG SRC="b.png">')).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not false-positive on inline-script comparisons or void-prefixed words", () => {
|
||||||
|
// Only the </script> closer counts: "<breadth" and "<imgWidth" hit the
|
||||||
|
// br/img alternatives but fail the \b word boundary (next char is a
|
||||||
|
// word char), and bare "a < b" comparisons match nothing.
|
||||||
|
expect(countElementTags("<script>if (a < b && x <breadth && y <imgWidth) {}</script>")).toBe(
|
||||||
|
1,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("is stable on empty and malformed input rather than throwing", () => {
|
it("is stable on empty and malformed input rather than throwing", () => {
|
||||||
|
|||||||
@@ -1247,13 +1247,22 @@ export function envInt(name: string, fallback: number): number {
|
|||||||
* routing decision, and a full linkedom parse of the exact documents that
|
* routing decision, and a full linkedom parse of the exact documents that
|
||||||
* matter here — the 20k-40k node ones — is the most expensive case. Precision
|
* matter here — the 20k-40k node ones — is the most expensive case. Precision
|
||||||
* is not needed. It feeds a threshold whose measured crossover is ~3.9k and
|
* is not needed. It feeds a threshold whose measured crossover is ~3.9k and
|
||||||
* whose default sits at 2500, so counting closing tags is comfortably inside
|
* whose default sits at 2500, so tag counting is comfortably inside the
|
||||||
* the margin. Undercounts void elements (`<img>`, `<br>`) and self-closing
|
* margin. Closing tags plus HTML void elements (`<img>`, `<br>`, …) — voids
|
||||||
* SVG nodes, which biases the count DOWN — the direction that opens the band
|
* matter because they skew EXPENSIVE to paint (images), and counting only
|
||||||
* — so the ceiling is the thing to keep conservative.
|
* closers would read an image gallery as a tiny comp and open the band on
|
||||||
|
* exactly the content most likely to lose it. Opening tags are deliberately
|
||||||
|
* NOT counted: compiled comps embed inline scripts where `a < b` would
|
||||||
|
* false-positive. Self-closing SVG children still undercount; acceptable.
|
||||||
|
*
|
||||||
|
* These semantics are FROZEN while the short-band baseline is being read —
|
||||||
|
* the fleet distribution recorded by the baseline release must be measured
|
||||||
|
* by the same counter that later gates routing, or the baseline is invalid.
|
||||||
*/
|
*/
|
||||||
export function countElementTags(html: string): number {
|
export function countElementTags(html: string): number {
|
||||||
const matches = html.match(/<\/[a-zA-Z]/g);
|
const matches = html.match(
|
||||||
|
/<\/[a-zA-Z]|<(?:img|br|hr|input|source|track|area|base|col|embed|link|meta|param|wbr)\b/gi,
|
||||||
|
);
|
||||||
return matches === null ? 0 : matches.length;
|
return matches === null ? 0 : matches.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2462,20 +2471,21 @@ async function executeRenderPipeline(input: {
|
|||||||
deShortBandMinFrames > 0 &&
|
deShortBandMinFrames > 0 &&
|
||||||
deShortBandMaxElements > 0 &&
|
deShortBandMaxElements > 0 &&
|
||||||
compositionElementCount <= deShortBandMaxElements;
|
compositionElementCount <= deShortBandMaxElements;
|
||||||
const deEffectiveMinFrames = deShortBandOpen
|
// Baseline-first sequencing: this release EVALUATES the band on every
|
||||||
? Math.min(deSingleMinFrames, deShortBandMinFrames)
|
// render and emits the decision, but only routes on it when
|
||||||
: deSingleMinFrames;
|
// HF_DE_SHORT_BAND_ROUTE=true (flipped by default in a follow-up release).
|
||||||
// Does the band even matter for this render? Only when the frame count
|
// The point is a difference-in-differences read: the cohort selector
|
||||||
// falls in the newly-opened window — at or above the original floor the
|
// (`de_short_band`) is computed identically before and after the flip —
|
||||||
// inversion fires regardless, and below `deShortBandMinFrames` nothing
|
// "applied" is counterfactual in the baseline release and factual after —
|
||||||
// fires either way. Keeps the telemetry reason from claiming credit (or
|
// and the skipped/oversize renders in the same frame band form a
|
||||||
// blame) on renders the change could not have affected.
|
// concurrent control that absorbs secular drift (content mix, version-
|
||||||
const deShortBandApplies =
|
// correlated populations, hardware). A plain before/after cannot
|
||||||
totalFrames >= deShortBandMinFrames && totalFrames < deSingleMinFrames;
|
// attribute a fleet perf shift to this change; this can.
|
||||||
|
const deShortBandRoute = process.env.HF_DE_SHORT_BAND_ROUTE === "true";
|
||||||
// "Would ANY multi-worker resolution be inverted?" — if workers resolve
|
// "Would ANY multi-worker resolution be inverted?" — if workers resolve
|
||||||
// to 1 naturally the outcome is identical either way.
|
// to 1 naturally the outcome is identical either way.
|
||||||
const WOULD_RESOLVE_MULTI_WORKER = 2;
|
const WOULD_RESOLVE_MULTI_WORKER = 2;
|
||||||
const deInversionEligible = shouldPreferSingleWorkerDrawElement({
|
const deInversionArgs = {
|
||||||
workerCount: WOULD_RESOLVE_MULTI_WORKER,
|
workerCount: WOULD_RESOLVE_MULTI_WORKER,
|
||||||
requestedWorkers: job.config.workers,
|
requestedWorkers: job.config.workers,
|
||||||
useDrawElement: cfg.useDrawElement,
|
useDrawElement: cfg.useDrawElement,
|
||||||
@@ -2483,7 +2493,7 @@ async function executeRenderPipeline(input: {
|
|||||||
forceScreenshot: captureForceScreenshot,
|
forceScreenshot: captureForceScreenshot,
|
||||||
outputFormat,
|
outputFormat,
|
||||||
totalFrames,
|
totalFrames,
|
||||||
minFrames: deEffectiveMinFrames,
|
minFrames: deSingleMinFrames,
|
||||||
singleWorkerStreamingOk: shouldUseStreamingEncode(cfg, outputFormat, 1, job.duration),
|
singleWorkerStreamingOk: shouldUseStreamingEncode(cfg, outputFormat, 1, job.duration),
|
||||||
layeredOrEffectRoute: hasHdrContent || compiled.hasShaderTransitions,
|
layeredOrEffectRoute: hasHdrContent || compiled.hasShaderTransitions,
|
||||||
supersampling: deviceScaleFactor > 1,
|
supersampling: deviceScaleFactor > 1,
|
||||||
@@ -2495,7 +2505,29 @@ async function executeRenderPipeline(input: {
|
|||||||
process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true" ||
|
process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true" ||
|
||||||
// Verified parallel DE streaming (opt-in) wants its parallelism kept.
|
// Verified parallel DE streaming (opt-in) wants its parallelism kept.
|
||||||
process.env.HF_DE_PARALLEL_STREAM === "true",
|
process.env.HF_DE_PARALLEL_STREAM === "true",
|
||||||
|
};
|
||||||
|
const invertAtBaseFloor = shouldPreferSingleWorkerDrawElement(deInversionArgs);
|
||||||
|
// Same render, same eligibility, band floor instead of 900. Math.min so a
|
||||||
|
// user override of HF_DE_SINGLE_MIN_FRAMES below the band floor keeps
|
||||||
|
// winning; HF_DE_SHORT_MIN_FRAMES=0 disables via the predicate's own
|
||||||
|
// minFrames > 0 check.
|
||||||
|
const invertAtBandFloor = shouldPreferSingleWorkerDrawElement({
|
||||||
|
...deInversionArgs,
|
||||||
|
minFrames: Math.min(deSingleMinFrames, deShortBandMinFrames),
|
||||||
});
|
});
|
||||||
|
// The band is DECISIVE only when it alone flips the decision — every
|
||||||
|
// other eligibility condition already passed and only the floor differed.
|
||||||
|
// Renders the band could not have affected (ineligible for any other
|
||||||
|
// reason, or already inverting at 900+) stay unset, so the telemetry
|
||||||
|
// cohort contains exactly the renders whose routing this change decides.
|
||||||
|
const deShortBandDecisive = invertAtBandFloor && !invertAtBaseFloor;
|
||||||
|
const deShortBand = deShortBandDecisive
|
||||||
|
? deShortBandOpen
|
||||||
|
? ("applied" as const)
|
||||||
|
: ("skipped_elements" as const)
|
||||||
|
: undefined;
|
||||||
|
const deInversionEligible =
|
||||||
|
deShortBandRoute && deShortBand === "applied" ? invertAtBandFloor : invertAtBaseFloor;
|
||||||
// DE parallel-router eligibility — see shouldPreferParallelDrawElement.
|
// DE parallel-router eligibility — see shouldPreferParallelDrawElement.
|
||||||
// Default-off (HF_DE_PARALLEL_ROUTER); HF_DE_PARALLEL_MIN_FRAMES default
|
// Default-off (HF_DE_PARALLEL_ROUTER); HF_DE_PARALLEL_MIN_FRAMES default
|
||||||
// 700, re-calibrated 2026-07-27 from the original safe-high 2000. A
|
// 700, re-calibrated 2026-07-27 from the original safe-high 2000. A
|
||||||
@@ -2749,11 +2781,7 @@ async function executeRenderPipeline(input: {
|
|||||||
// render so the fleet element-count distribution is readable, and so a
|
// render so the fleet element-count distribution is readable, and so a
|
||||||
// perf shift can be split into "the new band did it" vs "unchanged".
|
// perf shift can be split into "the new band did it" vs "unchanged".
|
||||||
compositionElementCount,
|
compositionElementCount,
|
||||||
deShortBand: deShortBandApplies
|
deShortBand,
|
||||||
? deShortBandOpen
|
|
||||||
? "applied"
|
|
||||||
: "skipped_elements"
|
|
||||||
: undefined,
|
|
||||||
// Same rationale as the counters above: carried on live capture
|
// Same rationale as the counters above: carried on live capture
|
||||||
// observability, not only the success-path perfSummary, so a crash /
|
// observability, not only the success-path perfSummary, so a crash /
|
||||||
// OOM / timeout still reports which GPU backend it happened on. That
|
// OOM / timeout still reports which GPU backend it happened on. That
|
||||||
@@ -3601,11 +3629,7 @@ async function executeRenderPipeline(input: {
|
|||||||
workerInversion: deWorkerInversion,
|
workerInversion: deWorkerInversion,
|
||||||
preInversionWorkers: deWorkerInversion ? preRoutingWorkerCount : undefined,
|
preInversionWorkers: deWorkerInversion ? preRoutingWorkerCount : undefined,
|
||||||
compositionElementCount,
|
compositionElementCount,
|
||||||
shortBand: deShortBandApplies
|
shortBand: deShortBand,
|
||||||
? deShortBandOpen
|
|
||||||
? "applied"
|
|
||||||
: "skipped_elements"
|
|
||||||
: undefined,
|
|
||||||
parallelRouter: deParallelRouter,
|
parallelRouter: deParallelRouter,
|
||||||
preRouterWorkers: deParallelRouter ? preRoutingWorkerCount : undefined,
|
preRouterWorkers: deParallelRouter ? preRoutingWorkerCount : undefined,
|
||||||
selfVerifyFallback: deSelfVerifyFallback,
|
selfVerifyFallback: deSelfVerifyFallback,
|
||||||
|
|||||||
Reference in New Issue
Block a user