@oxyhq/crowdsource-contracts 0.1.0
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 +129 -0
- package/dist/case-envelope.d.ts +1130 -0
- package/dist/case-envelope.d.ts.map +1 -0
- package/dist/case-envelope.js +383 -0
- package/dist/case-envelope.js.map +1 -0
- package/dist/decisions.d.ts +353 -0
- package/dist/decisions.d.ts.map +1 -0
- package/dist/decisions.js +198 -0
- package/dist/decisions.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/json-schema.d.ts +43 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +83 -0
- package/dist/json-schema.js.map +1 -0
- package/dist/policies.d.ts +286 -0
- package/dist/policies.d.ts.map +1 -0
- package/dist/policies.js +178 -0
- package/dist/policies.js.map +1 -0
- package/dist/primitives.d.ts +185 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +231 -0
- package/dist/primitives.js.map +1 -0
- package/dist/reputation-events.d.ts +349 -0
- package/dist/reputation-events.d.ts.map +1 -0
- package/dist/reputation-events.js +128 -0
- package/dist/reputation-events.js.map +1 -0
- package/dist/resources.d.ts +484 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +436 -0
- package/dist/resources.js.map +1 -0
- package/dist/reviews.d.ts +276 -0
- package/dist/reviews.d.ts.map +1 -0
- package/dist/reviews.js +144 -0
- package/dist/reviews.js.map +1 -0
- package/dist/taxonomy.d.ts +266 -0
- package/dist/taxonomy.d.ts.map +1 -0
- package/dist/taxonomy.js +282 -0
- package/dist/taxonomy.js.map +1 -0
- package/dist/webhooks.d.ts +604 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +192 -0
- package/dist/webhooks.js.map +1 -0
- package/package.json +56 -0
- package/src/case-envelope.ts +433 -0
- package/src/decisions.ts +216 -0
- package/src/index.ts +45 -0
- package/src/json-schema.ts +89 -0
- package/src/policies.ts +203 -0
- package/src/primitives.ts +283 -0
- package/src/reputation-events.ts +144 -0
- package/src/resources.ts +489 -0
- package/src/reviews.ts +159 -0
- package/src/taxonomy.ts +313 -0
- package/src/webhooks.ts +215 -0
package/dist/policies.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The application-policy layer (§6.1 layer 2) and policy versioning (§6.4).
|
|
4
|
+
*
|
|
5
|
+
* The taxonomy says what the material contains; a policy set says whether that
|
|
6
|
+
* violates THIS application's rules. The two are versioned independently and
|
|
7
|
+
* every decision records both (plus the Oxy conduct policy version), because
|
|
8
|
+
* §6.4 forbids a policy update from silently changing what a past decision
|
|
9
|
+
* meant.
|
|
10
|
+
*
|
|
11
|
+
* The security boundary of this module is §6.4's last line: "rules must be
|
|
12
|
+
* expressed as data, not as arbitrary code supplied by the tenant". A rule here
|
|
13
|
+
* therefore has an id, a title, the taxonomy codes it responds to, a default
|
|
14
|
+
* severity and recommended actions — and no expression, predicate, script,
|
|
15
|
+
* template or callback field of any kind. That absence is the control. It is
|
|
16
|
+
* enforced by `.strict()`: a policy set carrying an unrecognised key is
|
|
17
|
+
* rejected outright rather than accepted with the key quietly dropped, because
|
|
18
|
+
* "dropped" and "never evaluated" look identical right up until something
|
|
19
|
+
* decides to evaluate it.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.PolicySetVersionSchema = exports.PolicyRuleSchema = exports.PolicySetStatusSchema = exports.POLICY_SET_STATUSES = exports.ReputationPolicyVersionsSchema = exports.DecisionPolicyVersionsSchema = exports.CasePolicyRefSchema = exports.OXY_CONDUCT_POLICY_VERSION = exports.PolicyVersionSchema = exports.PolicyRuleIdSchema = exports.PolicySetIdSchema = void 0;
|
|
23
|
+
const zod_1 = require("zod");
|
|
24
|
+
const primitives_1 = require("./primitives");
|
|
25
|
+
const taxonomy_1 = require("./taxonomy");
|
|
26
|
+
/** A namespaced policy set id, e.g. `mention.community`. */
|
|
27
|
+
exports.PolicySetIdSchema = zod_1.z
|
|
28
|
+
.string()
|
|
29
|
+
.min(3)
|
|
30
|
+
.max(primitives_1.CONTRACT_LIMITS.IDENTIFIER_MAX_LENGTH)
|
|
31
|
+
.regex(/^[a-z][a-z0-9_]*(\.[a-z0-9_]+)+$/, 'must be a dotted lowercase namespace, e.g. "mention.community"');
|
|
32
|
+
/** A rule id within a policy set, e.g. `mention.harassment.2`. */
|
|
33
|
+
exports.PolicyRuleIdSchema = zod_1.z
|
|
34
|
+
.string()
|
|
35
|
+
.min(3)
|
|
36
|
+
.max(primitives_1.CONTRACT_LIMITS.IDENTIFIER_MAX_LENGTH)
|
|
37
|
+
.regex(/^[a-z][a-z0-9_]*(\.[a-z0-9_]+)+$/, 'must be a dotted lowercase namespace, e.g. "mention.harassment.2"');
|
|
38
|
+
/**
|
|
39
|
+
* An immutable policy version token, e.g. `2026.07`, `mention.2026.07`,
|
|
40
|
+
* `oxy.2026.1`.
|
|
41
|
+
*
|
|
42
|
+
* Deliberately not semver: the plan's own tokens are calendar-shaped and
|
|
43
|
+
* tenant-prefixed, and a policy version is an opaque label pointing at a frozen
|
|
44
|
+
* document — nothing compares two of them for ordering.
|
|
45
|
+
*/
|
|
46
|
+
exports.PolicyVersionSchema = zod_1.z
|
|
47
|
+
.string()
|
|
48
|
+
.min(1)
|
|
49
|
+
.max(64)
|
|
50
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, 'must start with a letter or digit and contain only letters, digits, ".", "_" or "-"');
|
|
51
|
+
/**
|
|
52
|
+
* The version of the Oxy Conduct Policy every decision is stamped with (§6.4).
|
|
53
|
+
*
|
|
54
|
+
* §6.1 assigns the third layer — "should this conduct affect global Oxy trust?"
|
|
55
|
+
* — to the Oxy Conduct Policy, evaluated by the Reputation Bridge. That bridge
|
|
56
|
+
* does not exist yet, and this constant is deliberately NOT a promise that it
|
|
57
|
+
* does. What it is: the label a decision published today records, so that when
|
|
58
|
+
* the bridge arrives it can tell which conduct policy a historical decision was
|
|
59
|
+
* decided under and refuse to re-interpret it under a newer one. §6.4's "a
|
|
60
|
+
* policy update never silently rewrites historical decisions" is unenforceable
|
|
61
|
+
* without a version on every decision from the first one.
|
|
62
|
+
*
|
|
63
|
+
* The value is Appendix B's. It is pinned here, in the package both the
|
|
64
|
+
* decision DTO and the reputation event import, so the two cannot drift into
|
|
65
|
+
* describing the same decision under two different conduct policies.
|
|
66
|
+
*/
|
|
67
|
+
exports.OXY_CONDUCT_POLICY_VERSION = 'oxy.2026.1';
|
|
68
|
+
/**
|
|
69
|
+
* The policy an envelope asks to be evaluated under (§5.1 `policy`).
|
|
70
|
+
*
|
|
71
|
+
* `locale` selects the language the policy text is shown to the reviewer in
|
|
72
|
+
* (Appendix A), and is not the language of the reported material — that is
|
|
73
|
+
* `resource.language`.
|
|
74
|
+
*/
|
|
75
|
+
exports.CasePolicyRefSchema = zod_1.z.strictObject({
|
|
76
|
+
policySetId: exports.PolicySetIdSchema,
|
|
77
|
+
version: exports.PolicyVersionSchema,
|
|
78
|
+
locale: primitives_1.LanguageTagSchema.optional(),
|
|
79
|
+
});
|
|
80
|
+
/**
|
|
81
|
+
* The three policy versions a decision is decided under (§6.4).
|
|
82
|
+
*
|
|
83
|
+
* Field names follow Appendix B (`taxonomy`), which is the reference Decision.
|
|
84
|
+
* §6.4's prose calls the same field `universalTaxonomyVersion` and §11.6's
|
|
85
|
+
* internal event calls it `universal`; see `ReputationPolicyVersionsSchema` for
|
|
86
|
+
* why both spellings survive.
|
|
87
|
+
*/
|
|
88
|
+
exports.DecisionPolicyVersionsSchema = zod_1.z.looseObject({
|
|
89
|
+
taxonomy: exports.PolicyVersionSchema,
|
|
90
|
+
application: exports.PolicyVersionSchema,
|
|
91
|
+
oxyConduct: exports.PolicyVersionSchema,
|
|
92
|
+
});
|
|
93
|
+
/**
|
|
94
|
+
* The same three versions as carried by the internal reputation event (§11.6).
|
|
95
|
+
*
|
|
96
|
+
* §11.6 spells the first key `universal` where Appendix B spells it `taxonomy`.
|
|
97
|
+
* Both are reference payloads in the approved plan and both are load-bearing
|
|
98
|
+
* for a different consumer, so each surface keeps the spelling its own
|
|
99
|
+
* reference document uses rather than one of the two documents being quietly
|
|
100
|
+
* rewritten here. Unifying them is a one-line change and worth doing when the
|
|
101
|
+
* event contract is agreed with OxyHQServices — it is called out in the package
|
|
102
|
+
* README as the single naming inconsistency the contract preserves on purpose.
|
|
103
|
+
*/
|
|
104
|
+
exports.ReputationPolicyVersionsSchema = zod_1.z.strictObject({
|
|
105
|
+
universal: exports.PolicyVersionSchema,
|
|
106
|
+
application: exports.PolicyVersionSchema,
|
|
107
|
+
oxyConduct: exports.PolicyVersionSchema,
|
|
108
|
+
});
|
|
109
|
+
/** §6.4: a draft may be edited, a published version may not. */
|
|
110
|
+
exports.POLICY_SET_STATUSES = ['draft', 'published'];
|
|
111
|
+
exports.PolicySetStatusSchema = zod_1.z.enum(exports.POLICY_SET_STATUSES);
|
|
112
|
+
/**
|
|
113
|
+
* One rule inside a policy version — data, never code.
|
|
114
|
+
*
|
|
115
|
+
* `taxonomyCodes` is what binds layer 2 back to layer 1: the rule declares
|
|
116
|
+
* which universal findings it responds to, so a jury classifies once (§9.2 step
|
|
117
|
+
* one) and any number of applications evaluate that same classification against
|
|
118
|
+
* their own rules (§6.2).
|
|
119
|
+
*/
|
|
120
|
+
exports.PolicyRuleSchema = zod_1.z.strictObject({
|
|
121
|
+
id: exports.PolicyRuleIdSchema,
|
|
122
|
+
title: zod_1.z.string().min(1).max(primitives_1.CONTRACT_LIMITS.SHORT_TEXT_MAX_LENGTH),
|
|
123
|
+
description: zod_1.z.string().max(primitives_1.CONTRACT_LIMITS.LONG_TEXT_MAX_LENGTH).optional(),
|
|
124
|
+
taxonomyCodes: zod_1.z.array(taxonomy_1.TaxonomyCodeSchema).min(1).max(primitives_1.CONTRACT_LIMITS.FINDINGS_MAX),
|
|
125
|
+
defaultSeverity: taxonomy_1.SeveritySchema.optional(),
|
|
126
|
+
recommendedActions: zod_1.z
|
|
127
|
+
.array(taxonomy_1.RecommendedActionSchema)
|
|
128
|
+
.max(primitives_1.CONTRACT_LIMITS.RECOMMENDED_ACTIONS_MAX)
|
|
129
|
+
.optional(),
|
|
130
|
+
});
|
|
131
|
+
const POLICY_RULES_MAX = 500;
|
|
132
|
+
/**
|
|
133
|
+
* A policy set at one version (§6.4).
|
|
134
|
+
*
|
|
135
|
+
* The `status`/`publishedAt` pairing is the immutability rule expressed where a
|
|
136
|
+
* schema can express it: a published version must record when it was published,
|
|
137
|
+
* and a draft must not claim to have been. Actual immutability — never
|
|
138
|
+
* rewriting a published version's rules — is a storage rule the registry
|
|
139
|
+
* enforces; no parse can see a second write.
|
|
140
|
+
*/
|
|
141
|
+
exports.PolicySetVersionSchema = zod_1.z
|
|
142
|
+
.strictObject({
|
|
143
|
+
policySetId: exports.PolicySetIdSchema,
|
|
144
|
+
version: exports.PolicyVersionSchema,
|
|
145
|
+
status: exports.PolicySetStatusSchema,
|
|
146
|
+
title: zod_1.z.string().min(1).max(primitives_1.CONTRACT_LIMITS.SHORT_TEXT_MAX_LENGTH),
|
|
147
|
+
locale: primitives_1.LanguageTagSchema.optional(),
|
|
148
|
+
publishedAt: primitives_1.TimestampSchema.optional(),
|
|
149
|
+
rules: zod_1.z.array(exports.PolicyRuleSchema).min(1).max(POLICY_RULES_MAX),
|
|
150
|
+
})
|
|
151
|
+
.superRefine((policySet, ctx) => {
|
|
152
|
+
if (policySet.status === 'published' && policySet.publishedAt === undefined) {
|
|
153
|
+
ctx.addIssue({
|
|
154
|
+
code: 'custom',
|
|
155
|
+
path: ['publishedAt'],
|
|
156
|
+
message: 'a published policy version must record publishedAt',
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (policySet.status === 'draft' && policySet.publishedAt !== undefined) {
|
|
160
|
+
ctx.addIssue({
|
|
161
|
+
code: 'custom',
|
|
162
|
+
path: ['publishedAt'],
|
|
163
|
+
message: 'a draft policy version must not record publishedAt',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const seen = new Set();
|
|
167
|
+
policySet.rules.forEach((rule, index) => {
|
|
168
|
+
if (seen.has(rule.id)) {
|
|
169
|
+
ctx.addIssue({
|
|
170
|
+
code: 'custom',
|
|
171
|
+
path: ['rules', index, 'id'],
|
|
172
|
+
message: `duplicate rule id "${rule.id}"`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
seen.add(rule.id);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
//# sourceMappingURL=policies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policies.js","sourceRoot":"","sources":["../src/policies.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scalar contracts shared by every CrowdSource surface.
|
|
3
|
+
*
|
|
4
|
+
* Nothing here belongs to a single contract module; everything here is depended
|
|
5
|
+
* on by several of them. Two properties drive most of the choices below:
|
|
6
|
+
*
|
|
7
|
+
* 1. **The envelope is content-addressed.** §7.3 derives `caseDedupKey` from
|
|
8
|
+
* `applicationId`, `subject.externalId`, the canonical envelope hash and
|
|
9
|
+
* the policy version. Two byte-different representations of the SAME value
|
|
10
|
+
* produce two different keys, therefore two cases, therefore two penalties
|
|
11
|
+
* for one incident — the exact opposite of the "one penalty per incident"
|
|
12
|
+
* invariant. So every scalar that can travel in more than one shape is
|
|
13
|
+
* pinned to ONE canonical shape here: digests are always `sha256:<hex>`,
|
|
14
|
+
* timestamps are always millisecond-precision UTC.
|
|
15
|
+
*
|
|
16
|
+
* 2. **`:` is a structural separator.** §7.3 joins the dedup key components
|
|
17
|
+
* with `:`. An identifier that may itself contain `:` makes that join
|
|
18
|
+
* ambiguous — two distinct tuples can flatten to the same string and merge
|
|
19
|
+
* two unrelated cases. Every identifier schema below therefore excludes
|
|
20
|
+
* `:`. (The same character is independently forbidden in BullMQ queue and
|
|
21
|
+
* job ids, so this also keeps identifiers usable as dispatch keys.)
|
|
22
|
+
*/
|
|
23
|
+
import { z } from 'zod';
|
|
24
|
+
/**
|
|
25
|
+
* Bounds applied across the contracts.
|
|
26
|
+
*
|
|
27
|
+
* §7.2.3 requires ingress to check "size limits, number of resources and MIME
|
|
28
|
+
* types" but the plan never states the numbers. These are the contract's own
|
|
29
|
+
* declared limits: generous enough not to reject real moderation material,
|
|
30
|
+
* finite so that no array or string in the contract is unbounded. A tenant may
|
|
31
|
+
* be held to something tighter by quota; nothing may exceed these.
|
|
32
|
+
*/
|
|
33
|
+
export declare const CONTRACT_LIMITS: Readonly<{
|
|
34
|
+
/** Envelope-local ids (`res_post`), policy ids, principal refs. */
|
|
35
|
+
readonly IDENTIFIER_MAX_LENGTH: 128;
|
|
36
|
+
/** Ids minted by the application (`mention_report_123`, `post_987`). */
|
|
37
|
+
readonly EXTERNAL_ID_MAX_LENGTH: 200;
|
|
38
|
+
readonly RESOURCES_PER_ENVELOPE_MAX: 50;
|
|
39
|
+
readonly RELATIONS_PER_ENVELOPE_MAX: 200;
|
|
40
|
+
readonly ALLEGATIONS_PER_ENVELOPE_MAX: 20;
|
|
41
|
+
readonly PRINCIPAL_BINDINGS_PER_ENVELOPE_MAX: 50;
|
|
42
|
+
/** A reported text resource. Long enough for an article, not a corpus. */
|
|
43
|
+
readonly TEXT_RESOURCE_MAX_LENGTH: 100000;
|
|
44
|
+
/** Titles, labels, display names, place names. */
|
|
45
|
+
readonly SHORT_TEXT_MAX_LENGTH: 500;
|
|
46
|
+
/** Descriptions, bios, reporter details, reviewer notes. */
|
|
47
|
+
readonly LONG_TEXT_MAX_LENGTH: 5000;
|
|
48
|
+
/** Audio transcript, document extracted text, link snapshot. */
|
|
49
|
+
readonly EXTRACTED_TEXT_MAX_LENGTH: 200000;
|
|
50
|
+
readonly METADATA_KEYS_MAX: 50;
|
|
51
|
+
readonly METADATA_KEY_MAX_LENGTH: 64;
|
|
52
|
+
readonly METADATA_STRING_VALUE_MAX_LENGTH: 1000;
|
|
53
|
+
/** §5.7 custom payloads: data, bounded, never a document tree. */
|
|
54
|
+
readonly CUSTOM_PAYLOAD_MAX_DEPTH: 5;
|
|
55
|
+
readonly CUSTOM_PAYLOAD_ARRAY_MAX_LENGTH: 100;
|
|
56
|
+
readonly CUSTOM_PAYLOAD_STRING_MAX_LENGTH: 10000;
|
|
57
|
+
readonly CONVERSATION_MESSAGES_MAX: 50;
|
|
58
|
+
readonly LISTING_MEDIA_REFS_MAX: 20;
|
|
59
|
+
readonly PROFILE_CLAIMS_MAX: 20;
|
|
60
|
+
readonly FINDINGS_MAX: 20;
|
|
61
|
+
readonly RESOURCE_REFS_PER_FINDING_MAX: 50;
|
|
62
|
+
readonly POLICY_RULE_IDS_MAX: 20;
|
|
63
|
+
readonly RECOMMENDED_ACTIONS_MAX: 10;
|
|
64
|
+
/** §13.6 defaults to 30 days "configurable by policy"; this is the ceiling. */
|
|
65
|
+
readonly RETENTION_DAYS_MAX: 3650;
|
|
66
|
+
/** A jury is 3, 5 or 7 today (§9.4). The bound only stops absurd values. */
|
|
67
|
+
readonly JURY_SIZE_MAX: 99;
|
|
68
|
+
}>;
|
|
69
|
+
/**
|
|
70
|
+
* Keys that must never appear in a caller-supplied object, at any depth.
|
|
71
|
+
*
|
|
72
|
+
* A payload is data. These three names are how a data payload stops being data
|
|
73
|
+
* the moment anything merges, clones or `Object.assign`s it into a prototype
|
|
74
|
+
* chain. Rejecting them at the contract boundary is cheap and total; sanitising
|
|
75
|
+
* them later depends on every consumer remembering to.
|
|
76
|
+
*
|
|
77
|
+
* One of the three behaves differently and it is worth knowing which, because
|
|
78
|
+
* the list reads as though it were doing all the work: Zod removes an own
|
|
79
|
+
* `__proto__` property from any input before a key schema or a strict-key check
|
|
80
|
+
* ever sees it, so `__proto__` is DROPPED rather than rejected, and this list
|
|
81
|
+
* never fires for it. The safety outcome is the same — nothing is polluted, and
|
|
82
|
+
* the key cannot survive into parsed output — but a test asserting that
|
|
83
|
+
* `__proto__` is *rejected* would fail, and a reader assuming this constant is
|
|
84
|
+
* what prevents pollution would be assuming the wrong thing. `constructor` and
|
|
85
|
+
* `prototype` are ordinary keys to Zod and are rejected here.
|
|
86
|
+
*/
|
|
87
|
+
export declare const FORBIDDEN_OBJECT_KEYS: readonly string[];
|
|
88
|
+
/**
|
|
89
|
+
* An id that is local to one envelope, policy set or contract payload.
|
|
90
|
+
*
|
|
91
|
+
* Deliberately opaque: the plan writes both slugs (`app_mention`,
|
|
92
|
+
* `mention.community`) and ULID-prefixed ids (`case_01...`) in the same fields,
|
|
93
|
+
* so pinning the format would reject the plan's own reference documents. What
|
|
94
|
+
* IS pinned is the character set — see the `:` note in the module comment.
|
|
95
|
+
*/
|
|
96
|
+
export declare const IdentifierSchema: z.ZodString;
|
|
97
|
+
/** An id minted by the reporting application, echoed back but never trusted. */
|
|
98
|
+
export declare const ExternalIdSchema: z.ZodString;
|
|
99
|
+
/**
|
|
100
|
+
* `sha256:<64 lowercase hex>` — the ONLY accepted digest form.
|
|
101
|
+
*
|
|
102
|
+
* Appendix A writes `"sha256:..."`; §5.8 writes `"..."`. Accepting both would
|
|
103
|
+
* let the same bytes be described two ways, and the canonical envelope hash
|
|
104
|
+
* would differ between them (see property 1 in the module comment). Appendix A
|
|
105
|
+
* is the reference document, so the prefixed form wins and the bare form is
|
|
106
|
+
* rejected rather than silently normalised — normalising would mean the digest
|
|
107
|
+
* CrowdSource stores is not the digest the application sent.
|
|
108
|
+
*/
|
|
109
|
+
export declare const Sha256DigestSchema: z.ZodString;
|
|
110
|
+
/**
|
|
111
|
+
* Millisecond-precision UTC, e.g. `2026-07-28T18:00:00.000Z`.
|
|
112
|
+
*
|
|
113
|
+
* Every timestamp in the plan is written this way, and it is what
|
|
114
|
+
* `Date.prototype.toISOString()` produces, so the canonical form costs a
|
|
115
|
+
* JavaScript integrator nothing. Offsets are rejected: `18:00+02:00` and
|
|
116
|
+
* `16:00Z` are the same instant and would hash differently.
|
|
117
|
+
*/
|
|
118
|
+
export declare const TimestampSchema: z.ZodISODateTime;
|
|
119
|
+
/**
|
|
120
|
+
* BCP 47, restricted to `language[-Script][-Region][-variant…]`.
|
|
121
|
+
*
|
|
122
|
+
* A syntactic subset, not the full grammar (no extensions, no private use, no
|
|
123
|
+
* grandfathered tags). Covers `es` and `es-ES` from the plan and everything a
|
|
124
|
+
* real application sends; a tag outside the subset is a signal worth rejecting
|
|
125
|
+
* at ingress rather than carrying into reviewer language eligibility.
|
|
126
|
+
*/
|
|
127
|
+
export declare const LanguageTagSchema: z.ZodString;
|
|
128
|
+
/**
|
|
129
|
+
* An absolute `http`/`https` URL.
|
|
130
|
+
*
|
|
131
|
+
* §7.2.7 rejects "dangerous schemes and executable resources". Scheme is a
|
|
132
|
+
* purely syntactic property, so the contract enforces it. Host reachability is
|
|
133
|
+
* NOT enforced here: deciding whether a host is internal needs DNS resolution
|
|
134
|
+
* and belongs to `safeFetch` at ingestion time. A scheme check that pretended
|
|
135
|
+
* to be an SSRF control would be the more dangerous of the two.
|
|
136
|
+
*/
|
|
137
|
+
export declare const HttpUrlSchema: z.ZodURL;
|
|
138
|
+
/** A `type/subtype` media type, without parameters. */
|
|
139
|
+
export declare const MimeTypeSchema: z.ZodString;
|
|
140
|
+
/** A key in any caller-supplied open bag. */
|
|
141
|
+
export declare const ObjectKeySchema: z.ZodString;
|
|
142
|
+
/** A single metadata value: scalar only, never a nested structure. */
|
|
143
|
+
export declare const MetadataValueSchema: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
|
|
144
|
+
/**
|
|
145
|
+
* An open key/value bag (§5.1 `metadata`, §5.3 the `metadata` resource type).
|
|
146
|
+
*
|
|
147
|
+
* Open by definition — the plan calls it "typed key value fields" and every
|
|
148
|
+
* tenant's keys differ — but flat and bounded. Allowing nesting here would make
|
|
149
|
+
* this the one field in the envelope through which an application could ship an
|
|
150
|
+
* arbitrary document tree past every other constraint in the contract.
|
|
151
|
+
*/
|
|
152
|
+
export declare const MetadataBagSchema: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>;
|
|
153
|
+
/** A JSON value that a §5.7 custom payload may contain. */
|
|
154
|
+
export type CustomPayloadValue = string | number | boolean | null | CustomPayloadValue[] | {
|
|
155
|
+
[key: string]: CustomPayloadValue;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* The value grammar for a §5.7 custom payload.
|
|
159
|
+
*
|
|
160
|
+
* Nesting is unrolled to a fixed depth rather than made recursive so the bound
|
|
161
|
+
* is part of the type, enforced by the same parse as everything else, and
|
|
162
|
+
* visible in the exported JSON Schema. Depth is what stops a payload being a
|
|
163
|
+
* document; `ObjectKeySchema` is what stops it being a prototype.
|
|
164
|
+
*
|
|
165
|
+
* Note what is deliberately absent: no lexical filter on string values. A
|
|
166
|
+
* moderation payload legitimately carries the exact hostile text under review —
|
|
167
|
+
* markup, script fragments, `javascript:` URLs quoted inside a harassment
|
|
168
|
+
* report. Rejecting those strings would reject the material the system exists
|
|
169
|
+
* to review. §5.7's boundary is structural, not lexical: the contract has no
|
|
170
|
+
* field anywhere that carries markup, a template, a component or a remote URL
|
|
171
|
+
* to be rendered, so there is nothing for such a string to be interpreted as.
|
|
172
|
+
*/
|
|
173
|
+
export declare const CustomPayloadValueSchema: z.ZodType<CustomPayloadValue, unknown, z.core.$ZodTypeInternals<CustomPayloadValue, unknown>>;
|
|
174
|
+
/** The top level of a §5.7 custom payload: always an object, never an array. */
|
|
175
|
+
export declare const CustomPayloadSchema: z.ZodRecord<z.ZodString, z.ZodType<CustomPayloadValue, unknown, z.core.$ZodTypeInternals<CustomPayloadValue, unknown>>>;
|
|
176
|
+
/** A number in the closed interval [0, 1] — §9.5 clamps confidence to it. */
|
|
177
|
+
export declare const UnitIntervalSchema: z.ZodNumber;
|
|
178
|
+
export type Identifier = z.infer<typeof IdentifierSchema>;
|
|
179
|
+
export type ExternalId = z.infer<typeof ExternalIdSchema>;
|
|
180
|
+
export type Sha256Digest = z.infer<typeof Sha256DigestSchema>;
|
|
181
|
+
export type Timestamp = z.infer<typeof TimestampSchema>;
|
|
182
|
+
export type LanguageTag = z.infer<typeof LanguageTagSchema>;
|
|
183
|
+
export type MetadataBag = z.infer<typeof MetadataBagSchema>;
|
|
184
|
+
export type CustomPayload = z.infer<typeof CustomPayloadSchema>;
|
|
185
|
+
//# sourceMappingURL=primitives.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../src/primitives.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe;IAC1B,mEAAmE;;IAEnE,wEAAwE;;;;;;IAQxE,0EAA0E;;IAE1E,kDAAkD;;IAElD,4DAA4D;;IAE5D,gEAAgE;;;;;IAOhE,kEAAkE;;;;;;;;;;;IAclE,+EAA+E;;IAE/E,4EAA4E;;EAEnE,CAAC;AAEZ;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAIjD,CAAC;AAIH;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,aAO1B,CAAC;AAEJ,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,aAO1B,CAAC;AAEJ;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,aAE+D,CAAC;AAE/F;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,kBAAkD,CAAC;AAE/E;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,aAK3B,CAAC;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,UAA6C,CAAC;AAExE,uDAAuD;AACvD,eAAO,MAAM,cAAc,aAMxB,CAAC;AAIJ,6CAA6C;AAC7C,eAAO,MAAM,eAAe,aAOxB,CAAC;AAEL,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,0EAK9B,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,oGAI1B,CAAC;AAEL,2DAA2D;AAC3D,MAAM,MAAM,kBAAkB,GAC1B,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,kBAAkB,EAAE,GACpB;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAAA;CAAE,CAAC;AAqB1C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,wBAAwB,+FAAkC,CAAC;AAExE,gFAAgF;AAChF,eAAO,MAAM,mBAAmB,yHAAsD,CAAC;AAEvF,6EAA6E;AAC7E,eAAO,MAAM,kBAAkB,aAA2B,CAAC;AAE3D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC1D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC1D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AACxD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC5D,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC5D,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Scalar contracts shared by every CrowdSource surface.
|
|
4
|
+
*
|
|
5
|
+
* Nothing here belongs to a single contract module; everything here is depended
|
|
6
|
+
* on by several of them. Two properties drive most of the choices below:
|
|
7
|
+
*
|
|
8
|
+
* 1. **The envelope is content-addressed.** §7.3 derives `caseDedupKey` from
|
|
9
|
+
* `applicationId`, `subject.externalId`, the canonical envelope hash and
|
|
10
|
+
* the policy version. Two byte-different representations of the SAME value
|
|
11
|
+
* produce two different keys, therefore two cases, therefore two penalties
|
|
12
|
+
* for one incident — the exact opposite of the "one penalty per incident"
|
|
13
|
+
* invariant. So every scalar that can travel in more than one shape is
|
|
14
|
+
* pinned to ONE canonical shape here: digests are always `sha256:<hex>`,
|
|
15
|
+
* timestamps are always millisecond-precision UTC.
|
|
16
|
+
*
|
|
17
|
+
* 2. **`:` is a structural separator.** §7.3 joins the dedup key components
|
|
18
|
+
* with `:`. An identifier that may itself contain `:` makes that join
|
|
19
|
+
* ambiguous — two distinct tuples can flatten to the same string and merge
|
|
20
|
+
* two unrelated cases. Every identifier schema below therefore excludes
|
|
21
|
+
* `:`. (The same character is independently forbidden in BullMQ queue and
|
|
22
|
+
* job ids, so this also keeps identifiers usable as dispatch keys.)
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.UnitIntervalSchema = exports.CustomPayloadSchema = exports.CustomPayloadValueSchema = exports.MetadataBagSchema = exports.MetadataValueSchema = exports.ObjectKeySchema = exports.MimeTypeSchema = exports.HttpUrlSchema = exports.LanguageTagSchema = exports.TimestampSchema = exports.Sha256DigestSchema = exports.ExternalIdSchema = exports.IdentifierSchema = exports.FORBIDDEN_OBJECT_KEYS = exports.CONTRACT_LIMITS = void 0;
|
|
26
|
+
const zod_1 = require("zod");
|
|
27
|
+
/**
|
|
28
|
+
* Bounds applied across the contracts.
|
|
29
|
+
*
|
|
30
|
+
* §7.2.3 requires ingress to check "size limits, number of resources and MIME
|
|
31
|
+
* types" but the plan never states the numbers. These are the contract's own
|
|
32
|
+
* declared limits: generous enough not to reject real moderation material,
|
|
33
|
+
* finite so that no array or string in the contract is unbounded. A tenant may
|
|
34
|
+
* be held to something tighter by quota; nothing may exceed these.
|
|
35
|
+
*/
|
|
36
|
+
exports.CONTRACT_LIMITS = Object.freeze({
|
|
37
|
+
/** Envelope-local ids (`res_post`), policy ids, principal refs. */
|
|
38
|
+
IDENTIFIER_MAX_LENGTH: 128,
|
|
39
|
+
/** Ids minted by the application (`mention_report_123`, `post_987`). */
|
|
40
|
+
EXTERNAL_ID_MAX_LENGTH: 200,
|
|
41
|
+
RESOURCES_PER_ENVELOPE_MAX: 50,
|
|
42
|
+
RELATIONS_PER_ENVELOPE_MAX: 200,
|
|
43
|
+
ALLEGATIONS_PER_ENVELOPE_MAX: 20,
|
|
44
|
+
PRINCIPAL_BINDINGS_PER_ENVELOPE_MAX: 50,
|
|
45
|
+
/** A reported text resource. Long enough for an article, not a corpus. */
|
|
46
|
+
TEXT_RESOURCE_MAX_LENGTH: 100000,
|
|
47
|
+
/** Titles, labels, display names, place names. */
|
|
48
|
+
SHORT_TEXT_MAX_LENGTH: 500,
|
|
49
|
+
/** Descriptions, bios, reporter details, reviewer notes. */
|
|
50
|
+
LONG_TEXT_MAX_LENGTH: 5000,
|
|
51
|
+
/** Audio transcript, document extracted text, link snapshot. */
|
|
52
|
+
EXTRACTED_TEXT_MAX_LENGTH: 200000,
|
|
53
|
+
METADATA_KEYS_MAX: 50,
|
|
54
|
+
METADATA_KEY_MAX_LENGTH: 64,
|
|
55
|
+
METADATA_STRING_VALUE_MAX_LENGTH: 1000,
|
|
56
|
+
/** §5.7 custom payloads: data, bounded, never a document tree. */
|
|
57
|
+
CUSTOM_PAYLOAD_MAX_DEPTH: 5,
|
|
58
|
+
CUSTOM_PAYLOAD_ARRAY_MAX_LENGTH: 100,
|
|
59
|
+
CUSTOM_PAYLOAD_STRING_MAX_LENGTH: 10000,
|
|
60
|
+
CONVERSATION_MESSAGES_MAX: 50,
|
|
61
|
+
LISTING_MEDIA_REFS_MAX: 20,
|
|
62
|
+
PROFILE_CLAIMS_MAX: 20,
|
|
63
|
+
FINDINGS_MAX: 20,
|
|
64
|
+
RESOURCE_REFS_PER_FINDING_MAX: 50,
|
|
65
|
+
POLICY_RULE_IDS_MAX: 20,
|
|
66
|
+
RECOMMENDED_ACTIONS_MAX: 10,
|
|
67
|
+
/** §13.6 defaults to 30 days "configurable by policy"; this is the ceiling. */
|
|
68
|
+
RETENTION_DAYS_MAX: 3650,
|
|
69
|
+
/** A jury is 3, 5 or 7 today (§9.4). The bound only stops absurd values. */
|
|
70
|
+
JURY_SIZE_MAX: 99,
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* Keys that must never appear in a caller-supplied object, at any depth.
|
|
74
|
+
*
|
|
75
|
+
* A payload is data. These three names are how a data payload stops being data
|
|
76
|
+
* the moment anything merges, clones or `Object.assign`s it into a prototype
|
|
77
|
+
* chain. Rejecting them at the contract boundary is cheap and total; sanitising
|
|
78
|
+
* them later depends on every consumer remembering to.
|
|
79
|
+
*
|
|
80
|
+
* One of the three behaves differently and it is worth knowing which, because
|
|
81
|
+
* the list reads as though it were doing all the work: Zod removes an own
|
|
82
|
+
* `__proto__` property from any input before a key schema or a strict-key check
|
|
83
|
+
* ever sees it, so `__proto__` is DROPPED rather than rejected, and this list
|
|
84
|
+
* never fires for it. The safety outcome is the same — nothing is polluted, and
|
|
85
|
+
* the key cannot survive into parsed output — but a test asserting that
|
|
86
|
+
* `__proto__` is *rejected* would fail, and a reader assuming this constant is
|
|
87
|
+
* what prevents pollution would be assuming the wrong thing. `constructor` and
|
|
88
|
+
* `prototype` are ordinary keys to Zod and are rejected here.
|
|
89
|
+
*/
|
|
90
|
+
exports.FORBIDDEN_OBJECT_KEYS = Object.freeze([
|
|
91
|
+
'__proto__',
|
|
92
|
+
'constructor',
|
|
93
|
+
'prototype',
|
|
94
|
+
]);
|
|
95
|
+
const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
96
|
+
/**
|
|
97
|
+
* An id that is local to one envelope, policy set or contract payload.
|
|
98
|
+
*
|
|
99
|
+
* Deliberately opaque: the plan writes both slugs (`app_mention`,
|
|
100
|
+
* `mention.community`) and ULID-prefixed ids (`case_01...`) in the same fields,
|
|
101
|
+
* so pinning the format would reject the plan's own reference documents. What
|
|
102
|
+
* IS pinned is the character set — see the `:` note in the module comment.
|
|
103
|
+
*/
|
|
104
|
+
exports.IdentifierSchema = zod_1.z
|
|
105
|
+
.string()
|
|
106
|
+
.min(1)
|
|
107
|
+
.max(exports.CONTRACT_LIMITS.IDENTIFIER_MAX_LENGTH)
|
|
108
|
+
.regex(IDENTIFIER_PATTERN, 'must start with a letter or digit and contain only letters, digits, ".", "_" or "-"');
|
|
109
|
+
/** An id minted by the reporting application, echoed back but never trusted. */
|
|
110
|
+
exports.ExternalIdSchema = zod_1.z
|
|
111
|
+
.string()
|
|
112
|
+
.min(1)
|
|
113
|
+
.max(exports.CONTRACT_LIMITS.EXTERNAL_ID_MAX_LENGTH)
|
|
114
|
+
.regex(IDENTIFIER_PATTERN, 'must start with a letter or digit and contain only letters, digits, ".", "_" or "-"');
|
|
115
|
+
/**
|
|
116
|
+
* `sha256:<64 lowercase hex>` — the ONLY accepted digest form.
|
|
117
|
+
*
|
|
118
|
+
* Appendix A writes `"sha256:..."`; §5.8 writes `"..."`. Accepting both would
|
|
119
|
+
* let the same bytes be described two ways, and the canonical envelope hash
|
|
120
|
+
* would differ between them (see property 1 in the module comment). Appendix A
|
|
121
|
+
* is the reference document, so the prefixed form wins and the bare form is
|
|
122
|
+
* rejected rather than silently normalised — normalising would mean the digest
|
|
123
|
+
* CrowdSource stores is not the digest the application sent.
|
|
124
|
+
*/
|
|
125
|
+
exports.Sha256DigestSchema = zod_1.z
|
|
126
|
+
.string()
|
|
127
|
+
.regex(/^sha256:[0-9a-f]{64}$/, 'must be "sha256:" followed by 64 lowercase hex characters');
|
|
128
|
+
/**
|
|
129
|
+
* Millisecond-precision UTC, e.g. `2026-07-28T18:00:00.000Z`.
|
|
130
|
+
*
|
|
131
|
+
* Every timestamp in the plan is written this way, and it is what
|
|
132
|
+
* `Date.prototype.toISOString()` produces, so the canonical form costs a
|
|
133
|
+
* JavaScript integrator nothing. Offsets are rejected: `18:00+02:00` and
|
|
134
|
+
* `16:00Z` are the same instant and would hash differently.
|
|
135
|
+
*/
|
|
136
|
+
exports.TimestampSchema = zod_1.z.iso.datetime({ offset: false, precision: 3 });
|
|
137
|
+
/**
|
|
138
|
+
* BCP 47, restricted to `language[-Script][-Region][-variant…]`.
|
|
139
|
+
*
|
|
140
|
+
* A syntactic subset, not the full grammar (no extensions, no private use, no
|
|
141
|
+
* grandfathered tags). Covers `es` and `es-ES` from the plan and everything a
|
|
142
|
+
* real application sends; a tag outside the subset is a signal worth rejecting
|
|
143
|
+
* at ingress rather than carrying into reviewer language eligibility.
|
|
144
|
+
*/
|
|
145
|
+
exports.LanguageTagSchema = zod_1.z
|
|
146
|
+
.string()
|
|
147
|
+
.regex(/^[a-z]{2,3}(-[A-Z][a-z]{3})?(-([A-Z]{2}|[0-9]{3}))?(-([0-9][A-Za-z0-9]{3}|[A-Za-z0-9]{5,8}))*$/, 'must be a BCP 47 tag of the form language[-Script][-Region][-variant]');
|
|
148
|
+
/**
|
|
149
|
+
* An absolute `http`/`https` URL.
|
|
150
|
+
*
|
|
151
|
+
* §7.2.7 rejects "dangerous schemes and executable resources". Scheme is a
|
|
152
|
+
* purely syntactic property, so the contract enforces it. Host reachability is
|
|
153
|
+
* NOT enforced here: deciding whether a host is internal needs DNS resolution
|
|
154
|
+
* and belongs to `safeFetch` at ingestion time. A scheme check that pretended
|
|
155
|
+
* to be an SSRF control would be the more dangerous of the two.
|
|
156
|
+
*/
|
|
157
|
+
exports.HttpUrlSchema = zod_1.z.url({ protocol: /^https?$/ }).max(2048);
|
|
158
|
+
/** A `type/subtype` media type, without parameters. */
|
|
159
|
+
exports.MimeTypeSchema = zod_1.z
|
|
160
|
+
.string()
|
|
161
|
+
.max(255)
|
|
162
|
+
.regex(/^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/, 'must be a lowercase "type/subtype" media type without parameters');
|
|
163
|
+
const METADATA_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9._-]*$/;
|
|
164
|
+
/** A key in any caller-supplied open bag. */
|
|
165
|
+
exports.ObjectKeySchema = zod_1.z
|
|
166
|
+
.string()
|
|
167
|
+
.min(1)
|
|
168
|
+
.max(exports.CONTRACT_LIMITS.METADATA_KEY_MAX_LENGTH)
|
|
169
|
+
.regex(METADATA_KEY_PATTERN, 'must start with a letter and contain only letters, digits, ".", "_" or "-"')
|
|
170
|
+
.refine((key) => !exports.FORBIDDEN_OBJECT_KEYS.includes(key), {
|
|
171
|
+
message: 'must not be a prototype-bearing key',
|
|
172
|
+
});
|
|
173
|
+
/** A single metadata value: scalar only, never a nested structure. */
|
|
174
|
+
exports.MetadataValueSchema = zod_1.z.union([
|
|
175
|
+
zod_1.z.string().max(exports.CONTRACT_LIMITS.METADATA_STRING_VALUE_MAX_LENGTH),
|
|
176
|
+
zod_1.z.number().finite(),
|
|
177
|
+
zod_1.z.boolean(),
|
|
178
|
+
zod_1.z.null(),
|
|
179
|
+
]);
|
|
180
|
+
/**
|
|
181
|
+
* An open key/value bag (§5.1 `metadata`, §5.3 the `metadata` resource type).
|
|
182
|
+
*
|
|
183
|
+
* Open by definition — the plan calls it "typed key value fields" and every
|
|
184
|
+
* tenant's keys differ — but flat and bounded. Allowing nesting here would make
|
|
185
|
+
* this the one field in the envelope through which an application could ship an
|
|
186
|
+
* arbitrary document tree past every other constraint in the contract.
|
|
187
|
+
*/
|
|
188
|
+
exports.MetadataBagSchema = zod_1.z
|
|
189
|
+
.record(exports.ObjectKeySchema, exports.MetadataValueSchema)
|
|
190
|
+
.refine((bag) => Object.keys(bag).length <= exports.CONTRACT_LIMITS.METADATA_KEYS_MAX, {
|
|
191
|
+
message: `must not have more than ${exports.CONTRACT_LIMITS.METADATA_KEYS_MAX} keys`,
|
|
192
|
+
});
|
|
193
|
+
const customPayloadScalarSchema = zod_1.z.union([
|
|
194
|
+
zod_1.z.string().max(exports.CONTRACT_LIMITS.CUSTOM_PAYLOAD_STRING_MAX_LENGTH),
|
|
195
|
+
zod_1.z.number().finite(),
|
|
196
|
+
zod_1.z.boolean(),
|
|
197
|
+
zod_1.z.null(),
|
|
198
|
+
]);
|
|
199
|
+
const buildCustomPayloadValueSchema = () => {
|
|
200
|
+
let value = customPayloadScalarSchema;
|
|
201
|
+
for (let depth = 1; depth < exports.CONTRACT_LIMITS.CUSTOM_PAYLOAD_MAX_DEPTH; depth += 1) {
|
|
202
|
+
value = zod_1.z.union([
|
|
203
|
+
customPayloadScalarSchema,
|
|
204
|
+
zod_1.z.array(value).max(exports.CONTRACT_LIMITS.CUSTOM_PAYLOAD_ARRAY_MAX_LENGTH),
|
|
205
|
+
zod_1.z.record(exports.ObjectKeySchema, value),
|
|
206
|
+
]);
|
|
207
|
+
}
|
|
208
|
+
return value;
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* The value grammar for a §5.7 custom payload.
|
|
212
|
+
*
|
|
213
|
+
* Nesting is unrolled to a fixed depth rather than made recursive so the bound
|
|
214
|
+
* is part of the type, enforced by the same parse as everything else, and
|
|
215
|
+
* visible in the exported JSON Schema. Depth is what stops a payload being a
|
|
216
|
+
* document; `ObjectKeySchema` is what stops it being a prototype.
|
|
217
|
+
*
|
|
218
|
+
* Note what is deliberately absent: no lexical filter on string values. A
|
|
219
|
+
* moderation payload legitimately carries the exact hostile text under review —
|
|
220
|
+
* markup, script fragments, `javascript:` URLs quoted inside a harassment
|
|
221
|
+
* report. Rejecting those strings would reject the material the system exists
|
|
222
|
+
* to review. §5.7's boundary is structural, not lexical: the contract has no
|
|
223
|
+
* field anywhere that carries markup, a template, a component or a remote URL
|
|
224
|
+
* to be rendered, so there is nothing for such a string to be interpreted as.
|
|
225
|
+
*/
|
|
226
|
+
exports.CustomPayloadValueSchema = buildCustomPayloadValueSchema();
|
|
227
|
+
/** The top level of a §5.7 custom payload: always an object, never an array. */
|
|
228
|
+
exports.CustomPayloadSchema = zod_1.z.record(exports.ObjectKeySchema, exports.CustomPayloadValueSchema);
|
|
229
|
+
/** A number in the closed interval [0, 1] — §9.5 clamps confidence to it. */
|
|
230
|
+
exports.UnitIntervalSchema = zod_1.z.number().min(0).max(1);
|
|
231
|
+
//# sourceMappingURL=primitives.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives.js","sourceRoot":"","sources":["../src/primitives.ts"],"names":[],"mappings":"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"}
|