@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.
- package/README.md +24 -2
- package/dist/{chunk-UQHPDQZX.js → chunk-GNL2RBO5.js} +133 -32
- package/dist/chunk-GNL2RBO5.js.map +1 -0
- package/dist/feedback-diagnostics.cjs +132 -31
- package/dist/feedback-diagnostics.cjs.map +1 -1
- package/dist/feedback-diagnostics.js +1 -1
- package/dist/index.cjs +187 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +54 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-UQHPDQZX.js.map +0 -1
package/README.md
CHANGED
|
@@ -113,6 +113,7 @@ reporting:
|
|
|
113
113
|
|
|
114
114
|
```ts
|
|
115
115
|
import {
|
|
116
|
+
FeedbackBugHealthMetricsProjectionSchema,
|
|
116
117
|
FeedbackBugPacketSchema,
|
|
117
118
|
FeedbackCommittedAcceptanceEvidenceSchema,
|
|
118
119
|
FeedbackPublicSummarySchema,
|
|
@@ -243,13 +244,34 @@ rather than by enumerating packet storage, preventing an immutable Blob written
|
|
|
243
244
|
before a failed commit from entering aggregates. See
|
|
244
245
|
[ADR-0011](./docs/adrs/adr-0011-identifier-free-feedback-acceptance-evidence.md).
|
|
245
246
|
|
|
247
|
+
`FeedbackBugHealthMetricsProjectionSchema` is the separate trusted input for
|
|
248
|
+
facts that accepted packets cannot supply: application rejection counts,
|
|
249
|
+
privacy-safe traffic denominators, and allowlisted abuse-control block bands.
|
|
250
|
+
It covers one exact UTC hour, is finalized no earlier than the window end, and
|
|
251
|
+
contains no raw edge event, request, reporter, client, route, narrative, or
|
|
252
|
+
pixel data. Abuse bands are unique, non-zero, and ordered by the frozen
|
|
253
|
+
`FEEDBACK_ABUSE_BLOCK_BANDS` vocabulary. A producer must derive the
|
|
254
|
+
`projectionId` deterministically from the hour and consumers must verify that
|
|
255
|
+
binding before materialization; the schema deliberately performs no
|
|
256
|
+
environment-dependent hashing. See
|
|
257
|
+
[ADR-0012](./docs/adrs/adr-0012-identifier-free-bug-health-metrics-projection.md).
|
|
258
|
+
|
|
259
|
+
Materialization manifests may identify a revision as a `metrics-source`
|
|
260
|
+
correction when an independently finalized identifier-free metric changed but
|
|
261
|
+
no packet arrived late. That reason requires `lateArrivalCount: 0`; ordinary
|
|
262
|
+
late-arrival corrections continue to require a positive late-arrival count.
|
|
263
|
+
|
|
246
264
|
All feedback contracts opt in to recursive unknown-field rejection, including
|
|
247
265
|
direct references and arrays of references. Unshaped references admit only
|
|
248
266
|
`type` and `id`; shaped references admit only those keys plus their declared
|
|
249
267
|
fields. Closed feedback vocabularies and definition objects are runtime frozen
|
|
250
268
|
before schemas capture them. Before recursive cloning, strict validation also
|
|
251
|
-
performs an iterative whole-value node/depth budget
|
|
252
|
-
|
|
269
|
+
performs an iterative whole-value node/depth budget and inspects own data
|
|
270
|
+
property descriptors. It rejects enumerable accessors, symbol keys, exotic
|
|
271
|
+
prototypes, sparse or oversized arrays, and malformed proxy traps without
|
|
272
|
+
evaluating accessor values or reflecting thrown messages, so malformed
|
|
273
|
+
containers hidden under scalar fields cannot exhaust the call stack or expand
|
|
274
|
+
into an attacker-selected logical array length. The
|
|
253
275
|
general schema default remains backwards-compatible stripping:
|
|
254
276
|
|
|
255
277
|
```ts
|
|
@@ -1446,45 +1446,113 @@ function getShape(def) {
|
|
|
1446
1446
|
var MAX_STRICT_VALIDATION_NODES = 1e4;
|
|
1447
1447
|
var MAX_STRICT_VALIDATION_DEPTH = 256;
|
|
1448
1448
|
var MAX_STRICT_VALIDATION_OBJECTS = MAX_STRICT_VALIDATION_NODES;
|
|
1449
|
-
function
|
|
1450
|
-
const
|
|
1451
|
-
|
|
1452
|
-
];
|
|
1449
|
+
function snapshotStrictInput(input) {
|
|
1450
|
+
const snapshot = Array.isArray(input) ? [] : {};
|
|
1451
|
+
const pending = [{ source: input, target: snapshot, depth: 0 }];
|
|
1453
1452
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
1454
|
-
let visitedNodes =
|
|
1453
|
+
let visitedNodes = 1;
|
|
1455
1454
|
try {
|
|
1456
1455
|
while (pending.length > 0) {
|
|
1457
1456
|
const current = pending.pop();
|
|
1458
1457
|
if (!current) {
|
|
1459
1458
|
continue;
|
|
1460
1459
|
}
|
|
1461
|
-
visitedNodes += 1;
|
|
1462
1460
|
if (visitedNodes > MAX_STRICT_VALIDATION_NODES || current.depth > MAX_STRICT_VALIDATION_DEPTH) {
|
|
1463
|
-
return false;
|
|
1461
|
+
return { valid: false };
|
|
1464
1462
|
}
|
|
1465
|
-
if (
|
|
1466
|
-
|
|
1463
|
+
if (seen.has(current.source)) {
|
|
1464
|
+
return { valid: false };
|
|
1465
|
+
}
|
|
1466
|
+
seen.add(current.source);
|
|
1467
|
+
const isArray = Array.isArray(current.source);
|
|
1468
|
+
const prototype = Object.getPrototypeOf(current.source);
|
|
1469
|
+
if (isArray && prototype !== Array.prototype || !isArray && prototype !== Object.prototype && prototype !== null) {
|
|
1470
|
+
return { valid: false };
|
|
1471
|
+
}
|
|
1472
|
+
const ownKeys = Reflect.ownKeys(current.source);
|
|
1473
|
+
if (visitedNodes + ownKeys.length > MAX_STRICT_VALIDATION_NODES) {
|
|
1474
|
+
return { valid: false };
|
|
1467
1475
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1476
|
+
const descriptors = /* @__PURE__ */ new Map();
|
|
1477
|
+
for (const key of ownKeys) {
|
|
1478
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
1479
|
+
current.source,
|
|
1480
|
+
key
|
|
1481
|
+
);
|
|
1482
|
+
if (descriptor === void 0) {
|
|
1483
|
+
return { valid: false };
|
|
1484
|
+
}
|
|
1485
|
+
descriptors.set(key, descriptor);
|
|
1470
1486
|
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1487
|
+
if (isArray) {
|
|
1488
|
+
const lengthDescriptor = descriptors.get("length");
|
|
1489
|
+
if (lengthDescriptor === void 0 || !("value" in lengthDescriptor) || !Number.isSafeInteger(lengthDescriptor.value) || lengthDescriptor.value < 0 || lengthDescriptor.value > MAX_STRICT_VALIDATION_NODES) {
|
|
1490
|
+
return { valid: false };
|
|
1491
|
+
}
|
|
1492
|
+
let elementCount = 0;
|
|
1493
|
+
for (const key of ownKeys) {
|
|
1494
|
+
if (key === "length") {
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
if (typeof key !== "string" || !/^(?:0|[1-9]\d*)$/.test(key)) {
|
|
1498
|
+
return { valid: false };
|
|
1499
|
+
}
|
|
1500
|
+
const descriptor = descriptors.get(key);
|
|
1501
|
+
if (descriptor === void 0 || descriptor.enumerable !== true || Number(key) >= lengthDescriptor.value) {
|
|
1502
|
+
return { valid: false };
|
|
1503
|
+
}
|
|
1504
|
+
elementCount += 1;
|
|
1505
|
+
}
|
|
1506
|
+
if (elementCount !== lengthDescriptor.value) {
|
|
1507
|
+
return { valid: false };
|
|
1508
|
+
}
|
|
1475
1509
|
}
|
|
1476
|
-
|
|
1510
|
+
visitedNodes += ownKeys.length;
|
|
1511
|
+
const keys = ownKeys.filter(
|
|
1512
|
+
(key) => descriptors.get(key)?.enumerable
|
|
1513
|
+
);
|
|
1477
1514
|
for (const key of keys) {
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1515
|
+
if (typeof key !== "string") {
|
|
1516
|
+
return { valid: false };
|
|
1517
|
+
}
|
|
1518
|
+
if (isArray && !/^(?:0|[1-9]\d*)$/.test(key)) {
|
|
1519
|
+
return { valid: false };
|
|
1520
|
+
}
|
|
1521
|
+
const descriptor = descriptors.get(key);
|
|
1522
|
+
if (descriptor === void 0 || !("value" in descriptor) || descriptor.get !== void 0 || descriptor.set !== void 0) {
|
|
1523
|
+
return { valid: false };
|
|
1524
|
+
}
|
|
1525
|
+
const child = descriptor.value;
|
|
1526
|
+
if (typeof child === "function") {
|
|
1527
|
+
return { valid: false };
|
|
1528
|
+
}
|
|
1529
|
+
if (typeof child === "object" && child !== null) {
|
|
1530
|
+
const childSnapshot = Array.isArray(child) ? [] : {};
|
|
1531
|
+
Object.defineProperty(current.target, key, {
|
|
1532
|
+
value: childSnapshot,
|
|
1533
|
+
enumerable: true,
|
|
1534
|
+
configurable: true,
|
|
1535
|
+
writable: true
|
|
1536
|
+
});
|
|
1537
|
+
pending.push({
|
|
1538
|
+
source: child,
|
|
1539
|
+
target: childSnapshot,
|
|
1540
|
+
depth: current.depth + 1
|
|
1541
|
+
});
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
Object.defineProperty(current.target, key, {
|
|
1545
|
+
value: child,
|
|
1546
|
+
enumerable: true,
|
|
1547
|
+
configurable: true,
|
|
1548
|
+
writable: true
|
|
1481
1549
|
});
|
|
1482
1550
|
}
|
|
1483
1551
|
}
|
|
1484
1552
|
} catch {
|
|
1485
|
-
return false;
|
|
1553
|
+
return { valid: false };
|
|
1486
1554
|
}
|
|
1487
|
-
return true;
|
|
1555
|
+
return { valid: true, value: snapshot };
|
|
1488
1556
|
}
|
|
1489
1557
|
function strictReferenceShape(def) {
|
|
1490
1558
|
return {
|
|
@@ -2051,8 +2119,10 @@ function createSchema(_shape, entityType, options = {
|
|
|
2051
2119
|
if (typeof input !== "object" || input === null) {
|
|
2052
2120
|
return { valid: false, errors: ["Input must be an object"] };
|
|
2053
2121
|
}
|
|
2122
|
+
let strictInputSnapshot;
|
|
2054
2123
|
if (options.unknownFields === "reject") {
|
|
2055
|
-
|
|
2124
|
+
const snapshot = snapshotStrictInput(input);
|
|
2125
|
+
if (!snapshot.valid) {
|
|
2056
2126
|
return {
|
|
2057
2127
|
valid: false,
|
|
2058
2128
|
errors: ["Strict validation complexity limit exceeded."],
|
|
@@ -2065,16 +2135,32 @@ function createSchema(_shape, entityType, options = {
|
|
|
2065
2135
|
]
|
|
2066
2136
|
};
|
|
2067
2137
|
}
|
|
2138
|
+
strictInputSnapshot = snapshot.value;
|
|
2068
2139
|
const scanState = {
|
|
2069
2140
|
remainingObjects: MAX_STRICT_VALIDATION_OBJECTS,
|
|
2070
2141
|
limitExceeded: false
|
|
2071
2142
|
};
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2143
|
+
let locations;
|
|
2144
|
+
try {
|
|
2145
|
+
locations = unknownFieldLocations(
|
|
2146
|
+
strictInputSnapshot,
|
|
2147
|
+
schema._shape,
|
|
2148
|
+
"root",
|
|
2149
|
+
scanState
|
|
2150
|
+
);
|
|
2151
|
+
} catch {
|
|
2152
|
+
return {
|
|
2153
|
+
valid: false,
|
|
2154
|
+
errors: ["Strict validation complexity limit exceeded."],
|
|
2155
|
+
issues: [
|
|
2156
|
+
{
|
|
2157
|
+
path: "root",
|
|
2158
|
+
code: "validation_complexity_limit",
|
|
2159
|
+
message: "Strict validation complexity limit exceeded."
|
|
2160
|
+
}
|
|
2161
|
+
]
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2078
2164
|
if (scanState.limitExceeded) {
|
|
2079
2165
|
return {
|
|
2080
2166
|
valid: false,
|
|
@@ -2102,7 +2188,23 @@ function createSchema(_shape, entityType, options = {
|
|
|
2102
2188
|
};
|
|
2103
2189
|
}
|
|
2104
2190
|
}
|
|
2105
|
-
|
|
2191
|
+
let working;
|
|
2192
|
+
try {
|
|
2193
|
+
working = strictInputSnapshot ?? (typeof input === "object" && input !== null ? deepClone(input) : { ...input });
|
|
2194
|
+
} catch {
|
|
2195
|
+
return {
|
|
2196
|
+
valid: false,
|
|
2197
|
+
errors: ["Input could not be safely cloned."],
|
|
2198
|
+
issues: [
|
|
2199
|
+
{
|
|
2200
|
+
path: "root",
|
|
2201
|
+
code: "unsafe_input",
|
|
2202
|
+
message: "Input could not be safely cloned."
|
|
2203
|
+
}
|
|
2204
|
+
]
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
const providedFieldNames = new Set(Object.keys(working));
|
|
2106
2208
|
if (working.type == null) working.type = entityType;
|
|
2107
2209
|
if (working.version == null) working.version = version;
|
|
2108
2210
|
const fromVersion = String(working.version ?? "0.0.0");
|
|
@@ -2219,9 +2321,8 @@ function createSchema(_shape, entityType, options = {
|
|
|
2219
2321
|
}
|
|
2220
2322
|
if (errors.length === 0 && options.schemaValidator) {
|
|
2221
2323
|
const castValue = result;
|
|
2222
|
-
const source = input;
|
|
2223
2324
|
const validationContext = Object.freeze({
|
|
2224
|
-
wasProvided: (fieldName) =>
|
|
2325
|
+
wasProvided: (fieldName) => providedFieldNames.has(fieldName)
|
|
2225
2326
|
});
|
|
2226
2327
|
if (!options.schemaValidator(castValue, validationContext)) {
|
|
2227
2328
|
errors.push("Schema-level validation failed.");
|
|
@@ -2603,4 +2704,4 @@ export {
|
|
|
2603
2704
|
FeedbackGameDiagnosticsSchema,
|
|
2604
2705
|
validateFeedbackGameDiagnostics
|
|
2605
2706
|
};
|
|
2606
|
-
//# sourceMappingURL=chunk-
|
|
2707
|
+
//# sourceMappingURL=chunk-GNL2RBO5.js.map
|