@plasius/schema 1.3.0 → 1.3.1

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.
Files changed (36) hide show
  1. package/README.md +188 -1
  2. package/THIRD_PARTY_NOTICES.md +75 -0
  3. package/dist/chunk-6FE4FLKF.js +254 -0
  4. package/dist/chunk-6FE4FLKF.js.map +1 -0
  5. package/dist/chunk-AG2NAPDC.js +117 -0
  6. package/dist/chunk-AG2NAPDC.js.map +1 -0
  7. package/dist/chunk-UQHPDQZX.js +2606 -0
  8. package/dist/chunk-UQHPDQZX.js.map +1 -0
  9. package/dist/feedback-diagnostics-BaniQAFS.d.cts +353 -0
  10. package/dist/feedback-diagnostics-CnsI0B9-.d.ts +353 -0
  11. package/dist/feedback-diagnostics-vocabulary.cjs +156 -0
  12. package/dist/feedback-diagnostics-vocabulary.cjs.map +1 -0
  13. package/dist/feedback-diagnostics-vocabulary.d.cts +70 -0
  14. package/dist/feedback-diagnostics-vocabulary.d.ts +70 -0
  15. package/dist/feedback-diagnostics-vocabulary.js +33 -0
  16. package/dist/feedback-diagnostics-vocabulary.js.map +1 -0
  17. package/dist/feedback-diagnostics.cjs +2696 -0
  18. package/dist/feedback-diagnostics.cjs.map +1 -0
  19. package/dist/feedback-diagnostics.d.cts +2 -0
  20. package/dist/feedback-diagnostics.d.ts +2 -0
  21. package/dist/feedback-diagnostics.js +39 -0
  22. package/dist/feedback-diagnostics.js.map +1 -0
  23. package/dist/feedback-unicode-profile.cjs +283 -0
  24. package/dist/feedback-unicode-profile.cjs.map +1 -0
  25. package/dist/feedback-unicode-profile.d.cts +28 -0
  26. package/dist/feedback-unicode-profile.d.ts +28 -0
  27. package/dist/feedback-unicode-profile.js +13 -0
  28. package/dist/feedback-unicode-profile.js.map +1 -0
  29. package/dist/index.cjs +2133 -14
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +879 -286
  32. package/dist/index.d.ts +879 -286
  33. package/dist/index.js +1516 -2300
  34. package/dist/index.js.map +1 -1
  35. package/package.json +36 -12
  36. package/unicode/feedback-unicode-15.1.0-unassigned.json +13 -0
package/dist/index.js CHANGED
@@ -1,511 +1,57 @@
1
- // src/field.builder.ts
2
- function mergeValidatorResult(previousResult, nextValid) {
3
- if (previousResult !== true) {
4
- return previousResult;
5
- }
6
- return nextValid;
7
- }
8
- var FieldBuilder = class _FieldBuilder {
9
- constructor(type, options = {}) {
10
- this.type = type;
11
- this._shape = options.shape;
12
- this.itemType = options.itemType;
13
- this.refType = options.refType;
14
- }
15
- type;
16
- _type;
17
- _storageType;
18
- isSystem = false;
19
- isImmutable = false;
20
- isRequired = true;
21
- _validator;
22
- _description = "";
23
- _version = "1.0.0";
24
- _default;
25
- _upgrade;
26
- _shape;
27
- itemType;
28
- refType;
29
- _pii = {
30
- classification: "none",
31
- action: "none",
32
- logHandling: "plain",
33
- purpose: "an ordinary value"
34
- };
35
- _exposure = "public";
36
- enumValues;
37
- immutable() {
38
- this.isImmutable = true;
39
- return this;
40
- }
41
- system() {
42
- this.isSystem = true;
43
- return this;
44
- }
45
- exposure(exposure) {
46
- this._exposure = exposure;
47
- return this;
48
- }
49
- internal() {
50
- return this.exposure("internal");
51
- }
52
- public() {
53
- return this.exposure("public");
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 validateName(value) {
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
- const namePattern = /^[\p{L}\p{M}'\-. ]+$/u;
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/validation/languageCode.BCP47.ts
784
- var IsoLanguageCode = /* @__PURE__ */ ((IsoLanguageCode2) => {
785
- IsoLanguageCode2["Afar"] = "aa";
786
- IsoLanguageCode2["Abkhazian"] = "ab";
787
- IsoLanguageCode2["Afrikaans"] = "af";
788
- IsoLanguageCode2["Akan"] = "ak";
789
- IsoLanguageCode2["Albanian"] = "sq";
790
- IsoLanguageCode2["Amharic"] = "am";
791
- IsoLanguageCode2["Arabic"] = "ar";
792
- IsoLanguageCode2["Aragonese"] = "an";
793
- IsoLanguageCode2["Armenian"] = "hy";
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 isExtensionSubtag(value) {
986
- return /^[A-Za-z0-9]{2,8}$/.test(value);
297
+ function registerComponentSchema(type, schema) {
298
+ componentSchemaRegistry.set(type, schema);
987
299
  }
988
- function isPrivateUseSingleton(value) {
989
- return value.toLowerCase() === "x";
300
+ function getComponentSchema(type) {
301
+ return componentSchemaRegistry.get(type);
990
302
  }
991
- function isPrivateUseSubtag(value) {
992
- return /^[A-Za-z0-9]{1,8}$/.test(value);
303
+ function getAllComponentSchemas() {
304
+ return Array.from(componentSchemaRegistry.entries());
993
305
  }
994
- function validateLanguage(value) {
995
- if (typeof value !== "string" || value.length === 0) return false;
996
- const parts = value.split("-");
997
- if (parts.some((part) => part.length === 0)) return false;
998
- const lowerTag = value.toLowerCase();
999
- if (GRANDFATHERED_TAGS.has(lowerTag)) return true;
1000
- if (parts[0]?.toLowerCase() === "x") {
1001
- return parts.length > 1 && parts.slice(1).every(isPrivateUseSubtag);
1002
- }
1003
- let i = 0;
1004
- const lang = parts[i];
1005
- if (!lang || !/^[A-Za-z]{2,3}$/.test(lang)) return false;
1006
- if (lang.length === 2 && !isIsoLanguageCode(lang)) return false;
1007
- i += 1;
1008
- if (lang.length <= 3) {
1009
- let extlangCount = 0;
1010
- while (i < parts.length && extlangCount < 3 && /^[A-Za-z]{3}$/.test(parts[i])) {
1011
- i += 1;
1012
- extlangCount += 1;
1013
- }
1014
- }
1015
- if (i < parts.length && isScriptSubtag(parts[i])) {
1016
- i += 1;
1017
- }
1018
- if (i < parts.length && isRegionSubtag(parts[i].toUpperCase())) {
1019
- i += 1;
1020
- }
1021
- const variants = /* @__PURE__ */ new Set();
1022
- while (i < parts.length && isVariantSubtag(parts[i])) {
1023
- const variant = parts[i].toLowerCase();
1024
- if (variants.has(variant)) return false;
1025
- variants.add(variant);
1026
- i += 1;
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
- const extensionSingletons = /* @__PURE__ */ new Set();
1029
- while (i < parts.length && isExtensionSingleton(parts[i])) {
1030
- const singleton = parts[i].toLowerCase();
1031
- if (extensionSingletons.has(singleton)) return false;
1032
- extensionSingletons.add(singleton);
1033
- i += 1;
1034
- if (!(i < parts.length && isExtensionSubtag(parts[i]))) return false;
1035
- while (i < parts.length && isExtensionSubtag(parts[i])) {
1036
- i += 1;
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
- if (i < parts.length && isPrivateUseSingleton(parts[i])) {
1040
- i += 1;
1041
- if (!(i < parts.length && isPrivateUseSubtag(parts[i]))) return false;
1042
- while (i < parts.length && isPrivateUseSubtag(parts[i])) {
1043
- i += 1;
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
- return i === parts.length;
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/pii.ts
1159
- function enforcePIIField(parentKey, key, value, def, enforcement = "none", errors, logger) {
1160
- const path = parentKey ? `${parentKey}.${key}` : key;
1161
- if (def?._pii?.classification === "high" && (def?.isRequired ?? true)) {
1162
- const missing = value === void 0 || value === null || value === "";
1163
- if (missing) {
1164
- const msg = `High PII field must not be empty: ${path}`;
1165
- if (enforcement === "strict") {
1166
- errors?.push(msg);
1167
- return { shortCircuit: true };
1168
- }
1169
- if (enforcement === "warn") {
1170
- logger?.warn?.(`WARN (PII Enforcement): ${msg}`);
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
- return { shortCircuit: false };
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
- function prepareForStorage(shape, input, encryptFn, hashFn) {
1177
- const build = (def, value, key, out, ctx = {}) => {
1178
- if (!def) return;
1179
- const isMissing = value === void 0 || value === null;
1180
- if (def._pii?.action === "encrypt") {
1181
- if (!isMissing) out[key + "Encrypted"] = encryptFn(value);
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
- function prepareForRead(shape, stored, decryptFn) {
1234
- const readValue = (def, key, container, ctx = {}) => {
1235
- if (!def) return container?.[key];
1236
- const action = def._pii?.action;
1237
- if (action === "encrypt") {
1238
- const enc = container?.[key + "Encrypted"] ?? container?.[`${ctx.parentKey ?? key}Encrypted`];
1239
- return enc === void 0 ? void 0 : decryptFn(enc);
1240
- }
1241
- if (action === "hash") {
1242
- const h = container?.[key + "Hash"] ?? container?.[`${ctx.parentKey ?? key}Hash`];
1243
- return h === void 0 ? void 0 : h;
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
- if (action === "clear")
1246
- return container?.hasOwnProperty(key) ? null : void 0;
1247
- if (def.type === "object" && def._shape) {
1248
- const obj = {};
1249
- const source = container && Object.prototype.hasOwnProperty.call(container, key) ? container[key] : container ?? {};
1250
- for (const [childKey, childDef] of Object.entries(def._shape)) {
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
- if (def.type === "array" && def.itemType) {
1256
- const arr = container?.[key];
1257
- if (!Array.isArray(arr)) return arr;
1258
- return arr.map(
1259
- (item) => readValue(def.itemType, key, item, { parentKey: key, isArrayItem: true })
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
- if (def.type === "ref") {
1263
- const ref = { ...container?.[key] ?? {} };
1264
- const refShape = def._shape;
1265
- if (refShape) {
1266
- for (const [childKey, childDef] of Object.entries(refShape)) {
1267
- ref[childKey] = readValue(childDef, childKey, ref);
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
- return result;
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
- return output;
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 piiFields;
1341
- }
1342
- function scrubPiiForDelete(shape, stored) {
1343
- const result = Array.isArray(stored) ? [...stored] : { ...stored };
1344
- const setAtPath = (target, path, val) => {
1345
- let cur = target;
1346
- for (let i = 0; i < path.length - 1; i++) {
1347
- const p = path[i];
1348
- if (p === void 0) return;
1349
- if (cur[p] === void 0) {
1350
- const next = path[i + 1];
1351
- cur[p] = typeof next === "number" ? [] : {};
1352
- }
1353
- cur = cur[p];
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 last = path[path.length - 1];
1356
- if (last === void 0) return;
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 visit = (def, host, path, keyName) => {
1360
- if (!def) return;
1361
- const applyPath = (targetKey) => {
1362
- const last = path[path.length - 1];
1363
- if (last === void 0) return;
1364
- if (typeof last === "number") {
1365
- setAtPath(result, [...path, targetKey], null);
1366
- } else {
1367
- setAtPath(result, [...path.slice(0, -1), targetKey], null);
1368
- }
1369
- };
1370
- if (def._pii?.action === "encrypt") {
1371
- const targetKey = `${keyName}Encrypted`;
1372
- if (host && Object.prototype.hasOwnProperty.call(host, targetKey)) {
1373
- applyPath(targetKey);
1374
- }
1375
- return;
1376
- }
1377
- if (def._pii?.action === "hash") {
1378
- const targetKey = `${keyName}Hash`;
1379
- if (host && Object.prototype.hasOwnProperty.call(host, targetKey)) {
1380
- applyPath(targetKey);
1381
- }
1382
- return;
1383
- }
1384
- if (def._pii?.action === "clear") {
1385
- if (host && Object.prototype.hasOwnProperty.call(host, keyName)) {
1386
- setAtPath(result, path, null);
1387
- }
1388
- return;
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
- if (def.type === "object" && def._shape) {
1391
- const obj = host && Object.prototype.hasOwnProperty.call(host, keyName) ? host[keyName] : host;
1392
- for (const [k, childDef] of Object.entries(def._shape)) {
1393
- visit(childDef, obj, [...path, k], k);
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
- if (def.type === "array" && def.itemType) {
1398
- const arr = host?.[keyName];
1399
- if (Array.isArray(arr)) {
1400
- arr.forEach(
1401
- (item, idx) => visit(def.itemType, item, [...path, idx], keyName)
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
- if (def.type === "ref") {
1407
- const refShape = def._shape;
1408
- const ref = host?.[keyName];
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
- for (const key in shape) {
1417
- visit(shape[key], stored, [key], key);
960
+ iso = `${windowKey}:00.000Z`;
961
+ expectedLength = 16;
962
+ duration = 5 * 60 * 1e3;
1418
963
  }
1419
- return result;
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
- if (value === null) {
1451
- return null;
1452
- }
1453
- switch (def?.type) {
1454
- case "string":
1455
- case "number":
1456
- case "boolean":
1457
- return deepClone(value);
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
- function cmpSemver(a, b) {
1532
- const pa = a.split(".").map((n) => parseInt(n, 10));
1533
- const pb = b.split(".").map((n) => parseInt(n, 10));
1534
- for (let i = 0; i < 3; i++) {
1535
- const ai = pa[i] ?? 0;
1536
- const bi = pb[i] ?? 0;
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
- return 0;
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
- const steps = [...spec].sort((s1, s2) => cmpSemver(s1.to, s2.to));
1547
- let working = { ...input };
1548
- let fromVersion = ctx.from;
1549
- for (const step of steps) {
1550
- if (cmpSemver(fromVersion, step.to) < 0 && cmpSemver(step.to, ctx.to) <= 0) {
1551
- ctx.log?.(`Upgrading entity from v${fromVersion} \u2192 v${step.to}`);
1552
- const res = step.run(working, { ...ctx, from: fromVersion, to: step.to });
1553
- if (!res || res.ok !== true || !res.value) {
1554
- return {
1555
- ok: false,
1556
- errors: res?.errors?.length ? res.errors : [`Failed to upgrade entity from v${fromVersion} to v${step.to}`]
1557
- };
1558
- }
1559
- working = res.value;
1560
- fromVersion = step.to;
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
- return { ok: true, value: working };
1564
- }
1565
- function validateEnum(parentKey, value, enumValues) {
1566
- if (!enumValues) return;
1567
- const values = Array.isArray(enumValues) ? enumValues : Array.from(enumValues);
1568
- if (Array.isArray(value)) {
1569
- const invalid = value.filter((v) => !values.includes(v));
1570
- if (invalid.length > 0) {
1571
- return `Field ${parentKey} contains invalid enum values: ${invalid.join(
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
- } else {
1576
- if (!values.includes(value)) {
1577
- return `Field ${parentKey} must be one of: ${values.join(", ")}`;
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
- function getEnumValues(def) {
1582
- const src = Array.isArray(def?.enum) ? def.enum : Array.isArray(def?.enumValues) ? def.enumValues : Array.isArray(def?._enum) ? def._enum : Array.isArray(def?._enumValues) ? def._enumValues : def?._enumSet instanceof Set ? Array.from(def._enumSet) : void 0;
1583
- if (!src) return void 0;
1584
- const ok = src.every(
1585
- (v) => typeof v === "string" || typeof v === "number"
1586
- );
1587
- return ok ? src : void 0;
1588
- }
1589
- function applyDefault(def, current) {
1590
- if ((current === void 0 || current === null) && typeof def?.getDefault === "function") {
1591
- const next = def.getDefault();
1592
- if (next !== void 0) {
1593
- return { value: next, applied: true };
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
- return { value: current, applied: false };
1597
- }
1598
- function isOptional(def) {
1599
- return def?.isRequired === false;
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
- path,
1611
- code: defaultCode,
1612
- message: issue
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
- path: issue.path ?? path,
1617
- code: issue.code || defaultCode,
1618
- message: issue.message
1093
+ valid: true,
1094
+ value: result.value
1619
1095
  };
1620
- }
1621
- function normalizeValidatorIssues(path, result, defaultCode) {
1622
- if (result === true) {
1623
- return [];
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
- if (result === false) {
1626
- return [
1627
- {
1628
- path,
1629
- code: defaultCode,
1630
- message: `Invalid value for field: ${path}`
1631
- }
1632
- ];
1104
+ );
1105
+ var FeedbackDerivedAnalysisSchema = createSchema(
1106
+ analysisProjectionShape(),
1107
+ "feedback-derived-analysis",
1108
+ {
1109
+ ...strictOptions,
1110
+ schemaValidator: isAnalysisProjectionConsistent
1633
1111
  }
1634
- if (Array.isArray(result)) {
1635
- return result.map((issue) => normalizeValidationIssue(path, issue, defaultCode));
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
- return [normalizeValidationIssue(path, result, defaultCode)];
1638
- }
1639
- function checkMissingRequired(parentKey, key, value, def, errors) {
1640
- if (value === void 0 || value === null) {
1641
- const path = parentKey ? `${parentKey}.${key}` : key;
1642
- if (!isOptional(def)) {
1643
- errors.push(`Missing required field: ${path}`);
1644
- }
1645
- return { missing: true };
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
- return { missing: false };
1648
- }
1649
- function checkImmutable(parentKey, key, value, def, existing, errors) {
1650
- if (def.isImmutable && existing && existing[key] !== void 0 && value !== existing[key]) {
1651
- const path = parentKey ? `${parentKey}.${key}` : key;
1652
- errors.push(`Field is immutable: ${path}`);
1653
- return { immutableViolation: true };
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
- return { immutableViolation: false };
1656
- }
1657
- function runCustomValidator(parentKey, key, value, def, errors, issues) {
1658
- const validator = getValidator(def);
1659
- if (validator && value !== void 0 && value !== null) {
1660
- const path = parentKey ? `${parentKey}.${key}` : key;
1661
- const normalizedIssues = normalizeValidatorIssues(path, validator(value), "invalid_value");
1662
- if (normalizedIssues.length > 0) {
1663
- errors.push(...normalizedIssues.map((issue) => issue.message));
1664
- if (issues) {
1665
- issues.push(...normalizedIssues);
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
- return { invalid: true };
1668
- }
1669
- }
1670
- return { invalid: false };
1671
- }
1672
- function validateStringField(parentKey, key, value, def, errors) {
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
- function validateBooleanField(parentKey, key, value, _def, errors) {
1701
- const path = parentKey ? `${parentKey}.${key}` : key;
1702
- if (typeof value !== "boolean") {
1703
- errors.push(`Field ${path} must be boolean`);
1704
- }
1705
- }
1706
- function validateObjectChildren(parentKey, obj, shape, errors, issues, existing, piiEnforcement, logger) {
1707
- for (const [childKey, childDef] of Object.entries(shape)) {
1708
- let childValue = obj[childKey];
1709
- const existingChild = existing && typeof existing === "object" ? existing[childKey] : void 0;
1710
- const { value: withDefault, applied } = applyDefault(childDef, childValue);
1711
- if (applied) {
1712
- obj[childKey] = withDefault;
1713
- childValue = withDefault;
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
- function validateObjectField(parentKey, key, value, def, errors, issues, existing, piiEnforcement, logger) {
1765
- const path = parentKey ? `${parentKey}.${key}` : key;
1766
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
1767
- errors.push(`Field ${path} must be object`);
1768
- return;
1769
- }
1770
- const objShape = getShape(def);
1771
- if (objShape)
1772
- validateObjectChildren(path, value, objShape, errors, issues, existing, piiEnforcement, logger);
1773
- }
1774
- function validateArrayOfStrings(parentKey, key, arr, itemDef, errors) {
1775
- const path = parentKey ? `${parentKey}.${key}` : key;
1776
- if (!arr.every((v) => typeof v === "string")) {
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
- const validator = getValidator(itemDef);
1788
- if (validator) {
1789
- arr.forEach((v, idx) => {
1790
- if (!validator(v)) {
1791
- errors.push(`Invalid value for field: ${path}[${idx}]`);
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
- function validateArrayOfNumbers(parentKey, key, arr, itemDef, errors) {
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
- const validator = getValidator(itemDef);
1809
- if (validator) {
1810
- arr.forEach((v, idx) => {
1811
- if (!validator(v)) {
1812
- errors.push(`Invalid value for field: ${path}[${idx}]`);
1813
- }
1814
- });
1815
- }
1816
- }
1817
- function validateArrayOfBooleans(parentKey, key, arr, _itemDef, errors) {
1818
- const path = parentKey ? `${parentKey}.${key}` : key;
1819
- if (!arr.every((v) => typeof v === "boolean")) {
1820
- errors.push(`Field ${path} must be boolean[]`);
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
- const validator = getValidator(_itemDef);
1823
- if (validator) {
1824
- arr.forEach((v, idx) => {
1825
- if (!validator(v)) {
1826
- errors.push(`Invalid value for field: ${path}[${idx}]`);
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
- function validateArrayOfObjects(parentKey, key, arr, itemDef, errors, issues, existing, piiEnforcement, logger) {
1832
- const path = parentKey ? `${parentKey}.${key}` : key;
1833
- if (!Array.isArray(arr) || !arr.every((v) => typeof v === "object" && v !== null && !Array.isArray(v))) {
1834
- errors.push(`Field ${path} must be object[]`);
1835
- return;
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
- const itemShape = getShape(itemDef);
1838
- if (!itemShape) return;
1839
- arr.forEach((item, idx) => {
1840
- const itemParent = `${path}[${idx}]`;
1841
- const existingItem = Array.isArray(existing) ? existing[idx] : void 0;
1842
- for (const [childKey, childDef] of Object.entries(itemShape)) {
1843
- let childValue = item[childKey];
1844
- const existingChild = existingItem && typeof existingItem === "object" ? existingItem[childKey] : void 0;
1845
- const { value: withDefault, applied } = applyDefault(
1846
- childDef,
1847
- childValue
1848
- );
1849
- if (applied) {
1850
- item[childKey] = withDefault;
1851
- childValue = withDefault;
1852
- }
1853
- const { missing } = checkMissingRequired(
1854
- itemParent,
1855
- childKey,
1856
- childValue,
1857
- childDef,
1858
- errors
1859
- );
1860
- if (missing) continue;
1861
- const { immutableViolation } = checkImmutable(
1862
- itemParent,
1863
- childKey,
1864
- childValue,
1865
- childDef,
1866
- existingItem,
1867
- errors
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
- if (immutableViolation) continue;
1870
- const { shortCircuit } = enforcePIIField(
1871
- itemParent,
1872
- childKey,
1873
- childValue,
1874
- childDef,
1875
- piiEnforcement,
1876
- errors,
1877
- logger
1489
+ const severityFiveCount = distributionCountFor(
1490
+ value.severityDistribution,
1491
+ "5"
1878
1492
  );
1879
- if (shortCircuit) continue;
1880
- const { invalid } = runCustomValidator(
1881
- itemParent,
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
- if (invalid) continue;
1889
- validateByType(
1890
- itemParent,
1891
- childKey,
1892
- childValue,
1893
- childDef,
1894
- errors,
1895
- issues,
1896
- existingChild,
1897
- piiEnforcement,
1898
- logger
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
- const itemType = def.itemType?.type;
1910
- if (itemType === "string")
1911
- return validateArrayOfStrings(parentKey, key, value, def.itemType, errors);
1912
- if (itemType === "number")
1913
- return validateArrayOfNumbers(parentKey, key, value, def.itemType, errors);
1914
- if (itemType === "boolean")
1915
- return validateArrayOfBooleans(parentKey, key, value, def.itemType, errors);
1916
- if (itemType === "object")
1917
- return validateArrayOfObjects(
1918
- parentKey,
1919
- key,
1920
- value,
1921
- def.itemType,
1922
- errors,
1923
- issues,
1924
- existing,
1925
- piiEnforcement,
1926
- logger
1927
- );
1928
- if (itemType === "ref") {
1929
- const expectedType = def.itemType.refType;
1930
- value.forEach((ref, idx) => {
1931
- if (!ref || typeof ref !== "object" || ref === null || typeof ref.type !== "string" || typeof ref.id !== "string" || expectedType && ref.type !== expectedType) {
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 version = options.version || "1.0.0";
2074
- const store = options.table || "";
2075
- const schemaUpgrade = options.schemaUpgrade;
2076
- const schema = {
2077
- // 🔗 Define the schema shape
2078
- _shape: {
2079
- ...systemFields,
2080
- ..._shape
2081
- },
2082
- // 🔗 Metadata about the schema
2083
- meta: { entityType, version },
2084
- // 🔗 Validate input against the schema
2085
- validate(input, existing) {
2086
- const errors = [];
2087
- const issues = [];
2088
- const result = {};
2089
- const piiMode = options.piiEnforcement ?? "none";
2090
- if (typeof input !== "object" || input === null) {
2091
- return { valid: false, errors: ["Input must be an object"] };
2092
- }
2093
- const working = typeof input === "object" && input !== null ? deepClone(input) : { ...input };
2094
- if (working.type == null) working.type = entityType;
2095
- if (working.version == null) working.version = version;
2096
- const fromVersion = String(working.version ?? "0.0.0");
2097
- const toVersion = String(version);
2098
- if (schemaUpgrade && cmpSemver(fromVersion, toVersion) < 0) {
2099
- const upgradeRes = applySchemaUpgrade(schemaUpgrade, { ...working }, {
2100
- from: fromVersion,
2101
- to: toVersion,
2102
- entityType,
2103
- describe: () => schema.describe()
2104
- });
2105
- if (!upgradeRes || upgradeRes.ok !== true || !upgradeRes.value) {
2106
- const errs = upgradeRes?.errors?.length ? upgradeRes.errors : [`Failed to upgrade entity from v${fromVersion} to v${toVersion}`];
2107
- errors.push(...errs);
2108
- return { valid: false, errors };
2109
- }
2110
- for (const k of Object.keys(working)) delete working[k];
2111
- Object.assign(working, upgradeRes.value);
2112
- working.type = entityType;
2113
- working.version = toVersion;
2114
- }
2115
- for (const key in schema._shape) {
2116
- const def = schema._shape[key];
2117
- let value = working[key];
2118
- const existingField = existing && typeof existing === "object" ? existing[key] : void 0;
2119
- if (!def) {
2120
- errors.push(`Field definition missing for: ${key}`);
2121
- continue;
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 (errors.length === 0 && options.schemaValidator) {
2196
- const castValue = result;
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
- return {
2202
- valid: errors.length === 0,
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 res = applySchemaUpgrade(
2225
- schemaUpgrade,
2226
- { ...input },
2227
- {
2228
- from: fromVersion,
2229
- to: toVersion,
2230
- entityType,
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
- if (res && res.ok && res.value) {
2236
- const out = { ...res.value, type: entityType, version: toVersion };
2237
- return { ok: true, value: out };
2238
- }
2239
- return {
2240
- ok: false,
2241
- errors: res?.errors?.length ? res.errors : [`Failed to upgrade entity from v${fromVersion} to v${toVersion}`]
2242
- };
2243
- },
2244
- /**
2245
- * Recursively validates entity references defined in this schema.
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
- return {
2448
- entityType,
2449
- version,
2450
- hasSchemaUpgrade: !!schemaUpgrade,
2451
- shape: description
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 eventIdField = () => field.string().min(1).max(128);
2528
- var eventKindField = () => field.string().enum(PLAYER_SYSTEM_EVENT_KINDS);
2529
- var eventTitleField = () => field.generalText().min(1).max(120).description("Player-visible event title");
2530
- var eventSummaryField = () => field.generalText().min(1).max(320).description("Player-visible event summary");
2531
- var observedEventFields = (defaultHighlighted = true) => ({
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 achievementTrackFields = () => ({
2563
- id: eventIdField(),
2564
- title: eventTitleField(),
2565
- current: field.number().min(0).max(1e3),
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 PlayerSystemObservedEventBatchSchema = createSchema(
1682
+ var FeedbackPublicSummarySchema = createSchema(
2570
1683
  {
2571
- source: field.string().min(1).max(120),
2572
- emittedAt: field.dateTimeISO(),
2573
- events: field.array(field.object(observedEventFields()))
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
- "player-system-observed-event-batch",
1697
+ "feedback-public-summary",
2576
1698
  {
2577
- version: "1.0.0",
2578
- piiEnforcement: "strict"
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 PlayerSystemNormalizedEventBatchSchema = createSchema(
1732
+ var FeedbackProcessorCheckpointSchema = createSchema(
2582
1733
  {
2583
- sourceBlobName: field.string().min(1).max(512),
2584
- processedAt: field.dateTimeISO(),
2585
- events: field.array(
2586
- field.object({
2587
- ...observedEventFields(false),
2588
- deterministicKey: field.string().min(1).max(256)
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
- "player-system-normalized-event-batch",
1741
+ "feedback-processor-checkpoint",
2593
1742
  {
2594
- version: "1.0.0",
2595
- piiEnforcement: "strict"
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 PlayerSystemCuratedSnapshotSchema = createSchema(
1747
+ var FeedbackMaterializationManifestSchema = createSchema(
2599
1748
  {
2600
- playerId: playerIdField(),
2601
- projectedAt: field.dateTimeISO(),
2602
- lastObservedAt: field.dateTimeISO(),
2603
- highlights: field.array(field.object(eventHighlightFields())),
2604
- events: field.array(field.object(eventEntryFields())),
2605
- achievements: field.array(field.object(achievementFields())),
2606
- tracks: field.array(field.object(achievementTrackFields()))
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
- "player-system-curated-snapshot",
1759
+ "feedback-materialization-manifest",
2609
1760
  {
2610
- version: "1.0.0",
2611
- piiEnforcement: "strict"
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,