@plasius/schema 1.3.0 → 1.4.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 +235 -1
- package/THIRD_PARTY_NOTICES.md +75 -0
- package/dist/chunk-6FE4FLKF.js +254 -0
- package/dist/chunk-6FE4FLKF.js.map +1 -0
- package/dist/chunk-AG2NAPDC.js +117 -0
- package/dist/chunk-AG2NAPDC.js.map +1 -0
- package/dist/chunk-UQHPDQZX.js +2606 -0
- package/dist/chunk-UQHPDQZX.js.map +1 -0
- package/dist/feedback-diagnostics-BaniQAFS.d.cts +353 -0
- package/dist/feedback-diagnostics-CnsI0B9-.d.ts +353 -0
- package/dist/feedback-diagnostics-vocabulary.cjs +156 -0
- package/dist/feedback-diagnostics-vocabulary.cjs.map +1 -0
- package/dist/feedback-diagnostics-vocabulary.d.cts +70 -0
- package/dist/feedback-diagnostics-vocabulary.d.ts +70 -0
- package/dist/feedback-diagnostics-vocabulary.js +33 -0
- package/dist/feedback-diagnostics-vocabulary.js.map +1 -0
- package/dist/feedback-diagnostics.cjs +2696 -0
- package/dist/feedback-diagnostics.cjs.map +1 -0
- package/dist/feedback-diagnostics.d.cts +2 -0
- package/dist/feedback-diagnostics.d.ts +2 -0
- package/dist/feedback-diagnostics.js +39 -0
- package/dist/feedback-diagnostics.js.map +1 -0
- package/dist/feedback-unicode-profile.cjs +283 -0
- package/dist/feedback-unicode-profile.cjs.map +1 -0
- package/dist/feedback-unicode-profile.d.cts +28 -0
- package/dist/feedback-unicode-profile.d.ts +28 -0
- package/dist/feedback-unicode-profile.js +13 -0
- package/dist/feedback-unicode-profile.js.map +1 -0
- package/dist/index.cjs +2133 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +879 -286
- package/dist/index.d.ts +879 -286
- package/dist/index.js +1516 -2300
- package/dist/index.js.map +1 -1
- package/package.json +51 -13
- package/unicode/feedback-unicode-15.1.0-unassigned.json +13 -0
package/dist/index.js
CHANGED
|
@@ -1,511 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
required() {
|
|
56
|
-
this.isRequired = true;
|
|
57
|
-
return this;
|
|
58
|
-
}
|
|
59
|
-
optional() {
|
|
60
|
-
this.isRequired = false;
|
|
61
|
-
return this;
|
|
62
|
-
}
|
|
63
|
-
validator(fn) {
|
|
64
|
-
this._validator = fn;
|
|
65
|
-
return this;
|
|
66
|
-
}
|
|
67
|
-
description(desc) {
|
|
68
|
-
this._description = desc;
|
|
69
|
-
return this;
|
|
70
|
-
}
|
|
71
|
-
default(value) {
|
|
72
|
-
this._default = value;
|
|
73
|
-
this.isRequired = false;
|
|
74
|
-
return this;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Configure an upgrader used when validating older entities against a newer schema.
|
|
78
|
-
* The upgrader receives the current field value and version context, and should
|
|
79
|
-
* return { ok: true, value } with the upgraded value, or { ok: false, error }.
|
|
80
|
-
*/
|
|
81
|
-
upgrade(fn) {
|
|
82
|
-
this._upgrade = fn;
|
|
83
|
-
return this;
|
|
84
|
-
}
|
|
85
|
-
getDefault() {
|
|
86
|
-
const v = this._default;
|
|
87
|
-
return typeof v === "function" ? v() : v;
|
|
88
|
-
}
|
|
89
|
-
version(ver) {
|
|
90
|
-
this._version = ver;
|
|
91
|
-
return this;
|
|
92
|
-
}
|
|
93
|
-
/// PID informs the schema PII handling of the manner in
|
|
94
|
-
/// which to handle data relating to this field.
|
|
95
|
-
PID(pii) {
|
|
96
|
-
this._pii = pii;
|
|
97
|
-
return this;
|
|
98
|
-
}
|
|
99
|
-
min(min) {
|
|
100
|
-
if (this.type === "number") {
|
|
101
|
-
const prevValidator = this._validator;
|
|
102
|
-
this._validator = (value) => {
|
|
103
|
-
const valid = typeof value === "number" && value >= min;
|
|
104
|
-
return prevValidator ? mergeValidatorResult(prevValidator(value), valid) : valid;
|
|
105
|
-
};
|
|
106
|
-
} else if (this.type === "string") {
|
|
107
|
-
const prevValidator = this._validator;
|
|
108
|
-
this._validator = (value) => {
|
|
109
|
-
const valid = typeof value === "string" && value.length >= min;
|
|
110
|
-
return prevValidator ? mergeValidatorResult(prevValidator(value), valid) : valid;
|
|
111
|
-
};
|
|
112
|
-
} else if (this.type === "array") {
|
|
113
|
-
const prevValidator = this._validator;
|
|
114
|
-
this._validator = (value) => {
|
|
115
|
-
const valid = Array.isArray(value) && value.length >= min;
|
|
116
|
-
return prevValidator ? mergeValidatorResult(prevValidator(value), valid) : valid;
|
|
117
|
-
};
|
|
118
|
-
} else {
|
|
119
|
-
throw new Error(
|
|
120
|
-
"Min is only supported on number, string, or array fields."
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
return this;
|
|
124
|
-
}
|
|
125
|
-
max(max) {
|
|
126
|
-
if (this.type === "number") {
|
|
127
|
-
const prevValidator = this._validator;
|
|
128
|
-
this._validator = (value) => {
|
|
129
|
-
const valid = typeof value === "number" && value <= max;
|
|
130
|
-
return prevValidator ? mergeValidatorResult(prevValidator(value), valid) : valid;
|
|
131
|
-
};
|
|
132
|
-
} else if (this.type === "string") {
|
|
133
|
-
const prevValidator = this._validator;
|
|
134
|
-
this._validator = (value) => {
|
|
135
|
-
const valid = typeof value === "string" && value.length <= max;
|
|
136
|
-
return prevValidator ? mergeValidatorResult(prevValidator(value), valid) : valid;
|
|
137
|
-
};
|
|
138
|
-
} else if (this.type === "array") {
|
|
139
|
-
const prevValidator = this._validator;
|
|
140
|
-
this._validator = (value) => {
|
|
141
|
-
const valid = Array.isArray(value) && value.length <= max;
|
|
142
|
-
return prevValidator ? mergeValidatorResult(prevValidator(value), valid) : valid;
|
|
143
|
-
};
|
|
144
|
-
} else {
|
|
145
|
-
throw new Error(
|
|
146
|
-
"Max is only supported on number, string, or array fields."
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
return this;
|
|
150
|
-
}
|
|
151
|
-
pattern(regex) {
|
|
152
|
-
if (this.type !== "string") {
|
|
153
|
-
throw new Error("Pattern is only supported on string fields.");
|
|
154
|
-
}
|
|
155
|
-
const prevValidator = this._validator;
|
|
156
|
-
this._validator = (value) => {
|
|
157
|
-
const valid = typeof value === "string" && regex.test(value);
|
|
158
|
-
return prevValidator ? mergeValidatorResult(prevValidator(value), valid) : valid;
|
|
159
|
-
};
|
|
160
|
-
return this;
|
|
161
|
-
}
|
|
162
|
-
enum(values) {
|
|
163
|
-
if (this.type !== "string" && this.type !== "number" && !(this.type === "array" && (this.itemType?.type === "string" || this.itemType?.type === "number"))) {
|
|
164
|
-
throw new Error(
|
|
165
|
-
"Enums are only supported on string or number fields or arrays of strings or numbers."
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
this.enumValues = values;
|
|
169
|
-
return this;
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Create a shallow clone with a different external type parameter.
|
|
173
|
-
* Note: shape and itemType are passed by reference (shallow). If you need
|
|
174
|
-
* deep isolation of nested FieldBuilders, clone them explicitly.
|
|
175
|
-
*/
|
|
176
|
-
as() {
|
|
177
|
-
const clone = new _FieldBuilder(this.type, {
|
|
178
|
-
shape: this._shape,
|
|
179
|
-
itemType: this.itemType,
|
|
180
|
-
refType: this.refType
|
|
181
|
-
});
|
|
182
|
-
clone.enumValues = this.enumValues;
|
|
183
|
-
clone.isImmutable = this.isImmutable;
|
|
184
|
-
clone.isSystem = this.isSystem;
|
|
185
|
-
clone.isRequired = this.isRequired;
|
|
186
|
-
clone._description = this._description;
|
|
187
|
-
clone._version = this._version;
|
|
188
|
-
clone._pii = this._pii;
|
|
189
|
-
clone._exposure = this._exposure;
|
|
190
|
-
clone._validator = this._validator;
|
|
191
|
-
clone._default = this._default;
|
|
192
|
-
clone._upgrade = this._upgrade;
|
|
193
|
-
return clone;
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
// src/validation/email.RFC5322.ts
|
|
198
|
-
var validateEmail = (value) => {
|
|
199
|
-
if (typeof value !== "string") return false;
|
|
200
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
201
|
-
return emailRegex.test(value);
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
// src/validation/phone.E.164.ts
|
|
205
|
-
var validatePhone = (value) => {
|
|
206
|
-
if (typeof value !== "string") return false;
|
|
207
|
-
const phoneRegex = /^\+[1-9]\d{1,14}$/;
|
|
208
|
-
return phoneRegex.test(value);
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
// src/validation/url.WHATWG.ts
|
|
212
|
-
var validateUrl = (value) => {
|
|
213
|
-
if (typeof value !== "string") return false;
|
|
214
|
-
try {
|
|
215
|
-
const url = new URL(value);
|
|
216
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
217
|
-
return true;
|
|
218
|
-
} catch {
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
// src/validation/uuid.RFC4122.ts
|
|
224
|
-
var validateUUID = (value) => {
|
|
225
|
-
if (typeof value !== "string") return false;
|
|
226
|
-
const standard = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
227
|
-
return standard.test(value) || value.toLowerCase() === "00000000-0000-0000-0000-000000000000" || value.toLowerCase() === "ffffffff-ffff-ffff-ffff-ffffffffffff";
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
// src/validation/dateTime.ISO8601.ts
|
|
231
|
-
var validateDateTimeISO = (value, options) => {
|
|
232
|
-
const mode = options?.mode ?? "datetime";
|
|
233
|
-
if (typeof value !== "string") return false;
|
|
234
|
-
if (mode === "datetime") {
|
|
235
|
-
const isoDateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
236
|
-
if (!isoDateTimeRegex.test(value)) return false;
|
|
237
|
-
const date = new Date(value);
|
|
238
|
-
if (Number.isNaN(date.getTime())) return false;
|
|
239
|
-
return true;
|
|
240
|
-
}
|
|
241
|
-
if (mode === "date") {
|
|
242
|
-
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
|
243
|
-
if (!dateRegex.test(value)) return false;
|
|
244
|
-
const date = new Date(value);
|
|
245
|
-
if (isNaN(date.getTime())) return false;
|
|
246
|
-
const [year, month, day] = value.split("-").map(Number);
|
|
247
|
-
return date.getUTCFullYear() === year && date.getUTCMonth() + 1 === month && date.getUTCDate() === day;
|
|
248
|
-
}
|
|
249
|
-
if (mode === "time") {
|
|
250
|
-
const timeRegex = /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d(\.\d+)?Z?$/;
|
|
251
|
-
return timeRegex.test(value);
|
|
252
|
-
}
|
|
253
|
-
return false;
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
// src/validation/countryCode.ISO3166.ts
|
|
257
|
-
var isoCountryCodes = /* @__PURE__ */ new Set([
|
|
258
|
-
"AD",
|
|
259
|
-
"AE",
|
|
260
|
-
"AF",
|
|
261
|
-
"AG",
|
|
262
|
-
"AI",
|
|
263
|
-
"AL",
|
|
264
|
-
"AM",
|
|
265
|
-
"AO",
|
|
266
|
-
"AQ",
|
|
267
|
-
"AR",
|
|
268
|
-
"AS",
|
|
269
|
-
"AT",
|
|
270
|
-
"AU",
|
|
271
|
-
"AW",
|
|
272
|
-
"AX",
|
|
273
|
-
"AZ",
|
|
274
|
-
"BA",
|
|
275
|
-
"BB",
|
|
276
|
-
"BD",
|
|
277
|
-
"BE",
|
|
278
|
-
"BF",
|
|
279
|
-
"BG",
|
|
280
|
-
"BH",
|
|
281
|
-
"BI",
|
|
282
|
-
"BJ",
|
|
283
|
-
"BL",
|
|
284
|
-
"BM",
|
|
285
|
-
"BN",
|
|
286
|
-
"BO",
|
|
287
|
-
"BQ",
|
|
288
|
-
"BR",
|
|
289
|
-
"BS",
|
|
290
|
-
"BT",
|
|
291
|
-
"BV",
|
|
292
|
-
"BW",
|
|
293
|
-
"BY",
|
|
294
|
-
"BZ",
|
|
295
|
-
"CA",
|
|
296
|
-
"CC",
|
|
297
|
-
"CD",
|
|
298
|
-
"CF",
|
|
299
|
-
"CG",
|
|
300
|
-
"CH",
|
|
301
|
-
"CI",
|
|
302
|
-
"CK",
|
|
303
|
-
"CL",
|
|
304
|
-
"CM",
|
|
305
|
-
"CN",
|
|
306
|
-
"CO",
|
|
307
|
-
"CR",
|
|
308
|
-
"CU",
|
|
309
|
-
"CV",
|
|
310
|
-
"CW",
|
|
311
|
-
"CX",
|
|
312
|
-
"CY",
|
|
313
|
-
"CZ",
|
|
314
|
-
"DE",
|
|
315
|
-
"DJ",
|
|
316
|
-
"DK",
|
|
317
|
-
"DM",
|
|
318
|
-
"DO",
|
|
319
|
-
"DZ",
|
|
320
|
-
"EC",
|
|
321
|
-
"EE",
|
|
322
|
-
"EG",
|
|
323
|
-
"EH",
|
|
324
|
-
"ER",
|
|
325
|
-
"ES",
|
|
326
|
-
"ET",
|
|
327
|
-
"FI",
|
|
328
|
-
"FJ",
|
|
329
|
-
"FM",
|
|
330
|
-
"FO",
|
|
331
|
-
"FR",
|
|
332
|
-
"GA",
|
|
333
|
-
"GB",
|
|
334
|
-
"GD",
|
|
335
|
-
"GE",
|
|
336
|
-
"GF",
|
|
337
|
-
"GG",
|
|
338
|
-
"GH",
|
|
339
|
-
"GI",
|
|
340
|
-
"GL",
|
|
341
|
-
"GM",
|
|
342
|
-
"GN",
|
|
343
|
-
"GP",
|
|
344
|
-
"GQ",
|
|
345
|
-
"GR",
|
|
346
|
-
"GT",
|
|
347
|
-
"GU",
|
|
348
|
-
"GW",
|
|
349
|
-
"GY",
|
|
350
|
-
"HK",
|
|
351
|
-
"HM",
|
|
352
|
-
"HN",
|
|
353
|
-
"HR",
|
|
354
|
-
"HT",
|
|
355
|
-
"HU",
|
|
356
|
-
"ID",
|
|
357
|
-
"IE",
|
|
358
|
-
"IL",
|
|
359
|
-
"IM",
|
|
360
|
-
"IN",
|
|
361
|
-
"IO",
|
|
362
|
-
"IQ",
|
|
363
|
-
"IR",
|
|
364
|
-
"IS",
|
|
365
|
-
"IT",
|
|
366
|
-
"JE",
|
|
367
|
-
"JM",
|
|
368
|
-
"JO",
|
|
369
|
-
"JP",
|
|
370
|
-
"KE",
|
|
371
|
-
"KG",
|
|
372
|
-
"KH",
|
|
373
|
-
"KI",
|
|
374
|
-
"KM",
|
|
375
|
-
"KN",
|
|
376
|
-
"KP",
|
|
377
|
-
"KR",
|
|
378
|
-
"KW",
|
|
379
|
-
"KY",
|
|
380
|
-
"KZ",
|
|
381
|
-
"LA",
|
|
382
|
-
"LB",
|
|
383
|
-
"LC",
|
|
384
|
-
"LI",
|
|
385
|
-
"LK",
|
|
386
|
-
"LR",
|
|
387
|
-
"LS",
|
|
388
|
-
"LT",
|
|
389
|
-
"LU",
|
|
390
|
-
"LV",
|
|
391
|
-
"LY",
|
|
392
|
-
"MA",
|
|
393
|
-
"MC",
|
|
394
|
-
"MD",
|
|
395
|
-
"ME",
|
|
396
|
-
"MF",
|
|
397
|
-
"MG",
|
|
398
|
-
"MH",
|
|
399
|
-
"MK",
|
|
400
|
-
"ML",
|
|
401
|
-
"MM",
|
|
402
|
-
"MN",
|
|
403
|
-
"MO",
|
|
404
|
-
"MP",
|
|
405
|
-
"MQ",
|
|
406
|
-
"MR",
|
|
407
|
-
"MS",
|
|
408
|
-
"MT",
|
|
409
|
-
"MU",
|
|
410
|
-
"MV",
|
|
411
|
-
"MW",
|
|
412
|
-
"MX",
|
|
413
|
-
"MY",
|
|
414
|
-
"MZ",
|
|
415
|
-
"NA",
|
|
416
|
-
"NC",
|
|
417
|
-
"NE",
|
|
418
|
-
"NF",
|
|
419
|
-
"NG",
|
|
420
|
-
"NI",
|
|
421
|
-
"NL",
|
|
422
|
-
"NO",
|
|
423
|
-
"NP",
|
|
424
|
-
"NR",
|
|
425
|
-
"NU",
|
|
426
|
-
"NZ",
|
|
427
|
-
"OM",
|
|
428
|
-
"PA",
|
|
429
|
-
"PE",
|
|
430
|
-
"PF",
|
|
431
|
-
"PG",
|
|
432
|
-
"PH",
|
|
433
|
-
"PK",
|
|
434
|
-
"PL",
|
|
435
|
-
"PM",
|
|
436
|
-
"PN",
|
|
437
|
-
"PR",
|
|
438
|
-
"PS",
|
|
439
|
-
"PT",
|
|
440
|
-
"PW",
|
|
441
|
-
"PY",
|
|
442
|
-
"QA",
|
|
443
|
-
"RE",
|
|
444
|
-
"RO",
|
|
445
|
-
"RS",
|
|
446
|
-
"RU",
|
|
447
|
-
"RW",
|
|
448
|
-
"SA",
|
|
449
|
-
"SB",
|
|
450
|
-
"SC",
|
|
451
|
-
"SD",
|
|
452
|
-
"SE",
|
|
453
|
-
"SG",
|
|
454
|
-
"SH",
|
|
455
|
-
"SI",
|
|
456
|
-
"SJ",
|
|
457
|
-
"SK",
|
|
458
|
-
"SL",
|
|
459
|
-
"SM",
|
|
460
|
-
"SN",
|
|
461
|
-
"SO",
|
|
462
|
-
"SR",
|
|
463
|
-
"SS",
|
|
464
|
-
"ST",
|
|
465
|
-
"SV",
|
|
466
|
-
"SX",
|
|
467
|
-
"SY",
|
|
468
|
-
"SZ",
|
|
469
|
-
"TC",
|
|
470
|
-
"TD",
|
|
471
|
-
"TF",
|
|
472
|
-
"TG",
|
|
473
|
-
"TH",
|
|
474
|
-
"TJ",
|
|
475
|
-
"TK",
|
|
476
|
-
"TL",
|
|
477
|
-
"TM",
|
|
478
|
-
"TN",
|
|
479
|
-
"TO",
|
|
480
|
-
"TR",
|
|
481
|
-
"TT",
|
|
482
|
-
"TV",
|
|
483
|
-
"TZ",
|
|
484
|
-
"UA",
|
|
485
|
-
"UG",
|
|
486
|
-
"UM",
|
|
487
|
-
"US",
|
|
488
|
-
"UY",
|
|
489
|
-
"UZ",
|
|
490
|
-
"VA",
|
|
491
|
-
"VC",
|
|
492
|
-
"VE",
|
|
493
|
-
"VG",
|
|
494
|
-
"VI",
|
|
495
|
-
"VN",
|
|
496
|
-
"VU",
|
|
497
|
-
"WF",
|
|
498
|
-
"WS",
|
|
499
|
-
"YE",
|
|
500
|
-
"YT",
|
|
501
|
-
"ZA",
|
|
502
|
-
"ZM",
|
|
503
|
-
"ZW"
|
|
504
|
-
]);
|
|
505
|
-
var validateCountryCode = (value) => {
|
|
506
|
-
if (typeof value !== "string") return false;
|
|
507
|
-
return isoCountryCodes.has(value.toUpperCase());
|
|
508
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
FeedbackGameDiagnosticsSchema,
|
|
3
|
+
FieldBuilder,
|
|
4
|
+
IsoLanguageCode,
|
|
5
|
+
SCHEMA_IDENTITY_POLICIES,
|
|
6
|
+
SCHEMA_UNKNOWN_FIELDS_POLICIES,
|
|
7
|
+
createSchema,
|
|
8
|
+
feedbackGameDiagnosticsShape,
|
|
9
|
+
field,
|
|
10
|
+
getAllSchemas,
|
|
11
|
+
getSchemaForType,
|
|
12
|
+
isExtensionSingleton,
|
|
13
|
+
isExtensionSubtag,
|
|
14
|
+
isFeedbackGameDiagnosticsConsistent,
|
|
15
|
+
isIsoLanguageCode,
|
|
16
|
+
isPrivateUseSingleton,
|
|
17
|
+
isPrivateUseSubtag,
|
|
18
|
+
isRegionSubtag,
|
|
19
|
+
isScriptSubtag,
|
|
20
|
+
isVariantSubtag,
|
|
21
|
+
isoCountryCodes,
|
|
22
|
+
renderSchemaDescription,
|
|
23
|
+
validateCountryCode,
|
|
24
|
+
validateDateTimeISO,
|
|
25
|
+
validateEmail,
|
|
26
|
+
validateFeedbackGameDiagnostics,
|
|
27
|
+
validateLanguage,
|
|
28
|
+
validatePhone,
|
|
29
|
+
validateRichText,
|
|
30
|
+
validateSafeText,
|
|
31
|
+
validateSemVer,
|
|
32
|
+
validateUUID,
|
|
33
|
+
validateUrl
|
|
34
|
+
} from "./chunk-UQHPDQZX.js";
|
|
35
|
+
import {
|
|
36
|
+
FEEDBACK_BACKEND_BUCKETS,
|
|
37
|
+
FEEDBACK_CONTRACT_VERSION,
|
|
38
|
+
FEEDBACK_FRAME_RATE_BUCKETS,
|
|
39
|
+
FEEDBACK_FRAME_TIME_BUCKETS,
|
|
40
|
+
FEEDBACK_GAME_COUNTER_CODES,
|
|
41
|
+
FEEDBACK_GAME_COUNTER_MAX_COUNT,
|
|
42
|
+
FEEDBACK_GAME_COUNTER_MAX_ITEMS,
|
|
43
|
+
FEEDBACK_GAME_ERROR_CODES,
|
|
44
|
+
FEEDBACK_GAME_FEATURE_IDS,
|
|
45
|
+
FEEDBACK_GAME_PROVENANCE_CONTRACTS,
|
|
46
|
+
FEEDBACK_GAME_PROVENANCE_CONTRACT_IDS,
|
|
47
|
+
FEEDBACK_GAME_SURFACE_IDS,
|
|
48
|
+
FEEDBACK_RENDERER_BUCKETS,
|
|
49
|
+
FEEDBACK_VIEWPORT_BUCKETS
|
|
50
|
+
} from "./chunk-AG2NAPDC.js";
|
|
51
|
+
import {
|
|
52
|
+
FEEDBACK_UNICODE_PROFILE_ID,
|
|
53
|
+
containsFeedbackUnicodeProfileUnsupportedText
|
|
54
|
+
} from "./chunk-6FE4FLKF.js";
|
|
509
55
|
|
|
510
56
|
// src/validation/currencyCode.ISO4217.ts
|
|
511
57
|
var isoCurrencyCodes = /* @__PURE__ */ new Set([
|
|
@@ -695,63 +241,14 @@ var validateCurrencyCode = (value) => {
|
|
|
695
241
|
return isoCurrencyCodes.has(value.toUpperCase());
|
|
696
242
|
};
|
|
697
243
|
|
|
698
|
-
// src/validation/generalText.OWASP.ts
|
|
699
|
-
function validateSafeText(value) {
|
|
700
|
-
if (typeof value !== "string") return false;
|
|
701
|
-
const trimmed = value.trim();
|
|
702
|
-
if (trimmed.length === 0) return false;
|
|
703
|
-
for (let i = 0; i < trimmed.length; i++) {
|
|
704
|
-
const code = trimmed.codePointAt(i);
|
|
705
|
-
if (code !== void 0 && (code >= 0 && code <= 31 || code === 127)) {
|
|
706
|
-
return false;
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
if (/['"<>\\{}();]/.test(trimmed)) return false;
|
|
710
|
-
if (/(--|\b(SELECT|UPDATE|DELETE|INSERT|DROP|ALTER|EXEC|UNION|GRANT|REVOKE)\b|\/\*|\*\/|@@)/i.test(
|
|
711
|
-
trimmed
|
|
712
|
-
))
|
|
713
|
-
return false;
|
|
714
|
-
if (trimmed.includes("\0")) return false;
|
|
715
|
-
if (trimmed.length > 1024) return false;
|
|
716
|
-
return true;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
// src/validation/version.SEMVER2.0.0.ts
|
|
720
|
-
function validateSemVer(value) {
|
|
721
|
-
if (typeof value !== "string") return false;
|
|
722
|
-
return /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/.test(
|
|
723
|
-
value
|
|
724
|
-
);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
244
|
// src/validation/percentage.ISO80000-1.ts
|
|
728
245
|
function validatePercentage(value) {
|
|
729
246
|
if (typeof value !== "number") return false;
|
|
730
247
|
return value >= 0 && value <= 100;
|
|
731
248
|
}
|
|
732
249
|
|
|
733
|
-
// src/validation/richtext.OWASP.ts
|
|
734
|
-
function validateRichText(value) {
|
|
735
|
-
if (typeof value !== "string") return false;
|
|
736
|
-
const trimmed = value.trim();
|
|
737
|
-
if (trimmed.length === 0) return true;
|
|
738
|
-
if (/<(script|iframe|object|embed|style|link|meta|base|form|input|button|textarea|select)\b/i.test(
|
|
739
|
-
trimmed
|
|
740
|
-
)) {
|
|
741
|
-
return false;
|
|
742
|
-
}
|
|
743
|
-
if (/javascript:/i.test(trimmed)) {
|
|
744
|
-
return false;
|
|
745
|
-
}
|
|
746
|
-
if (/on\w+=["']?/i.test(trimmed)) {
|
|
747
|
-
return false;
|
|
748
|
-
}
|
|
749
|
-
if (trimmed.length > 1e4) return false;
|
|
750
|
-
return true;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
250
|
// src/validation/name.OWASP.ts
|
|
754
|
-
function
|
|
251
|
+
function validateNameLike(value, pattern) {
|
|
755
252
|
if (typeof value !== "string") return false;
|
|
756
253
|
const trimmed = value.trim();
|
|
757
254
|
if (trimmed.length === 0) return false;
|
|
@@ -760,10 +257,15 @@ function validateName(value) {
|
|
|
760
257
|
const cp = ch.codePointAt(0);
|
|
761
258
|
if (cp >= 0 && cp <= 31 || cp === 127) return false;
|
|
762
259
|
}
|
|
763
|
-
|
|
764
|
-
if (!namePattern.test(trimmed)) return false;
|
|
260
|
+
if (!pattern.test(trimmed)) return false;
|
|
765
261
|
return true;
|
|
766
262
|
}
|
|
263
|
+
function validateName(value) {
|
|
264
|
+
return validateNameLike(value, /^[\p{L}\p{M}'\-. ]+$/u);
|
|
265
|
+
}
|
|
266
|
+
function validateDisplayName(value) {
|
|
267
|
+
return validateNameLike(value, /^[\p{L}\p{M}\p{Nd}'\-. ]+$/u);
|
|
268
|
+
}
|
|
767
269
|
|
|
768
270
|
// src/validation/user.MS-GOOGLE-APPLE.ts
|
|
769
271
|
function validateUserId(value) {
|
|
@@ -780,1838 +282,1547 @@ function validateUserIdArray(value) {
|
|
|
780
282
|
return value.every(validateUserId);
|
|
781
283
|
}
|
|
782
284
|
|
|
783
|
-
// src/
|
|
784
|
-
var
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
IsoLanguageCode2["Assamese"] = "as";
|
|
795
|
-
IsoLanguageCode2["Avaric"] = "av";
|
|
796
|
-
IsoLanguageCode2["Aymara"] = "ay";
|
|
797
|
-
IsoLanguageCode2["Azerbaijani"] = "az";
|
|
798
|
-
IsoLanguageCode2["Bashkir"] = "ba";
|
|
799
|
-
IsoLanguageCode2["Bambara"] = "bm";
|
|
800
|
-
IsoLanguageCode2["Basque"] = "eu";
|
|
801
|
-
IsoLanguageCode2["Belarusian"] = "be";
|
|
802
|
-
IsoLanguageCode2["Bengali"] = "bn";
|
|
803
|
-
IsoLanguageCode2["Bislama"] = "bi";
|
|
804
|
-
IsoLanguageCode2["Bosnian"] = "bs";
|
|
805
|
-
IsoLanguageCode2["Breton"] = "br";
|
|
806
|
-
IsoLanguageCode2["Bulgarian"] = "bg";
|
|
807
|
-
IsoLanguageCode2["Burmese"] = "my";
|
|
808
|
-
IsoLanguageCode2["Catalan"] = "ca";
|
|
809
|
-
IsoLanguageCode2["Chamorro"] = "ch";
|
|
810
|
-
IsoLanguageCode2["Chechen"] = "ce";
|
|
811
|
-
IsoLanguageCode2["Chinese"] = "zh";
|
|
812
|
-
IsoLanguageCode2["ChurchSlavic"] = "cu";
|
|
813
|
-
IsoLanguageCode2["Chuvash"] = "cv";
|
|
814
|
-
IsoLanguageCode2["Cornish"] = "kw";
|
|
815
|
-
IsoLanguageCode2["Corsican"] = "co";
|
|
816
|
-
IsoLanguageCode2["Cree"] = "cr";
|
|
817
|
-
IsoLanguageCode2["Croatian"] = "hr";
|
|
818
|
-
IsoLanguageCode2["Czech"] = "cs";
|
|
819
|
-
IsoLanguageCode2["Danish"] = "da";
|
|
820
|
-
IsoLanguageCode2["Divehi"] = "dv";
|
|
821
|
-
IsoLanguageCode2["Dutch"] = "nl";
|
|
822
|
-
IsoLanguageCode2["Dzongkha"] = "dz";
|
|
823
|
-
IsoLanguageCode2["English"] = "en";
|
|
824
|
-
IsoLanguageCode2["Esperanto"] = "eo";
|
|
825
|
-
IsoLanguageCode2["Estonian"] = "et";
|
|
826
|
-
IsoLanguageCode2["Ewe"] = "ee";
|
|
827
|
-
IsoLanguageCode2["Faroese"] = "fo";
|
|
828
|
-
IsoLanguageCode2["Fijian"] = "fj";
|
|
829
|
-
IsoLanguageCode2["Finnish"] = "fi";
|
|
830
|
-
IsoLanguageCode2["French"] = "fr";
|
|
831
|
-
IsoLanguageCode2["WesternFrisian"] = "fy";
|
|
832
|
-
IsoLanguageCode2["Fulah"] = "ff";
|
|
833
|
-
IsoLanguageCode2["Gaelic"] = "gd";
|
|
834
|
-
IsoLanguageCode2["Galician"] = "gl";
|
|
835
|
-
IsoLanguageCode2["Ganda"] = "lg";
|
|
836
|
-
IsoLanguageCode2["Georgian"] = "ka";
|
|
837
|
-
IsoLanguageCode2["German"] = "de";
|
|
838
|
-
IsoLanguageCode2["Greek"] = "el";
|
|
839
|
-
IsoLanguageCode2["Kalaallisut"] = "kl";
|
|
840
|
-
IsoLanguageCode2["Guarani"] = "gn";
|
|
841
|
-
IsoLanguageCode2["Gujarati"] = "gu";
|
|
842
|
-
IsoLanguageCode2["Haitian"] = "ht";
|
|
843
|
-
IsoLanguageCode2["Hausa"] = "ha";
|
|
844
|
-
IsoLanguageCode2["Hebrew"] = "he";
|
|
845
|
-
IsoLanguageCode2["Herero"] = "hz";
|
|
846
|
-
IsoLanguageCode2["Hindi"] = "hi";
|
|
847
|
-
IsoLanguageCode2["HiriMotu"] = "ho";
|
|
848
|
-
IsoLanguageCode2["Hungarian"] = "hu";
|
|
849
|
-
IsoLanguageCode2["Icelandic"] = "is";
|
|
850
|
-
IsoLanguageCode2["Ido"] = "io";
|
|
851
|
-
IsoLanguageCode2["Igbo"] = "ig";
|
|
852
|
-
IsoLanguageCode2["Indonesian"] = "id";
|
|
853
|
-
IsoLanguageCode2["Interlingua"] = "ia";
|
|
854
|
-
IsoLanguageCode2["Interlingue"] = "ie";
|
|
855
|
-
IsoLanguageCode2["Inuktitut"] = "iu";
|
|
856
|
-
IsoLanguageCode2["Inupiaq"] = "ik";
|
|
857
|
-
IsoLanguageCode2["Irish"] = "ga";
|
|
858
|
-
IsoLanguageCode2["Italian"] = "it";
|
|
859
|
-
IsoLanguageCode2["Japanese"] = "ja";
|
|
860
|
-
IsoLanguageCode2["Javanese"] = "jv";
|
|
861
|
-
IsoLanguageCode2["Kannada"] = "kn";
|
|
862
|
-
IsoLanguageCode2["Kanuri"] = "kr";
|
|
863
|
-
IsoLanguageCode2["Kashmiri"] = "ks";
|
|
864
|
-
IsoLanguageCode2["Kazakh"] = "kk";
|
|
865
|
-
IsoLanguageCode2["CentralKhmer"] = "km";
|
|
866
|
-
IsoLanguageCode2["Kikuyu"] = "ki";
|
|
867
|
-
IsoLanguageCode2["Kinyarwanda"] = "rw";
|
|
868
|
-
IsoLanguageCode2["Kyrgyz"] = "ky";
|
|
869
|
-
IsoLanguageCode2["Komi"] = "kv";
|
|
870
|
-
IsoLanguageCode2["Kongo"] = "kg";
|
|
871
|
-
IsoLanguageCode2["Korean"] = "ko";
|
|
872
|
-
IsoLanguageCode2["Kuanyama"] = "kj";
|
|
873
|
-
IsoLanguageCode2["Kurdish"] = "ku";
|
|
874
|
-
IsoLanguageCode2["Lao"] = "lo";
|
|
875
|
-
IsoLanguageCode2["Latin"] = "la";
|
|
876
|
-
IsoLanguageCode2["Latvian"] = "lv";
|
|
877
|
-
IsoLanguageCode2["Limburgan"] = "li";
|
|
878
|
-
IsoLanguageCode2["Lingala"] = "ln";
|
|
879
|
-
IsoLanguageCode2["Lithuanian"] = "lt";
|
|
880
|
-
IsoLanguageCode2["LubaKatanga"] = "lu";
|
|
881
|
-
IsoLanguageCode2["Luxembourgish"] = "lb";
|
|
882
|
-
IsoLanguageCode2["Macedonian"] = "mk";
|
|
883
|
-
IsoLanguageCode2["Malagasy"] = "mg";
|
|
884
|
-
IsoLanguageCode2["Malay"] = "ms";
|
|
885
|
-
IsoLanguageCode2["Malayalam"] = "ml";
|
|
886
|
-
IsoLanguageCode2["Maltese"] = "mt";
|
|
887
|
-
IsoLanguageCode2["Manx"] = "gv";
|
|
888
|
-
IsoLanguageCode2["Maori"] = "mi";
|
|
889
|
-
IsoLanguageCode2["Marathi"] = "mr";
|
|
890
|
-
IsoLanguageCode2["Marshallese"] = "mh";
|
|
891
|
-
IsoLanguageCode2["Mongolian"] = "mn";
|
|
892
|
-
IsoLanguageCode2["Nauru"] = "na";
|
|
893
|
-
IsoLanguageCode2["Navajo"] = "nv";
|
|
894
|
-
IsoLanguageCode2["NorthNdebele"] = "nd";
|
|
895
|
-
IsoLanguageCode2["SouthNdebele"] = "nr";
|
|
896
|
-
IsoLanguageCode2["Ndonga"] = "ng";
|
|
897
|
-
IsoLanguageCode2["Nepali"] = "ne";
|
|
898
|
-
IsoLanguageCode2["Norwegian"] = "no";
|
|
899
|
-
IsoLanguageCode2["NorwegianBokmal"] = "nb";
|
|
900
|
-
IsoLanguageCode2["NorwegianNynorsk"] = "nn";
|
|
901
|
-
IsoLanguageCode2["SichuanYi"] = "ii";
|
|
902
|
-
IsoLanguageCode2["Occitan"] = "oc";
|
|
903
|
-
IsoLanguageCode2["Ojibwa"] = "oj";
|
|
904
|
-
IsoLanguageCode2["Oriya"] = "or";
|
|
905
|
-
IsoLanguageCode2["Oromo"] = "om";
|
|
906
|
-
IsoLanguageCode2["Ossetian"] = "os";
|
|
907
|
-
IsoLanguageCode2["Pali"] = "pi";
|
|
908
|
-
IsoLanguageCode2["Pashto"] = "ps";
|
|
909
|
-
IsoLanguageCode2["Persian"] = "fa";
|
|
910
|
-
IsoLanguageCode2["Polish"] = "pl";
|
|
911
|
-
IsoLanguageCode2["Portuguese"] = "pt";
|
|
912
|
-
IsoLanguageCode2["Punjabi"] = "pa";
|
|
913
|
-
IsoLanguageCode2["Quechua"] = "qu";
|
|
914
|
-
IsoLanguageCode2["Romansh"] = "rm";
|
|
915
|
-
IsoLanguageCode2["Romanian"] = "ro";
|
|
916
|
-
IsoLanguageCode2["Rundi"] = "rn";
|
|
917
|
-
IsoLanguageCode2["Russian"] = "ru";
|
|
918
|
-
IsoLanguageCode2["Samoan"] = "sm";
|
|
919
|
-
IsoLanguageCode2["Sango"] = "sg";
|
|
920
|
-
IsoLanguageCode2["Sanskrit"] = "sa";
|
|
921
|
-
IsoLanguageCode2["Sardinian"] = "sc";
|
|
922
|
-
IsoLanguageCode2["Serbian"] = "sr";
|
|
923
|
-
IsoLanguageCode2["Shona"] = "sn";
|
|
924
|
-
IsoLanguageCode2["Sindhi"] = "sd";
|
|
925
|
-
IsoLanguageCode2["Sinhala"] = "si";
|
|
926
|
-
IsoLanguageCode2["Slovak"] = "sk";
|
|
927
|
-
IsoLanguageCode2["Slovenian"] = "sl";
|
|
928
|
-
IsoLanguageCode2["Somali"] = "so";
|
|
929
|
-
IsoLanguageCode2["SouthernSotho"] = "st";
|
|
930
|
-
IsoLanguageCode2["Spanish"] = "es";
|
|
931
|
-
IsoLanguageCode2["Sundanese"] = "su";
|
|
932
|
-
IsoLanguageCode2["Swahili"] = "sw";
|
|
933
|
-
IsoLanguageCode2["Swati"] = "ss";
|
|
934
|
-
IsoLanguageCode2["Swedish"] = "sv";
|
|
935
|
-
IsoLanguageCode2["Tagalog"] = "tl";
|
|
936
|
-
IsoLanguageCode2["Tahitian"] = "ty";
|
|
937
|
-
IsoLanguageCode2["Tajik"] = "tg";
|
|
938
|
-
IsoLanguageCode2["Tamil"] = "ta";
|
|
939
|
-
IsoLanguageCode2["Tatar"] = "tt";
|
|
940
|
-
IsoLanguageCode2["Telugu"] = "te";
|
|
941
|
-
IsoLanguageCode2["Thai"] = "th";
|
|
942
|
-
IsoLanguageCode2["Tibetan"] = "bo";
|
|
943
|
-
IsoLanguageCode2["Tigrinya"] = "ti";
|
|
944
|
-
IsoLanguageCode2["Tonga"] = "to";
|
|
945
|
-
IsoLanguageCode2["Tsonga"] = "ts";
|
|
946
|
-
IsoLanguageCode2["Tswana"] = "tn";
|
|
947
|
-
IsoLanguageCode2["Turkish"] = "tr";
|
|
948
|
-
IsoLanguageCode2["Turkmen"] = "tk";
|
|
949
|
-
IsoLanguageCode2["Twi"] = "tw";
|
|
950
|
-
IsoLanguageCode2["Uighur"] = "ug";
|
|
951
|
-
IsoLanguageCode2["Ukrainian"] = "uk";
|
|
952
|
-
IsoLanguageCode2["Urdu"] = "ur";
|
|
953
|
-
IsoLanguageCode2["Uzbek"] = "uz";
|
|
954
|
-
IsoLanguageCode2["Venda"] = "ve";
|
|
955
|
-
IsoLanguageCode2["Vietnamese"] = "vi";
|
|
956
|
-
IsoLanguageCode2["Volapuk"] = "vo";
|
|
957
|
-
IsoLanguageCode2["Walloon"] = "wa";
|
|
958
|
-
IsoLanguageCode2["Welsh"] = "cy";
|
|
959
|
-
IsoLanguageCode2["Wolof"] = "wo";
|
|
960
|
-
IsoLanguageCode2["Xhosa"] = "xh";
|
|
961
|
-
IsoLanguageCode2["Yiddish"] = "yi";
|
|
962
|
-
IsoLanguageCode2["Yoruba"] = "yo";
|
|
963
|
-
IsoLanguageCode2["Zhuang"] = "za";
|
|
964
|
-
IsoLanguageCode2["Zulu"] = "zu";
|
|
965
|
-
return IsoLanguageCode2;
|
|
966
|
-
})(IsoLanguageCode || {});
|
|
967
|
-
var ISO_LANGUAGE_SET = new Set(
|
|
968
|
-
Object.values(IsoLanguageCode)
|
|
969
|
-
);
|
|
970
|
-
function isIsoLanguageCode(value) {
|
|
971
|
-
return typeof value === "string" && ISO_LANGUAGE_SET.has(value.toLowerCase());
|
|
972
|
-
}
|
|
973
|
-
function isRegionSubtag(value) {
|
|
974
|
-
return /^[A-Za-z]{2}$/.test(value) || /^\d{3}$/.test(value);
|
|
975
|
-
}
|
|
976
|
-
function isScriptSubtag(value) {
|
|
977
|
-
return /^[A-Za-z]{4}$/.test(value);
|
|
978
|
-
}
|
|
979
|
-
function isVariantSubtag(value) {
|
|
980
|
-
return /^([0-9][A-Za-z0-9]{3}|[A-Za-z0-9]{5,8})$/.test(value);
|
|
981
|
-
}
|
|
982
|
-
function isExtensionSingleton(value) {
|
|
983
|
-
return /^[0-9A-WY-Za-wy-z]$/.test(value);
|
|
285
|
+
// src/components.ts
|
|
286
|
+
var componentSchemaRegistry = /* @__PURE__ */ new Map();
|
|
287
|
+
function createComponentSchema(shape, name, version, tableName = "", schemaValidator = () => true) {
|
|
288
|
+
const schema = createSchema(shape, name, {
|
|
289
|
+
version,
|
|
290
|
+
piiEnforcement: "strict",
|
|
291
|
+
table: tableName,
|
|
292
|
+
schemaValidator
|
|
293
|
+
});
|
|
294
|
+
registerComponentSchema(name, schema);
|
|
295
|
+
return schema;
|
|
984
296
|
}
|
|
985
|
-
function
|
|
986
|
-
|
|
297
|
+
function registerComponentSchema(type, schema) {
|
|
298
|
+
componentSchemaRegistry.set(type, schema);
|
|
987
299
|
}
|
|
988
|
-
function
|
|
989
|
-
return
|
|
300
|
+
function getComponentSchema(type) {
|
|
301
|
+
return componentSchemaRegistry.get(type);
|
|
990
302
|
}
|
|
991
|
-
function
|
|
992
|
-
return
|
|
303
|
+
function getAllComponentSchemas() {
|
|
304
|
+
return Array.from(componentSchemaRegistry.entries());
|
|
993
305
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
306
|
+
|
|
307
|
+
// src/player-system-events.ts
|
|
308
|
+
var PLAYER_SYSTEM_EVENT_KINDS = [
|
|
309
|
+
"combat",
|
|
310
|
+
"social",
|
|
311
|
+
"mission",
|
|
312
|
+
"crafting",
|
|
313
|
+
"world"
|
|
314
|
+
];
|
|
315
|
+
var PLAYER_SYSTEM_ACHIEVEMENT_TIERS = [
|
|
316
|
+
"bronze",
|
|
317
|
+
"silver",
|
|
318
|
+
"gold"
|
|
319
|
+
];
|
|
320
|
+
var PLAYER_SYSTEM_ACHIEVEMENT_TRACK_STATUSES = [
|
|
321
|
+
"tracking",
|
|
322
|
+
"earned"
|
|
323
|
+
];
|
|
324
|
+
var playerIdField = () => field.string().min(1).max(128).PID({
|
|
325
|
+
classification: "low",
|
|
326
|
+
action: "none",
|
|
327
|
+
logHandling: "pseudonym",
|
|
328
|
+
purpose: "a stable player-scoped storage identifier"
|
|
329
|
+
}).description(
|
|
330
|
+
"Stable player identifier; the only personal identifier retained in event projections"
|
|
331
|
+
);
|
|
332
|
+
var eventIdField = () => field.string().min(1).max(128);
|
|
333
|
+
var eventKindField = () => field.string().enum(PLAYER_SYSTEM_EVENT_KINDS);
|
|
334
|
+
var eventTitleField = () => field.generalText().min(1).max(120).description("Player-visible event title");
|
|
335
|
+
var eventSummaryField = () => field.generalText().min(1).max(320).description("Player-visible event summary");
|
|
336
|
+
var observedEventFields = (defaultHighlighted = true) => ({
|
|
337
|
+
id: eventIdField(),
|
|
338
|
+
playerId: playerIdField(),
|
|
339
|
+
occurredAt: field.dateTimeISO(),
|
|
340
|
+
kind: eventKindField(),
|
|
341
|
+
title: eventTitleField(),
|
|
342
|
+
summary: eventSummaryField(),
|
|
343
|
+
highlighted: defaultHighlighted ? field.boolean().default(false) : field.boolean()
|
|
344
|
+
});
|
|
345
|
+
var eventHighlightFields = () => ({
|
|
346
|
+
id: eventIdField(),
|
|
347
|
+
title: eventTitleField(),
|
|
348
|
+
summary: eventSummaryField(),
|
|
349
|
+
occurredAt: field.dateTimeISO(),
|
|
350
|
+
kind: eventKindField()
|
|
351
|
+
});
|
|
352
|
+
var eventEntryFields = () => ({
|
|
353
|
+
id: eventIdField(),
|
|
354
|
+
title: eventTitleField(),
|
|
355
|
+
summary: eventSummaryField(),
|
|
356
|
+
occurredAt: field.dateTimeISO(),
|
|
357
|
+
kind: eventKindField(),
|
|
358
|
+
highlighted: field.boolean()
|
|
359
|
+
});
|
|
360
|
+
var achievementFields = () => ({
|
|
361
|
+
id: eventIdField(),
|
|
362
|
+
title: eventTitleField(),
|
|
363
|
+
tier: field.string().enum(PLAYER_SYSTEM_ACHIEVEMENT_TIERS),
|
|
364
|
+
progressLabel: eventSummaryField(),
|
|
365
|
+
earnedAt: field.dateTimeISO().optional()
|
|
366
|
+
});
|
|
367
|
+
var achievementTrackFields = () => ({
|
|
368
|
+
id: eventIdField(),
|
|
369
|
+
title: eventTitleField(),
|
|
370
|
+
current: field.number().min(0).max(1e3),
|
|
371
|
+
target: field.number().min(1).max(1e3),
|
|
372
|
+
status: field.string().enum(PLAYER_SYSTEM_ACHIEVEMENT_TRACK_STATUSES)
|
|
373
|
+
});
|
|
374
|
+
var PlayerSystemObservedEventBatchSchema = createSchema(
|
|
375
|
+
{
|
|
376
|
+
source: field.string().min(1).max(120),
|
|
377
|
+
emittedAt: field.dateTimeISO(),
|
|
378
|
+
events: field.array(field.object(observedEventFields()))
|
|
379
|
+
},
|
|
380
|
+
"player-system-observed-event-batch",
|
|
381
|
+
{
|
|
382
|
+
version: "1.0.0",
|
|
383
|
+
piiEnforcement: "strict"
|
|
1027
384
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
385
|
+
);
|
|
386
|
+
var PlayerSystemNormalizedEventBatchSchema = createSchema(
|
|
387
|
+
{
|
|
388
|
+
sourceBlobName: field.string().min(1).max(512),
|
|
389
|
+
processedAt: field.dateTimeISO(),
|
|
390
|
+
events: field.array(
|
|
391
|
+
field.object({
|
|
392
|
+
...observedEventFields(false),
|
|
393
|
+
deterministicKey: field.string().min(1).max(256)
|
|
394
|
+
})
|
|
395
|
+
)
|
|
396
|
+
},
|
|
397
|
+
"player-system-normalized-event-batch",
|
|
398
|
+
{
|
|
399
|
+
version: "1.0.0",
|
|
400
|
+
piiEnforcement: "strict"
|
|
1038
401
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
402
|
+
);
|
|
403
|
+
var PlayerSystemCuratedSnapshotSchema = createSchema(
|
|
404
|
+
{
|
|
405
|
+
playerId: playerIdField(),
|
|
406
|
+
projectedAt: field.dateTimeISO(),
|
|
407
|
+
lastObservedAt: field.dateTimeISO(),
|
|
408
|
+
highlights: field.array(field.object(eventHighlightFields())),
|
|
409
|
+
events: field.array(field.object(eventEntryFields())),
|
|
410
|
+
achievements: field.array(field.object(achievementFields())),
|
|
411
|
+
tracks: field.array(field.object(achievementTrackFields()))
|
|
412
|
+
},
|
|
413
|
+
"player-system-curated-snapshot",
|
|
414
|
+
{
|
|
415
|
+
version: "1.0.0",
|
|
416
|
+
piiEnforcement: "strict"
|
|
1045
417
|
}
|
|
1046
|
-
|
|
1047
|
-
}
|
|
1048
|
-
var GRANDFATHERED_TAGS = /* @__PURE__ */ new Set([
|
|
1049
|
-
"art-lojban",
|
|
1050
|
-
"cel-gaulish",
|
|
1051
|
-
"en-gb-oed",
|
|
1052
|
-
"i-ami",
|
|
1053
|
-
"i-bnn",
|
|
1054
|
-
"i-default",
|
|
1055
|
-
"i-enochian",
|
|
1056
|
-
"i-hak",
|
|
1057
|
-
"i-klingon",
|
|
1058
|
-
"i-lux",
|
|
1059
|
-
"i-mingo",
|
|
1060
|
-
"i-navajo",
|
|
1061
|
-
"i-pwn",
|
|
1062
|
-
"i-tao",
|
|
1063
|
-
"i-tay",
|
|
1064
|
-
"i-tsu",
|
|
1065
|
-
"no-bok",
|
|
1066
|
-
"no-nyn",
|
|
1067
|
-
"sgn-be-fr",
|
|
1068
|
-
"sgn-be-nl",
|
|
1069
|
-
"sgn-ch-de",
|
|
1070
|
-
"zh-guoyu",
|
|
1071
|
-
"zh-hakka",
|
|
1072
|
-
"zh-min",
|
|
1073
|
-
"zh-min-nan",
|
|
1074
|
-
"zh-xiang"
|
|
1075
|
-
]);
|
|
1076
|
-
|
|
1077
|
-
// src/field.ts
|
|
1078
|
-
var field = {
|
|
1079
|
-
string: () => new FieldBuilder("string"),
|
|
1080
|
-
number: () => new FieldBuilder("number"),
|
|
1081
|
-
boolean: () => new FieldBuilder("boolean"),
|
|
1082
|
-
object: (fields) => new FieldBuilder("object", { shape: fields }),
|
|
1083
|
-
array: (itemType) => new FieldBuilder("array", { itemType }),
|
|
1084
|
-
ref: (refType) => new FieldBuilder("ref", { refType }),
|
|
1085
|
-
email: () => new FieldBuilder("string").validator(validateEmail).PID({
|
|
1086
|
-
classification: "high",
|
|
1087
|
-
action: "encrypt",
|
|
1088
|
-
logHandling: "redact",
|
|
1089
|
-
purpose: "an email address"
|
|
1090
|
-
}).description("An email address"),
|
|
1091
|
-
phone: () => new FieldBuilder("string").validator(validatePhone).PID({
|
|
1092
|
-
classification: "high",
|
|
1093
|
-
action: "encrypt",
|
|
1094
|
-
logHandling: "redact",
|
|
1095
|
-
purpose: "a phone number"
|
|
1096
|
-
}).description("A phone number"),
|
|
1097
|
-
url: () => new FieldBuilder("string").validator(validateUrl).PID({
|
|
1098
|
-
classification: "low",
|
|
1099
|
-
action: "hash",
|
|
1100
|
-
logHandling: "pseudonym",
|
|
1101
|
-
purpose: "a URL"
|
|
1102
|
-
}).description("A URL"),
|
|
1103
|
-
uuid: () => new FieldBuilder("string").PID({
|
|
1104
|
-
classification: "low",
|
|
1105
|
-
action: "hash",
|
|
1106
|
-
logHandling: "pseudonym",
|
|
1107
|
-
purpose: "a UUID"
|
|
1108
|
-
}).validator(validateUUID).description("A UUID"),
|
|
1109
|
-
dateTimeISO: () => new FieldBuilder("string").PID({
|
|
1110
|
-
classification: "none",
|
|
1111
|
-
action: "none",
|
|
1112
|
-
logHandling: "plain",
|
|
1113
|
-
purpose: "a date string"
|
|
1114
|
-
}).validator(validateDateTimeISO).description("A date string in ISO 8601 format"),
|
|
1115
|
-
dateISO: () => new FieldBuilder("string").PID({
|
|
1116
|
-
classification: "none",
|
|
1117
|
-
action: "none",
|
|
1118
|
-
logHandling: "plain",
|
|
1119
|
-
purpose: "a date string"
|
|
1120
|
-
}).validator((s) => validateDateTimeISO(s, { mode: "date" })).description("A date string in ISO 8601 format (date only)"),
|
|
1121
|
-
timeISO: () => new FieldBuilder("string").PID({
|
|
1122
|
-
classification: "none",
|
|
1123
|
-
action: "none",
|
|
1124
|
-
logHandling: "plain",
|
|
1125
|
-
purpose: "a time string"
|
|
1126
|
-
}).validator((s) => validateDateTimeISO(s, { mode: "time" })).description("A time string in ISO 8601 format (time only)"),
|
|
1127
|
-
richText: () => new FieldBuilder("string").PID({
|
|
1128
|
-
classification: "low",
|
|
1129
|
-
action: "clear",
|
|
1130
|
-
logHandling: "omit",
|
|
1131
|
-
purpose: "rich text content"
|
|
1132
|
-
}).validator(validateRichText).description("Rich text content, may include basic HTML formatting"),
|
|
1133
|
-
generalText: () => new FieldBuilder("string").PID({
|
|
1134
|
-
classification: "none",
|
|
1135
|
-
action: "none",
|
|
1136
|
-
logHandling: "plain",
|
|
1137
|
-
purpose: "Plain text content"
|
|
1138
|
-
}).validator(validateSafeText).description("Standard text content, no HTML allowed"),
|
|
1139
|
-
latitude: () => new FieldBuilder("number").PID({
|
|
1140
|
-
classification: "low",
|
|
1141
|
-
action: "clear",
|
|
1142
|
-
logHandling: "omit",
|
|
1143
|
-
purpose: "Latitude in decimal degrees, WGS 84 (ISO 6709)"
|
|
1144
|
-
}).min(-90).max(90).description("Latitude in decimal degrees, WGS 84 (ISO 6709)"),
|
|
1145
|
-
longitude: () => new FieldBuilder("number").PID({
|
|
1146
|
-
classification: "low",
|
|
1147
|
-
action: "clear",
|
|
1148
|
-
logHandling: "omit",
|
|
1149
|
-
purpose: "Longitude in decimal degrees, WGS 84 (ISO 6709)"
|
|
1150
|
-
}).min(-180).max(180).description("Longitude in decimal degrees, WGS 84 (ISO 6709)"),
|
|
1151
|
-
version: () => new FieldBuilder("string").validator(validateSemVer).description("A semantic version string, e.g. '1.0.0'"),
|
|
1152
|
-
countryCode: () => new FieldBuilder("string").validator(validateCountryCode).description("An ISO 3166 country code, e.g. 'US', 'GB', 'FR'"),
|
|
1153
|
-
languageCode: () => new FieldBuilder("string").validator(validateLanguage).description(
|
|
1154
|
-
"An BCP 47 structured language code, primarily ISO 639-1 and optionally with ISO 3166-1 alpha-2 country code, e.g. 'en', 'en-US', 'fr', 'fr-FR'"
|
|
1155
|
-
)
|
|
1156
|
-
};
|
|
418
|
+
);
|
|
1157
419
|
|
|
1158
|
-
// src/
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
420
|
+
// src/feedback.ts
|
|
421
|
+
var FEEDBACK_UNICODE_NORMALIZATION_PROFILE = FEEDBACK_UNICODE_PROFILE_ID;
|
|
422
|
+
var FEEDBACK_BUG_COOLDOWN_SECONDS = Object.freeze([
|
|
423
|
+
5 * 60,
|
|
424
|
+
15 * 60,
|
|
425
|
+
60 * 60,
|
|
426
|
+
6 * 60 * 60,
|
|
427
|
+
24 * 60 * 60
|
|
428
|
+
]);
|
|
429
|
+
var FEEDBACK_REVIEW_COOLDOWN_SECONDS = 30 * 24 * 60 * 60;
|
|
430
|
+
var FEEDBACK_ISSUE_TYPES = [
|
|
431
|
+
"visual-layout",
|
|
432
|
+
"functionality",
|
|
433
|
+
"gameplay",
|
|
434
|
+
"performance-stability",
|
|
435
|
+
"accessibility",
|
|
436
|
+
"content-localisation",
|
|
437
|
+
"account-access",
|
|
438
|
+
"privacy-security",
|
|
439
|
+
"other"
|
|
440
|
+
];
|
|
441
|
+
var FEEDBACK_PERSISTABLE_ISSUE_TYPES = FEEDBACK_ISSUE_TYPES.filter(
|
|
442
|
+
(issueType) => issueType !== "privacy-security"
|
|
443
|
+
);
|
|
444
|
+
var FEEDBACK_SEVERITY_LEVELS = [
|
|
445
|
+
{ value: 1, id: "cosmetic", labelKey: "feedback.severity.cosmetic" },
|
|
446
|
+
{ value: 2, id: "minor", labelKey: "feedback.severity.minor" },
|
|
447
|
+
{ value: 3, id: "disruptive", labelKey: "feedback.severity.disruptive" },
|
|
448
|
+
{ value: 4, id: "blocking", labelKey: "feedback.severity.blocking" },
|
|
449
|
+
{ value: 5, id: "critical", labelKey: "feedback.severity.critical" }
|
|
450
|
+
];
|
|
451
|
+
var FEEDBACK_SATISFACTION_LEVELS = [
|
|
452
|
+
{ value: 1, id: "very-poor", labelKey: "feedback.satisfaction.very-poor" },
|
|
453
|
+
{ value: 2, id: "poor", labelKey: "feedback.satisfaction.poor" },
|
|
454
|
+
{ value: 3, id: "fair", labelKey: "feedback.satisfaction.fair" },
|
|
455
|
+
{ value: 4, id: "good", labelKey: "feedback.satisfaction.good" },
|
|
456
|
+
{ value: 5, id: "excellent", labelKey: "feedback.satisfaction.excellent" }
|
|
457
|
+
];
|
|
458
|
+
var FEEDBACK_SENTIMENT_BUCKETS = [
|
|
459
|
+
"very-negative",
|
|
460
|
+
"negative",
|
|
461
|
+
"mixed",
|
|
462
|
+
"neutral",
|
|
463
|
+
"positive",
|
|
464
|
+
"very-positive",
|
|
465
|
+
"not-detected"
|
|
466
|
+
];
|
|
467
|
+
var FEEDBACK_INTENT_IDS = [
|
|
468
|
+
"visual-layout",
|
|
469
|
+
"functionality",
|
|
470
|
+
"gameplay",
|
|
471
|
+
"performance-stability",
|
|
472
|
+
"accessibility",
|
|
473
|
+
"content-localisation",
|
|
474
|
+
"account-access",
|
|
475
|
+
"other",
|
|
476
|
+
"praise",
|
|
477
|
+
"suggestion",
|
|
478
|
+
"confusion",
|
|
479
|
+
"frustration"
|
|
480
|
+
];
|
|
481
|
+
var FEEDBACK_THEME_IDS = [
|
|
482
|
+
"navigation",
|
|
483
|
+
"visual-design",
|
|
484
|
+
"game-controls",
|
|
485
|
+
"rendering",
|
|
486
|
+
"responsiveness",
|
|
487
|
+
"stability",
|
|
488
|
+
"accessibility",
|
|
489
|
+
"localisation",
|
|
490
|
+
"account-access",
|
|
491
|
+
"overall-experience",
|
|
492
|
+
"other"
|
|
493
|
+
];
|
|
494
|
+
var FEEDBACK_SURFACE_IDS = [
|
|
495
|
+
"site.about",
|
|
496
|
+
"admin.analytics",
|
|
497
|
+
"admin.capabilities",
|
|
498
|
+
"admin.feature-flags",
|
|
499
|
+
"admin.feedback",
|
|
500
|
+
"admin.moderation",
|
|
501
|
+
"admin.users",
|
|
502
|
+
"site.chatbot",
|
|
503
|
+
"site.generator",
|
|
504
|
+
"site.gpu-demo",
|
|
505
|
+
"site.home",
|
|
506
|
+
"game.player-system",
|
|
507
|
+
"account.profile",
|
|
508
|
+
"site.video"
|
|
509
|
+
];
|
|
510
|
+
var FEEDBACK_SURFACE_DEFINITIONS = [
|
|
511
|
+
{
|
|
512
|
+
id: "site.about",
|
|
513
|
+
labelKey: "feedback.surface.about",
|
|
514
|
+
kind: "public",
|
|
515
|
+
gameDiagnosticsEligible: false
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
id: "admin.analytics",
|
|
519
|
+
labelKey: "feedback.surface.adminAnalytics",
|
|
520
|
+
kind: "admin",
|
|
521
|
+
gameDiagnosticsEligible: false
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
id: "admin.capabilities",
|
|
525
|
+
labelKey: "feedback.surface.adminCapabilities",
|
|
526
|
+
kind: "admin",
|
|
527
|
+
gameDiagnosticsEligible: false
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
id: "admin.feature-flags",
|
|
531
|
+
labelKey: "feedback.surface.adminFeatureFlags",
|
|
532
|
+
kind: "admin",
|
|
533
|
+
gameDiagnosticsEligible: false
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
id: "admin.feedback",
|
|
537
|
+
labelKey: "feedback.surface.adminFeedback",
|
|
538
|
+
kind: "admin",
|
|
539
|
+
gameDiagnosticsEligible: false
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
id: "admin.moderation",
|
|
543
|
+
labelKey: "feedback.surface.adminModeration",
|
|
544
|
+
kind: "admin",
|
|
545
|
+
gameDiagnosticsEligible: false
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
id: "admin.users",
|
|
549
|
+
labelKey: "feedback.surface.adminUsers",
|
|
550
|
+
kind: "admin",
|
|
551
|
+
gameDiagnosticsEligible: false
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
id: "site.chatbot",
|
|
555
|
+
labelKey: "feedback.surface.chatbot",
|
|
556
|
+
kind: "public",
|
|
557
|
+
gameDiagnosticsEligible: false
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
id: "site.generator",
|
|
561
|
+
labelKey: "feedback.surface.generator",
|
|
562
|
+
kind: "game",
|
|
563
|
+
gameDiagnosticsEligible: true
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
id: "site.gpu-demo",
|
|
567
|
+
labelKey: "feedback.surface.gpuDemo",
|
|
568
|
+
kind: "game",
|
|
569
|
+
gameDiagnosticsEligible: true
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
id: "site.home",
|
|
573
|
+
labelKey: "feedback.surface.home",
|
|
574
|
+
kind: "public",
|
|
575
|
+
gameDiagnosticsEligible: false
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
id: "game.player-system",
|
|
579
|
+
labelKey: "feedback.surface.playerSystem",
|
|
580
|
+
kind: "game",
|
|
581
|
+
gameDiagnosticsEligible: false
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
id: "account.profile",
|
|
585
|
+
labelKey: "feedback.surface.profile",
|
|
586
|
+
kind: "authenticated",
|
|
587
|
+
gameDiagnosticsEligible: false
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
id: "site.video",
|
|
591
|
+
labelKey: "feedback.surface.video",
|
|
592
|
+
kind: "public",
|
|
593
|
+
gameDiagnosticsEligible: false
|
|
1173
594
|
}
|
|
1174
|
-
|
|
595
|
+
];
|
|
596
|
+
var FEEDBACK_STRUCTURED_ONLY_REASON_CODES = [
|
|
597
|
+
"analysis-unavailable",
|
|
598
|
+
"invalid-envelope",
|
|
599
|
+
"invalid-sensitive-payload",
|
|
600
|
+
"language-not-enabled",
|
|
601
|
+
"request-too-large",
|
|
602
|
+
"length-required"
|
|
603
|
+
];
|
|
604
|
+
var FEEDBACK_PRIVACY_POLICY_VERSIONS = [
|
|
605
|
+
"feedback-privacy-en-v1"
|
|
606
|
+
];
|
|
607
|
+
var FEEDBACK_ANALYSIS_MODEL_VERSIONS = [
|
|
608
|
+
"feedback-en-rules-v1"
|
|
609
|
+
];
|
|
610
|
+
for (const vocabulary of [
|
|
611
|
+
FEEDBACK_BUG_COOLDOWN_SECONDS,
|
|
612
|
+
FEEDBACK_ISSUE_TYPES,
|
|
613
|
+
FEEDBACK_PERSISTABLE_ISSUE_TYPES,
|
|
614
|
+
FEEDBACK_SEVERITY_LEVELS,
|
|
615
|
+
FEEDBACK_SATISFACTION_LEVELS,
|
|
616
|
+
FEEDBACK_SENTIMENT_BUCKETS,
|
|
617
|
+
FEEDBACK_INTENT_IDS,
|
|
618
|
+
FEEDBACK_THEME_IDS,
|
|
619
|
+
FEEDBACK_SURFACE_IDS,
|
|
620
|
+
FEEDBACK_SURFACE_DEFINITIONS,
|
|
621
|
+
FEEDBACK_STRUCTURED_ONLY_REASON_CODES,
|
|
622
|
+
FEEDBACK_PRIVACY_POLICY_VERSIONS,
|
|
623
|
+
FEEDBACK_ANALYSIS_MODEL_VERSIONS
|
|
624
|
+
]) {
|
|
625
|
+
Object.freeze(vocabulary);
|
|
1175
626
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
return;
|
|
1183
|
-
}
|
|
1184
|
-
if (def._pii?.action === "hash") {
|
|
1185
|
-
if (!isMissing) out[key + "Hash"] = hashFn(value);
|
|
1186
|
-
return;
|
|
1187
|
-
}
|
|
1188
|
-
if (def._pii?.action === "clear") {
|
|
1189
|
-
if (!isMissing) out[key] = null;
|
|
1190
|
-
return;
|
|
1191
|
-
}
|
|
1192
|
-
if (def.type === "object" && def._shape) {
|
|
1193
|
-
const obj = {};
|
|
1194
|
-
for (const [childKey, childDef] of Object.entries(def._shape)) {
|
|
1195
|
-
build(childDef, value?.[childKey], childKey, obj, { parentKey: key });
|
|
1196
|
-
}
|
|
1197
|
-
out[key] = obj;
|
|
1198
|
-
return;
|
|
1199
|
-
}
|
|
1200
|
-
if (def.type === "array" && def.itemType && Array.isArray(value)) {
|
|
1201
|
-
out[key] = value.map((item) => {
|
|
1202
|
-
const obj = {};
|
|
1203
|
-
build(def.itemType, item, key, obj, {
|
|
1204
|
-
parentKey: key,
|
|
1205
|
-
isArrayItem: true
|
|
1206
|
-
});
|
|
1207
|
-
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
1208
|
-
return obj[key];
|
|
1209
|
-
}
|
|
1210
|
-
return Object.keys(obj).length > 0 ? obj : item;
|
|
1211
|
-
});
|
|
1212
|
-
return;
|
|
1213
|
-
}
|
|
1214
|
-
if (def.type === "ref") {
|
|
1215
|
-
const ref = { ...value };
|
|
1216
|
-
const refShape = def._shape;
|
|
1217
|
-
if (refShape && value) {
|
|
1218
|
-
for (const [childKey, childDef] of Object.entries(refShape)) {
|
|
1219
|
-
build(childDef, value[childKey], childKey, ref, { parentKey: key });
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
out[key] = ref;
|
|
1223
|
-
return;
|
|
1224
|
-
}
|
|
1225
|
-
out[key] = value;
|
|
1226
|
-
};
|
|
1227
|
-
const result = {};
|
|
1228
|
-
for (const key in shape) {
|
|
1229
|
-
build(shape[key], input?.[key], key, result);
|
|
1230
|
-
}
|
|
1231
|
-
return result;
|
|
627
|
+
for (const entry of [
|
|
628
|
+
...FEEDBACK_SEVERITY_LEVELS,
|
|
629
|
+
...FEEDBACK_SATISFACTION_LEVELS,
|
|
630
|
+
...FEEDBACK_SURFACE_DEFINITIONS
|
|
631
|
+
]) {
|
|
632
|
+
Object.freeze(entry);
|
|
1232
633
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
634
|
+
var SAFE_ID_PATTERN = /^[a-z0-9][a-z0-9._:-]*$/i;
|
|
635
|
+
var INGESTION_KEY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
636
|
+
var BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
637
|
+
var OPAQUE_UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
638
|
+
var DISALLOWED_NARRATIVE_SYNTAX_PATTERN = /<|>|(?:https?|ftp|mailto|javascript|data|blob|file):|\/\/|www\.|\]\s*\(/i;
|
|
639
|
+
var DISALLOWED_UNICODE_FORMAT_PATTERN = /\p{Cf}/u;
|
|
640
|
+
var MAX_NARRATIVE_CHARACTERS = 4e3;
|
|
641
|
+
var MAX_NARRATIVE_UTF16_CODE_UNITS = 8e3;
|
|
642
|
+
var MAX_COUNT = 1e9;
|
|
643
|
+
var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
644
|
+
var BUG_HOUR_WINDOW_PATTERN = /^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])T(?:[01]\d|2[0-3])$/;
|
|
645
|
+
var REVIEW_DAY_WINDOW_PATTERN = /^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$/;
|
|
646
|
+
var RECONCILIATION_WINDOW_PATTERN = /^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])T(?:[01]\d|2[0-3]):(?:[0-5]\d)$/;
|
|
647
|
+
var strictOptions = {
|
|
648
|
+
version: FEEDBACK_CONTRACT_VERSION,
|
|
649
|
+
piiEnforcement: "strict",
|
|
650
|
+
unknownFields: "reject",
|
|
651
|
+
identity: "exact"
|
|
652
|
+
};
|
|
653
|
+
var uuidField = () => field.string().validator((value) => OPAQUE_UUID_V4_PATTERN.test(value)).description("Opaque UUID generated for the feedback workflow");
|
|
654
|
+
var draftIdField = () => field.string().validator((value) => OPAQUE_UUID_V4_PATTERN.test(value)).description("Opaque identifier for one short-lived structured draft").PID({
|
|
655
|
+
classification: "low",
|
|
656
|
+
action: "none",
|
|
657
|
+
logHandling: "omit",
|
|
658
|
+
purpose: "short-lived feedback draft lookup that must never enter logs"
|
|
659
|
+
});
|
|
660
|
+
var safeIdField = (maximum = 128) => field.string().min(1).max(maximum).pattern(SAFE_ID_PATTERN);
|
|
661
|
+
var ingestionKeyIdField = () => field.string().min(1).max(80).pattern(INGESTION_KEY_ID_PATTERN);
|
|
662
|
+
var decodeCanonicalBase64Url = (value) => {
|
|
663
|
+
if (!BASE64URL_PATTERN.test(value) || value.length % 4 === 1) {
|
|
664
|
+
return void 0;
|
|
665
|
+
}
|
|
666
|
+
const finalValue = BASE64URL_ALPHABET.indexOf(value.at(-1) ?? "");
|
|
667
|
+
if (finalValue < 0 || value.length % 4 === 2 && (finalValue & 15) !== 0 || value.length % 4 === 3 && (finalValue & 3) !== 0) {
|
|
668
|
+
return void 0;
|
|
669
|
+
}
|
|
670
|
+
const output = [];
|
|
671
|
+
let accumulator = 0;
|
|
672
|
+
let bitCount = 0;
|
|
673
|
+
for (const character of value) {
|
|
674
|
+
const sextet = BASE64URL_ALPHABET.indexOf(character);
|
|
675
|
+
if (sextet < 0) {
|
|
676
|
+
return void 0;
|
|
1244
677
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
obj[childKey] = readValue(childDef, childKey, source);
|
|
1252
|
-
}
|
|
1253
|
-
return obj;
|
|
678
|
+
accumulator = accumulator << 6 | sextet;
|
|
679
|
+
bitCount += 6;
|
|
680
|
+
while (bitCount >= 8) {
|
|
681
|
+
bitCount -= 8;
|
|
682
|
+
output.push(accumulator >> bitCount & 255);
|
|
683
|
+
accumulator &= (1 << bitCount) - 1;
|
|
1254
684
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
685
|
+
}
|
|
686
|
+
return Uint8Array.from(output);
|
|
687
|
+
};
|
|
688
|
+
var isCanonicalRsaModulus = (value) => {
|
|
689
|
+
const bytes = decodeCanonicalBase64Url(value);
|
|
690
|
+
return bytes !== void 0 && (bytes.length === 256 || bytes.length === 384 || bytes.length === 512) && (bytes[0] ?? 0) >= 128 && ((bytes.at(-1) ?? 0) & 1) === 1;
|
|
691
|
+
};
|
|
692
|
+
var isCanonicalRsaExponent = (value) => {
|
|
693
|
+
const bytes = decodeCanonicalBase64Url(value);
|
|
694
|
+
if (bytes === void 0 || bytes.length < 1 || bytes.length > 4 || bytes[0] === 0) {
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
const exponent = bytes.reduce(
|
|
698
|
+
(total, byte) => total * 256 + byte,
|
|
699
|
+
0
|
|
700
|
+
);
|
|
701
|
+
return exponent >= 3 && exponent % 2 === 1;
|
|
702
|
+
};
|
|
703
|
+
var surfaceIdField = () => field.string().enum(FEEDBACK_SURFACE_IDS);
|
|
704
|
+
var countField = () => field.number().validator((value) => Number.isSafeInteger(value)).min(0).max(MAX_COUNT);
|
|
705
|
+
var positiveCountField = (maximum = MAX_COUNT) => field.number().validator((value) => Number.isSafeInteger(value)).min(1).max(maximum);
|
|
706
|
+
var redactionCountField = () => field.number().validator((value) => Number.isSafeInteger(value)).min(0).max(MAX_NARRATIVE_CHARACTERS);
|
|
707
|
+
var revisionField = () => field.number().validator((value) => Number.isSafeInteger(value)).min(0).max(1e6);
|
|
708
|
+
var ratingField = () => field.number().validator((value) => Number.isInteger(value)).min(1).max(5);
|
|
709
|
+
var narrativeTextField = () => field.string().validator(
|
|
710
|
+
(value) => typeof value === "string" && value.length >= 1 && value.length <= MAX_NARRATIVE_UTF16_CODE_UNITS && !containsFeedbackUnicodeProfileUnsupportedText(value) && value.normalize("NFKC") === value && [...value].length <= MAX_NARRATIVE_CHARACTERS && !DISALLOWED_NARRATIVE_SYNTAX_PATTERN.test(value) && !DISALLOWED_UNICODE_FORMAT_PATTERN.test(value) && ![...value].some((character) => {
|
|
711
|
+
const codePoint = character.codePointAt(0);
|
|
712
|
+
return codePoint !== void 0 && (codePoint < 32 || codePoint === 127);
|
|
713
|
+
})
|
|
714
|
+
).min(1).max(MAX_NARRATIVE_UTF16_CODE_UNITS).PID({
|
|
715
|
+
classification: "high",
|
|
716
|
+
action: "clear",
|
|
717
|
+
logHandling: "omit",
|
|
718
|
+
purpose: "transient feedback narrative that must be discarded after classification"
|
|
719
|
+
});
|
|
720
|
+
var transientCorrelationIdField = () => field.string().validator((value) => OPAQUE_UUID_V4_PATTERN.test(value)).PID({
|
|
721
|
+
classification: "low",
|
|
722
|
+
action: "clear",
|
|
723
|
+
logHandling: "omit",
|
|
724
|
+
purpose: "one-use transient feedback correlation"
|
|
725
|
+
});
|
|
726
|
+
var transientEncryptedField = (minimum, maximum, allowedLengths) => field.string().min(minimum).max(maximum).pattern(BASE64URL_PATTERN).validator(
|
|
727
|
+
(value) => {
|
|
728
|
+
const decoded = decodeCanonicalBase64Url(value);
|
|
729
|
+
return decoded !== void 0 && decoded.length > 0 && (allowedLengths === void 0 || allowedLengths.includes(value.length));
|
|
730
|
+
}
|
|
731
|
+
).PID({
|
|
732
|
+
classification: "high",
|
|
733
|
+
action: "clear",
|
|
734
|
+
logHandling: "omit",
|
|
735
|
+
purpose: "encrypted transient feedback narrative material"
|
|
736
|
+
});
|
|
737
|
+
var richTextNodeShape = () => ({
|
|
738
|
+
type: field.string().enum(["text"]),
|
|
739
|
+
text: narrativeTextField(),
|
|
740
|
+
marks: field.array(
|
|
741
|
+
field.string().enum(["bold", "italic", "underline"])
|
|
742
|
+
).max(3).optional()
|
|
743
|
+
});
|
|
744
|
+
var richTextBlockShape = () => ({
|
|
745
|
+
type: field.string().enum(["paragraph", "listItem"]),
|
|
746
|
+
depth: field.number().validator((value) => Number.isInteger(value)).min(0).max(4),
|
|
747
|
+
listType: field.string().enum(["bullet"]).optional(),
|
|
748
|
+
children: field.array(field.object(richTextNodeShape())).min(1).max(128)
|
|
749
|
+
});
|
|
750
|
+
var nestedIdentityShape = (type) => ({
|
|
751
|
+
type: field.string().enum([type]),
|
|
752
|
+
version: field.string().enum([FEEDBACK_CONTRACT_VERSION])
|
|
753
|
+
});
|
|
754
|
+
var envelopeShape = (includeIdentity = false) => ({
|
|
755
|
+
...includeIdentity ? nestedIdentityShape("feedback-encrypted-narrative-envelope") : {},
|
|
756
|
+
schemaVersion: field.string().enum(["1"]),
|
|
757
|
+
keyId: ingestionKeyIdField(),
|
|
758
|
+
algorithm: field.string().enum(["RSA-OAEP-256+A256GCM"]),
|
|
759
|
+
wrappedKey: transientEncryptedField(342, 683, [342, 512, 683]),
|
|
760
|
+
iv: transientEncryptedField(16, 16, [16]),
|
|
761
|
+
ciphertext: transientEncryptedField(2, 49152),
|
|
762
|
+
authenticationTag: transientEncryptedField(22, 22, [22])
|
|
763
|
+
});
|
|
764
|
+
var analysisProjectionShape = (includeIdentity = false) => ({
|
|
765
|
+
...includeIdentity ? nestedIdentityShape("feedback-derived-analysis") : {},
|
|
766
|
+
status: field.string().enum(["analyzed", "structured-only"]),
|
|
767
|
+
sentiment: field.string().enum(FEEDBACK_SENTIMENT_BUCKETS).optional(),
|
|
768
|
+
intentIds: field.array(field.string().enum(FEEDBACK_INTENT_IDS)).max(8),
|
|
769
|
+
themeIds: field.array(field.string().enum(FEEDBACK_THEME_IDS)).max(8),
|
|
770
|
+
confidence: field.string().enum(["low", "medium", "high"]).optional(),
|
|
771
|
+
policyVersion: field.string().enum(FEEDBACK_PRIVACY_POLICY_VERSIONS),
|
|
772
|
+
modelVersion: field.string().enum(FEEDBACK_ANALYSIS_MODEL_VERSIONS),
|
|
773
|
+
deterministicRedactionCount: redactionCountField(),
|
|
774
|
+
scannerRedactionCount: redactionCountField()
|
|
775
|
+
});
|
|
776
|
+
var analysisReceiptShape = () => ({
|
|
777
|
+
receiptId: transientCorrelationIdField(),
|
|
778
|
+
...analysisProjectionShape(),
|
|
779
|
+
analyzedAt: field.dateTimeISO()
|
|
780
|
+
});
|
|
781
|
+
var isAnalysisProjectionConsistent = (value) => {
|
|
782
|
+
const status = value.status;
|
|
783
|
+
const intentIds = value.intentIds;
|
|
784
|
+
const themeIds = value.themeIds;
|
|
785
|
+
if (!Array.isArray(intentIds) || !Array.isArray(themeIds)) {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
if (new Set(intentIds).size !== intentIds.length || new Set(themeIds).size !== themeIds.length) {
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
if (status === "structured-only") {
|
|
792
|
+
return value.sentiment === void 0 && value.confidence === void 0 && intentIds.length === 0 && themeIds.length === 0;
|
|
793
|
+
}
|
|
794
|
+
return status === "analyzed" && value.sentiment !== void 0 && value.confidence !== void 0;
|
|
795
|
+
};
|
|
796
|
+
var optionalDraftFields = () => ({
|
|
797
|
+
surfaceId: surfaceIdField().optional(),
|
|
798
|
+
issueType: field.string().enum(FEEDBACK_ISSUE_TYPES).optional(),
|
|
799
|
+
severity: ratingField().optional(),
|
|
800
|
+
satisfaction: ratingField().optional(),
|
|
801
|
+
analysisReceiptId: transientCorrelationIdField().optional()
|
|
802
|
+
});
|
|
803
|
+
var surfaceEntryShape = () => ({
|
|
804
|
+
id: surfaceIdField(),
|
|
805
|
+
labelKey: safeIdField(160),
|
|
806
|
+
kind: field.string().enum(["public", "authenticated", "admin", "game"]),
|
|
807
|
+
gameDiagnosticsEligible: field.boolean()
|
|
808
|
+
});
|
|
809
|
+
var areSurfacesConsistent = (surfaces) => {
|
|
810
|
+
if (!Array.isArray(surfaces)) {
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
const surfaceIds = surfaces.map(
|
|
814
|
+
(surface) => surface.id
|
|
815
|
+
);
|
|
816
|
+
return new Set(surfaceIds).size === surfaceIds.length && surfaces.every(
|
|
817
|
+
(surface) => {
|
|
818
|
+
const definition = FEEDBACK_SURFACE_DEFINITIONS.find(
|
|
819
|
+
({ id }) => id === surface.id
|
|
1260
820
|
);
|
|
821
|
+
return definition !== void 0 && surface.labelKey === definition.labelKey && surface.kind === definition.kind && surface.gameDiagnosticsEligible === definition.gameDiagnosticsEligible;
|
|
1261
822
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
return ref;
|
|
1271
|
-
}
|
|
1272
|
-
return container?.[key];
|
|
1273
|
-
};
|
|
1274
|
-
const result = {};
|
|
1275
|
-
for (const key in shape) {
|
|
1276
|
-
result[key] = readValue(shape[key], key, stored);
|
|
823
|
+
);
|
|
824
|
+
};
|
|
825
|
+
var isDraftShapeConsistent = (value) => {
|
|
826
|
+
const hasDirtyField = value.surfaceId !== void 0 || value.issueType !== void 0 || value.severity !== void 0 || value.satisfaction !== void 0 || value.analysisReceiptId !== void 0 || value.analysis !== void 0;
|
|
827
|
+
if (!hasDirtyField) {
|
|
828
|
+
return false;
|
|
1277
829
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
function sanitizeForLog(shape, data, pseudonymFn) {
|
|
1281
|
-
const visit = (def, value, ctx = {}) => {
|
|
1282
|
-
if (!def) return void 0;
|
|
1283
|
-
const handling = def._pii?.logHandling;
|
|
1284
|
-
if (handling === "omit") return void 0;
|
|
1285
|
-
if (handling === "redact") return "[REDACTED]";
|
|
1286
|
-
if (handling === "pseudonym") return pseudonymFn(value);
|
|
1287
|
-
if (def.type === "object" && def._shape) {
|
|
1288
|
-
const obj = {};
|
|
1289
|
-
const src = value ?? {};
|
|
1290
|
-
for (const [k, childDef] of Object.entries(def._shape)) {
|
|
1291
|
-
const child = visit(childDef, src[k], { parentKey: k });
|
|
1292
|
-
if (child !== void 0) obj[k] = child;
|
|
1293
|
-
}
|
|
1294
|
-
return obj;
|
|
1295
|
-
}
|
|
1296
|
-
if (def.type === "array" && def.itemType) {
|
|
1297
|
-
if (!Array.isArray(value)) return void 0;
|
|
1298
|
-
const arr = value.map((v) => visit(def.itemType, v, { parentKey: ctx.parentKey })).filter((v) => v !== void 0);
|
|
1299
|
-
return arr;
|
|
1300
|
-
}
|
|
1301
|
-
if (def.type === "ref") {
|
|
1302
|
-
const ref = value ? { type: value.type, id: value.id } : {};
|
|
1303
|
-
const refShape = def._shape;
|
|
1304
|
-
const src = value ?? {};
|
|
1305
|
-
if (refShape) {
|
|
1306
|
-
for (const [k, childDef] of Object.entries(refShape)) {
|
|
1307
|
-
const child = visit(childDef, src[k]);
|
|
1308
|
-
if (child !== void 0) ref[k] = child;
|
|
1309
|
-
}
|
|
1310
|
-
} else if (value) {
|
|
1311
|
-
ref.type = value.type;
|
|
1312
|
-
ref.id = value.id;
|
|
1313
|
-
}
|
|
1314
|
-
return ref;
|
|
1315
|
-
}
|
|
1316
|
-
return value;
|
|
1317
|
-
};
|
|
1318
|
-
const output = {};
|
|
1319
|
-
for (const key in shape) {
|
|
1320
|
-
const child = visit(shape[key], data?.[key]);
|
|
1321
|
-
if (child !== void 0) output[key] = child;
|
|
830
|
+
if (value.kind === "bug") {
|
|
831
|
+
return value.satisfaction === void 0;
|
|
1322
832
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
function getPiiAudit(shape) {
|
|
1326
|
-
const piiFields = [];
|
|
1327
|
-
for (const key in shape) {
|
|
1328
|
-
const def = shape[key];
|
|
1329
|
-
if (!def) continue;
|
|
1330
|
-
if (def._pii && def._pii.classification !== "none") {
|
|
1331
|
-
piiFields.push({
|
|
1332
|
-
field: key,
|
|
1333
|
-
classification: def._pii.classification,
|
|
1334
|
-
action: def._pii.action,
|
|
1335
|
-
logHandling: def._pii.logHandling,
|
|
1336
|
-
purpose: def._pii.purpose
|
|
1337
|
-
});
|
|
1338
|
-
}
|
|
833
|
+
if (value.kind === "review") {
|
|
834
|
+
return value.surfaceId === void 0 && value.issueType === void 0 && value.severity === void 0;
|
|
1339
835
|
}
|
|
1340
|
-
return
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
836
|
+
return false;
|
|
837
|
+
};
|
|
838
|
+
var isContextConsistent = (value) => {
|
|
839
|
+
if (!areSurfacesConsistent(value.surfaces)) {
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
if (value.selectedSurfaceId !== void 0 && !value.surfaces.some(
|
|
843
|
+
(surface) => surface.id === value.selectedSurfaceId
|
|
844
|
+
)) {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
const hasProjectedSurface = value.surfaces.length > 0;
|
|
848
|
+
const hasDiagnosticsSurface = value.surfaces.some(
|
|
849
|
+
(surface) => surface.gameDiagnosticsEligible === true
|
|
850
|
+
);
|
|
851
|
+
if (value.eligibility.bugReport && !hasProjectedSurface || value.eligibility.gameDiagnostics && (!value.eligibility.bugReport || !hasDiagnosticsSurface)) {
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
const generatedAt = Date.parse(String(value.generatedAt));
|
|
855
|
+
if (!Number.isFinite(generatedAt)) {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
const boundedFutureAvailabilityIsValid = (candidate, maximumSeconds) => {
|
|
859
|
+
if (candidate === void 0) {
|
|
860
|
+
return true;
|
|
1354
861
|
}
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1357
|
-
cur[last] = val;
|
|
862
|
+
const availableAt = Date.parse(String(candidate));
|
|
863
|
+
return Number.isFinite(availableAt) && availableAt > generatedAt && availableAt - generatedAt <= maximumSeconds * 1e3;
|
|
1358
864
|
};
|
|
1359
|
-
const
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
865
|
+
const maximumBugCooldownSeconds = FEEDBACK_BUG_COOLDOWN_SECONDS[FEEDBACK_BUG_COOLDOWN_SECONDS.length - 1] ?? 0;
|
|
866
|
+
if (!boundedFutureAvailabilityIsValid(
|
|
867
|
+
value.eligibility.bugAvailableAt,
|
|
868
|
+
maximumBugCooldownSeconds
|
|
869
|
+
) || !boundedFutureAvailabilityIsValid(
|
|
870
|
+
value.eligibility.reviewAvailableAt,
|
|
871
|
+
FEEDBACK_REVIEW_COOLDOWN_SECONDS
|
|
872
|
+
)) {
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
875
|
+
const bugAvailableAt = value.eligibility.bugAvailableAt === void 0 ? void 0 : Date.parse(String(value.eligibility.bugAvailableAt));
|
|
876
|
+
const bugRetryAfterSeconds = value.eligibility.bugRetryAfterSeconds;
|
|
877
|
+
const bugCooldownIsConsistent = bugAvailableAt === void 0 && bugRetryAfterSeconds === void 0 || bugAvailableAt !== void 0 && typeof bugRetryAfterSeconds === "number" && bugRetryAfterSeconds === Math.ceil((bugAvailableAt - generatedAt) / 1e3);
|
|
878
|
+
if (!bugCooldownIsConsistent || value.eligibility.bugReport && (value.eligibility.bugAvailableAt !== void 0 || value.eligibility.bugRetryAfterSeconds !== void 0) || value.eligibility.review && value.eligibility.reviewAvailableAt !== void 0 || value.eligibility.bugReport && !value.flags.bugReport || value.eligibility.review && !value.flags.review || value.eligibility.gameDiagnostics && !value.flags.gameDiagnostics) {
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
if (!value.flags.transientAnalysis) {
|
|
882
|
+
return value.ingestionKey === void 0;
|
|
883
|
+
}
|
|
884
|
+
if (value.ingestionKey === void 0) {
|
|
885
|
+
return false;
|
|
886
|
+
}
|
|
887
|
+
const expiresAt = Date.parse(String(value.ingestionKey.expiresAt));
|
|
888
|
+
return Number.isFinite(expiresAt) && expiresAt > generatedAt && expiresAt - generatedAt <= 10 * 60 * 1e3;
|
|
889
|
+
};
|
|
890
|
+
var distributionShape = (idField = () => safeIdField(128)) => ({
|
|
891
|
+
id: idField(),
|
|
892
|
+
count: countField()
|
|
893
|
+
});
|
|
894
|
+
var hasUniqueIds = (values) => Array.isArray(values) && new Set(
|
|
895
|
+
values.map(
|
|
896
|
+
(value) => typeof value === "object" && value !== null ? value.id : void 0
|
|
897
|
+
)
|
|
898
|
+
).size === values.length;
|
|
899
|
+
var distributionTotal = (values) => Array.isArray(values) ? values.reduce(
|
|
900
|
+
(total, value) => total + (typeof value === "object" && value !== null && typeof value.count === "number" ? value.count : 0),
|
|
901
|
+
0
|
|
902
|
+
) : 0;
|
|
903
|
+
var distributionCountsAtMost = (values, maximum) => Array.isArray(values) && values.every(
|
|
904
|
+
(value) => typeof value === "object" && value !== null && typeof value.count === "number" && value.count <= maximum
|
|
905
|
+
);
|
|
906
|
+
var advisoryShape = () => ({
|
|
907
|
+
code: field.string().enum(
|
|
908
|
+
[
|
|
909
|
+
"severity-five",
|
|
910
|
+
"critical-regression",
|
|
911
|
+
"satisfaction-drop",
|
|
912
|
+
"processor-stale"
|
|
913
|
+
]
|
|
914
|
+
),
|
|
915
|
+
level: field.string().enum(["advisory", "critical"]),
|
|
916
|
+
triggerCount: positiveCountField(),
|
|
917
|
+
recommendationIds: field.array(
|
|
918
|
+
field.string().enum(
|
|
919
|
+
[
|
|
920
|
+
"inspect-release",
|
|
921
|
+
"verify-renderer-health",
|
|
922
|
+
"review-top-intents",
|
|
923
|
+
"verify-processor-health"
|
|
924
|
+
]
|
|
925
|
+
)
|
|
926
|
+
).min(1).max(8)
|
|
927
|
+
});
|
|
928
|
+
var orderedWindow = (value, durationMilliseconds) => {
|
|
929
|
+
const start = Date.parse(String(value.windowStart));
|
|
930
|
+
const end = Date.parse(String(value.windowEnd));
|
|
931
|
+
const generatedAt = Date.parse(String(value.generatedAt));
|
|
932
|
+
return Number.isFinite(start) && Number.isFinite(end) && Number.isFinite(generatedAt) && new Date(start).toISOString() === value.windowStart && new Date(end).toISOString() === value.windowEnd && start % durationMilliseconds === 0 && end - start === durationMilliseconds && generatedAt >= end;
|
|
933
|
+
};
|
|
934
|
+
var processorWindow = (processor, windowKey) => {
|
|
935
|
+
let iso;
|
|
936
|
+
let expectedLength;
|
|
937
|
+
let duration;
|
|
938
|
+
if (processor === "bug-hourly") {
|
|
939
|
+
if (!BUG_HOUR_WINDOW_PATTERN.test(windowKey)) {
|
|
940
|
+
return void 0;
|
|
1389
941
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
return;
|
|
942
|
+
iso = `${windowKey}:00:00.000Z`;
|
|
943
|
+
expectedLength = 13;
|
|
944
|
+
duration = 60 * 60 * 1e3;
|
|
945
|
+
} else if (processor === "review-daily") {
|
|
946
|
+
if (!REVIEW_DAY_WINDOW_PATTERN.test(windowKey)) {
|
|
947
|
+
return void 0;
|
|
1396
948
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
}
|
|
1404
|
-
return;
|
|
949
|
+
iso = `${windowKey}T00:00:00.000Z`;
|
|
950
|
+
expectedLength = 10;
|
|
951
|
+
duration = 24 * 60 * 60 * 1e3;
|
|
952
|
+
} else {
|
|
953
|
+
if (!RECONCILIATION_WINDOW_PATTERN.test(windowKey)) {
|
|
954
|
+
return void 0;
|
|
1405
955
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
if (refShape && ref) {
|
|
1410
|
-
for (const [k, childDef] of Object.entries(refShape)) {
|
|
1411
|
-
visit(childDef, ref, [...path, k], k);
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
956
|
+
const minute = Number(windowKey.slice(-2));
|
|
957
|
+
if (minute % 5 !== 0) {
|
|
958
|
+
return void 0;
|
|
1414
959
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
960
|
+
iso = `${windowKey}:00.000Z`;
|
|
961
|
+
expectedLength = 16;
|
|
962
|
+
duration = 5 * 60 * 1e3;
|
|
1418
963
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
// src/schema.ts
|
|
1423
|
-
var globalSchemaRegistry = /* @__PURE__ */ new Map();
|
|
1424
|
-
function deepClone(value) {
|
|
1425
|
-
const seen = /* @__PURE__ */ new WeakMap();
|
|
1426
|
-
const cloneAny = (val) => {
|
|
1427
|
-
if (val === null || typeof val !== "object") return val;
|
|
1428
|
-
if (seen.has(val)) throw new TypeError("Cannot clone circular structures");
|
|
1429
|
-
if (val instanceof Date) return new Date(val.getTime());
|
|
1430
|
-
if (Array.isArray(val)) {
|
|
1431
|
-
const arr = [];
|
|
1432
|
-
seen.set(val, arr);
|
|
1433
|
-
val.forEach((item) => arr.push(cloneAny(item)));
|
|
1434
|
-
return arr;
|
|
1435
|
-
}
|
|
1436
|
-
const out = {};
|
|
1437
|
-
seen.set(val, out);
|
|
1438
|
-
for (const [k, v] of Object.entries(val)) out[k] = cloneAny(v);
|
|
1439
|
-
return out;
|
|
1440
|
-
};
|
|
1441
|
-
return cloneAny(value);
|
|
1442
|
-
}
|
|
1443
|
-
function shouldIncludeField(def, options) {
|
|
1444
|
-
return options.includeInternal === true || (def?._exposure ?? "public") !== "internal";
|
|
1445
|
-
}
|
|
1446
|
-
function serializeFieldValue(def, value, options) {
|
|
1447
|
-
if (value === void 0) {
|
|
964
|
+
const start = Date.parse(iso);
|
|
965
|
+
if (!Number.isFinite(start) || new Date(start).toISOString().slice(0, expectedLength) !== windowKey) {
|
|
1448
966
|
return void 0;
|
|
1449
967
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
case "ref": {
|
|
1459
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1460
|
-
return deepClone(value);
|
|
1461
|
-
}
|
|
1462
|
-
const shape = getShape(def);
|
|
1463
|
-
if (!shape || Object.keys(shape).length === 0) {
|
|
1464
|
-
return deepClone(value);
|
|
1465
|
-
}
|
|
1466
|
-
const serializedRef = {};
|
|
1467
|
-
if (typeof value.type === "string") {
|
|
1468
|
-
serializedRef.type = value.type;
|
|
1469
|
-
}
|
|
1470
|
-
if (typeof value.id === "string") {
|
|
1471
|
-
serializedRef.id = value.id;
|
|
1472
|
-
}
|
|
1473
|
-
for (const [childKey, childDef] of Object.entries(shape)) {
|
|
1474
|
-
if (!shouldIncludeField(childDef, options)) {
|
|
1475
|
-
continue;
|
|
1476
|
-
}
|
|
1477
|
-
if (!Object.prototype.hasOwnProperty.call(value, childKey)) {
|
|
1478
|
-
continue;
|
|
1479
|
-
}
|
|
1480
|
-
const serializedChild = serializeFieldValue(
|
|
1481
|
-
childDef,
|
|
1482
|
-
value[childKey],
|
|
1483
|
-
options
|
|
1484
|
-
);
|
|
1485
|
-
if (serializedChild !== void 0) {
|
|
1486
|
-
serializedRef[childKey] = serializedChild;
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
return serializedRef;
|
|
1490
|
-
}
|
|
1491
|
-
case "object": {
|
|
1492
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1493
|
-
return deepClone(value);
|
|
1494
|
-
}
|
|
1495
|
-
const shape = getShape(def);
|
|
1496
|
-
if (!shape || Object.keys(shape).length === 0) {
|
|
1497
|
-
return deepClone(value);
|
|
1498
|
-
}
|
|
1499
|
-
const serializedObject = {};
|
|
1500
|
-
for (const [childKey, childDef] of Object.entries(shape)) {
|
|
1501
|
-
if (!shouldIncludeField(childDef, options)) {
|
|
1502
|
-
continue;
|
|
1503
|
-
}
|
|
1504
|
-
if (!Object.prototype.hasOwnProperty.call(value, childKey)) {
|
|
1505
|
-
continue;
|
|
1506
|
-
}
|
|
1507
|
-
const serializedChild = serializeFieldValue(
|
|
1508
|
-
childDef,
|
|
1509
|
-
value[childKey],
|
|
1510
|
-
options
|
|
1511
|
-
);
|
|
1512
|
-
if (serializedChild !== void 0) {
|
|
1513
|
-
serializedObject[childKey] = serializedChild;
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
return serializedObject;
|
|
1517
|
-
}
|
|
1518
|
-
case "array": {
|
|
1519
|
-
if (!Array.isArray(value)) {
|
|
1520
|
-
return deepClone(value);
|
|
1521
|
-
}
|
|
1522
|
-
if (!def.itemType) {
|
|
1523
|
-
return deepClone(value);
|
|
1524
|
-
}
|
|
1525
|
-
return value.map((item) => serializeFieldValue(def.itemType, item, options));
|
|
1526
|
-
}
|
|
1527
|
-
default:
|
|
1528
|
-
return deepClone(value);
|
|
968
|
+
return { start, duration };
|
|
969
|
+
};
|
|
970
|
+
var isProcessorArtifactConsistent = (value, artifact) => {
|
|
971
|
+
const processor = value.processor;
|
|
972
|
+
const windowKey = String(value.windowKey);
|
|
973
|
+
const window = processorWindow(processor, windowKey);
|
|
974
|
+
if (window === void 0) {
|
|
975
|
+
return false;
|
|
1529
976
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
if (ai > bi) return 1;
|
|
1538
|
-
if (ai < bi) return -1;
|
|
977
|
+
const completedAt = Date.parse(
|
|
978
|
+
String(
|
|
979
|
+
artifact === "checkpoint" ? value.completedAt : value.generatedAt
|
|
980
|
+
)
|
|
981
|
+
);
|
|
982
|
+
if (!Number.isFinite(completedAt) || completedAt < window.start + window.duration) {
|
|
983
|
+
return false;
|
|
1539
984
|
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
function applySchemaUpgrade(spec, input, ctx) {
|
|
1543
|
-
if (typeof spec === "function") {
|
|
1544
|
-
return spec(input, ctx);
|
|
985
|
+
if (artifact === "checkpoint" && value.checkpointId !== `checkpoint:${processor}:${windowKey}`) {
|
|
986
|
+
return false;
|
|
1545
987
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
988
|
+
return true;
|
|
989
|
+
};
|
|
990
|
+
var roundRate = (value) => Math.round((value + Number.EPSILON) * 1e6) / 1e6;
|
|
991
|
+
var rateMatches = (actual, expected) => typeof actual === "number" && actual === roundRate(expected);
|
|
992
|
+
var distributionCountFor = (values, id) => {
|
|
993
|
+
if (!Array.isArray(values)) {
|
|
994
|
+
return 0;
|
|
995
|
+
}
|
|
996
|
+
const entry = values.find(
|
|
997
|
+
(value) => typeof value === "object" && value !== null && value.id === id
|
|
998
|
+
);
|
|
999
|
+
return typeof entry === "object" && entry !== null && typeof entry.count === "number" ? entry.count : 0;
|
|
1000
|
+
};
|
|
1001
|
+
var advisoryCodesAreConsistent = (advisories, expected) => {
|
|
1002
|
+
if (!Array.isArray(advisories)) {
|
|
1003
|
+
return false;
|
|
1562
1004
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1005
|
+
const expectedCodes = Object.entries(expected).filter(
|
|
1006
|
+
(entry) => entry[1] !== void 0
|
|
1007
|
+
).map(([code]) => code);
|
|
1008
|
+
const codes = advisories.map(
|
|
1009
|
+
(advisory) => advisory?.code
|
|
1010
|
+
);
|
|
1011
|
+
return new Set(codes).size === codes.length && codes.every(
|
|
1012
|
+
(code) => typeof code === "string" && expectedCodes.includes(code)
|
|
1013
|
+
) && codes.length === expectedCodes.length && expectedCodes.every((code) => codes.includes(code)) && advisories.every((advisory) => {
|
|
1014
|
+
const value = advisory;
|
|
1015
|
+
const code = typeof value.code === "string" ? value.code : void 0;
|
|
1016
|
+
const expectation = code === void 0 ? void 0 : expected[code];
|
|
1017
|
+
if (expectation === void 0 || value.level !== expectation.level || value.triggerCount !== expectation.triggerCount || !Array.isArray(value.recommendationIds) || value.recommendationIds.length !== expectation.recommendationIds.length) {
|
|
1018
|
+
return false;
|
|
1574
1019
|
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1020
|
+
return value.recommendationIds.every(
|
|
1021
|
+
(recommendation, index) => recommendation === expectation.recommendationIds[index]
|
|
1022
|
+
);
|
|
1023
|
+
});
|
|
1024
|
+
};
|
|
1025
|
+
var FeedbackRichTextAstSchema = createSchema(
|
|
1026
|
+
{
|
|
1027
|
+
schemaVersion: field.string().enum(["1"]),
|
|
1028
|
+
children: field.array(field.object(richTextBlockShape())).min(1).max(128)
|
|
1029
|
+
},
|
|
1030
|
+
"doc",
|
|
1031
|
+
{
|
|
1032
|
+
...strictOptions,
|
|
1033
|
+
schemaValidator: (value) => {
|
|
1034
|
+
const blocks = Array.isArray(value.children) ? value.children : [];
|
|
1035
|
+
const blocksAreConsistent = blocks.every(
|
|
1036
|
+
(block) => {
|
|
1037
|
+
const listShapeIsValid = block.type === "paragraph" && block.listType === void 0 || block.type === "listItem" && block.listType === "bullet";
|
|
1038
|
+
const marksAreUnique = Array.isArray(block.children) && block.children.every(
|
|
1039
|
+
(child) => child.marks === void 0 || Array.isArray(child.marks) && new Set(child.marks).size === child.marks.length
|
|
1040
|
+
);
|
|
1041
|
+
return listShapeIsValid && marksAreUnique;
|
|
1042
|
+
}
|
|
1043
|
+
);
|
|
1044
|
+
const characterCount = blocks.reduce(
|
|
1045
|
+
(blockTotal, block) => blockTotal + (Array.isArray(block.children) ? block.children.reduce(
|
|
1046
|
+
(nodeTotal, node) => nodeTotal + (typeof node.text === "string" ? [...node.text].length : 0),
|
|
1047
|
+
0
|
|
1048
|
+
) : 0),
|
|
1049
|
+
0
|
|
1050
|
+
);
|
|
1051
|
+
const extractedCharacterCount = characterCount + Math.max(0, blocks.length - 1);
|
|
1052
|
+
const nodeCount = blocks.reduce(
|
|
1053
|
+
(total, block) => total + (Array.isArray(block.children) ? block.children.length : 0),
|
|
1054
|
+
0
|
|
1055
|
+
);
|
|
1056
|
+
return value.type === "doc" && value.schemaVersion === "1" && blocksAreConsistent && nodeCount <= 256 && extractedCharacterCount > 0 && extractedCharacterCount <= MAX_NARRATIVE_CHARACTERS;
|
|
1578
1057
|
}
|
|
1579
1058
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1059
|
+
);
|
|
1060
|
+
var FeedbackEncryptedNarrativeEnvelopeSchema = createSchema(
|
|
1061
|
+
envelopeShape(),
|
|
1062
|
+
"feedback-encrypted-narrative-envelope",
|
|
1063
|
+
strictOptions
|
|
1064
|
+
);
|
|
1065
|
+
var FeedbackTransientAnalysisRequestSchema = createSchema(
|
|
1066
|
+
{
|
|
1067
|
+
schemaVersion: field.string().enum(["1"]),
|
|
1068
|
+
requestId: transientCorrelationIdField(),
|
|
1069
|
+
purpose: field.string().enum(["bug", "review"]),
|
|
1070
|
+
surfaceId: surfaceIdField().optional(),
|
|
1071
|
+
deterministicRedactionCount: redactionCountField(),
|
|
1072
|
+
envelope: field.object(envelopeShape(true))
|
|
1073
|
+
},
|
|
1074
|
+
"feedback-transient-analysis-request",
|
|
1075
|
+
{
|
|
1076
|
+
...strictOptions,
|
|
1077
|
+
schemaValidator: (value, context) => {
|
|
1078
|
+
const surfaceWasProvided = context?.wasProvided("surfaceId") ?? Object.prototype.hasOwnProperty.call(value, "surfaceId");
|
|
1079
|
+
return value.purpose === "bug" && value.surfaceId !== void 0 || value.purpose === "review" && !surfaceWasProvided;
|
|
1594
1080
|
}
|
|
1595
1081
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
}
|
|
1601
|
-
function getValidator(def) {
|
|
1602
|
-
return def?._validator ?? void 0;
|
|
1603
|
-
}
|
|
1604
|
-
function getShape(def) {
|
|
1605
|
-
return def?._shape ?? def?.shape ?? void 0;
|
|
1606
|
-
}
|
|
1607
|
-
function normalizeValidationIssue(path, issue, defaultCode) {
|
|
1608
|
-
if (typeof issue === "string") {
|
|
1082
|
+
);
|
|
1083
|
+
var validateFeedbackTransientAnalysisRequest = (input) => {
|
|
1084
|
+
const result = FeedbackTransientAnalysisRequestSchema.validate(input);
|
|
1085
|
+
if (!result.valid || result.value === void 0) {
|
|
1609
1086
|
return {
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1087
|
+
valid: false,
|
|
1088
|
+
errors: result.errors ?? ["Transient analysis validation failed."],
|
|
1089
|
+
...result.issues === void 0 ? {} : { issues: result.issues }
|
|
1613
1090
|
};
|
|
1614
1091
|
}
|
|
1615
1092
|
return {
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
message: issue.message
|
|
1093
|
+
valid: true,
|
|
1094
|
+
value: result.value
|
|
1619
1095
|
};
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1096
|
+
};
|
|
1097
|
+
var FeedbackAnalysisReceiptSchema = createSchema(
|
|
1098
|
+
analysisReceiptShape(),
|
|
1099
|
+
"feedback-analysis-receipt",
|
|
1100
|
+
{
|
|
1101
|
+
...strictOptions,
|
|
1102
|
+
schemaValidator: (value) => value.status === "analyzed" && isAnalysisProjectionConsistent(value)
|
|
1624
1103
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
];
|
|
1104
|
+
);
|
|
1105
|
+
var FeedbackDerivedAnalysisSchema = createSchema(
|
|
1106
|
+
analysisProjectionShape(),
|
|
1107
|
+
"feedback-derived-analysis",
|
|
1108
|
+
{
|
|
1109
|
+
...strictOptions,
|
|
1110
|
+
schemaValidator: isAnalysisProjectionConsistent
|
|
1633
1111
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1112
|
+
);
|
|
1113
|
+
var FeedbackSurfaceCatalogSchema = createSchema(
|
|
1114
|
+
{
|
|
1115
|
+
generatedAt: field.dateTimeISO(),
|
|
1116
|
+
catalogVersion: safeIdField(64),
|
|
1117
|
+
surfaces: field.array(field.object(surfaceEntryShape())).max(256)
|
|
1118
|
+
},
|
|
1119
|
+
"feedback-surface-catalog",
|
|
1120
|
+
{
|
|
1121
|
+
...strictOptions,
|
|
1122
|
+
schemaValidator: (value) => areSurfacesConsistent(value.surfaces)
|
|
1636
1123
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1124
|
+
);
|
|
1125
|
+
var FeedbackContextSchema = createSchema(
|
|
1126
|
+
{
|
|
1127
|
+
generatedAt: field.dateTimeISO(),
|
|
1128
|
+
catalogVersion: safeIdField(64),
|
|
1129
|
+
surfaces: field.array(field.object(surfaceEntryShape())).max(256),
|
|
1130
|
+
selectedSurfaceId: surfaceIdField().optional(),
|
|
1131
|
+
flags: field.object({
|
|
1132
|
+
bugReport: field.boolean(),
|
|
1133
|
+
review: field.boolean(),
|
|
1134
|
+
transientAnalysis: field.boolean(),
|
|
1135
|
+
reporting: field.boolean(),
|
|
1136
|
+
admin: field.boolean(),
|
|
1137
|
+
mcp: field.boolean(),
|
|
1138
|
+
publicHonesty: field.boolean(),
|
|
1139
|
+
gameDiagnostics: field.boolean(),
|
|
1140
|
+
anonymous: field.boolean()
|
|
1141
|
+
}),
|
|
1142
|
+
eligibility: field.object({
|
|
1143
|
+
bugReport: field.boolean(),
|
|
1144
|
+
review: field.boolean(),
|
|
1145
|
+
gameDiagnostics: field.boolean(),
|
|
1146
|
+
bugAvailableAt: field.dateTimeISO().optional(),
|
|
1147
|
+
reviewAvailableAt: field.dateTimeISO().optional(),
|
|
1148
|
+
bugRetryAfterSeconds: positiveCountField().optional()
|
|
1149
|
+
}),
|
|
1150
|
+
ingestionKey: field.object({
|
|
1151
|
+
keyId: ingestionKeyIdField(),
|
|
1152
|
+
algorithm: field.string().enum(["RSA-OAEP-256"]),
|
|
1153
|
+
publicJwk: field.object({
|
|
1154
|
+
kty: field.string().enum(["RSA"]),
|
|
1155
|
+
alg: field.string().enum(["RSA-OAEP-256"]),
|
|
1156
|
+
use: field.string().enum(["enc"]),
|
|
1157
|
+
key_ops: field.array(field.string().enum(["wrapKey"])).min(1).max(1),
|
|
1158
|
+
n: field.string().min(342).max(683).pattern(BASE64URL_PATTERN).validator(isCanonicalRsaModulus),
|
|
1159
|
+
e: field.string().min(2).max(6).pattern(BASE64URL_PATTERN).validator(isCanonicalRsaExponent),
|
|
1160
|
+
ext: field.boolean().validator((value) => value === true)
|
|
1161
|
+
}),
|
|
1162
|
+
expiresAt: field.dateTimeISO()
|
|
1163
|
+
}).optional()
|
|
1164
|
+
},
|
|
1165
|
+
"feedback-context",
|
|
1166
|
+
{
|
|
1167
|
+
...strictOptions,
|
|
1168
|
+
schemaValidator: isContextConsistent
|
|
1646
1169
|
}
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1170
|
+
);
|
|
1171
|
+
var FeedbackDraftUpsertRequestSchema = createSchema(
|
|
1172
|
+
{
|
|
1173
|
+
draftId: draftIdField(),
|
|
1174
|
+
kind: field.string().enum(["bug", "review"]),
|
|
1175
|
+
revision: revisionField(),
|
|
1176
|
+
...optionalDraftFields()
|
|
1177
|
+
},
|
|
1178
|
+
"feedback-draft-upsert-request",
|
|
1179
|
+
{
|
|
1180
|
+
...strictOptions,
|
|
1181
|
+
schemaValidator: isDraftShapeConsistent
|
|
1654
1182
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1183
|
+
);
|
|
1184
|
+
var FeedbackDraftPacketSchema = createSchema(
|
|
1185
|
+
{
|
|
1186
|
+
draftId: draftIdField(),
|
|
1187
|
+
kind: field.string().enum(["bug", "review"]),
|
|
1188
|
+
revision: revisionField(),
|
|
1189
|
+
surfaceId: surfaceIdField().optional(),
|
|
1190
|
+
issueType: field.string().enum(FEEDBACK_ISSUE_TYPES).optional(),
|
|
1191
|
+
severity: ratingField().optional(),
|
|
1192
|
+
satisfaction: ratingField().optional(),
|
|
1193
|
+
analysis: field.object(analysisProjectionShape(true)).optional(),
|
|
1194
|
+
serverUpdatedAt: field.dateTimeISO(),
|
|
1195
|
+
expiresAt: field.dateTimeISO()
|
|
1196
|
+
},
|
|
1197
|
+
"feedback-draft-packet",
|
|
1198
|
+
{
|
|
1199
|
+
...strictOptions,
|
|
1200
|
+
schemaValidator: (value) => {
|
|
1201
|
+
if (!isDraftShapeConsistent(value)) {
|
|
1202
|
+
return false;
|
|
1666
1203
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1674
|
-
if (typeof value !== "string") {
|
|
1675
|
-
errors.push(`Field ${path} must be string`);
|
|
1676
|
-
return;
|
|
1677
|
-
}
|
|
1678
|
-
const enumValues = getEnumValues(def);
|
|
1679
|
-
if (Array.isArray(enumValues)) {
|
|
1680
|
-
const enumError = validateEnum(path, value, enumValues);
|
|
1681
|
-
if (enumError) {
|
|
1682
|
-
errors.push(enumError);
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
function validateNumberField(parentKey, key, value, def, errors) {
|
|
1687
|
-
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1688
|
-
if (typeof value !== "number") {
|
|
1689
|
-
errors.push(`Field ${path} must be number`);
|
|
1690
|
-
return;
|
|
1691
|
-
}
|
|
1692
|
-
const enumValues = getEnumValues(def);
|
|
1693
|
-
if (Array.isArray(enumValues)) {
|
|
1694
|
-
const enumError = validateEnum(path, value, enumValues);
|
|
1695
|
-
if (enumError) {
|
|
1696
|
-
errors.push(enumError);
|
|
1204
|
+
if (value.analysis !== void 0 && !isAnalysisProjectionConsistent(value.analysis)) {
|
|
1205
|
+
return false;
|
|
1206
|
+
}
|
|
1207
|
+
const updatedAt = Date.parse(String(value.serverUpdatedAt));
|
|
1208
|
+
const expiresAt = Date.parse(String(value.expiresAt));
|
|
1209
|
+
return Number.isFinite(updatedAt) && Number.isFinite(expiresAt) && expiresAt > updatedAt && expiresAt - updatedAt <= 24 * 60 * 60 * 1e3;
|
|
1697
1210
|
}
|
|
1698
1211
|
}
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1212
|
+
);
|
|
1213
|
+
var FeedbackDraftReceiptSchema = createSchema(
|
|
1214
|
+
{
|
|
1215
|
+
draftId: draftIdField(),
|
|
1216
|
+
revision: revisionField(),
|
|
1217
|
+
savedAt: field.dateTimeISO(),
|
|
1218
|
+
expiresAt: field.dateTimeISO()
|
|
1219
|
+
},
|
|
1220
|
+
"feedback-draft-receipt",
|
|
1221
|
+
{
|
|
1222
|
+
...strictOptions,
|
|
1223
|
+
schemaValidator: (value) => {
|
|
1224
|
+
const savedAt = Date.parse(String(value.savedAt));
|
|
1225
|
+
const expiresAt = Date.parse(String(value.expiresAt));
|
|
1226
|
+
return Number.isFinite(savedAt) && Number.isFinite(expiresAt) && expiresAt > savedAt && expiresAt - savedAt <= 24 * 60 * 60 * 1e3;
|
|
1714
1227
|
}
|
|
1715
|
-
const { missing } = checkMissingRequired(
|
|
1716
|
-
parentKey,
|
|
1717
|
-
childKey,
|
|
1718
|
-
childValue,
|
|
1719
|
-
childDef,
|
|
1720
|
-
errors
|
|
1721
|
-
);
|
|
1722
|
-
if (missing) continue;
|
|
1723
|
-
const { immutableViolation } = checkImmutable(
|
|
1724
|
-
parentKey,
|
|
1725
|
-
childKey,
|
|
1726
|
-
childValue,
|
|
1727
|
-
childDef,
|
|
1728
|
-
existing,
|
|
1729
|
-
errors
|
|
1730
|
-
);
|
|
1731
|
-
if (immutableViolation) continue;
|
|
1732
|
-
const { shortCircuit } = enforcePIIField(
|
|
1733
|
-
parentKey,
|
|
1734
|
-
childKey,
|
|
1735
|
-
childValue,
|
|
1736
|
-
childDef,
|
|
1737
|
-
piiEnforcement,
|
|
1738
|
-
errors,
|
|
1739
|
-
logger
|
|
1740
|
-
);
|
|
1741
|
-
if (shortCircuit) continue;
|
|
1742
|
-
const { invalid } = runCustomValidator(
|
|
1743
|
-
parentKey,
|
|
1744
|
-
childKey,
|
|
1745
|
-
childValue,
|
|
1746
|
-
childDef,
|
|
1747
|
-
errors,
|
|
1748
|
-
issues
|
|
1749
|
-
);
|
|
1750
|
-
if (invalid) continue;
|
|
1751
|
-
validateByType(
|
|
1752
|
-
parentKey,
|
|
1753
|
-
childKey,
|
|
1754
|
-
childValue,
|
|
1755
|
-
childDef,
|
|
1756
|
-
errors,
|
|
1757
|
-
issues,
|
|
1758
|
-
existingChild,
|
|
1759
|
-
piiEnforcement,
|
|
1760
|
-
logger
|
|
1761
|
-
);
|
|
1762
1228
|
}
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
errors.push(`Field ${path} must be string[]`);
|
|
1778
|
-
return;
|
|
1779
|
-
}
|
|
1780
|
-
const enumValues = getEnumValues(itemDef);
|
|
1781
|
-
if (Array.isArray(enumValues)) {
|
|
1782
|
-
const enumError = validateEnum(path, arr, enumValues);
|
|
1783
|
-
if (enumError) {
|
|
1784
|
-
errors.push(enumError);
|
|
1785
|
-
}
|
|
1229
|
+
);
|
|
1230
|
+
var FeedbackBugSubmissionRequestSchema = createSchema(
|
|
1231
|
+
{
|
|
1232
|
+
draftId: draftIdField().optional(),
|
|
1233
|
+
surfaceId: surfaceIdField(),
|
|
1234
|
+
issueType: field.string().enum(FEEDBACK_ISSUE_TYPES),
|
|
1235
|
+
severity: ratingField(),
|
|
1236
|
+
analysisReceiptId: transientCorrelationIdField().optional(),
|
|
1237
|
+
gameDiagnostics: field.object(feedbackGameDiagnosticsShape(true)).optional()
|
|
1238
|
+
},
|
|
1239
|
+
"feedback-bug-submission-request",
|
|
1240
|
+
{
|
|
1241
|
+
...strictOptions,
|
|
1242
|
+
schemaValidator: (value) => value.issueType !== "privacy-security" && (value.gameDiagnostics === void 0 || isFeedbackGameDiagnosticsConsistent(value.gameDiagnostics) && value.gameDiagnostics.surfaceId === value.surfaceId)
|
|
1786
1243
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1244
|
+
);
|
|
1245
|
+
var FeedbackReviewSubmissionRequestSchema = createSchema(
|
|
1246
|
+
{
|
|
1247
|
+
draftId: draftIdField().optional(),
|
|
1248
|
+
satisfaction: ratingField(),
|
|
1249
|
+
analysisReceiptId: transientCorrelationIdField().optional()
|
|
1250
|
+
},
|
|
1251
|
+
"feedback-review-submission-request",
|
|
1252
|
+
strictOptions
|
|
1253
|
+
);
|
|
1254
|
+
var FeedbackAcceptanceReceiptSchema = createSchema(
|
|
1255
|
+
{
|
|
1256
|
+
packetId: uuidField(),
|
|
1257
|
+
kind: field.string().enum(["bug", "review"]),
|
|
1258
|
+
acceptedAt: field.dateTimeISO(),
|
|
1259
|
+
nextEligibleAt: field.dateTimeISO()
|
|
1260
|
+
},
|
|
1261
|
+
"feedback-acceptance-receipt",
|
|
1262
|
+
{
|
|
1263
|
+
...strictOptions,
|
|
1264
|
+
schemaValidator: (value) => {
|
|
1265
|
+
const acceptedAt = Date.parse(String(value.acceptedAt));
|
|
1266
|
+
const nextEligibleAt = Date.parse(String(value.nextEligibleAt));
|
|
1267
|
+
const cooldownMilliseconds = nextEligibleAt - acceptedAt;
|
|
1268
|
+
if (!Number.isFinite(acceptedAt) || !Number.isFinite(nextEligibleAt) || cooldownMilliseconds <= 0 || cooldownMilliseconds % 1e3 !== 0) {
|
|
1269
|
+
return false;
|
|
1792
1270
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1798
|
-
if (!arr.every((v) => typeof v === "number")) {
|
|
1799
|
-
errors.push(`Field ${path} must be number[]`);
|
|
1800
|
-
}
|
|
1801
|
-
const enumValues = getEnumValues(itemDef);
|
|
1802
|
-
if (Array.isArray(enumValues)) {
|
|
1803
|
-
const enumError = validateEnum(path, arr, enumValues);
|
|
1804
|
-
if (enumError) {
|
|
1805
|
-
errors.push(enumError);
|
|
1271
|
+
const cooldownSeconds = cooldownMilliseconds / 1e3;
|
|
1272
|
+
return value.kind === "review" ? cooldownSeconds === FEEDBACK_REVIEW_COOLDOWN_SECONDS : FEEDBACK_BUG_COOLDOWN_SECONDS.includes(
|
|
1273
|
+
cooldownSeconds
|
|
1274
|
+
);
|
|
1806
1275
|
}
|
|
1807
1276
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1277
|
+
);
|
|
1278
|
+
var FeedbackStructuredOnlyAnalysisResultSchema = createSchema(
|
|
1279
|
+
{
|
|
1280
|
+
status: field.string().enum(["structured-only-required"]),
|
|
1281
|
+
reasonCode: field.string().enum(FEEDBACK_STRUCTURED_ONLY_REASON_CODES)
|
|
1282
|
+
},
|
|
1283
|
+
"feedback-structured-only-analysis-result",
|
|
1284
|
+
strictOptions
|
|
1285
|
+
);
|
|
1286
|
+
var FeedbackBugPacketSchema = createSchema(
|
|
1287
|
+
{
|
|
1288
|
+
packetId: uuidField(),
|
|
1289
|
+
acceptedAt: field.dateTimeISO(),
|
|
1290
|
+
surfaceId: surfaceIdField(),
|
|
1291
|
+
issueType: field.string().enum(FEEDBACK_PERSISTABLE_ISSUE_TYPES),
|
|
1292
|
+
severity: ratingField(),
|
|
1293
|
+
releaseId: safeIdField(128),
|
|
1294
|
+
buildId: safeIdField(128),
|
|
1295
|
+
analysis: field.object(analysisProjectionShape(true)).optional(),
|
|
1296
|
+
gameDiagnostics: field.object(feedbackGameDiagnosticsShape(true)).optional()
|
|
1297
|
+
},
|
|
1298
|
+
"feedback-bug-packet",
|
|
1299
|
+
{
|
|
1300
|
+
...strictOptions,
|
|
1301
|
+
schemaValidator: (value) => (value.analysis === void 0 || isAnalysisProjectionConsistent(value.analysis)) && (value.gameDiagnostics === void 0 || isFeedbackGameDiagnosticsConsistent(value.gameDiagnostics) && value.gameDiagnostics.surfaceId === value.surfaceId)
|
|
1821
1302
|
}
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1303
|
+
);
|
|
1304
|
+
var FeedbackReviewPacketSchema = createSchema(
|
|
1305
|
+
{
|
|
1306
|
+
packetId: uuidField(),
|
|
1307
|
+
acceptedAt: field.dateTimeISO(),
|
|
1308
|
+
satisfaction: ratingField(),
|
|
1309
|
+
analysis: field.object(analysisProjectionShape(true)).optional()
|
|
1310
|
+
},
|
|
1311
|
+
"feedback-review-packet",
|
|
1312
|
+
{
|
|
1313
|
+
...strictOptions,
|
|
1314
|
+
schemaValidator: (value) => value.analysis === void 0 || isAnalysisProjectionConsistent(value.analysis)
|
|
1829
1315
|
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1316
|
+
);
|
|
1317
|
+
var FeedbackGameReconstructionManifestSchema = createSchema(
|
|
1318
|
+
{
|
|
1319
|
+
reconstructionId: uuidField(),
|
|
1320
|
+
bugPacketId: uuidField(),
|
|
1321
|
+
createdAt: field.dateTimeISO(),
|
|
1322
|
+
expiresAt: field.dateTimeISO(),
|
|
1323
|
+
curatedAssetSetId: safeIdField(128),
|
|
1324
|
+
noticeKey: safeIdField(160),
|
|
1325
|
+
diagnostics: field.object(feedbackGameDiagnosticsShape(true))
|
|
1326
|
+
},
|
|
1327
|
+
"feedback-game-reconstruction-manifest",
|
|
1328
|
+
{
|
|
1329
|
+
...strictOptions,
|
|
1330
|
+
schemaValidator: (value) => {
|
|
1331
|
+
const createdAt = Date.parse(String(value.createdAt));
|
|
1332
|
+
const expiresAt = Date.parse(String(value.expiresAt));
|
|
1333
|
+
return isFeedbackGameDiagnosticsConsistent(value.diagnostics) && Number.isFinite(createdAt) && Number.isFinite(expiresAt) && expiresAt > createdAt && expiresAt - createdAt <= 30 * 24 * 60 * 60 * 1e3;
|
|
1334
|
+
}
|
|
1836
1335
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1336
|
+
);
|
|
1337
|
+
var FeedbackHourlyBugReportSchema = createSchema(
|
|
1338
|
+
{
|
|
1339
|
+
reportId: uuidField(),
|
|
1340
|
+
windowStart: field.dateTimeISO(),
|
|
1341
|
+
windowEnd: field.dateTimeISO(),
|
|
1342
|
+
revision: revisionField(),
|
|
1343
|
+
generatedAt: field.dateTimeISO(),
|
|
1344
|
+
acceptedCount: countField(),
|
|
1345
|
+
rejectedCount: countField(),
|
|
1346
|
+
diagnosticsAttachedCount: countField(),
|
|
1347
|
+
deterministicRedactionCount: countField(),
|
|
1348
|
+
scannerRedactionCount: countField(),
|
|
1349
|
+
processorLagSeconds: countField(),
|
|
1350
|
+
rates: field.object({
|
|
1351
|
+
rejectionRate: field.number().min(0).max(1),
|
|
1352
|
+
deterministicRedactionsPerAccepted: field.number().min(0).max(4e3),
|
|
1353
|
+
scannerRedactionsPerAccepted: field.number().min(0).max(4e3)
|
|
1354
|
+
}),
|
|
1355
|
+
traffic: field.object({
|
|
1356
|
+
denominator: countField(),
|
|
1357
|
+
acceptedPerTenThousand: field.number().min(0).max(MAX_COUNT).optional()
|
|
1358
|
+
}),
|
|
1359
|
+
targetDistribution: field.array(field.object(distributionShape(surfaceIdField))).max(256),
|
|
1360
|
+
issueTypeDistribution: field.array(
|
|
1361
|
+
field.object(
|
|
1362
|
+
distributionShape(
|
|
1363
|
+
() => field.string().enum(FEEDBACK_PERSISTABLE_ISSUE_TYPES)
|
|
1364
|
+
)
|
|
1365
|
+
)
|
|
1366
|
+
).max(FEEDBACK_ISSUE_TYPES.length),
|
|
1367
|
+
severityDistribution: field.array(
|
|
1368
|
+
field.object(
|
|
1369
|
+
distributionShape(
|
|
1370
|
+
() => field.string().enum(["1", "2", "3", "4", "5"])
|
|
1371
|
+
)
|
|
1372
|
+
)
|
|
1373
|
+
).max(5),
|
|
1374
|
+
intentDistribution: field.array(
|
|
1375
|
+
field.object(
|
|
1376
|
+
distributionShape(
|
|
1377
|
+
() => field.string().enum(FEEDBACK_INTENT_IDS)
|
|
1378
|
+
)
|
|
1379
|
+
)
|
|
1380
|
+
).max(FEEDBACK_INTENT_IDS.length),
|
|
1381
|
+
buildDistribution: field.array(field.object(distributionShape())).max(128),
|
|
1382
|
+
rendererDistribution: field.array(
|
|
1383
|
+
field.object(
|
|
1384
|
+
distributionShape(
|
|
1385
|
+
() => field.string().enum(FEEDBACK_RENDERER_BUCKETS)
|
|
1386
|
+
)
|
|
1387
|
+
)
|
|
1388
|
+
).max(FEEDBACK_RENDERER_BUCKETS.length),
|
|
1389
|
+
backendDistribution: field.array(
|
|
1390
|
+
field.object(
|
|
1391
|
+
distributionShape(
|
|
1392
|
+
() => field.string().enum(FEEDBACK_BACKEND_BUCKETS)
|
|
1393
|
+
)
|
|
1394
|
+
)
|
|
1395
|
+
).max(FEEDBACK_BACKEND_BUCKETS.length),
|
|
1396
|
+
viewportDistribution: field.array(
|
|
1397
|
+
field.object(
|
|
1398
|
+
distributionShape(
|
|
1399
|
+
() => field.string().enum(FEEDBACK_VIEWPORT_BUCKETS)
|
|
1400
|
+
)
|
|
1401
|
+
)
|
|
1402
|
+
).max(FEEDBACK_VIEWPORT_BUCKETS.length),
|
|
1403
|
+
frameRateDistribution: field.array(
|
|
1404
|
+
field.object(
|
|
1405
|
+
distributionShape(
|
|
1406
|
+
() => field.string().enum(FEEDBACK_FRAME_RATE_BUCKETS)
|
|
1407
|
+
)
|
|
1408
|
+
)
|
|
1409
|
+
).max(FEEDBACK_FRAME_RATE_BUCKETS.length),
|
|
1410
|
+
frameTimeDistribution: field.array(
|
|
1411
|
+
field.object(
|
|
1412
|
+
distributionShape(
|
|
1413
|
+
() => field.string().enum(FEEDBACK_FRAME_TIME_BUCKETS)
|
|
1414
|
+
)
|
|
1415
|
+
)
|
|
1416
|
+
).max(FEEDBACK_FRAME_TIME_BUCKETS.length),
|
|
1417
|
+
diagnosticFeatureDistribution: field.array(
|
|
1418
|
+
field.object(
|
|
1419
|
+
distributionShape(
|
|
1420
|
+
() => field.string().enum(FEEDBACK_GAME_FEATURE_IDS)
|
|
1421
|
+
)
|
|
1422
|
+
)
|
|
1423
|
+
).max(FEEDBACK_GAME_FEATURE_IDS.length),
|
|
1424
|
+
diagnosticCounterDistribution: field.array(
|
|
1425
|
+
field.object(
|
|
1426
|
+
distributionShape(
|
|
1427
|
+
() => field.string().enum(FEEDBACK_GAME_COUNTER_CODES)
|
|
1428
|
+
)
|
|
1429
|
+
)
|
|
1430
|
+
).max(FEEDBACK_GAME_COUNTER_CODES.length),
|
|
1431
|
+
diagnosticErrorDistribution: field.array(
|
|
1432
|
+
field.object(
|
|
1433
|
+
distributionShape(
|
|
1434
|
+
() => field.string().enum(FEEDBACK_GAME_ERROR_CODES)
|
|
1435
|
+
)
|
|
1436
|
+
)
|
|
1437
|
+
).max(FEEDBACK_GAME_ERROR_CODES.length),
|
|
1438
|
+
abuseBlockBands: field.array(
|
|
1439
|
+
field.object(
|
|
1440
|
+
distributionShape(
|
|
1441
|
+
() => field.string().enum(
|
|
1442
|
+
[
|
|
1443
|
+
"five-minutes",
|
|
1444
|
+
"fifteen-minutes",
|
|
1445
|
+
"one-hour",
|
|
1446
|
+
"six-hours",
|
|
1447
|
+
"twenty-four-hours",
|
|
1448
|
+
"fail-closed",
|
|
1449
|
+
"edge-blocked"
|
|
1450
|
+
]
|
|
1451
|
+
)
|
|
1452
|
+
)
|
|
1453
|
+
)
|
|
1454
|
+
).max(16),
|
|
1455
|
+
comparison: field.object({
|
|
1456
|
+
previousHourRatio: field.number().min(0).max(1e3),
|
|
1457
|
+
sevenDaySameHourRatio: field.number().min(0).max(1e3)
|
|
1458
|
+
}),
|
|
1459
|
+
advisories: field.array(field.object(advisoryShape())).max(32)
|
|
1460
|
+
},
|
|
1461
|
+
"feedback-hourly-bug-report",
|
|
1462
|
+
{
|
|
1463
|
+
...strictOptions,
|
|
1464
|
+
schemaValidator: (value) => {
|
|
1465
|
+
const distributions = [
|
|
1466
|
+
value.targetDistribution,
|
|
1467
|
+
value.issueTypeDistribution,
|
|
1468
|
+
value.severityDistribution,
|
|
1469
|
+
value.intentDistribution,
|
|
1470
|
+
value.buildDistribution,
|
|
1471
|
+
value.rendererDistribution,
|
|
1472
|
+
value.backendDistribution,
|
|
1473
|
+
value.viewportDistribution,
|
|
1474
|
+
value.frameRateDistribution,
|
|
1475
|
+
value.frameTimeDistribution,
|
|
1476
|
+
value.diagnosticFeatureDistribution,
|
|
1477
|
+
value.diagnosticCounterDistribution,
|
|
1478
|
+
value.diagnosticErrorDistribution,
|
|
1479
|
+
value.abuseBlockBands
|
|
1480
|
+
];
|
|
1481
|
+
const submittedCount = value.acceptedCount + value.rejectedCount;
|
|
1482
|
+
const expectedRejectionRate = submittedCount === 0 ? 0 : value.rejectedCount / submittedCount;
|
|
1483
|
+
const expectedDeterministicRate = value.acceptedCount === 0 ? 0 : value.deterministicRedactionCount / value.acceptedCount;
|
|
1484
|
+
const expectedScannerRate = value.acceptedCount === 0 ? 0 : value.scannerRedactionCount / value.acceptedCount;
|
|
1485
|
+
const trafficIsConsistent = value.traffic.denominator === 0 && value.traffic.acceptedPerTenThousand === void 0 || value.traffic.denominator > 0 && rateMatches(
|
|
1486
|
+
value.traffic.acceptedPerTenThousand,
|
|
1487
|
+
value.acceptedCount * 1e4 / value.traffic.denominator
|
|
1868
1488
|
);
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
childKey,
|
|
1873
|
-
childValue,
|
|
1874
|
-
childDef,
|
|
1875
|
-
piiEnforcement,
|
|
1876
|
-
errors,
|
|
1877
|
-
logger
|
|
1489
|
+
const severityFiveCount = distributionCountFor(
|
|
1490
|
+
value.severityDistribution,
|
|
1491
|
+
"5"
|
|
1878
1492
|
);
|
|
1879
|
-
|
|
1880
|
-
const
|
|
1881
|
-
|
|
1882
|
-
childKey,
|
|
1883
|
-
childValue,
|
|
1884
|
-
childDef,
|
|
1885
|
-
errors,
|
|
1886
|
-
issues
|
|
1493
|
+
const criticalRegression = severityFiveCount >= 3 || value.acceptedCount >= 5 && value.comparison.sevenDaySameHourRatio >= 3;
|
|
1494
|
+
const lagSeconds = Math.floor(
|
|
1495
|
+
(Date.parse(String(value.generatedAt)) - Date.parse(String(value.windowEnd))) / 1e3
|
|
1887
1496
|
);
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1497
|
+
return orderedWindow(value, 60 * 60 * 1e3) && value.revision >= 1 && value.processorLagSeconds === lagSeconds && value.diagnosticsAttachedCount <= value.acceptedCount && (value.acceptedCount > 0 || value.deterministicRedactionCount === 0 && value.scannerRedactionCount === 0) && rateMatches(value.rates.rejectionRate, expectedRejectionRate) && rateMatches(
|
|
1498
|
+
value.rates.deterministicRedactionsPerAccepted,
|
|
1499
|
+
expectedDeterministicRate
|
|
1500
|
+
) && rateMatches(
|
|
1501
|
+
value.rates.scannerRedactionsPerAccepted,
|
|
1502
|
+
expectedScannerRate
|
|
1503
|
+
) && trafficIsConsistent && distributions.every(hasUniqueIds) && advisoryCodesAreConsistent(value.advisories, {
|
|
1504
|
+
"severity-five": severityFiveCount > 0 ? {
|
|
1505
|
+
level: "advisory",
|
|
1506
|
+
triggerCount: severityFiveCount,
|
|
1507
|
+
recommendationIds: ["inspect-release"]
|
|
1508
|
+
} : void 0,
|
|
1509
|
+
"critical-regression": criticalRegression ? {
|
|
1510
|
+
level: "critical",
|
|
1511
|
+
triggerCount: severityFiveCount >= 3 ? severityFiveCount : value.acceptedCount,
|
|
1512
|
+
recommendationIds: [
|
|
1513
|
+
"inspect-release",
|
|
1514
|
+
"verify-renderer-health"
|
|
1515
|
+
]
|
|
1516
|
+
} : void 0,
|
|
1517
|
+
"processor-stale": lagSeconds > 10 * 60 ? {
|
|
1518
|
+
level: "critical",
|
|
1519
|
+
triggerCount: lagSeconds,
|
|
1520
|
+
recommendationIds: ["verify-processor-health"]
|
|
1521
|
+
} : void 0
|
|
1522
|
+
}) && distributionTotal(value.targetDistribution) === value.acceptedCount && distributionTotal(value.issueTypeDistribution) === value.acceptedCount && distributionTotal(value.severityDistribution) === value.acceptedCount && distributionCountsAtMost(
|
|
1523
|
+
value.intentDistribution,
|
|
1524
|
+
value.acceptedCount
|
|
1525
|
+
) && distributionTotal(value.intentDistribution) <= value.acceptedCount * 8 && distributionTotal(value.buildDistribution) === value.acceptedCount && distributionTotal(value.rendererDistribution) === value.diagnosticsAttachedCount && distributionTotal(value.backendDistribution) === value.diagnosticsAttachedCount && distributionTotal(value.viewportDistribution) === value.diagnosticsAttachedCount && distributionTotal(value.frameRateDistribution) === value.diagnosticsAttachedCount && distributionTotal(value.frameTimeDistribution) === value.diagnosticsAttachedCount && distributionTotal(value.diagnosticFeatureDistribution) <= value.diagnosticsAttachedCount * FEEDBACK_GAME_FEATURE_IDS.length && distributionCountsAtMost(
|
|
1526
|
+
value.diagnosticFeatureDistribution,
|
|
1527
|
+
value.diagnosticsAttachedCount
|
|
1528
|
+
) && distributionTotal(value.diagnosticCounterDistribution) <= value.diagnosticsAttachedCount * FEEDBACK_GAME_COUNTER_CODES.length * 1e4 && distributionCountsAtMost(
|
|
1529
|
+
value.diagnosticCounterDistribution,
|
|
1530
|
+
value.diagnosticsAttachedCount * 1e4
|
|
1531
|
+
) && distributionTotal(value.diagnosticErrorDistribution) <= value.diagnosticsAttachedCount * FEEDBACK_GAME_ERROR_CODES.length && distributionCountsAtMost(
|
|
1532
|
+
value.diagnosticErrorDistribution,
|
|
1533
|
+
value.diagnosticsAttachedCount
|
|
1899
1534
|
);
|
|
1900
1535
|
}
|
|
1901
|
-
});
|
|
1902
|
-
}
|
|
1903
|
-
function validateArrayField(parentKey, key, value, def, errors, issues, existing, piiEnforcement, logger) {
|
|
1904
|
-
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
1905
|
-
if (!Array.isArray(value)) {
|
|
1906
|
-
errors.push(`Field ${key} must be an array`);
|
|
1907
|
-
return;
|
|
1908
1536
|
}
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
if (
|
|
1917
|
-
return
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
errors.push(
|
|
1933
|
-
`Field ${path}[${idx}] must be a reference object with type: ${expectedType}`
|
|
1934
|
-
);
|
|
1537
|
+
);
|
|
1538
|
+
var rollingSatisfactionShape = () => ({
|
|
1539
|
+
period: field.string().enum(["7-days", "30-days", "90-days"]),
|
|
1540
|
+
reviewCount: countField(),
|
|
1541
|
+
meanStars: field.number().min(1).max(5).optional()
|
|
1542
|
+
});
|
|
1543
|
+
var satisfactionStatistics = (distribution, total) => {
|
|
1544
|
+
if (!Array.isArray(distribution) || total <= 0) {
|
|
1545
|
+
return void 0;
|
|
1546
|
+
}
|
|
1547
|
+
const counts = [1, 2, 3, 4, 5].map(
|
|
1548
|
+
(star) => distributionCountFor(distribution, String(star))
|
|
1549
|
+
);
|
|
1550
|
+
const weightedTotal = counts.reduce(
|
|
1551
|
+
(sum, count, index) => sum + count * (index + 1),
|
|
1552
|
+
0
|
|
1553
|
+
);
|
|
1554
|
+
const valueAtPosition = (position) => {
|
|
1555
|
+
let cumulative = 0;
|
|
1556
|
+
for (let index = 0; index < counts.length; index += 1) {
|
|
1557
|
+
cumulative += counts[index] ?? 0;
|
|
1558
|
+
if (cumulative >= position) {
|
|
1559
|
+
return index + 1;
|
|
1935
1560
|
}
|
|
1936
|
-
});
|
|
1937
|
-
const refShape = getShape(def.itemType);
|
|
1938
|
-
if (refShape) {
|
|
1939
|
-
const existingRefs = Array.isArray(existing) ? existing : [];
|
|
1940
|
-
value.forEach((ref, idx) => {
|
|
1941
|
-
if (ref && typeof ref === "object" && ref !== null) {
|
|
1942
|
-
const existingRef = existingRefs[idx];
|
|
1943
|
-
for (const [childKey, childDef] of Object.entries(refShape)) {
|
|
1944
|
-
const childPath = `${path}[${idx}].${childKey}`;
|
|
1945
|
-
let childValue = ref[childKey];
|
|
1946
|
-
const existingChild = existingRef && typeof existingRef === "object" ? existingRef[childKey] : void 0;
|
|
1947
|
-
const { value: withDefault, applied } = applyDefault(
|
|
1948
|
-
childDef,
|
|
1949
|
-
childValue
|
|
1950
|
-
);
|
|
1951
|
-
if (applied) {
|
|
1952
|
-
ref[childKey] = withDefault;
|
|
1953
|
-
childValue = withDefault;
|
|
1954
|
-
}
|
|
1955
|
-
if ((childValue === void 0 || childValue === null) && !isOptional(childDef)) {
|
|
1956
|
-
errors.push(`Missing required field: ${childPath}`);
|
|
1957
|
-
continue;
|
|
1958
|
-
}
|
|
1959
|
-
const { immutableViolation } = checkImmutable(
|
|
1960
|
-
`${path}[${idx}]`,
|
|
1961
|
-
childKey,
|
|
1962
|
-
childValue,
|
|
1963
|
-
childDef,
|
|
1964
|
-
existingRef,
|
|
1965
|
-
errors
|
|
1966
|
-
);
|
|
1967
|
-
if (immutableViolation) continue;
|
|
1968
|
-
const { shortCircuit } = enforcePIIField(
|
|
1969
|
-
`${path}[${idx}]`,
|
|
1970
|
-
childKey,
|
|
1971
|
-
childValue,
|
|
1972
|
-
childDef,
|
|
1973
|
-
piiEnforcement,
|
|
1974
|
-
errors,
|
|
1975
|
-
logger
|
|
1976
|
-
);
|
|
1977
|
-
if (shortCircuit) continue;
|
|
1978
|
-
const childValidator = getValidator(childDef);
|
|
1979
|
-
if (childValidator && childValue !== void 0 && childValue !== null) {
|
|
1980
|
-
const normalizedIssues = normalizeValidatorIssues(
|
|
1981
|
-
childPath,
|
|
1982
|
-
childValidator(childValue),
|
|
1983
|
-
"invalid_value"
|
|
1984
|
-
);
|
|
1985
|
-
if (normalizedIssues.length > 0) {
|
|
1986
|
-
errors.push(...normalizedIssues.map((issue) => issue.message));
|
|
1987
|
-
issues.push(...normalizedIssues);
|
|
1988
|
-
continue;
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
validateByType(
|
|
1992
|
-
`${path}[${idx}]`,
|
|
1993
|
-
childKey,
|
|
1994
|
-
childValue,
|
|
1995
|
-
childDef,
|
|
1996
|
-
errors,
|
|
1997
|
-
issues,
|
|
1998
|
-
existingChild,
|
|
1999
|
-
piiEnforcement,
|
|
2000
|
-
logger
|
|
2001
|
-
);
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
});
|
|
2005
1561
|
}
|
|
2006
|
-
return;
|
|
2007
|
-
}
|
|
2008
|
-
errors.push(`Field ${path} has unsupported array item type`);
|
|
2009
|
-
}
|
|
2010
|
-
function validateRefField(parentKey, key, value, def, errors, _existing, _piiEnforcement, _logger) {
|
|
2011
|
-
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
2012
|
-
if (typeof value !== "object" || value === null || typeof value.type !== "string" || typeof value.id !== "string") {
|
|
2013
|
-
errors.push(`Field ${path} must be { type: string; id: string }`);
|
|
2014
|
-
return;
|
|
2015
|
-
}
|
|
2016
|
-
const expectedType = def.refType;
|
|
2017
|
-
if (expectedType && value.type !== expectedType) {
|
|
2018
|
-
errors.push(
|
|
2019
|
-
`Field ${path} must reference type: ${expectedType} (got ${value.type})`
|
|
2020
|
-
);
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
function validateByType(parentKey, key, value, def, errors, issues, existing, piiEnforcement, logger) {
|
|
2024
|
-
const path = parentKey ? `${parentKey}.${key}` : key;
|
|
2025
|
-
switch (def.type) {
|
|
2026
|
-
case "string":
|
|
2027
|
-
return validateStringField(parentKey, key, value, def, errors);
|
|
2028
|
-
case "number":
|
|
2029
|
-
return validateNumberField(parentKey, key, value, def, errors);
|
|
2030
|
-
case "boolean":
|
|
2031
|
-
return validateBooleanField(parentKey, key, value, def, errors);
|
|
2032
|
-
case "object":
|
|
2033
|
-
return validateObjectField(
|
|
2034
|
-
parentKey,
|
|
2035
|
-
key,
|
|
2036
|
-
value,
|
|
2037
|
-
def,
|
|
2038
|
-
errors,
|
|
2039
|
-
issues,
|
|
2040
|
-
existing,
|
|
2041
|
-
piiEnforcement,
|
|
2042
|
-
logger
|
|
2043
|
-
);
|
|
2044
|
-
case "array":
|
|
2045
|
-
return validateArrayField(
|
|
2046
|
-
parentKey,
|
|
2047
|
-
key,
|
|
2048
|
-
value,
|
|
2049
|
-
def,
|
|
2050
|
-
errors,
|
|
2051
|
-
issues,
|
|
2052
|
-
existing,
|
|
2053
|
-
piiEnforcement,
|
|
2054
|
-
logger
|
|
2055
|
-
);
|
|
2056
|
-
case "ref":
|
|
2057
|
-
return validateRefField(parentKey, key, value, def, errors, existing, piiEnforcement, logger);
|
|
2058
|
-
default:
|
|
2059
|
-
errors.push(`Unknown type for field ${path}: ${def.type}`);
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
function createSchema(_shape, entityType, options = {
|
|
2063
|
-
version: "1.0.0",
|
|
2064
|
-
table: "",
|
|
2065
|
-
schemaValidator: () => true,
|
|
2066
|
-
piiEnforcement: "none",
|
|
2067
|
-
schemaUpgrade: void 0
|
|
2068
|
-
}) {
|
|
2069
|
-
const systemFields = {
|
|
2070
|
-
type: field.string().immutable().system(),
|
|
2071
|
-
version: field.string().immutable().system().validator(validateSemVer)
|
|
1562
|
+
return 5;
|
|
2072
1563
|
};
|
|
2073
|
-
const
|
|
2074
|
-
const
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
}
|
|
2123
|
-
const { value: withDefault, applied } = applyDefault(def, value);
|
|
2124
|
-
if (applied) {
|
|
2125
|
-
working[key] = withDefault;
|
|
2126
|
-
value = withDefault;
|
|
2127
|
-
}
|
|
2128
|
-
const { missing } = checkMissingRequired("", key, value, def, errors);
|
|
2129
|
-
if (missing) continue;
|
|
2130
|
-
const { immutableViolation } = checkImmutable(
|
|
2131
|
-
"",
|
|
2132
|
-
key,
|
|
2133
|
-
value,
|
|
2134
|
-
def,
|
|
2135
|
-
existing,
|
|
2136
|
-
errors
|
|
2137
|
-
);
|
|
2138
|
-
if (immutableViolation) continue;
|
|
2139
|
-
const { shortCircuit } = enforcePIIField(
|
|
2140
|
-
"",
|
|
2141
|
-
key,
|
|
2142
|
-
value,
|
|
2143
|
-
def,
|
|
2144
|
-
piiMode,
|
|
2145
|
-
errors,
|
|
2146
|
-
console
|
|
2147
|
-
);
|
|
2148
|
-
if (shortCircuit) continue;
|
|
2149
|
-
const validateField = (val) => {
|
|
2150
|
-
const localErrors = [];
|
|
2151
|
-
const localIssues = [];
|
|
2152
|
-
const { invalid } = runCustomValidator(
|
|
2153
|
-
"",
|
|
2154
|
-
key,
|
|
2155
|
-
val,
|
|
2156
|
-
def,
|
|
2157
|
-
localErrors,
|
|
2158
|
-
localIssues
|
|
2159
|
-
);
|
|
2160
|
-
if (!invalid) {
|
|
2161
|
-
validateByType("", key, val, def, localErrors, localIssues, existingField, piiMode, console);
|
|
2162
|
-
}
|
|
2163
|
-
issues.push(...localIssues);
|
|
2164
|
-
return localErrors;
|
|
2165
|
-
};
|
|
2166
|
-
let fieldValue = value;
|
|
2167
|
-
let fieldErrors = validateField(fieldValue);
|
|
2168
|
-
const entityFrom = String(working.version ?? "0.0.0");
|
|
2169
|
-
const entityTo = String(version);
|
|
2170
|
-
const fieldTo = String(def._version ?? entityTo);
|
|
2171
|
-
const hasUpgrader = typeof def._upgrade === "function";
|
|
2172
|
-
if (fieldErrors.length > 0 && hasUpgrader && cmpSemver(entityFrom, entityTo) < 0) {
|
|
2173
|
-
const up = def._upgrade;
|
|
2174
|
-
const res = up(fieldValue, {
|
|
2175
|
-
entityFrom,
|
|
2176
|
-
entityTo,
|
|
2177
|
-
fieldTo,
|
|
2178
|
-
fieldName: key
|
|
2179
|
-
});
|
|
2180
|
-
if (res && res.ok) {
|
|
2181
|
-
fieldValue = res.value;
|
|
2182
|
-
fieldErrors = validateField(fieldValue);
|
|
2183
|
-
} else {
|
|
2184
|
-
fieldErrors.push(
|
|
2185
|
-
res?.error || `Failed to upgrade field ${key} from v${entityFrom} to v${entityTo}`
|
|
2186
|
-
);
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
result[key] = fieldValue;
|
|
2190
|
-
if (fieldErrors.length !== 0) {
|
|
2191
|
-
errors.push(...fieldErrors);
|
|
2192
|
-
continue;
|
|
2193
|
-
}
|
|
1564
|
+
const lower = valueAtPosition(Math.ceil(total / 2));
|
|
1565
|
+
const upper = valueAtPosition(Math.floor(total / 2) + 1);
|
|
1566
|
+
return {
|
|
1567
|
+
mean: Math.round(weightedTotal / total * 10) / 10,
|
|
1568
|
+
median: (lower + upper) / 2
|
|
1569
|
+
};
|
|
1570
|
+
};
|
|
1571
|
+
var FeedbackDailySatisfactionReportSchema = createSchema(
|
|
1572
|
+
{
|
|
1573
|
+
reportId: uuidField(),
|
|
1574
|
+
windowStart: field.dateTimeISO(),
|
|
1575
|
+
windowEnd: field.dateTimeISO(),
|
|
1576
|
+
revision: revisionField(),
|
|
1577
|
+
generatedAt: field.dateTimeISO(),
|
|
1578
|
+
processorLagSeconds: countField(),
|
|
1579
|
+
acceptedReviewCount: countField(),
|
|
1580
|
+
meanStars: field.number().min(1).max(5).optional(),
|
|
1581
|
+
medianStars: field.number().min(1).max(5).optional(),
|
|
1582
|
+
starDistribution: field.array(
|
|
1583
|
+
field.object(
|
|
1584
|
+
distributionShape(
|
|
1585
|
+
() => field.string().enum(["1", "2", "3", "4", "5"])
|
|
1586
|
+
)
|
|
1587
|
+
)
|
|
1588
|
+
).max(5),
|
|
1589
|
+
sentimentDistribution: field.array(
|
|
1590
|
+
field.object(
|
|
1591
|
+
distributionShape(
|
|
1592
|
+
() => field.string().enum(FEEDBACK_SENTIMENT_BUCKETS)
|
|
1593
|
+
)
|
|
1594
|
+
)
|
|
1595
|
+
).max(FEEDBACK_SENTIMENT_BUCKETS.length),
|
|
1596
|
+
intentDistribution: field.array(
|
|
1597
|
+
field.object(
|
|
1598
|
+
distributionShape(
|
|
1599
|
+
() => field.string().enum(FEEDBACK_INTENT_IDS)
|
|
1600
|
+
)
|
|
1601
|
+
)
|
|
1602
|
+
).max(FEEDBACK_INTENT_IDS.length),
|
|
1603
|
+
rollingWindows: field.array(field.object(rollingSatisfactionShape())).max(3),
|
|
1604
|
+
previousPeriodDeltaStars: field.number().min(-4).max(4).optional(),
|
|
1605
|
+
advisories: field.array(field.object(advisoryShape())).max(32)
|
|
1606
|
+
},
|
|
1607
|
+
"feedback-daily-satisfaction-report",
|
|
1608
|
+
{
|
|
1609
|
+
...strictOptions,
|
|
1610
|
+
schemaValidator: (value) => {
|
|
1611
|
+
if (!orderedWindow(value, 24 * 60 * 60 * 1e3)) {
|
|
1612
|
+
return false;
|
|
2194
1613
|
}
|
|
2195
|
-
if (
|
|
2196
|
-
|
|
2197
|
-
if (!options.schemaValidator(castValue)) {
|
|
2198
|
-
errors.push("Schema-level validation failed.");
|
|
2199
|
-
}
|
|
1614
|
+
if (value.acceptedReviewCount === 0 && (value.meanStars !== void 0 || value.medianStars !== void 0)) {
|
|
1615
|
+
return false;
|
|
2200
1616
|
}
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
value: result,
|
|
2204
|
-
errors,
|
|
2205
|
-
issues
|
|
2206
|
-
};
|
|
2207
|
-
},
|
|
2208
|
-
// specific validator for a schema to allow conditional validation
|
|
2209
|
-
schemaValidator: options.schemaValidator,
|
|
2210
|
-
// <== expose it here!
|
|
2211
|
-
/**
|
|
2212
|
-
* Runs the optional schema-level upgrade function once, without validating.
|
|
2213
|
-
* Useful for offline migrations or testing migration logic.
|
|
2214
|
-
*/
|
|
2215
|
-
upgrade(input, log) {
|
|
2216
|
-
const fromVersion = String(input?.version ?? "0.0.0");
|
|
2217
|
-
const toVersion = String(version);
|
|
2218
|
-
if (!schemaUpgrade || cmpSemver(fromVersion, toVersion) >= 0) {
|
|
2219
|
-
return {
|
|
2220
|
-
ok: true,
|
|
2221
|
-
value: { ...input, type: entityType, version: toVersion }
|
|
2222
|
-
};
|
|
1617
|
+
if (value.acceptedReviewCount > 0 && (value.meanStars === void 0 || value.medianStars === void 0)) {
|
|
1618
|
+
return false;
|
|
2223
1619
|
}
|
|
2224
|
-
const
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
describe: () => schema.describe(),
|
|
2232
|
-
log
|
|
2233
|
-
}
|
|
1620
|
+
const distributions = [
|
|
1621
|
+
value.starDistribution,
|
|
1622
|
+
value.sentimentDistribution,
|
|
1623
|
+
value.intentDistribution
|
|
1624
|
+
];
|
|
1625
|
+
const periods = value.rollingWindows.map(
|
|
1626
|
+
(window) => window.period
|
|
2234
1627
|
);
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
*
|
|
2247
|
-
* Traverses fields of type `ref` and arrays of `ref` and resolves each target
|
|
2248
|
-
* entity using the provided `resolveEntity` function. When `autoValidate` is
|
|
2249
|
-
* enabled (default) and the field's `refPolicy` is `eager`, the referenced
|
|
2250
|
-
* entity's schema is fetched and validated via `validateComposition` up to
|
|
2251
|
-
* `maxDepth` levels.
|
|
2252
|
-
*
|
|
2253
|
-
* Skips fields not listed in `onlyFields` when provided. Prevents cycles via
|
|
2254
|
-
* a `visited` set in `validatorContext`.
|
|
2255
|
-
*
|
|
2256
|
-
* @param entity The root entity to validate (must include `type` and `id`).
|
|
2257
|
-
* @param options Options controlling traversal and resolution behavior.
|
|
2258
|
-
* @param options.resolveEntity Function to resolve a referenced entity by type and id.
|
|
2259
|
-
* @param options.validatorContext Internal context (visited set) to prevent cycles.
|
|
2260
|
-
* @param options.maxDepth Maximum depth for recursive validation (default: 5).
|
|
2261
|
-
* @param options.onlyFields Optional whitelist of field names to validate.
|
|
2262
|
-
* @param options.log Optional logger for traversal/debug output.
|
|
2263
|
-
*
|
|
2264
|
-
* @throws Error if a broken reference is encountered (target cannot be resolved).
|
|
2265
|
-
*/
|
|
2266
|
-
async validateComposition(entity, options2) {
|
|
2267
|
-
const {
|
|
2268
|
-
resolveEntity,
|
|
2269
|
-
validatorContext = { visited: /* @__PURE__ */ new Set() },
|
|
2270
|
-
maxDepth = 5,
|
|
2271
|
-
log
|
|
2272
|
-
} = options2;
|
|
2273
|
-
const entityKey = `${entity.type}:${entity.id}`;
|
|
2274
|
-
if (validatorContext.visited.has(entityKey)) {
|
|
2275
|
-
log?.(`Skipping already visited entity ${entityKey}`);
|
|
2276
|
-
return;
|
|
2277
|
-
}
|
|
2278
|
-
validatorContext.visited.add(entityKey);
|
|
2279
|
-
log?.(`Validating composition for entity ${entityKey}`);
|
|
2280
|
-
for (const [key, def] of Object.entries(schema._shape)) {
|
|
2281
|
-
if (options2.onlyFields && !options2.onlyFields.includes(key)) {
|
|
2282
|
-
log?.(`Skipping field ${key} (not in onlyFields)`);
|
|
2283
|
-
continue;
|
|
2284
|
-
}
|
|
2285
|
-
const autoValidate = def.autoValidate !== false;
|
|
2286
|
-
const refPolicy = def.refPolicy ?? "eager";
|
|
2287
|
-
const value = entity[key];
|
|
2288
|
-
if (!value) continue;
|
|
2289
|
-
if (def.type === "ref") {
|
|
2290
|
-
const refType = def.refType;
|
|
2291
|
-
if (!refType)
|
|
2292
|
-
throw new Error(`Missing refType for reference field ${key}`);
|
|
2293
|
-
const ref = value;
|
|
2294
|
-
if (ref.type && ref.type !== refType) {
|
|
2295
|
-
throw new Error(
|
|
2296
|
-
`Reference type mismatch for field ${key}: expected ${refType}, got ${ref.type}`
|
|
2297
|
-
);
|
|
2298
|
-
}
|
|
2299
|
-
const target = await options2.resolveEntity(refType, ref.id);
|
|
2300
|
-
if (!target)
|
|
2301
|
-
throw new Error(
|
|
2302
|
-
`Broken reference: ${refType} ${ref.id} in field ${key}`
|
|
2303
|
-
);
|
|
2304
|
-
log?.(`Resolved ${refType} ${ref.id} from field ${key}`);
|
|
2305
|
-
if (autoValidate && refPolicy === "eager") {
|
|
2306
|
-
const targetSchema = getSchemaForType(refType);
|
|
2307
|
-
if (options2.maxDepth > 0 && targetSchema) {
|
|
2308
|
-
await targetSchema.validateComposition(target, {
|
|
2309
|
-
...options2,
|
|
2310
|
-
maxDepth: options2.maxDepth - 1
|
|
2311
|
-
});
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
|
-
} else if (def.type === "array" && def.itemType?.type === "ref") {
|
|
2315
|
-
const refType = def.itemType.refType;
|
|
2316
|
-
if (!refType)
|
|
2317
|
-
throw new Error(`Missing refType for reference array field ${key}`);
|
|
2318
|
-
const refs = value;
|
|
2319
|
-
for (const ref of refs) {
|
|
2320
|
-
if (ref.type && ref.type !== refType) {
|
|
2321
|
-
throw new Error(
|
|
2322
|
-
`Reference type mismatch for field ${key}: expected ${refType}, got ${ref.type}`
|
|
2323
|
-
);
|
|
2324
|
-
}
|
|
2325
|
-
const target = await options2.resolveEntity(refType, ref.id);
|
|
2326
|
-
if (!target)
|
|
2327
|
-
throw new Error(
|
|
2328
|
-
`Broken reference: ${refType} ${ref.id} in field ${key}`
|
|
2329
|
-
);
|
|
2330
|
-
log?.(`Resolved ${refType} ${ref.id} from field ${key}`);
|
|
2331
|
-
if (autoValidate && refPolicy === "eager") {
|
|
2332
|
-
const targetSchema = getSchemaForType(refType);
|
|
2333
|
-
if (options2.maxDepth > 0 && targetSchema) {
|
|
2334
|
-
await targetSchema.validateComposition(target, {
|
|
2335
|
-
...options2,
|
|
2336
|
-
maxDepth: options2.maxDepth - 1
|
|
2337
|
-
});
|
|
2338
|
-
}
|
|
2339
|
-
}
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
},
|
|
2344
|
-
/**
|
|
2345
|
-
* Returns the configured table name for this schema.
|
|
2346
|
-
*
|
|
2347
|
-
* @throws Error if no store/table name has been defined for this schema.
|
|
2348
|
-
*/
|
|
2349
|
-
tableName() {
|
|
2350
|
-
if (!store || store === "") {
|
|
2351
|
-
throw new Error("Store is not defined for this schema");
|
|
2352
|
-
}
|
|
2353
|
-
return store;
|
|
2354
|
-
},
|
|
2355
|
-
serialize(input, options2 = {}) {
|
|
2356
|
-
if (typeof input !== "object" || input === null) {
|
|
2357
|
-
return {};
|
|
2358
|
-
}
|
|
2359
|
-
const serialized = {};
|
|
2360
|
-
for (const [key, def] of Object.entries(schema._shape)) {
|
|
2361
|
-
if (!shouldIncludeField(def, options2)) {
|
|
2362
|
-
continue;
|
|
2363
|
-
}
|
|
2364
|
-
if (!Object.prototype.hasOwnProperty.call(input, key)) {
|
|
2365
|
-
continue;
|
|
2366
|
-
}
|
|
2367
|
-
const value = serializeFieldValue(def, input[key], options2);
|
|
2368
|
-
if (value !== void 0) {
|
|
2369
|
-
serialized[key] = value;
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
return serialized;
|
|
2373
|
-
},
|
|
2374
|
-
/**
|
|
2375
|
-
* Transforms an input object for persistence by applying PII protection
|
|
2376
|
-
* according to field annotations (e.g., encryption and hashing).
|
|
2377
|
-
*
|
|
2378
|
-
* @param input The raw entity data.
|
|
2379
|
-
* @param encryptFn Function used to encrypt sensitive values.
|
|
2380
|
-
* @param hashFn Function used to hash sensitive values.
|
|
2381
|
-
* @returns A new object safe to store.
|
|
2382
|
-
*/
|
|
2383
|
-
prepareForStorage(input, encryptFn, hashFn) {
|
|
2384
|
-
return prepareForStorage(_shape, input, encryptFn, hashFn);
|
|
2385
|
-
},
|
|
2386
|
-
/**
|
|
2387
|
-
* Reverses storage transformations for read paths (e.g., decrypts values)
|
|
2388
|
-
* according to PII annotations, returning a consumer-friendly object.
|
|
2389
|
-
*
|
|
2390
|
-
* @param stored Data retrieved from storage.
|
|
2391
|
-
* @param decryptFn Function used to decrypt values that were encrypted on write.
|
|
2392
|
-
* @returns A new object suitable for application consumption.
|
|
2393
|
-
*/
|
|
2394
|
-
prepareForRead(stored, decryptFn) {
|
|
2395
|
-
return prepareForRead(_shape, stored, decryptFn);
|
|
2396
|
-
},
|
|
2397
|
-
/**
|
|
2398
|
-
* Produces a log-safe copy of the provided data by redacting or pseudonymizing
|
|
2399
|
-
* PII fields in accordance with field annotations.
|
|
2400
|
-
*
|
|
2401
|
-
* @param data Arbitrary data to sanitize for logging.
|
|
2402
|
-
* @param pseudonymFn Function producing stable pseudonyms for sensitive values.
|
|
2403
|
-
* @returns A copy safe to emit to logs.
|
|
2404
|
-
*/
|
|
2405
|
-
sanitizeForLog(data, pseudonymFn) {
|
|
2406
|
-
return sanitizeForLog(_shape, data, pseudonymFn);
|
|
2407
|
-
},
|
|
2408
|
-
/**
|
|
2409
|
-
* Returns a list of fields annotated with PII metadata for auditing purposes.
|
|
2410
|
-
* Each entry includes classification, required action, and optional log policy.
|
|
2411
|
-
*/
|
|
2412
|
-
getPiiAudit() {
|
|
2413
|
-
return getPiiAudit(_shape);
|
|
2414
|
-
},
|
|
2415
|
-
/**
|
|
2416
|
-
* Produces a copy of stored data suitable for data deletion flows by scrubbing
|
|
2417
|
-
* or blanking PII per field annotations.
|
|
2418
|
-
*
|
|
2419
|
-
* @param stored Data as persisted.
|
|
2420
|
-
* @returns A copy with PII removed or neutralized for deletion.
|
|
2421
|
-
*/
|
|
2422
|
-
scrubPiiForDelete(stored) {
|
|
2423
|
-
return scrubPiiForDelete(_shape, stored);
|
|
2424
|
-
},
|
|
2425
|
-
/**
|
|
2426
|
-
* Returns a normalized description of the schema suitable for documentation
|
|
2427
|
-
* or UI rendering (type, optionality, enum values, PII flags, etc.).
|
|
2428
|
-
*/
|
|
2429
|
-
describe() {
|
|
2430
|
-
const description = {};
|
|
2431
|
-
for (const [key, def] of Object.entries(schema._shape)) {
|
|
2432
|
-
description[key] = {
|
|
2433
|
-
type: def.type,
|
|
2434
|
-
optional: isOptional(def),
|
|
2435
|
-
immutable: !!def.isImmutable,
|
|
2436
|
-
system: !!def.isSystem,
|
|
2437
|
-
description: def._description ?? "",
|
|
2438
|
-
version: def._version ?? "",
|
|
2439
|
-
deprecated: def.deprecated ?? false,
|
|
2440
|
-
deprecatedVersion: def.deprecatedVersion === void 0 ? null : def.deprecatedVersion,
|
|
2441
|
-
enum: getEnumValues(def) ?? null,
|
|
2442
|
-
refType: def.refType ?? null,
|
|
2443
|
-
pii: !def._pii || def._pii.classification === "none" ? null : def._pii,
|
|
2444
|
-
exposure: def._exposure ?? "public"
|
|
2445
|
-
};
|
|
1628
|
+
const sevenDayWindow = value.rollingWindows.find(
|
|
1629
|
+
(window) => window.period === "7-days"
|
|
1630
|
+
);
|
|
1631
|
+
const thirtyDayWindow = value.rollingWindows.find(
|
|
1632
|
+
(window) => window.period === "30-days"
|
|
1633
|
+
);
|
|
1634
|
+
const ninetyDayWindow = value.rollingWindows.find(
|
|
1635
|
+
(window) => window.period === "90-days"
|
|
1636
|
+
);
|
|
1637
|
+
if (sevenDayWindow === void 0 || thirtyDayWindow === void 0 || ninetyDayWindow === void 0) {
|
|
1638
|
+
return false;
|
|
2446
1639
|
}
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
1640
|
+
const statistics = satisfactionStatistics(
|
|
1641
|
+
value.starDistribution,
|
|
1642
|
+
value.acceptedReviewCount
|
|
1643
|
+
);
|
|
1644
|
+
const lagSeconds = Math.floor(
|
|
1645
|
+
(Date.parse(String(value.generatedAt)) - Date.parse(String(value.windowEnd))) / 1e3
|
|
1646
|
+
);
|
|
1647
|
+
const satisfactionDrop = sevenDayWindow.reviewCount >= 20 && typeof value.previousPeriodDeltaStars === "number" && value.previousPeriodDeltaStars <= -0.3;
|
|
1648
|
+
return value.revision >= 1 && value.processorLagSeconds === lagSeconds && distributions.every(hasUniqueIds) && distributionTotal(value.starDistribution) === value.acceptedReviewCount && distributionTotal(value.sentimentDistribution) <= value.acceptedReviewCount && distributionCountsAtMost(
|
|
1649
|
+
value.sentimentDistribution,
|
|
1650
|
+
value.acceptedReviewCount
|
|
1651
|
+
) && distributionTotal(value.intentDistribution) <= value.acceptedReviewCount * 8 && distributionCountsAtMost(
|
|
1652
|
+
value.intentDistribution,
|
|
1653
|
+
value.acceptedReviewCount
|
|
1654
|
+
) && periods.length === 3 && new Set(periods).size === 3 && (statistics === void 0 && value.meanStars === void 0 && value.medianStars === void 0 || statistics !== void 0 && value.meanStars === statistics.mean && value.medianStars === statistics.median) && advisoryCodesAreConsistent(value.advisories, {
|
|
1655
|
+
"satisfaction-drop": satisfactionDrop ? {
|
|
1656
|
+
level: "advisory",
|
|
1657
|
+
triggerCount: sevenDayWindow.reviewCount,
|
|
1658
|
+
recommendationIds: ["review-top-intents"]
|
|
1659
|
+
} : void 0,
|
|
1660
|
+
"processor-stale": lagSeconds > 2.5 * 60 * 60 ? {
|
|
1661
|
+
level: "critical",
|
|
1662
|
+
triggerCount: lagSeconds,
|
|
1663
|
+
recommendationIds: ["verify-processor-health"]
|
|
1664
|
+
} : void 0
|
|
1665
|
+
}) && value.rollingWindows.every(
|
|
1666
|
+
(window) => window.reviewCount === 0 && window.meanStars === void 0 || window.reviewCount > 0 && window.meanStars !== void 0
|
|
1667
|
+
) && value.acceptedReviewCount <= sevenDayWindow.reviewCount && sevenDayWindow.reviewCount <= thirtyDayWindow.reviewCount && thirtyDayWindow.reviewCount <= ninetyDayWindow.reviewCount;
|
|
2453
1668
|
}
|
|
2454
|
-
}
|
|
2455
|
-
globalSchemaRegistry.set(entityType, schema);
|
|
2456
|
-
return schema;
|
|
2457
|
-
}
|
|
2458
|
-
function getSchemaForType(type) {
|
|
2459
|
-
return globalSchemaRegistry.get(type);
|
|
2460
|
-
}
|
|
2461
|
-
function getAllSchemas() {
|
|
2462
|
-
return Array.from(globalSchemaRegistry.values());
|
|
2463
|
-
}
|
|
2464
|
-
function renderSchemaDescription(schema) {
|
|
2465
|
-
const meta = schema.describe();
|
|
2466
|
-
return {
|
|
2467
|
-
title: `${meta.entityType} (v${meta.version})`,
|
|
2468
|
-
fields: Object.entries(meta.shape).map(([name, def]) => ({
|
|
2469
|
-
name,
|
|
2470
|
-
type: def.type,
|
|
2471
|
-
optional: def.optional,
|
|
2472
|
-
description: def.description,
|
|
2473
|
-
deprecated: def.deprecated,
|
|
2474
|
-
pii: def.pii ? def.pii.classification : void 0,
|
|
2475
|
-
exposure: def.exposure
|
|
2476
|
-
}))
|
|
2477
|
-
};
|
|
2478
|
-
}
|
|
2479
|
-
|
|
2480
|
-
// src/components.ts
|
|
2481
|
-
var componentSchemaRegistry = /* @__PURE__ */ new Map();
|
|
2482
|
-
function createComponentSchema(shape, name, version, tableName = "", schemaValidator = () => true) {
|
|
2483
|
-
const schema = createSchema(shape, name, {
|
|
2484
|
-
version,
|
|
2485
|
-
piiEnforcement: "strict",
|
|
2486
|
-
table: tableName,
|
|
2487
|
-
schemaValidator
|
|
2488
|
-
});
|
|
2489
|
-
registerComponentSchema(name, schema);
|
|
2490
|
-
return schema;
|
|
2491
|
-
}
|
|
2492
|
-
function registerComponentSchema(type, schema) {
|
|
2493
|
-
componentSchemaRegistry.set(type, schema);
|
|
2494
|
-
}
|
|
2495
|
-
function getComponentSchema(type) {
|
|
2496
|
-
return componentSchemaRegistry.get(type);
|
|
2497
|
-
}
|
|
2498
|
-
function getAllComponentSchemas() {
|
|
2499
|
-
return Array.from(componentSchemaRegistry.entries());
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
// src/player-system-events.ts
|
|
2503
|
-
var PLAYER_SYSTEM_EVENT_KINDS = [
|
|
2504
|
-
"combat",
|
|
2505
|
-
"social",
|
|
2506
|
-
"mission",
|
|
2507
|
-
"crafting",
|
|
2508
|
-
"world"
|
|
2509
|
-
];
|
|
2510
|
-
var PLAYER_SYSTEM_ACHIEVEMENT_TIERS = [
|
|
2511
|
-
"bronze",
|
|
2512
|
-
"silver",
|
|
2513
|
-
"gold"
|
|
2514
|
-
];
|
|
2515
|
-
var PLAYER_SYSTEM_ACHIEVEMENT_TRACK_STATUSES = [
|
|
2516
|
-
"tracking",
|
|
2517
|
-
"earned"
|
|
2518
|
-
];
|
|
2519
|
-
var playerIdField = () => field.string().min(1).max(128).PID({
|
|
2520
|
-
classification: "low",
|
|
2521
|
-
action: "none",
|
|
2522
|
-
logHandling: "pseudonym",
|
|
2523
|
-
purpose: "a stable player-scoped storage identifier"
|
|
2524
|
-
}).description(
|
|
2525
|
-
"Stable player identifier; the only personal identifier retained in event projections"
|
|
1669
|
+
}
|
|
2526
1670
|
);
|
|
2527
|
-
var
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
id: eventIdField(),
|
|
2533
|
-
playerId: playerIdField(),
|
|
2534
|
-
occurredAt: field.dateTimeISO(),
|
|
2535
|
-
kind: eventKindField(),
|
|
2536
|
-
title: eventTitleField(),
|
|
2537
|
-
summary: eventSummaryField(),
|
|
2538
|
-
highlighted: defaultHighlighted ? field.boolean().default(false) : field.boolean()
|
|
2539
|
-
});
|
|
2540
|
-
var eventHighlightFields = () => ({
|
|
2541
|
-
id: eventIdField(),
|
|
2542
|
-
title: eventTitleField(),
|
|
2543
|
-
summary: eventSummaryField(),
|
|
2544
|
-
occurredAt: field.dateTimeISO(),
|
|
2545
|
-
kind: eventKindField()
|
|
2546
|
-
});
|
|
2547
|
-
var eventEntryFields = () => ({
|
|
2548
|
-
id: eventIdField(),
|
|
2549
|
-
title: eventTitleField(),
|
|
2550
|
-
summary: eventSummaryField(),
|
|
2551
|
-
occurredAt: field.dateTimeISO(),
|
|
2552
|
-
kind: eventKindField(),
|
|
2553
|
-
highlighted: field.boolean()
|
|
2554
|
-
});
|
|
2555
|
-
var achievementFields = () => ({
|
|
2556
|
-
id: eventIdField(),
|
|
2557
|
-
title: eventTitleField(),
|
|
2558
|
-
tier: field.string().enum(PLAYER_SYSTEM_ACHIEVEMENT_TIERS),
|
|
2559
|
-
progressLabel: eventSummaryField(),
|
|
2560
|
-
earnedAt: field.dateTimeISO().optional()
|
|
1671
|
+
var publicWeeklyPointShape = () => ({
|
|
1672
|
+
weekStart: field.dateTimeISO(),
|
|
1673
|
+
state: field.string().enum(["published", "suppressed"]),
|
|
1674
|
+
acceptedReviewCount: positiveCountField().optional(),
|
|
1675
|
+
averageStars: field.number().min(1).max(5).optional()
|
|
2561
1676
|
});
|
|
2562
|
-
var
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
target: field.number().min(1).max(1e3),
|
|
2567
|
-
status: field.string().enum(PLAYER_SYSTEM_ACHIEVEMENT_TRACK_STATUSES)
|
|
1677
|
+
var publicComparisonShape = () => ({
|
|
1678
|
+
previousAcceptedReviewCount: positiveCountField(),
|
|
1679
|
+
previousAverageStars: field.number().min(1).max(5),
|
|
1680
|
+
deltaStars: field.number().min(-4).max(4)
|
|
2568
1681
|
});
|
|
2569
|
-
var
|
|
1682
|
+
var FeedbackPublicSummarySchema = createSchema(
|
|
2570
1683
|
{
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
1684
|
+
snapshotId: uuidField(),
|
|
1685
|
+
generatedAt: field.dateTimeISO(),
|
|
1686
|
+
asOf: field.dateTimeISO(),
|
|
1687
|
+
freshness: field.string().enum(["fresh", "stale"]),
|
|
1688
|
+
reportAgeSeconds: countField(),
|
|
1689
|
+
rollingWindowDays: field.number().enum([90]),
|
|
1690
|
+
state: field.string().enum(["published", "suppressed"]),
|
|
1691
|
+
acceptedReviewCount: positiveCountField().optional(),
|
|
1692
|
+
averageStars: field.number().min(1).max(5).optional(),
|
|
1693
|
+
trend: field.string().enum(["up", "flat", "down"]).optional(),
|
|
1694
|
+
comparison: field.object(publicComparisonShape()).optional(),
|
|
1695
|
+
weeklyPoints: field.array(field.object(publicWeeklyPointShape())).min(13).max(13)
|
|
2574
1696
|
},
|
|
2575
|
-
"
|
|
1697
|
+
"feedback-public-summary",
|
|
2576
1698
|
{
|
|
2577
|
-
|
|
2578
|
-
|
|
1699
|
+
...strictOptions,
|
|
1700
|
+
schemaValidator: (value) => {
|
|
1701
|
+
const summaryIsSuppressed = value.state === "suppressed" && value.acceptedReviewCount === void 0 && value.averageStars === void 0 && value.trend === void 0 && value.comparison === void 0;
|
|
1702
|
+
const comparisonDelta = value.comparison === void 0 ? void 0 : roundRate(
|
|
1703
|
+
value.averageStars - value.comparison.previousAverageStars
|
|
1704
|
+
);
|
|
1705
|
+
const expectedTrend = comparisonDelta === void 0 ? void 0 : comparisonDelta >= 0.1 ? "up" : comparisonDelta <= -0.1 ? "down" : "flat";
|
|
1706
|
+
const comparisonIsConsistent = value.comparison === void 0 && value.trend === void 0 || value.comparison !== void 0 && value.comparison.previousAcceptedReviewCount >= 10 && value.comparison.deltaStars === comparisonDelta && value.trend === expectedTrend;
|
|
1707
|
+
const summaryIsPublished = value.state === "published" && typeof value.acceptedReviewCount === "number" && value.acceptedReviewCount >= 10 && typeof value.averageStars === "number" && comparisonIsConsistent;
|
|
1708
|
+
if (!summaryIsSuppressed && !summaryIsPublished) {
|
|
1709
|
+
return false;
|
|
1710
|
+
}
|
|
1711
|
+
const weekStarts = value.weeklyPoints.map(
|
|
1712
|
+
(point) => point.weekStart
|
|
1713
|
+
);
|
|
1714
|
+
const asOf = Date.parse(String(value.asOf));
|
|
1715
|
+
const generatedAt = Date.parse(String(value.generatedAt));
|
|
1716
|
+
if (!Number.isFinite(asOf) || !Number.isFinite(generatedAt) || new Date(asOf).toISOString() !== value.asOf || new Date(asOf).getUTCHours() !== 0 || new Date(asOf).getUTCMinutes() !== 0 || new Date(asOf).getUTCSeconds() !== 0 || new Date(asOf).getUTCMilliseconds() !== 0 || generatedAt < asOf || value.reportAgeSeconds !== Math.floor((generatedAt - asOf) / 1e3)) {
|
|
1717
|
+
return false;
|
|
1718
|
+
}
|
|
1719
|
+
const daysSinceMonday = (new Date(asOf).getUTCDay() + 6) % 7;
|
|
1720
|
+
const lastWeekStart = asOf - daysSinceMonday * 24 * 60 * 60 * 1e3;
|
|
1721
|
+
const weeksAreCanonical = weekStarts.every(
|
|
1722
|
+
(weekStart, index) => Date.parse(weekStart) === lastWeekStart - (12 - index) * 7 * 24 * 60 * 60 * 1e3
|
|
1723
|
+
);
|
|
1724
|
+
return new Set(weekStarts).size === weekStarts.length && weeksAreCanonical && (value.state !== "suppressed" || value.weeklyPoints.every(
|
|
1725
|
+
(point) => point.state === "suppressed"
|
|
1726
|
+
)) && value.weeklyPoints.every(
|
|
1727
|
+
(point) => point.state === "suppressed" && point.acceptedReviewCount === void 0 && point.averageStars === void 0 || point.state === "published" && typeof point.acceptedReviewCount === "number" && point.acceptedReviewCount >= 10 && typeof point.averageStars === "number" && (value.state !== "published" || point.acceptedReviewCount <= value.acceptedReviewCount)
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
2579
1730
|
}
|
|
2580
1731
|
);
|
|
2581
|
-
var
|
|
1732
|
+
var FeedbackProcessorCheckpointSchema = createSchema(
|
|
2582
1733
|
{
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
})
|
|
2590
|
-
)
|
|
1734
|
+
checkpointId: safeIdField(160),
|
|
1735
|
+
processor: field.string().enum(["bug-hourly", "review-daily", "commit-reconciliation"]),
|
|
1736
|
+
windowKey: safeIdField(160),
|
|
1737
|
+
revision: revisionField(),
|
|
1738
|
+
completedAt: field.dateTimeISO(),
|
|
1739
|
+
reportId: uuidField().optional()
|
|
2591
1740
|
},
|
|
2592
|
-
"
|
|
1741
|
+
"feedback-processor-checkpoint",
|
|
2593
1742
|
{
|
|
2594
|
-
|
|
2595
|
-
|
|
1743
|
+
...strictOptions,
|
|
1744
|
+
schemaValidator: (value) => isProcessorArtifactConsistent(value, "checkpoint") && value.revision >= 1 && (value.processor === "commit-reconciliation" && value.reportId === void 0 || value.processor !== "commit-reconciliation" && value.reportId !== void 0)
|
|
2596
1745
|
}
|
|
2597
1746
|
);
|
|
2598
|
-
var
|
|
1747
|
+
var FeedbackMaterializationManifestSchema = createSchema(
|
|
2599
1748
|
{
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
1749
|
+
manifestId: uuidField(),
|
|
1750
|
+
processor: field.string().enum(["bug-hourly", "review-daily", "commit-reconciliation"]),
|
|
1751
|
+
windowKey: safeIdField(160),
|
|
1752
|
+
revision: revisionField(),
|
|
1753
|
+
generatedAt: field.dateTimeISO(),
|
|
1754
|
+
sourcePacketCount: countField(),
|
|
1755
|
+
lateArrivalCount: countField(),
|
|
1756
|
+
outputReportId: uuidField().optional(),
|
|
1757
|
+
status: field.string().enum(["published", "corrected", "no-op"])
|
|
2607
1758
|
},
|
|
2608
|
-
"
|
|
1759
|
+
"feedback-materialization-manifest",
|
|
2609
1760
|
{
|
|
2610
|
-
|
|
2611
|
-
|
|
1761
|
+
...strictOptions,
|
|
1762
|
+
schemaValidator: (value) => {
|
|
1763
|
+
if (!isProcessorArtifactConsistent(value, "manifest") || value.lateArrivalCount > value.sourcePacketCount) {
|
|
1764
|
+
return false;
|
|
1765
|
+
}
|
|
1766
|
+
if (value.processor === "commit-reconciliation") {
|
|
1767
|
+
return value.status === "no-op" && value.revision >= 1 && value.outputReportId === void 0 && value.lateArrivalCount === 0;
|
|
1768
|
+
}
|
|
1769
|
+
return value.status === "published" && value.revision === 1 && value.lateArrivalCount === 0 && value.outputReportId !== void 0 || value.status === "corrected" && value.revision >= 2 && value.lateArrivalCount > 0 && value.outputReportId !== void 0;
|
|
1770
|
+
}
|
|
2612
1771
|
}
|
|
2613
1772
|
);
|
|
2614
1773
|
export {
|
|
1774
|
+
FEEDBACK_ANALYSIS_MODEL_VERSIONS,
|
|
1775
|
+
FEEDBACK_BACKEND_BUCKETS,
|
|
1776
|
+
FEEDBACK_BUG_COOLDOWN_SECONDS,
|
|
1777
|
+
FEEDBACK_CONTRACT_VERSION,
|
|
1778
|
+
FEEDBACK_FRAME_RATE_BUCKETS,
|
|
1779
|
+
FEEDBACK_FRAME_TIME_BUCKETS,
|
|
1780
|
+
FEEDBACK_GAME_COUNTER_CODES,
|
|
1781
|
+
FEEDBACK_GAME_COUNTER_MAX_COUNT,
|
|
1782
|
+
FEEDBACK_GAME_COUNTER_MAX_ITEMS,
|
|
1783
|
+
FEEDBACK_GAME_ERROR_CODES,
|
|
1784
|
+
FEEDBACK_GAME_FEATURE_IDS,
|
|
1785
|
+
FEEDBACK_GAME_PROVENANCE_CONTRACTS,
|
|
1786
|
+
FEEDBACK_GAME_PROVENANCE_CONTRACT_IDS,
|
|
1787
|
+
FEEDBACK_GAME_SURFACE_IDS,
|
|
1788
|
+
FEEDBACK_INTENT_IDS,
|
|
1789
|
+
FEEDBACK_ISSUE_TYPES,
|
|
1790
|
+
FEEDBACK_PERSISTABLE_ISSUE_TYPES,
|
|
1791
|
+
FEEDBACK_PRIVACY_POLICY_VERSIONS,
|
|
1792
|
+
FEEDBACK_RENDERER_BUCKETS,
|
|
1793
|
+
FEEDBACK_REVIEW_COOLDOWN_SECONDS,
|
|
1794
|
+
FEEDBACK_SATISFACTION_LEVELS,
|
|
1795
|
+
FEEDBACK_SENTIMENT_BUCKETS,
|
|
1796
|
+
FEEDBACK_SEVERITY_LEVELS,
|
|
1797
|
+
FEEDBACK_STRUCTURED_ONLY_REASON_CODES,
|
|
1798
|
+
FEEDBACK_SURFACE_DEFINITIONS,
|
|
1799
|
+
FEEDBACK_SURFACE_IDS,
|
|
1800
|
+
FEEDBACK_THEME_IDS,
|
|
1801
|
+
FEEDBACK_UNICODE_NORMALIZATION_PROFILE,
|
|
1802
|
+
FEEDBACK_VIEWPORT_BUCKETS,
|
|
1803
|
+
FeedbackAcceptanceReceiptSchema,
|
|
1804
|
+
FeedbackAnalysisReceiptSchema,
|
|
1805
|
+
FeedbackBugPacketSchema,
|
|
1806
|
+
FeedbackBugSubmissionRequestSchema,
|
|
1807
|
+
FeedbackContextSchema,
|
|
1808
|
+
FeedbackDailySatisfactionReportSchema,
|
|
1809
|
+
FeedbackDerivedAnalysisSchema,
|
|
1810
|
+
FeedbackDraftPacketSchema,
|
|
1811
|
+
FeedbackDraftReceiptSchema,
|
|
1812
|
+
FeedbackDraftUpsertRequestSchema,
|
|
1813
|
+
FeedbackEncryptedNarrativeEnvelopeSchema,
|
|
1814
|
+
FeedbackGameDiagnosticsSchema,
|
|
1815
|
+
FeedbackGameReconstructionManifestSchema,
|
|
1816
|
+
FeedbackHourlyBugReportSchema,
|
|
1817
|
+
FeedbackMaterializationManifestSchema,
|
|
1818
|
+
FeedbackProcessorCheckpointSchema,
|
|
1819
|
+
FeedbackPublicSummarySchema,
|
|
1820
|
+
FeedbackReviewPacketSchema,
|
|
1821
|
+
FeedbackReviewSubmissionRequestSchema,
|
|
1822
|
+
FeedbackRichTextAstSchema,
|
|
1823
|
+
FeedbackStructuredOnlyAnalysisResultSchema,
|
|
1824
|
+
FeedbackSurfaceCatalogSchema,
|
|
1825
|
+
FeedbackTransientAnalysisRequestSchema,
|
|
2615
1826
|
FieldBuilder,
|
|
2616
1827
|
IsoLanguageCode,
|
|
2617
1828
|
PLAYER_SYSTEM_ACHIEVEMENT_TIERS,
|
|
@@ -2620,6 +1831,8 @@ export {
|
|
|
2620
1831
|
PlayerSystemCuratedSnapshotSchema,
|
|
2621
1832
|
PlayerSystemNormalizedEventBatchSchema,
|
|
2622
1833
|
PlayerSystemObservedEventBatchSchema,
|
|
1834
|
+
SCHEMA_IDENTITY_POLICIES,
|
|
1835
|
+
SCHEMA_UNKNOWN_FIELDS_POLICIES,
|
|
2623
1836
|
createComponentSchema,
|
|
2624
1837
|
createSchema,
|
|
2625
1838
|
field,
|
|
@@ -2642,7 +1855,10 @@ export {
|
|
|
2642
1855
|
validateCountryCode,
|
|
2643
1856
|
validateCurrencyCode,
|
|
2644
1857
|
validateDateTimeISO,
|
|
1858
|
+
validateDisplayName,
|
|
2645
1859
|
validateEmail,
|
|
1860
|
+
validateFeedbackGameDiagnostics,
|
|
1861
|
+
validateFeedbackTransientAnalysisRequest,
|
|
2646
1862
|
validateLanguage,
|
|
2647
1863
|
validateName,
|
|
2648
1864
|
validatePercentage,
|