event-contract-builder 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.
@@ -0,0 +1,221 @@
1
+ // src/cnl.ts
2
+ var COMPARATOR_PHRASES = {
3
+ "greater-than": "is greater than",
4
+ "greater-than-or-equal": "is greater than or equal to",
5
+ "less-than": "is less than",
6
+ "less-than-or-equal": "is less than or equal to",
7
+ "equal-to": "is exactly equal to",
8
+ "between-inclusive": "is greater than or equal to the lower bound and less than or equal to the upper bound of",
9
+ occurs: "occurs",
10
+ "does-not-occur": "does not occur"
11
+ };
12
+ var TEMPLATES = {
13
+ binaryThreshold: "This contract resolves YES if {METRIC}, as published by {PUBLISHER} ({SOURCE}), measured over {WINDOW}, {COMPARATOR_PHRASE} {THRESHOLD} {UNIT}, applying the {REVISION_POLICY} as of the resolution deadline; otherwise it resolves NO.",
14
+ binaryOccurrence: "This contract resolves YES if the following event {COMPARATOR_PHRASE} within {WINDOW}: {EVENT_CLAUSE} Otherwise it resolves NO.",
15
+ bucketMembership: "This contract resolves to the single bucket outcome listed in section payout whose range contains the value of {METRIC}, as published by {PUBLISHER} ({SOURCE}), measured over {WINDOW}, applying the {REVISION_POLICY}; bucket lower bounds are inclusive and upper bounds are exclusive."
16
+ };
17
+ var UNMET_DISPOSITION_PHRASES = {
18
+ "void-and-refund": "is voided and all positions are refunded at acquisition price",
19
+ "resolve-no": "resolves NO",
20
+ "resolve-to-floor": "settles at the floor value stated in section payout",
21
+ "exchange-determination-per-rulebook": "is resolved by exchange determination under the rulebook"
22
+ };
23
+ var REVISION_POLICY_PHRASES = {
24
+ "first-published-value": "first published value",
25
+ "value-as-of-observation-time": "value displayed at the stated observation time",
26
+ "final-revised-value": "final revised value"
27
+ };
28
+ function phraseFor(phrases, key, label) {
29
+ const phrase = phrases[key];
30
+ if (phrase === undefined) {
31
+ throw new Error(`Unsupported ${label}: ${key}`);
32
+ }
33
+ return phrase;
34
+ }
35
+ function windowPhrase(spec) {
36
+ const w = spec.resolution.observationWindow;
37
+ return `the period from ${w.start} to ${w.end} (${w.timezone})`;
38
+ }
39
+ function renderCanonicalStatement(spec) {
40
+ const c = spec.resolution.criterion;
41
+ const primary = spec.resolution.sources.find((s) => s.id === spec.resolution.primarySourceId);
42
+ const W = windowPhrase(spec);
43
+ switch (c.kind) {
44
+ case "threshold": {
45
+ const threshold = c.comparator === "between-inclusive" ? `the range [${c.threshold}, ${c.thresholdUpper}]` : String(c.threshold);
46
+ return TEMPLATES.binaryThreshold.replace("{METRIC}", c.metric.name).replace("{PUBLISHER}", primary.publisher).replace("{SOURCE}", primary.name).replace("{WINDOW}", W).replace("{COMPARATOR_PHRASE}", phraseFor(COMPARATOR_PHRASES, c.comparator, "comparator")).replace("{THRESHOLD}", threshold).replace("{UNIT}", c.metric.unit).replace("{REVISION_POLICY}", phraseFor(REVISION_POLICY_PHRASES, c.metric.revisionPolicy, "revision policy"));
47
+ }
48
+ case "occurrence":
49
+ return TEMPLATES.binaryOccurrence.replace("{COMPARATOR_PHRASE}", phraseFor(COMPARATOR_PHRASES, c.comparator, "comparator")).replace("{WINDOW}", W).replace("{EVENT_CLAUSE}", c.eventClause);
50
+ case "bucket-membership":
51
+ return TEMPLATES.bucketMembership.replace("{METRIC}", c.metric.name).replace("{PUBLISHER}", primary.publisher).replace("{SOURCE}", primary.name).replace("{WINDOW}", W).replace("{REVISION_POLICY}", phraseFor(REVISION_POLICY_PHRASES, c.metric.revisionPolicy, "revision policy"));
52
+ }
53
+ }
54
+
55
+ // src/schema/event-contract.ts
56
+ import { z as z2 } from "zod";
57
+
58
+ // src/schema/resolution.ts
59
+ import { z } from "zod";
60
+ var IsoDateTime = z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})$/, "Must be ISO 8601 date-time with explicit offset (e.g. 2026-07-15T16:00:00-04:00)").describe("ISO 8601 date-time with mandatory UTC offset");
61
+ var IanaTimezone = z.string().regex(/^[A-Za-z]+\/[A-Za-z_+\-]+(\/[A-Za-z_+\-]+)?$|^UTC$/, "Must be an IANA timezone or UTC").describe("IANA timezone identifier (e.g. America/New_York) or UTC");
62
+ var Slug = z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "Must be lowercase kebab-case").describe("Stable lowercase kebab-case identifier");
63
+ var CnlSentence = z.string().min(20).max(600).regex(/^[A-Z].*\.$/s, "Must be a complete sentence (capitalized, ending in a period)").refine((s) => !/\b(approximately|roughly|about|around|reasonable|significant|materially|generally|etc\.?)\b/i.test(s), "CNL sentences must not contain hedging/vague terms (approximately, roughly, significant, etc.)").describe("A single precise sentence in the controlled vocabulary; vague terms are rejected");
64
+ var Metric = z.object({
65
+ name: z.string().min(3).describe("Exact name of the data point as the source publishes it"),
66
+ unit: z.string().min(1),
67
+ extraction: CnlSentence.describe("Exactly which table/field/series ID the value is read from"),
68
+ revisionPolicy: z.enum([
69
+ "first-published-value",
70
+ "value-as-of-observation-time",
71
+ "final-revised-value"
72
+ ])
73
+ });
74
+ var ThresholdCriterion = z.object({
75
+ kind: z.literal("threshold"),
76
+ metric: Metric,
77
+ comparator: z.enum([
78
+ "greater-than",
79
+ "greater-than-or-equal",
80
+ "less-than",
81
+ "less-than-or-equal",
82
+ "equal-to",
83
+ "between-inclusive"
84
+ ]),
85
+ threshold: z.number(),
86
+ thresholdUpper: z.number().optional()
87
+ });
88
+ var OccurrenceCriterion = z.object({
89
+ kind: z.literal("occurrence"),
90
+ comparator: z.enum(["occurs", "does-not-occur"]),
91
+ eventClause: CnlSentence,
92
+ evidenceStandard: CnlSentence
93
+ });
94
+ var BucketMembershipCriterion = z.object({
95
+ kind: z.literal("bucket-membership"),
96
+ metric: Metric
97
+ });
98
+ var Criterion = z.discriminatedUnion("kind", [
99
+ ThresholdCriterion,
100
+ OccurrenceCriterion,
101
+ BucketMembershipCriterion
102
+ ]).describe("Structured resolution criterion; canonicalStatement is rendered from this");
103
+ var DataSource = z.object({
104
+ id: Slug,
105
+ name: z.string().min(3),
106
+ publisher: z.string().min(2).describe("Organization that produces the data"),
107
+ url: z.url(),
108
+ datasetId: z.string().optional(),
109
+ publicationSchedule: z.string().min(10).describe("When and how often the value is published"),
110
+ publiclyAccessible: z.boolean(),
111
+ independenceNote: z.string().min(30).describe("Publisher's independence from market participants")
112
+ });
113
+ var Fallback = z.object({
114
+ trigger: z.enum([
115
+ "primary-not-published-by-deadline",
116
+ "primary-discontinued",
117
+ "primary-methodology-materially-changed",
118
+ "primary-retracted-or-corrected"
119
+ ]),
120
+ sourceId: Slug.describe("id of a source in resolution.sources"),
121
+ procedure: CnlSentence
122
+ });
123
+ var TerminalAmbiguityPolicy = z.enum([
124
+ "resolve-no",
125
+ "resolve-to-floor",
126
+ "void-and-refund",
127
+ "exchange-determination-per-rulebook"
128
+ ]).describe("Pre-committed disposition when no source/criterion can determine the outcome");
129
+ var Resolution = z.object({
130
+ criterion: Criterion,
131
+ canonicalStatement: CnlSentence,
132
+ observationWindow: z.object({
133
+ start: IsoDateTime,
134
+ end: IsoDateTime,
135
+ timezone: IanaTimezone.describe("Governing timezone for any date words in CNL sentences")
136
+ }),
137
+ sources: z.array(DataSource).min(1).describe("All sources; first usable one per fallback order governs"),
138
+ primarySourceId: Slug,
139
+ fallbacks: z.array(Fallback).max(5).default([]),
140
+ resolutionDeadline: IsoDateTime,
141
+ earlyResolution: z.discriminatedUnion("allowed", [
142
+ z.object({ allowed: z.literal(false) }),
143
+ z.object({
144
+ allowed: z.literal(true),
145
+ condition: CnlSentence.describe("CNL condition under which the outcome is irreversibly determined early")
146
+ })
147
+ ]),
148
+ terminalAmbiguityPolicy: TerminalAmbiguityPolicy,
149
+ edgeCases: z.array(z.object({
150
+ scenario: CnlSentence,
151
+ disposition: CnlSentence
152
+ })).min(3).max(40).describe("At least 3 pre-decided edge cases (revisions, ties, postponements, source outages...)"),
153
+ disputeWindowHours: z.number().int().min(0).max(168)
154
+ }).describe("Complete resolution mechanics: criterion, sources, fallbacks, deadline, edge cases").superRefine((r, ctx) => {
155
+ const ids = new Set(r.sources.map((s) => s.id));
156
+ if (!ids.has(r.primarySourceId)) {
157
+ ctx.addIssue({
158
+ code: "custom",
159
+ path: ["primarySourceId"],
160
+ message: "primarySourceId must match a source id"
161
+ });
162
+ }
163
+ for (const [i, f] of r.fallbacks.entries()) {
164
+ if (!ids.has(f.sourceId)) {
165
+ ctx.addIssue({
166
+ code: "custom",
167
+ path: ["fallbacks", i, "sourceId"],
168
+ message: "fallback sourceId must match a source id"
169
+ });
170
+ }
171
+ }
172
+ if (r.criterion.kind === "threshold") {
173
+ const hasUpper = r.criterion.thresholdUpper !== undefined;
174
+ const needsUpper = r.criterion.comparator === "between-inclusive";
175
+ if (needsUpper !== hasUpper) {
176
+ ctx.addIssue({
177
+ code: "custom",
178
+ path: ["criterion", "thresholdUpper"],
179
+ message: "thresholdUpper is required iff comparator is between-inclusive"
180
+ });
181
+ }
182
+ }
183
+ const phrase = "comparator" in r.criterion ? COMPARATOR_PHRASES[r.criterion.comparator] : undefined;
184
+ if (phrase && !r.canonicalStatement.includes(phrase)) {
185
+ ctx.addIssue({
186
+ code: "custom",
187
+ path: ["canonicalStatement"],
188
+ message: `canonicalStatement must contain the fixed CNL phrase "${phrase}"`
189
+ });
190
+ }
191
+ });
192
+
193
+ // src/schema/event-contract.ts
194
+ var EventContractSpec = z2.object({
195
+ dsl: z2.literal("event-contract-cnl/0.1"),
196
+ resolution: Resolution
197
+ }).describe("Draft event-contract specification (CNL DSL v0.1) for pre-DCM review");
198
+ // src/schema/outcome.ts
199
+ import { z as z3 } from "zod";
200
+ var OutcomeSchema = z3.object({
201
+ type: z3.literal("binary").describe("Outcome type discriminator."),
202
+ values: z3.tuple([z3.literal("Yes"), z3.literal("No")]).describe("Binary outcome values.")
203
+ });
204
+ export {
205
+ renderCanonicalStatement,
206
+ UNMET_DISPOSITION_PHRASES,
207
+ TerminalAmbiguityPolicy,
208
+ TEMPLATES,
209
+ Slug,
210
+ Resolution,
211
+ OutcomeSchema,
212
+ Metric,
213
+ IsoDateTime,
214
+ IanaTimezone,
215
+ Fallback,
216
+ EventContractSpec,
217
+ DataSource,
218
+ Criterion,
219
+ CnlSentence,
220
+ COMPARATOR_PHRASES
221
+ };
@@ -0,0 +1,99 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Root schema for one event-contract specification document (one YAML file).
4
+ */
5
+ export declare const EventContractSpec: z.ZodObject<{
6
+ dsl: z.ZodLiteral<"event-contract-cnl/0.1">;
7
+ resolution: z.ZodObject<{
8
+ criterion: z.ZodDiscriminatedUnion<[z.ZodObject<{
9
+ kind: z.ZodLiteral<"threshold">;
10
+ metric: z.ZodObject<{
11
+ name: z.ZodString;
12
+ unit: z.ZodString;
13
+ extraction: z.ZodString;
14
+ revisionPolicy: z.ZodEnum<{
15
+ "first-published-value": "first-published-value";
16
+ "value-as-of-observation-time": "value-as-of-observation-time";
17
+ "final-revised-value": "final-revised-value";
18
+ }>;
19
+ }, z.core.$strip>;
20
+ comparator: z.ZodEnum<{
21
+ "greater-than": "greater-than";
22
+ "greater-than-or-equal": "greater-than-or-equal";
23
+ "less-than": "less-than";
24
+ "less-than-or-equal": "less-than-or-equal";
25
+ "equal-to": "equal-to";
26
+ "between-inclusive": "between-inclusive";
27
+ }>;
28
+ threshold: z.ZodNumber;
29
+ thresholdUpper: z.ZodOptional<z.ZodNumber>;
30
+ }, z.core.$strip>, z.ZodObject<{
31
+ kind: z.ZodLiteral<"occurrence">;
32
+ comparator: z.ZodEnum<{
33
+ occurs: "occurs";
34
+ "does-not-occur": "does-not-occur";
35
+ }>;
36
+ eventClause: z.ZodString;
37
+ evidenceStandard: z.ZodString;
38
+ }, z.core.$strip>, z.ZodObject<{
39
+ kind: z.ZodLiteral<"bucket-membership">;
40
+ metric: z.ZodObject<{
41
+ name: z.ZodString;
42
+ unit: z.ZodString;
43
+ extraction: z.ZodString;
44
+ revisionPolicy: z.ZodEnum<{
45
+ "first-published-value": "first-published-value";
46
+ "value-as-of-observation-time": "value-as-of-observation-time";
47
+ "final-revised-value": "final-revised-value";
48
+ }>;
49
+ }, z.core.$strip>;
50
+ }, z.core.$strip>], "kind">;
51
+ canonicalStatement: z.ZodString;
52
+ observationWindow: z.ZodObject<{
53
+ start: z.ZodString;
54
+ end: z.ZodString;
55
+ timezone: z.ZodString;
56
+ }, z.core.$strip>;
57
+ sources: z.ZodArray<z.ZodObject<{
58
+ id: z.ZodString;
59
+ name: z.ZodString;
60
+ publisher: z.ZodString;
61
+ url: z.ZodURL;
62
+ datasetId: z.ZodOptional<z.ZodString>;
63
+ publicationSchedule: z.ZodString;
64
+ publiclyAccessible: z.ZodBoolean;
65
+ independenceNote: z.ZodString;
66
+ }, z.core.$strip>>;
67
+ primarySourceId: z.ZodString;
68
+ fallbacks: z.ZodDefault<z.ZodArray<z.ZodObject<{
69
+ trigger: z.ZodEnum<{
70
+ "primary-not-published-by-deadline": "primary-not-published-by-deadline";
71
+ "primary-discontinued": "primary-discontinued";
72
+ "primary-methodology-materially-changed": "primary-methodology-materially-changed";
73
+ "primary-retracted-or-corrected": "primary-retracted-or-corrected";
74
+ }>;
75
+ sourceId: z.ZodString;
76
+ procedure: z.ZodString;
77
+ }, z.core.$strip>>>;
78
+ resolutionDeadline: z.ZodString;
79
+ earlyResolution: z.ZodDiscriminatedUnion<[z.ZodObject<{
80
+ allowed: z.ZodLiteral<false>;
81
+ }, z.core.$strip>, z.ZodObject<{
82
+ allowed: z.ZodLiteral<true>;
83
+ condition: z.ZodString;
84
+ }, z.core.$strip>], "allowed">;
85
+ terminalAmbiguityPolicy: z.ZodEnum<{
86
+ "resolve-no": "resolve-no";
87
+ "resolve-to-floor": "resolve-to-floor";
88
+ "void-and-refund": "void-and-refund";
89
+ "exchange-determination-per-rulebook": "exchange-determination-per-rulebook";
90
+ }>;
91
+ edgeCases: z.ZodArray<z.ZodObject<{
92
+ scenario: z.ZodString;
93
+ disposition: z.ZodString;
94
+ }, z.core.$strip>>;
95
+ disputeWindowHours: z.ZodNumber;
96
+ }, z.core.$strip>;
97
+ }, z.core.$strip>;
98
+ export type EventContractSpecT = z.infer<typeof EventContractSpec>;
99
+ //# sourceMappingURL=event-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-contract.d.ts","sourceRoot":"","sources":["../../../src/schema/event-contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAQ3B,CAAC;AAEJ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
@@ -0,0 +1,200 @@
1
+ // src/cnl.ts
2
+ var COMPARATOR_PHRASES = {
3
+ "greater-than": "is greater than",
4
+ "greater-than-or-equal": "is greater than or equal to",
5
+ "less-than": "is less than",
6
+ "less-than-or-equal": "is less than or equal to",
7
+ "equal-to": "is exactly equal to",
8
+ "between-inclusive": "is greater than or equal to the lower bound and less than or equal to the upper bound of",
9
+ occurs: "occurs",
10
+ "does-not-occur": "does not occur"
11
+ };
12
+ var TEMPLATES = {
13
+ binaryThreshold: "This contract resolves YES if {METRIC}, as published by {PUBLISHER} ({SOURCE}), measured over {WINDOW}, {COMPARATOR_PHRASE} {THRESHOLD} {UNIT}, applying the {REVISION_POLICY} as of the resolution deadline; otherwise it resolves NO.",
14
+ binaryOccurrence: "This contract resolves YES if the following event {COMPARATOR_PHRASE} within {WINDOW}: {EVENT_CLAUSE} Otherwise it resolves NO.",
15
+ bucketMembership: "This contract resolves to the single bucket outcome listed in section payout whose range contains the value of {METRIC}, as published by {PUBLISHER} ({SOURCE}), measured over {WINDOW}, applying the {REVISION_POLICY}; bucket lower bounds are inclusive and upper bounds are exclusive."
16
+ };
17
+ var UNMET_DISPOSITION_PHRASES = {
18
+ "void-and-refund": "is voided and all positions are refunded at acquisition price",
19
+ "resolve-no": "resolves NO",
20
+ "resolve-to-floor": "settles at the floor value stated in section payout",
21
+ "exchange-determination-per-rulebook": "is resolved by exchange determination under the rulebook"
22
+ };
23
+ var REVISION_POLICY_PHRASES = {
24
+ "first-published-value": "first published value",
25
+ "value-as-of-observation-time": "value displayed at the stated observation time",
26
+ "final-revised-value": "final revised value"
27
+ };
28
+ function phraseFor(phrases, key, label) {
29
+ const phrase = phrases[key];
30
+ if (phrase === undefined) {
31
+ throw new Error(`Unsupported ${label}: ${key}`);
32
+ }
33
+ return phrase;
34
+ }
35
+ function windowPhrase(spec) {
36
+ const w = spec.resolution.observationWindow;
37
+ return `the period from ${w.start} to ${w.end} (${w.timezone})`;
38
+ }
39
+ function renderCanonicalStatement(spec) {
40
+ const c = spec.resolution.criterion;
41
+ const primary = spec.resolution.sources.find((s) => s.id === spec.resolution.primarySourceId);
42
+ const W = windowPhrase(spec);
43
+ switch (c.kind) {
44
+ case "threshold": {
45
+ const threshold = c.comparator === "between-inclusive" ? `the range [${c.threshold}, ${c.thresholdUpper}]` : String(c.threshold);
46
+ return TEMPLATES.binaryThreshold.replace("{METRIC}", c.metric.name).replace("{PUBLISHER}", primary.publisher).replace("{SOURCE}", primary.name).replace("{WINDOW}", W).replace("{COMPARATOR_PHRASE}", phraseFor(COMPARATOR_PHRASES, c.comparator, "comparator")).replace("{THRESHOLD}", threshold).replace("{UNIT}", c.metric.unit).replace("{REVISION_POLICY}", phraseFor(REVISION_POLICY_PHRASES, c.metric.revisionPolicy, "revision policy"));
47
+ }
48
+ case "occurrence":
49
+ return TEMPLATES.binaryOccurrence.replace("{COMPARATOR_PHRASE}", phraseFor(COMPARATOR_PHRASES, c.comparator, "comparator")).replace("{WINDOW}", W).replace("{EVENT_CLAUSE}", c.eventClause);
50
+ case "bucket-membership":
51
+ return TEMPLATES.bucketMembership.replace("{METRIC}", c.metric.name).replace("{PUBLISHER}", primary.publisher).replace("{SOURCE}", primary.name).replace("{WINDOW}", W).replace("{REVISION_POLICY}", phraseFor(REVISION_POLICY_PHRASES, c.metric.revisionPolicy, "revision policy"));
52
+ }
53
+ }
54
+
55
+ // src/schema/event-contract.ts
56
+ import { z as z2 } from "zod";
57
+
58
+ // src/schema/resolution.ts
59
+ import { z } from "zod";
60
+ var IsoDateTime = z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})$/, "Must be ISO 8601 date-time with explicit offset (e.g. 2026-07-15T16:00:00-04:00)").describe("ISO 8601 date-time with mandatory UTC offset");
61
+ var IanaTimezone = z.string().regex(/^[A-Za-z]+\/[A-Za-z_+\-]+(\/[A-Za-z_+\-]+)?$|^UTC$/, "Must be an IANA timezone or UTC").describe("IANA timezone identifier (e.g. America/New_York) or UTC");
62
+ var Slug = z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "Must be lowercase kebab-case").describe("Stable lowercase kebab-case identifier");
63
+ var CnlSentence = z.string().min(20).max(600).regex(/^[A-Z].*\.$/s, "Must be a complete sentence (capitalized, ending in a period)").refine((s) => !/\b(approximately|roughly|about|around|reasonable|significant|materially|generally|etc\.?)\b/i.test(s), "CNL sentences must not contain hedging/vague terms (approximately, roughly, significant, etc.)").describe("A single precise sentence in the controlled vocabulary; vague terms are rejected");
64
+ var Metric = z.object({
65
+ name: z.string().min(3).describe("Exact name of the data point as the source publishes it"),
66
+ unit: z.string().min(1),
67
+ extraction: CnlSentence.describe("Exactly which table/field/series ID the value is read from"),
68
+ revisionPolicy: z.enum([
69
+ "first-published-value",
70
+ "value-as-of-observation-time",
71
+ "final-revised-value"
72
+ ])
73
+ });
74
+ var ThresholdCriterion = z.object({
75
+ kind: z.literal("threshold"),
76
+ metric: Metric,
77
+ comparator: z.enum([
78
+ "greater-than",
79
+ "greater-than-or-equal",
80
+ "less-than",
81
+ "less-than-or-equal",
82
+ "equal-to",
83
+ "between-inclusive"
84
+ ]),
85
+ threshold: z.number(),
86
+ thresholdUpper: z.number().optional()
87
+ });
88
+ var OccurrenceCriterion = z.object({
89
+ kind: z.literal("occurrence"),
90
+ comparator: z.enum(["occurs", "does-not-occur"]),
91
+ eventClause: CnlSentence,
92
+ evidenceStandard: CnlSentence
93
+ });
94
+ var BucketMembershipCriterion = z.object({
95
+ kind: z.literal("bucket-membership"),
96
+ metric: Metric
97
+ });
98
+ var Criterion = z.discriminatedUnion("kind", [
99
+ ThresholdCriterion,
100
+ OccurrenceCriterion,
101
+ BucketMembershipCriterion
102
+ ]).describe("Structured resolution criterion; canonicalStatement is rendered from this");
103
+ var DataSource = z.object({
104
+ id: Slug,
105
+ name: z.string().min(3),
106
+ publisher: z.string().min(2).describe("Organization that produces the data"),
107
+ url: z.url(),
108
+ datasetId: z.string().optional(),
109
+ publicationSchedule: z.string().min(10).describe("When and how often the value is published"),
110
+ publiclyAccessible: z.boolean(),
111
+ independenceNote: z.string().min(30).describe("Publisher's independence from market participants")
112
+ });
113
+ var Fallback = z.object({
114
+ trigger: z.enum([
115
+ "primary-not-published-by-deadline",
116
+ "primary-discontinued",
117
+ "primary-methodology-materially-changed",
118
+ "primary-retracted-or-corrected"
119
+ ]),
120
+ sourceId: Slug.describe("id of a source in resolution.sources"),
121
+ procedure: CnlSentence
122
+ });
123
+ var TerminalAmbiguityPolicy = z.enum([
124
+ "resolve-no",
125
+ "resolve-to-floor",
126
+ "void-and-refund",
127
+ "exchange-determination-per-rulebook"
128
+ ]).describe("Pre-committed disposition when no source/criterion can determine the outcome");
129
+ var Resolution = z.object({
130
+ criterion: Criterion,
131
+ canonicalStatement: CnlSentence,
132
+ observationWindow: z.object({
133
+ start: IsoDateTime,
134
+ end: IsoDateTime,
135
+ timezone: IanaTimezone.describe("Governing timezone for any date words in CNL sentences")
136
+ }),
137
+ sources: z.array(DataSource).min(1).describe("All sources; first usable one per fallback order governs"),
138
+ primarySourceId: Slug,
139
+ fallbacks: z.array(Fallback).max(5).default([]),
140
+ resolutionDeadline: IsoDateTime,
141
+ earlyResolution: z.discriminatedUnion("allowed", [
142
+ z.object({ allowed: z.literal(false) }),
143
+ z.object({
144
+ allowed: z.literal(true),
145
+ condition: CnlSentence.describe("CNL condition under which the outcome is irreversibly determined early")
146
+ })
147
+ ]),
148
+ terminalAmbiguityPolicy: TerminalAmbiguityPolicy,
149
+ edgeCases: z.array(z.object({
150
+ scenario: CnlSentence,
151
+ disposition: CnlSentence
152
+ })).min(3).max(40).describe("At least 3 pre-decided edge cases (revisions, ties, postponements, source outages...)"),
153
+ disputeWindowHours: z.number().int().min(0).max(168)
154
+ }).describe("Complete resolution mechanics: criterion, sources, fallbacks, deadline, edge cases").superRefine((r, ctx) => {
155
+ const ids = new Set(r.sources.map((s) => s.id));
156
+ if (!ids.has(r.primarySourceId)) {
157
+ ctx.addIssue({
158
+ code: "custom",
159
+ path: ["primarySourceId"],
160
+ message: "primarySourceId must match a source id"
161
+ });
162
+ }
163
+ for (const [i, f] of r.fallbacks.entries()) {
164
+ if (!ids.has(f.sourceId)) {
165
+ ctx.addIssue({
166
+ code: "custom",
167
+ path: ["fallbacks", i, "sourceId"],
168
+ message: "fallback sourceId must match a source id"
169
+ });
170
+ }
171
+ }
172
+ if (r.criterion.kind === "threshold") {
173
+ const hasUpper = r.criterion.thresholdUpper !== undefined;
174
+ const needsUpper = r.criterion.comparator === "between-inclusive";
175
+ if (needsUpper !== hasUpper) {
176
+ ctx.addIssue({
177
+ code: "custom",
178
+ path: ["criterion", "thresholdUpper"],
179
+ message: "thresholdUpper is required iff comparator is between-inclusive"
180
+ });
181
+ }
182
+ }
183
+ const phrase = "comparator" in r.criterion ? COMPARATOR_PHRASES[r.criterion.comparator] : undefined;
184
+ if (phrase && !r.canonicalStatement.includes(phrase)) {
185
+ ctx.addIssue({
186
+ code: "custom",
187
+ path: ["canonicalStatement"],
188
+ message: `canonicalStatement must contain the fixed CNL phrase "${phrase}"`
189
+ });
190
+ }
191
+ });
192
+
193
+ // src/schema/event-contract.ts
194
+ var EventContractSpec = z2.object({
195
+ dsl: z2.literal("event-contract-cnl/0.1"),
196
+ resolution: Resolution
197
+ }).describe("Draft event-contract specification (CNL DSL v0.1) for pre-DCM review");
198
+ export {
199
+ EventContractSpec
200
+ };
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Phase 1 supports binary outcomes only. This will become a
4
+ * `z.discriminatedUnion("type", [...])` once categorical and scalar
5
+ * outcomes are added (see PLAN.md roadmap).
6
+ */
7
+ export declare const OutcomeSchema: z.ZodObject<{
8
+ type: z.ZodLiteral<"binary">;
9
+ values: z.ZodTuple<[z.ZodLiteral<"Yes">, z.ZodLiteral<"No">], null>;
10
+ }, z.core.$strip>;
11
+ //# sourceMappingURL=outcome.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outcome.d.ts","sourceRoot":"","sources":["../../../src/schema/outcome.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;iBAGxB,CAAC"}