@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.
- package/README.md +35 -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 +206 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -1
- package/dist/index.d.ts +62 -1
- package/dist/index.js +72 -13
- 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,7 +113,9 @@ reporting:
|
|
|
113
113
|
|
|
114
114
|
```ts
|
|
115
115
|
import {
|
|
116
|
+
FeedbackBugHealthMetricsProjectionSchema,
|
|
116
117
|
FeedbackBugPacketSchema,
|
|
118
|
+
FeedbackCommittedAcceptanceEvidenceSchema,
|
|
117
119
|
FeedbackPublicSummarySchema,
|
|
118
120
|
FeedbackRichTextAstSchema,
|
|
119
121
|
FeedbackReviewPacketSchema,
|
|
@@ -227,13 +229,44 @@ hours and review suppression at 30 days; acceptance receipts distinguish bugs
|
|
|
227
229
|
from reviews and accept only the five-step bug ladder or exact 30-day review
|
|
228
230
|
period.
|
|
229
231
|
|
|
232
|
+
`FeedbackCommittedAcceptanceEvidenceSchema` is a separate, durable proof that
|
|
233
|
+
the control-plane commit for one immutable packet completed. It contains only
|
|
234
|
+
the canonical packet UUID, `bug`/`review` kind, and the packet's canonical
|
|
235
|
+
server-owned UTC acceptance time. Canonical timestamp validation rejects
|
|
236
|
+
impossible calendar or clock values as ordinary closed validation failures; it
|
|
237
|
+
does not expose the rejected value or a native date exception. It cannot
|
|
238
|
+
express a control-state key, reservation, idempotency value, reporter
|
|
239
|
+
pseudonym, request metadata, Blob locator, content hash, narrative, ciphertext,
|
|
240
|
+
or pixels, and its PII audit is empty. Only a trusted delivery worker may
|
|
241
|
+
construct this evidence after an atomic control-plane commit; it is not an
|
|
242
|
+
intake request. Report processors must select packets through this evidence
|
|
243
|
+
rather than by enumerating packet storage, preventing an immutable Blob written
|
|
244
|
+
before a failed commit from entering aggregates. See
|
|
245
|
+
[ADR-0011](./docs/adrs/adr-0011-identifier-free-feedback-acceptance-evidence.md).
|
|
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
|
+
|
|
230
259
|
All feedback contracts opt in to recursive unknown-field rejection, including
|
|
231
260
|
direct references and arrays of references. Unshaped references admit only
|
|
232
261
|
`type` and `id`; shaped references admit only those keys plus their declared
|
|
233
262
|
fields. Closed feedback vocabularies and definition objects are runtime frozen
|
|
234
263
|
before schemas capture them. Before recursive cloning, strict validation also
|
|
235
|
-
performs an iterative whole-value node/depth budget
|
|
236
|
-
|
|
264
|
+
performs an iterative whole-value node/depth budget and inspects own data
|
|
265
|
+
property descriptors. It rejects enumerable accessors, symbol keys, exotic
|
|
266
|
+
prototypes, sparse or oversized arrays, and malformed proxy traps without
|
|
267
|
+
evaluating accessor values or reflecting thrown messages, so malformed
|
|
268
|
+
containers hidden under scalar fields cannot exhaust the call stack or expand
|
|
269
|
+
into an attacker-selected logical array length. The
|
|
237
270
|
general schema default remains backwards-compatible stripping:
|
|
238
271
|
|
|
239
272
|
```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
|