@plasius/schema 1.4.1 → 1.4.3

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  FeedbackGameDiagnosticsSchema,
3
3
  validateFeedbackGameDiagnostics
4
- } from "./chunk-UQHPDQZX.js";
4
+ } from "./chunk-GNL2RBO5.js";
5
5
  import {
6
6
  FEEDBACK_BACKEND_BUCKETS,
7
7
  FEEDBACK_CONTRACT_VERSION,
package/dist/index.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ FEEDBACK_ABUSE_BLOCK_BANDS: () => FEEDBACK_ABUSE_BLOCK_BANDS,
23
24
  FEEDBACK_ANALYSIS_MODEL_VERSIONS: () => FEEDBACK_ANALYSIS_MODEL_VERSIONS,
24
25
  FEEDBACK_BACKEND_BUCKETS: () => FEEDBACK_BACKEND_BUCKETS,
25
26
  FEEDBACK_BUG_COOLDOWN_SECONDS: () => FEEDBACK_BUG_COOLDOWN_SECONDS,
@@ -51,6 +52,7 @@ __export(index_exports, {
51
52
  FEEDBACK_VIEWPORT_BUCKETS: () => FEEDBACK_VIEWPORT_BUCKETS,
52
53
  FeedbackAcceptanceReceiptSchema: () => FeedbackAcceptanceReceiptSchema,
53
54
  FeedbackAnalysisReceiptSchema: () => FeedbackAnalysisReceiptSchema,
55
+ FeedbackBugHealthMetricsProjectionSchema: () => FeedbackBugHealthMetricsProjectionSchema,
54
56
  FeedbackBugPacketSchema: () => FeedbackBugPacketSchema,
55
57
  FeedbackBugSubmissionRequestSchema: () => FeedbackBugSubmissionRequestSchema,
56
58
  FeedbackCommittedAcceptanceEvidenceSchema: () => FeedbackCommittedAcceptanceEvidenceSchema,
@@ -1783,45 +1785,113 @@ function getShape(def) {
1783
1785
  var MAX_STRICT_VALIDATION_NODES = 1e4;
1784
1786
  var MAX_STRICT_VALIDATION_DEPTH = 256;
1785
1787
  var MAX_STRICT_VALIDATION_OBJECTS = MAX_STRICT_VALIDATION_NODES;
1786
- function strictInputIsWithinComplexityLimit(input) {
1787
- const pending = [
1788
- { value: input, depth: 0 }
1789
- ];
1788
+ function snapshotStrictInput(input) {
1789
+ const snapshot = Array.isArray(input) ? [] : {};
1790
+ const pending = [{ source: input, target: snapshot, depth: 0 }];
1790
1791
  const seen = /* @__PURE__ */ new WeakSet();
1791
- let visitedNodes = 0;
1792
+ let visitedNodes = 1;
1792
1793
  try {
1793
1794
  while (pending.length > 0) {
1794
1795
  const current = pending.pop();
1795
1796
  if (!current) {
1796
1797
  continue;
1797
1798
  }
1798
- visitedNodes += 1;
1799
1799
  if (visitedNodes > MAX_STRICT_VALIDATION_NODES || current.depth > MAX_STRICT_VALIDATION_DEPTH) {
1800
- return false;
1800
+ return { valid: false };
1801
1801
  }
1802
- if (typeof current.value !== "object" || current.value === null) {
1803
- continue;
1802
+ if (seen.has(current.source)) {
1803
+ return { valid: false };
1804
1804
  }
1805
- if (seen.has(current.value)) {
1806
- return false;
1805
+ seen.add(current.source);
1806
+ const isArray = Array.isArray(current.source);
1807
+ const prototype = Object.getPrototypeOf(current.source);
1808
+ if (isArray && prototype !== Array.prototype || !isArray && prototype !== Object.prototype && prototype !== null) {
1809
+ return { valid: false };
1807
1810
  }
1808
- seen.add(current.value);
1809
- const keys = Object.keys(current.value);
1810
- if (visitedNodes + pending.length + keys.length > MAX_STRICT_VALIDATION_NODES) {
1811
- return false;
1811
+ const ownKeys = Reflect.ownKeys(current.source);
1812
+ if (visitedNodes + ownKeys.length > MAX_STRICT_VALIDATION_NODES) {
1813
+ return { valid: false };
1812
1814
  }
1813
- const record = current.value;
1815
+ const descriptors = /* @__PURE__ */ new Map();
1816
+ for (const key of ownKeys) {
1817
+ const descriptor = Object.getOwnPropertyDescriptor(
1818
+ current.source,
1819
+ key
1820
+ );
1821
+ if (descriptor === void 0) {
1822
+ return { valid: false };
1823
+ }
1824
+ descriptors.set(key, descriptor);
1825
+ }
1826
+ if (isArray) {
1827
+ const lengthDescriptor = descriptors.get("length");
1828
+ if (lengthDescriptor === void 0 || !("value" in lengthDescriptor) || !Number.isSafeInteger(lengthDescriptor.value) || lengthDescriptor.value < 0 || lengthDescriptor.value > MAX_STRICT_VALIDATION_NODES) {
1829
+ return { valid: false };
1830
+ }
1831
+ let elementCount = 0;
1832
+ for (const key of ownKeys) {
1833
+ if (key === "length") {
1834
+ continue;
1835
+ }
1836
+ if (typeof key !== "string" || !/^(?:0|[1-9]\d*)$/.test(key)) {
1837
+ return { valid: false };
1838
+ }
1839
+ const descriptor = descriptors.get(key);
1840
+ if (descriptor === void 0 || descriptor.enumerable !== true || Number(key) >= lengthDescriptor.value) {
1841
+ return { valid: false };
1842
+ }
1843
+ elementCount += 1;
1844
+ }
1845
+ if (elementCount !== lengthDescriptor.value) {
1846
+ return { valid: false };
1847
+ }
1848
+ }
1849
+ visitedNodes += ownKeys.length;
1850
+ const keys = ownKeys.filter(
1851
+ (key) => descriptors.get(key)?.enumerable
1852
+ );
1814
1853
  for (const key of keys) {
1815
- pending.push({
1816
- value: record[key],
1817
- depth: current.depth + 1
1854
+ if (typeof key !== "string") {
1855
+ return { valid: false };
1856
+ }
1857
+ if (isArray && !/^(?:0|[1-9]\d*)$/.test(key)) {
1858
+ return { valid: false };
1859
+ }
1860
+ const descriptor = descriptors.get(key);
1861
+ if (descriptor === void 0 || !("value" in descriptor) || descriptor.get !== void 0 || descriptor.set !== void 0) {
1862
+ return { valid: false };
1863
+ }
1864
+ const child = descriptor.value;
1865
+ if (typeof child === "function") {
1866
+ return { valid: false };
1867
+ }
1868
+ if (typeof child === "object" && child !== null) {
1869
+ const childSnapshot = Array.isArray(child) ? [] : {};
1870
+ Object.defineProperty(current.target, key, {
1871
+ value: childSnapshot,
1872
+ enumerable: true,
1873
+ configurable: true,
1874
+ writable: true
1875
+ });
1876
+ pending.push({
1877
+ source: child,
1878
+ target: childSnapshot,
1879
+ depth: current.depth + 1
1880
+ });
1881
+ continue;
1882
+ }
1883
+ Object.defineProperty(current.target, key, {
1884
+ value: child,
1885
+ enumerable: true,
1886
+ configurable: true,
1887
+ writable: true
1818
1888
  });
1819
1889
  }
1820
1890
  }
1821
1891
  } catch {
1822
- return false;
1892
+ return { valid: false };
1823
1893
  }
1824
- return true;
1894
+ return { valid: true, value: snapshot };
1825
1895
  }
1826
1896
  function strictReferenceShape(def) {
1827
1897
  return {
@@ -2388,8 +2458,10 @@ function createSchema(_shape, entityType, options = {
2388
2458
  if (typeof input !== "object" || input === null) {
2389
2459
  return { valid: false, errors: ["Input must be an object"] };
2390
2460
  }
2461
+ let strictInputSnapshot;
2391
2462
  if (options.unknownFields === "reject") {
2392
- if (!strictInputIsWithinComplexityLimit(input)) {
2463
+ const snapshot = snapshotStrictInput(input);
2464
+ if (!snapshot.valid) {
2393
2465
  return {
2394
2466
  valid: false,
2395
2467
  errors: ["Strict validation complexity limit exceeded."],
@@ -2402,16 +2474,32 @@ function createSchema(_shape, entityType, options = {
2402
2474
  ]
2403
2475
  };
2404
2476
  }
2477
+ strictInputSnapshot = snapshot.value;
2405
2478
  const scanState = {
2406
2479
  remainingObjects: MAX_STRICT_VALIDATION_OBJECTS,
2407
2480
  limitExceeded: false
2408
2481
  };
2409
- const locations = unknownFieldLocations(
2410
- input,
2411
- schema._shape,
2412
- "root",
2413
- scanState
2414
- );
2482
+ let locations;
2483
+ try {
2484
+ locations = unknownFieldLocations(
2485
+ strictInputSnapshot,
2486
+ schema._shape,
2487
+ "root",
2488
+ scanState
2489
+ );
2490
+ } catch {
2491
+ return {
2492
+ valid: false,
2493
+ errors: ["Strict validation complexity limit exceeded."],
2494
+ issues: [
2495
+ {
2496
+ path: "root",
2497
+ code: "validation_complexity_limit",
2498
+ message: "Strict validation complexity limit exceeded."
2499
+ }
2500
+ ]
2501
+ };
2502
+ }
2415
2503
  if (scanState.limitExceeded) {
2416
2504
  return {
2417
2505
  valid: false,
@@ -2439,7 +2527,23 @@ function createSchema(_shape, entityType, options = {
2439
2527
  };
2440
2528
  }
2441
2529
  }
2442
- const working = typeof input === "object" && input !== null ? deepClone(input) : { ...input };
2530
+ let working;
2531
+ try {
2532
+ working = strictInputSnapshot ?? (typeof input === "object" && input !== null ? deepClone(input) : { ...input });
2533
+ } catch {
2534
+ return {
2535
+ valid: false,
2536
+ errors: ["Input could not be safely cloned."],
2537
+ issues: [
2538
+ {
2539
+ path: "root",
2540
+ code: "unsafe_input",
2541
+ message: "Input could not be safely cloned."
2542
+ }
2543
+ ]
2544
+ };
2545
+ }
2546
+ const providedFieldNames = new Set(Object.keys(working));
2443
2547
  if (working.type == null) working.type = entityType;
2444
2548
  if (working.version == null) working.version = version;
2445
2549
  const fromVersion = String(working.version ?? "0.0.0");
@@ -2556,9 +2660,8 @@ function createSchema(_shape, entityType, options = {
2556
2660
  }
2557
2661
  if (errors.length === 0 && options.schemaValidator) {
2558
2662
  const castValue = result;
2559
- const source = input;
2560
2663
  const validationContext = Object.freeze({
2561
- wasProvided: (fieldName) => Object.prototype.hasOwnProperty.call(source, fieldName)
2664
+ wasProvided: (fieldName) => providedFieldNames.has(fieldName)
2562
2665
  });
2563
2666
  if (!options.schemaValidator(castValue, validationContext)) {
2564
2667
  errors.push("Schema-level validation failed.");
@@ -3398,6 +3501,15 @@ var FEEDBACK_BUG_COOLDOWN_SECONDS = Object.freeze([
3398
3501
  24 * 60 * 60
3399
3502
  ]);
3400
3503
  var FEEDBACK_REVIEW_COOLDOWN_SECONDS = 30 * 24 * 60 * 60;
3504
+ var FEEDBACK_ABUSE_BLOCK_BANDS = Object.freeze([
3505
+ "five-minutes",
3506
+ "fifteen-minutes",
3507
+ "one-hour",
3508
+ "six-hours",
3509
+ "twenty-four-hours",
3510
+ "fail-closed",
3511
+ "edge-blocked"
3512
+ ]);
3401
3513
  var FEEDBACK_ISSUE_TYPES = [
3402
3514
  "visual-layout",
3403
3515
  "functionality",
@@ -4305,6 +4417,43 @@ var FeedbackCommittedAcceptanceEvidenceSchema = createSchema(
4305
4417
  "feedback-committed-acceptance-evidence",
4306
4418
  strictOptions
4307
4419
  );
4420
+ var FeedbackBugHealthMetricsProjectionSchema = createSchema(
4421
+ {
4422
+ projectionId: uuidField(),
4423
+ windowStart: canonicalServerUtcField(),
4424
+ windowEnd: canonicalServerUtcField(),
4425
+ observedAt: canonicalServerUtcField(),
4426
+ finalized: field.boolean().validator((value) => value === true),
4427
+ rejectedCount: countField(),
4428
+ trafficDenominator: countField(),
4429
+ abuseBlockBands: field.array(
4430
+ field.object({
4431
+ id: field.string().enum(FEEDBACK_ABUSE_BLOCK_BANDS),
4432
+ count: positiveCountField2()
4433
+ })
4434
+ ).max(FEEDBACK_ABUSE_BLOCK_BANDS.length)
4435
+ },
4436
+ "feedback-bug-health-metrics-projection",
4437
+ {
4438
+ ...strictOptions,
4439
+ schemaValidator: (value) => {
4440
+ const blockBands = value.abuseBlockBands;
4441
+ let previousIndex = -1;
4442
+ const canonicalBands = blockBands.every((bucket) => {
4443
+ const index = FEEDBACK_ABUSE_BLOCK_BANDS.indexOf(bucket.id);
4444
+ if (index <= previousIndex) {
4445
+ return false;
4446
+ }
4447
+ previousIndex = index;
4448
+ return true;
4449
+ });
4450
+ return orderedWindow(
4451
+ { ...value, generatedAt: value.observedAt },
4452
+ 60 * 60 * 1e3
4453
+ ) && Date.parse(String(value.observedAt)) >= Date.parse(String(value.windowEnd)) && canonicalBands;
4454
+ }
4455
+ }
4456
+ );
4308
4457
  var FeedbackGameReconstructionManifestSchema = createSchema(
4309
4458
  {
4310
4459
  reconstructionId: uuidField(),
@@ -4429,20 +4578,10 @@ var FeedbackHourlyBugReportSchema = createSchema(
4429
4578
  abuseBlockBands: field.array(
4430
4579
  field.object(
4431
4580
  distributionShape(
4432
- () => field.string().enum(
4433
- [
4434
- "five-minutes",
4435
- "fifteen-minutes",
4436
- "one-hour",
4437
- "six-hours",
4438
- "twenty-four-hours",
4439
- "fail-closed",
4440
- "edge-blocked"
4441
- ]
4442
- )
4581
+ () => field.string().enum(FEEDBACK_ABUSE_BLOCK_BANDS)
4443
4582
  )
4444
4583
  )
4445
- ).max(16),
4584
+ ).max(FEEDBACK_ABUSE_BLOCK_BANDS.length),
4446
4585
  comparison: field.object({
4447
4586
  previousHourRatio: field.number().min(0).max(1e3),
4448
4587
  sevenDaySameHourRatio: field.number().min(0).max(1e3)
@@ -4744,6 +4883,7 @@ var FeedbackMaterializationManifestSchema = createSchema(
4744
4883
  generatedAt: field.dateTimeISO(),
4745
4884
  sourcePacketCount: countField(),
4746
4885
  lateArrivalCount: countField(),
4886
+ correctionReason: field.string().enum(["late-arrival", "metrics-source"]).optional(),
4747
4887
  outputReportId: uuidField().optional(),
4748
4888
  status: field.string().enum(["published", "corrected", "no-op"])
4749
4889
  },
@@ -4755,14 +4895,15 @@ var FeedbackMaterializationManifestSchema = createSchema(
4755
4895
  return false;
4756
4896
  }
4757
4897
  if (value.processor === "commit-reconciliation") {
4758
- return value.status === "no-op" && value.revision >= 1 && value.outputReportId === void 0 && value.lateArrivalCount === 0;
4898
+ return value.status === "no-op" && value.revision >= 1 && value.outputReportId === void 0 && value.correctionReason === void 0 && value.lateArrivalCount === 0;
4759
4899
  }
4760
- 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;
4900
+ return value.status === "published" && value.revision === 1 && value.lateArrivalCount === 0 && value.correctionReason === void 0 && value.outputReportId !== void 0 || value.status === "corrected" && value.revision >= 2 && (value.correctionReason === "metrics-source" && value.lateArrivalCount === 0 || (value.correctionReason === void 0 || value.correctionReason === "late-arrival") && value.lateArrivalCount > 0) && value.outputReportId !== void 0;
4761
4901
  }
4762
4902
  }
4763
4903
  );
4764
4904
  // Annotate the CommonJS export names for ESM import in node:
4765
4905
  0 && (module.exports = {
4906
+ FEEDBACK_ABUSE_BLOCK_BANDS,
4766
4907
  FEEDBACK_ANALYSIS_MODEL_VERSIONS,
4767
4908
  FEEDBACK_BACKEND_BUCKETS,
4768
4909
  FEEDBACK_BUG_COOLDOWN_SECONDS,
@@ -4794,6 +4935,7 @@ var FeedbackMaterializationManifestSchema = createSchema(
4794
4935
  FEEDBACK_VIEWPORT_BUCKETS,
4795
4936
  FeedbackAcceptanceReceiptSchema,
4796
4937
  FeedbackAnalysisReceiptSchema,
4938
+ FeedbackBugHealthMetricsProjectionSchema,
4797
4939
  FeedbackBugPacketSchema,
4798
4940
  FeedbackBugSubmissionRequestSchema,
4799
4941
  FeedbackCommittedAcceptanceEvidenceSchema,