@plasius/schema 1.4.0 → 1.4.2

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,8 +52,10 @@ __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,
58
+ FeedbackCommittedAcceptanceEvidenceSchema: () => FeedbackCommittedAcceptanceEvidenceSchema,
56
59
  FeedbackContextSchema: () => FeedbackContextSchema,
57
60
  FeedbackDailySatisfactionReportSchema: () => FeedbackDailySatisfactionReportSchema,
58
61
  FeedbackDerivedAnalysisSchema: () => FeedbackDerivedAnalysisSchema,
@@ -1782,45 +1785,113 @@ function getShape(def) {
1782
1785
  var MAX_STRICT_VALIDATION_NODES = 1e4;
1783
1786
  var MAX_STRICT_VALIDATION_DEPTH = 256;
1784
1787
  var MAX_STRICT_VALIDATION_OBJECTS = MAX_STRICT_VALIDATION_NODES;
1785
- function strictInputIsWithinComplexityLimit(input) {
1786
- const pending = [
1787
- { value: input, depth: 0 }
1788
- ];
1788
+ function snapshotStrictInput(input) {
1789
+ const snapshot = Array.isArray(input) ? [] : {};
1790
+ const pending = [{ source: input, target: snapshot, depth: 0 }];
1789
1791
  const seen = /* @__PURE__ */ new WeakSet();
1790
- let visitedNodes = 0;
1792
+ let visitedNodes = 1;
1791
1793
  try {
1792
1794
  while (pending.length > 0) {
1793
1795
  const current = pending.pop();
1794
1796
  if (!current) {
1795
1797
  continue;
1796
1798
  }
1797
- visitedNodes += 1;
1798
1799
  if (visitedNodes > MAX_STRICT_VALIDATION_NODES || current.depth > MAX_STRICT_VALIDATION_DEPTH) {
1799
- return false;
1800
+ return { valid: false };
1800
1801
  }
1801
- if (typeof current.value !== "object" || current.value === null) {
1802
- continue;
1802
+ if (seen.has(current.source)) {
1803
+ return { valid: false };
1803
1804
  }
1804
- if (seen.has(current.value)) {
1805
- 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 };
1806
1810
  }
1807
- seen.add(current.value);
1808
- const keys = Object.keys(current.value);
1809
- if (visitedNodes + pending.length + keys.length > MAX_STRICT_VALIDATION_NODES) {
1810
- return false;
1811
+ const ownKeys = Reflect.ownKeys(current.source);
1812
+ if (visitedNodes + ownKeys.length > MAX_STRICT_VALIDATION_NODES) {
1813
+ return { valid: false };
1814
+ }
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);
1811
1825
  }
1812
- const record = current.value;
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
+ );
1813
1853
  for (const key of keys) {
1814
- pending.push({
1815
- value: record[key],
1816
- 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
1817
1888
  });
1818
1889
  }
1819
1890
  }
1820
1891
  } catch {
1821
- return false;
1892
+ return { valid: false };
1822
1893
  }
1823
- return true;
1894
+ return { valid: true, value: snapshot };
1824
1895
  }
1825
1896
  function strictReferenceShape(def) {
1826
1897
  return {
@@ -2387,8 +2458,10 @@ function createSchema(_shape, entityType, options = {
2387
2458
  if (typeof input !== "object" || input === null) {
2388
2459
  return { valid: false, errors: ["Input must be an object"] };
2389
2460
  }
2461
+ let strictInputSnapshot;
2390
2462
  if (options.unknownFields === "reject") {
2391
- if (!strictInputIsWithinComplexityLimit(input)) {
2463
+ const snapshot = snapshotStrictInput(input);
2464
+ if (!snapshot.valid) {
2392
2465
  return {
2393
2466
  valid: false,
2394
2467
  errors: ["Strict validation complexity limit exceeded."],
@@ -2401,16 +2474,32 @@ function createSchema(_shape, entityType, options = {
2401
2474
  ]
2402
2475
  };
2403
2476
  }
2477
+ strictInputSnapshot = snapshot.value;
2404
2478
  const scanState = {
2405
2479
  remainingObjects: MAX_STRICT_VALIDATION_OBJECTS,
2406
2480
  limitExceeded: false
2407
2481
  };
2408
- const locations = unknownFieldLocations(
2409
- input,
2410
- schema._shape,
2411
- "root",
2412
- scanState
2413
- );
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
+ }
2414
2503
  if (scanState.limitExceeded) {
2415
2504
  return {
2416
2505
  valid: false,
@@ -2438,7 +2527,23 @@ function createSchema(_shape, entityType, options = {
2438
2527
  };
2439
2528
  }
2440
2529
  }
2441
- 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));
2442
2547
  if (working.type == null) working.type = entityType;
2443
2548
  if (working.version == null) working.version = version;
2444
2549
  const fromVersion = String(working.version ?? "0.0.0");
@@ -2555,9 +2660,8 @@ function createSchema(_shape, entityType, options = {
2555
2660
  }
2556
2661
  if (errors.length === 0 && options.schemaValidator) {
2557
2662
  const castValue = result;
2558
- const source = input;
2559
2663
  const validationContext = Object.freeze({
2560
- wasProvided: (fieldName) => Object.prototype.hasOwnProperty.call(source, fieldName)
2664
+ wasProvided: (fieldName) => providedFieldNames.has(fieldName)
2561
2665
  });
2562
2666
  if (!options.schemaValidator(castValue, validationContext)) {
2563
2667
  errors.push("Schema-level validation failed.");
@@ -3397,6 +3501,15 @@ var FEEDBACK_BUG_COOLDOWN_SECONDS = Object.freeze([
3397
3501
  24 * 60 * 60
3398
3502
  ]);
3399
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
+ ]);
3400
3513
  var FEEDBACK_ISSUE_TYPES = [
3401
3514
  "visual-layout",
3402
3515
  "functionality",
@@ -3614,6 +3727,7 @@ var BASE64URL_ALPHABET2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0
3614
3727
  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])$/;
3615
3728
  var REVIEW_DAY_WINDOW_PATTERN = /^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$/;
3616
3729
  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)$/;
3730
+ var CANONICAL_SERVER_UTC_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
3617
3731
  var strictOptions = {
3618
3732
  version: FEEDBACK_CONTRACT_VERSION,
3619
3733
  piiEnforcement: "strict",
@@ -3621,6 +3735,16 @@ var strictOptions = {
3621
3735
  identity: "exact"
3622
3736
  };
3623
3737
  var uuidField = () => field.string().validator((value) => OPAQUE_UUID_V4_PATTERN.test(value)).description("Opaque UUID generated for the feedback workflow");
3738
+ var isCanonicalServerUtc = (value) => {
3739
+ if (typeof value !== "string" || !CANONICAL_SERVER_UTC_PATTERN.test(value)) {
3740
+ return false;
3741
+ }
3742
+ const timestamp = Date.parse(value);
3743
+ return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value;
3744
+ };
3745
+ var canonicalServerUtcField = () => field.dateTimeISO().validator(isCanonicalServerUtc).description(
3746
+ "Canonical millisecond-precision UTC timestamp supplied by the trusted server"
3747
+ );
3624
3748
  var draftIdField = () => field.string().validator((value) => OPAQUE_UUID_V4_PATTERN.test(value)).description("Opaque identifier for one short-lived structured draft").PID({
3625
3749
  classification: "low",
3626
3750
  action: "none",
@@ -4284,6 +4408,52 @@ var FeedbackReviewPacketSchema = createSchema(
4284
4408
  schemaValidator: (value) => value.analysis === void 0 || isAnalysisProjectionConsistent(value.analysis)
4285
4409
  }
4286
4410
  );
4411
+ var FeedbackCommittedAcceptanceEvidenceSchema = createSchema(
4412
+ {
4413
+ packetId: uuidField(),
4414
+ packetKind: field.string().enum(["bug", "review"]),
4415
+ acceptedAt: canonicalServerUtcField()
4416
+ },
4417
+ "feedback-committed-acceptance-evidence",
4418
+ strictOptions
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
+ );
4287
4457
  var FeedbackGameReconstructionManifestSchema = createSchema(
4288
4458
  {
4289
4459
  reconstructionId: uuidField(),
@@ -4408,20 +4578,10 @@ var FeedbackHourlyBugReportSchema = createSchema(
4408
4578
  abuseBlockBands: field.array(
4409
4579
  field.object(
4410
4580
  distributionShape(
4411
- () => field.string().enum(
4412
- [
4413
- "five-minutes",
4414
- "fifteen-minutes",
4415
- "one-hour",
4416
- "six-hours",
4417
- "twenty-four-hours",
4418
- "fail-closed",
4419
- "edge-blocked"
4420
- ]
4421
- )
4581
+ () => field.string().enum(FEEDBACK_ABUSE_BLOCK_BANDS)
4422
4582
  )
4423
4583
  )
4424
- ).max(16),
4584
+ ).max(FEEDBACK_ABUSE_BLOCK_BANDS.length),
4425
4585
  comparison: field.object({
4426
4586
  previousHourRatio: field.number().min(0).max(1e3),
4427
4587
  sevenDaySameHourRatio: field.number().min(0).max(1e3)
@@ -4742,6 +4902,7 @@ var FeedbackMaterializationManifestSchema = createSchema(
4742
4902
  );
4743
4903
  // Annotate the CommonJS export names for ESM import in node:
4744
4904
  0 && (module.exports = {
4905
+ FEEDBACK_ABUSE_BLOCK_BANDS,
4745
4906
  FEEDBACK_ANALYSIS_MODEL_VERSIONS,
4746
4907
  FEEDBACK_BACKEND_BUCKETS,
4747
4908
  FEEDBACK_BUG_COOLDOWN_SECONDS,
@@ -4773,8 +4934,10 @@ var FeedbackMaterializationManifestSchema = createSchema(
4773
4934
  FEEDBACK_VIEWPORT_BUCKETS,
4774
4935
  FeedbackAcceptanceReceiptSchema,
4775
4936
  FeedbackAnalysisReceiptSchema,
4937
+ FeedbackBugHealthMetricsProjectionSchema,
4776
4938
  FeedbackBugPacketSchema,
4777
4939
  FeedbackBugSubmissionRequestSchema,
4940
+ FeedbackCommittedAcceptanceEvidenceSchema,
4778
4941
  FeedbackContextSchema,
4779
4942
  FeedbackDailySatisfactionReportSchema,
4780
4943
  FeedbackDerivedAnalysisSchema,