@sonordev/contracts 2.17.0 → 2.20.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/CHANGELOG.md +40 -0
- package/README.md +514 -1
- package/dist/broadcast-layouts.d.ts +8 -0
- package/dist/broadcast-layouts.js +4 -0
- package/dist/broadcast-layouts.mjs +3 -1
- package/dist/decision-roles.d.ts +39 -0
- package/dist/decision-roles.js +31 -0
- package/dist/decision-roles.mjs +28 -0
- package/dist/funnels.d.ts +46 -0
- package/dist/funnels.js +35 -4
- package/dist/funnels.mjs +33 -5
- package/dist/resonance-constants.d.ts +4 -0
- package/dist/resonance-constants.js +10 -0
- package/dist/resonance-constants.mjs +6 -0
- package/dist/resonance.d.ts +6962 -0
- package/dist/resonance.js +1809 -0
- package/dist/resonance.mjs +1650 -0
- package/dist/signal-experiments.d.ts +219 -0
- package/dist/signal-experiments.js +270 -0
- package/dist/signal-experiments.mjs +260 -0
- package/dist/wire-schema.d.ts +36 -0
- package/docs.json +21 -0
- package/package.json +24 -3
|
@@ -0,0 +1,1650 @@
|
|
|
1
|
+
// src/contracts/resonance-constants.ts
|
|
2
|
+
var RESONANCE_ATTRIBUTION_MODEL = "first_party_last_touch_marker_v1";
|
|
3
|
+
var RESONANCE_SOURCE_MARKER_PARAMETER = "sonor_source";
|
|
4
|
+
var RESONANCE_SUBJECT_HEADER = "X-Sonor-Resonance-Subject";
|
|
5
|
+
|
|
6
|
+
// src/contracts/wire-schema.ts
|
|
7
|
+
function createWireSchema(fail3, isValidationError) {
|
|
8
|
+
const own = (v, k) => Object.prototype.hasOwnProperty.call(v, k);
|
|
9
|
+
const field3 = (schema, read) => ({ schema, read });
|
|
10
|
+
const text3 = (max = 200, pattern, min = 1) => field3({ type: "string", minLength: min, maxLength: max, ...pattern ? { pattern: pattern.source } : {} }, (v, p) => typeof v === "string" && v.length >= min && v.length <= max && (!pattern || pattern.test(v)) ? v : fail3(p, "invalid string"));
|
|
11
|
+
const choice3 = (...values) => field3({ type: "string", enum: values }, (v, p) => typeof v === "string" && values.includes(v) ? v : fail3(p, "unsupported value"));
|
|
12
|
+
const literal3 = (value) => field3({ const: value }, (v, p) => v === value ? value : fail3(p, "unexpected value"));
|
|
13
|
+
const integer3 = (min = 0, max = Number.MAX_SAFE_INTEGER) => field3({ type: "integer", minimum: min, maximum: max }, (v, p) => typeof v === "number" && Number.isSafeInteger(v) && v >= min && v <= max ? v : fail3(p, "invalid integer"));
|
|
14
|
+
const bool2 = field3({ type: "boolean" }, (v, p) => typeof v === "boolean" ? v : fail3(p, "must be boolean"));
|
|
15
|
+
const optional3 = (f) => ({ ...f, optional: true });
|
|
16
|
+
const nullable3 = (f) => field3({ anyOf: [f.schema, { type: "null" }] }, (v, p) => v === null ? null : f.read(v, p));
|
|
17
|
+
const list3 = (f, max = 100, min = 0) => field3({ type: "array", items: f.schema, minItems: min, maxItems: max }, (v, p) => {
|
|
18
|
+
if (!Array.isArray(v) || v.length < min || v.length > max) fail3(p, "invalid array");
|
|
19
|
+
return v.map((x, i) => f.read(x, `${p}[${i}]`));
|
|
20
|
+
});
|
|
21
|
+
const object3 = (fields2) => field3({ type: "object", additionalProperties: false, properties: Object.fromEntries(Object.entries(fields2).map(([k, f]) => [k, f.schema])), required: Object.keys(fields2).filter((k) => !("optional" in fields2[k])) }, (v, p) => {
|
|
22
|
+
if (!v || typeof v !== "object" || Array.isArray(v) || ![Object.prototype, null].includes(Object.getPrototypeOf(v))) fail3(p, "must be a plain object");
|
|
23
|
+
const source2 = v, result = {};
|
|
24
|
+
for (const key of Reflect.ownKeys(source2)) if (typeof key !== "string" || !own(fields2, key)) fail3(p, "unknown field");
|
|
25
|
+
for (const [key, f] of Object.entries(fields2)) {
|
|
26
|
+
if (!own(source2, key)) {
|
|
27
|
+
if ("optional" in f) continue;
|
|
28
|
+
fail3(`${p}.${key}`, "required");
|
|
29
|
+
}
|
|
30
|
+
const descriptor = Object.getOwnPropertyDescriptor(source2, key);
|
|
31
|
+
if (!own(descriptor, "value")) fail3(`${p}.${key}`, "accessors are not wire data");
|
|
32
|
+
result[key] = f.read(descriptor.value, `${p}.${key}`);
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
});
|
|
36
|
+
const union3 = (...fields2) => field3({ oneOf: fields2.map((f) => f.schema) }, (v, p) => {
|
|
37
|
+
const found = [];
|
|
38
|
+
for (const f of fields2) {
|
|
39
|
+
try {
|
|
40
|
+
found.push(f.read(v, p));
|
|
41
|
+
} catch (e) {
|
|
42
|
+
if (!isValidationError(e)) throw e;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return found.length === 1 ? found[0] : fail3(p, "must match exactly one supported shape");
|
|
46
|
+
});
|
|
47
|
+
const refine3 = (f, check) => field3(f.schema, (v, p) => {
|
|
48
|
+
const result = f.read(v, p);
|
|
49
|
+
check(result, p);
|
|
50
|
+
return result;
|
|
51
|
+
});
|
|
52
|
+
return { field: field3, text: text3, choice: choice3, literal: literal3, integer: integer3, bool: bool2, optional: optional3, nullable: nullable3, list: list3, object: object3, union: union3, refine: refine3 };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/contracts/sites-normalize.ts
|
|
56
|
+
function normalizeSiteHost(site2) {
|
|
57
|
+
if (!site2 || typeof site2 !== "string") return null;
|
|
58
|
+
let s = site2.trim().toLowerCase();
|
|
59
|
+
if (!s) return null;
|
|
60
|
+
s = s.replace(/^https?:\/\//, "");
|
|
61
|
+
const slash = s.indexOf("/");
|
|
62
|
+
if (slash >= 0) s = s.slice(0, slash);
|
|
63
|
+
const colon = s.indexOf(":");
|
|
64
|
+
if (colon >= 0) s = s.slice(0, colon);
|
|
65
|
+
s = s.replace(/\.+$/, "");
|
|
66
|
+
return s || null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/contracts/sites.ts
|
|
70
|
+
function stripWww(host) {
|
|
71
|
+
return host.startsWith("www.") ? host.slice(4) : host;
|
|
72
|
+
}
|
|
73
|
+
function sameSite(a, b) {
|
|
74
|
+
if (!a || !b) return false;
|
|
75
|
+
return stripWww(a) === stripWww(b);
|
|
76
|
+
}
|
|
77
|
+
function hostFromUrl(value) {
|
|
78
|
+
if (!value || !value.startsWith("http://") && !value.startsWith("https://")) return null;
|
|
79
|
+
try {
|
|
80
|
+
return normalizeSiteHost(new URL(value).host);
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function isNormalizedSiteHost(value) {
|
|
86
|
+
return typeof value === "string" && value.length > 0 && normalizeSiteHost(value) === value;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/contracts/public-url-host.ts
|
|
90
|
+
function isPrivateIpv4(a, b) {
|
|
91
|
+
return a === 127 || a === 10 || a === 0 || a === 192 && b === 168 || a === 172 && b >= 16 && b <= 31 || a === 169 && b === 254 || a === 100 && b >= 64 && b <= 127 || a >= 224;
|
|
92
|
+
}
|
|
93
|
+
function ipv6Words(host) {
|
|
94
|
+
if (!host.includes(":") || !/^[a-f\d:.]+$/.test(host)) return null;
|
|
95
|
+
let canonical2;
|
|
96
|
+
try {
|
|
97
|
+
canonical2 = new URL(`http://[${host}]/`).hostname.slice(1, -1);
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const sides = canonical2.split("::");
|
|
102
|
+
const left = sides[0] ? sides[0].split(":") : [];
|
|
103
|
+
const right = sides.length > 1 && sides[1] ? sides[1].split(":") : [];
|
|
104
|
+
return [...left, ...Array(sides.length > 1 ? 8 - left.length - right.length : 0).fill("0"), ...right].map((word) => parseInt(word, 16));
|
|
105
|
+
}
|
|
106
|
+
function isBlockedPublicUrlHost(rawHost) {
|
|
107
|
+
const host = rawHost.toLowerCase().replace(/^\[|\]$/g, "");
|
|
108
|
+
const blockedName = host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal");
|
|
109
|
+
if (host.includes(":") && host.includes("%")) return true;
|
|
110
|
+
const ipv4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
111
|
+
const blockedIp = !!ipv4 && isPrivateIpv4(Number(ipv4[1]), Number(ipv4[2]));
|
|
112
|
+
const words = ipv6Words(host);
|
|
113
|
+
const blockedIpv6 = !!words && // Unspecified/loopback, unique-local, link-local, deprecated site-local
|
|
114
|
+
// and multicast destinations aren't public logo/webhook endpoints.
|
|
115
|
+
(words.every((word) => word === 0) || words.slice(0, 7).every((word) => word === 0) && words[7] === 1 || (words[0] & 65024) === 64512 || (words[0] & 65472) === 65152 || (words[0] & 65472) === 65216 || (words[0] & 65280) === 65280 || // IPv4-mapped and older IPv4-compatible forms carry an IPv4 destination.
|
|
116
|
+
words.slice(0, 5).every((word) => word === 0) && [0, 65535].includes(words[5]) && isPrivateIpv4(words[6] >> 8, words[6] & 255));
|
|
117
|
+
return blockedName || blockedIp || blockedIpv6;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/contracts/signal-experiments.ts
|
|
121
|
+
var SignalExperimentValidationError = class extends Error {
|
|
122
|
+
constructor(path2, reason) {
|
|
123
|
+
super(`${path2}: ${reason}`);
|
|
124
|
+
this.path = path2;
|
|
125
|
+
this.name = "SignalExperimentValidationError";
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
var fail = (path2, reason) => {
|
|
129
|
+
throw new SignalExperimentValidationError(path2, reason);
|
|
130
|
+
};
|
|
131
|
+
var { field, text, choice, literal, integer, optional, nullable, list, object, union, refine } = createWireSchema(fail, (error) => error instanceof SignalExperimentValidationError);
|
|
132
|
+
var percent = field({ type: "number", minimum: 0, maximum: 100 }, (v, p) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 100 ? v : fail(p, "must be a finite percentage from 0 to 100"));
|
|
133
|
+
var page = refine(text(2048, /^\/(?!\/)[^?#\s\\]*$/), (v, p) => {
|
|
134
|
+
const parsed = new URL(v, "https://example.com");
|
|
135
|
+
if (parsed.pathname !== v) fail(p, "must be a canonical page path");
|
|
136
|
+
});
|
|
137
|
+
var variant = object({ key: text(80), name: text(160), weight: percent, description: optional(text(1e3, void 0, 0)) });
|
|
138
|
+
var variants = refine(list(variant, 50, 2), (v, p) => {
|
|
139
|
+
if (new Set(v.map((x) => x.key)).size !== v.length) fail(p, "variant keys must be unique");
|
|
140
|
+
if (Math.abs(v.reduce((sum, x) => sum + x.weight, 0) - 100) > 0.01) fail(p, "variant weights must sum to 100");
|
|
141
|
+
});
|
|
142
|
+
var targeting = object({ pages: optional(list(page, 100)), segments: optional(list(text(160), 100)), devices: optional(list(choice("desktop", "mobile", "tablet"), 3)), geo: optional(list(text(160), 100)) });
|
|
143
|
+
var fields = { name: text(160), description: text(1e4, void 0, 0), variants, traffic_allocation: percent, targeting_rules: targeting };
|
|
144
|
+
var create = object({ ...fields, description: optional(fields.description), traffic_allocation: optional(fields.traffic_allocation), targeting_rules: optional(fields.targeting_rules) });
|
|
145
|
+
var update = object({ name: optional(fields.name), description: optional(fields.description), variants: optional(fields.variants), traffic_allocation: optional(fields.traffic_allocation), targeting_rules: optional(fields.targeting_rules) });
|
|
146
|
+
var parseSignalExperimentCreate = (v) => create.read(v, "experimentCreate");
|
|
147
|
+
var parseSignalExperimentUpdate = (v) => update.read(v, "experimentUpdate");
|
|
148
|
+
var SIGNAL_EXPERIMENT_CREATE_JSON_SCHEMA = create.schema;
|
|
149
|
+
var SIGNAL_EXPERIMENT_UPDATE_JSON_SCHEMA = update.schema;
|
|
150
|
+
var measurementUuid = text(36, /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
|
|
151
|
+
var measurementRevision = text(128, /^[a-zA-Z0-9._:-]+$/);
|
|
152
|
+
var measurementInstant = refine(text(30, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/), (v, p) => {
|
|
153
|
+
if (!Number.isFinite(Date.parse(v)) || new Date(v).toISOString().replace(".000Z", "Z") !== v.replace(".000Z", "Z")) fail(p, "invalid UTC instant");
|
|
154
|
+
});
|
|
155
|
+
var measurementSite = refine(text(253), (v, p) => {
|
|
156
|
+
if (!isNormalizedSiteHost(v) || isBlockedPublicUrlHost(v)) fail(p, "must be a normalized public site host");
|
|
157
|
+
});
|
|
158
|
+
var measurementCount = text(30, /^(?:0|[1-9][0-9]*)$/);
|
|
159
|
+
var measurementScope = object({
|
|
160
|
+
projectId: measurementUuid,
|
|
161
|
+
experimentId: measurementUuid,
|
|
162
|
+
site: measurementSite,
|
|
163
|
+
experimentRevision: integer(1),
|
|
164
|
+
goalRevision: measurementRevision,
|
|
165
|
+
campaignId: measurementUuid,
|
|
166
|
+
campaignVersionId: measurementUuid,
|
|
167
|
+
assignedFrom: measurementInstant,
|
|
168
|
+
assignedTo: measurementInstant
|
|
169
|
+
});
|
|
170
|
+
var assignmentCounts = {
|
|
171
|
+
variantKey: text(80),
|
|
172
|
+
assignedUnits: measurementCount,
|
|
173
|
+
exposureEvents: measurementCount,
|
|
174
|
+
exposedUnits: measurementCount,
|
|
175
|
+
matureAssignedUnits: measurementCount,
|
|
176
|
+
immatureAssignedUnits: measurementCount,
|
|
177
|
+
assignmentsWithoutExposure: measurementCount
|
|
178
|
+
};
|
|
179
|
+
var availableVariant = object({ ...assignmentCounts, acceptedFormEvents: measurementCount, acceptedFormUnits: measurementCount, matureAcceptedFormUnits: measurementCount });
|
|
180
|
+
var unsupportedVariant = object({ ...assignmentCounts, acceptedFormEvents: literal(null), acceptedFormUnits: literal(null), matureAcceptedFormUnits: literal(null) });
|
|
181
|
+
var businessMetrics = object({
|
|
182
|
+
uniqueQualifiedContacts: object({ state: literal("unavailable"), reasonCode: literal("original_qualified_lineage_not_bound") }),
|
|
183
|
+
wonValue: object({ state: literal("unavailable"), reasonCode: literal("original_deal_lineage_not_bound") }),
|
|
184
|
+
paidRevenue: object({ state: literal("unavailable"), reasonCode: literal("original_paid_source_lineage_not_bound") })
|
|
185
|
+
});
|
|
186
|
+
var goalCountingUnit = choice("unique_source", "unique_qualified_contact", "net_order");
|
|
187
|
+
var originalGoalFields = { sourceId: measurementUuid, sourceRevision: measurementRevision, countingUnit: goalCountingUnit };
|
|
188
|
+
var formSourceKind = choice("accepted_form", "qualified_lead");
|
|
189
|
+
var unsupportedSourceKind = choice("completed_booking", "net_paid_order", "approved_application");
|
|
190
|
+
var observedFields = {
|
|
191
|
+
schemaVersion: literal(1),
|
|
192
|
+
mode: literal("trusted_source"),
|
|
193
|
+
state: literal("observed"),
|
|
194
|
+
observedAt: measurementInstant,
|
|
195
|
+
scope: measurementScope,
|
|
196
|
+
countingUnit: literal("consent_subject"),
|
|
197
|
+
coverage: literal("assigned_population_only"),
|
|
198
|
+
semanticRole: literal("accepted_form_diagnostic"),
|
|
199
|
+
businessMetrics
|
|
200
|
+
};
|
|
201
|
+
var observedMeasurement = refine(union(
|
|
202
|
+
object({
|
|
203
|
+
...observedFields,
|
|
204
|
+
originalGoal: object({ ...originalGoalFields, sourceKind: formSourceKind }),
|
|
205
|
+
diagnosticType: literal("accepted_form"),
|
|
206
|
+
diagnosticState: literal("available"),
|
|
207
|
+
variants: list(availableVariant, 50)
|
|
208
|
+
}),
|
|
209
|
+
object({
|
|
210
|
+
...observedFields,
|
|
211
|
+
originalGoal: object({ ...originalGoalFields, sourceKind: unsupportedSourceKind }),
|
|
212
|
+
diagnosticType: literal(null),
|
|
213
|
+
diagnosticState: literal("unsupported_original_source"),
|
|
214
|
+
variants: list(unsupportedVariant, 50)
|
|
215
|
+
})
|
|
216
|
+
), (value, p) => {
|
|
217
|
+
if (Date.parse(value.scope.assignedFrom) >= Date.parse(value.scope.assignedTo) || Date.parse(value.scope.assignedTo) > Date.parse(value.observedAt))
|
|
218
|
+
fail(p + ".scope", "requires an increasing assignment cohort ending by the native observation");
|
|
219
|
+
if (new Set(value.variants.map((v) => v.variantKey)).size !== value.variants.length) fail(p + ".variants", "variant keys must be unique");
|
|
220
|
+
for (const [index, v] of value.variants.entries()) {
|
|
221
|
+
const assigned = BigInt(v.assignedUnits), exposed = BigInt(v.exposedUnits), mature = BigInt(v.matureAssignedUnits);
|
|
222
|
+
const at = `${p}.variants[${index}]`;
|
|
223
|
+
if (exposed > assigned || BigInt(v.exposureEvents) < exposed || BigInt(v.assignmentsWithoutExposure) !== assigned - exposed || mature + BigInt(v.immatureAssignedUnits) !== assigned)
|
|
224
|
+
fail(at, "assignment, exposure and maturity counts are inconsistent");
|
|
225
|
+
if (v.acceptedFormUnits !== null && v.acceptedFormEvents !== null && v.matureAcceptedFormUnits !== null) {
|
|
226
|
+
const accepted = BigInt(v.acceptedFormUnits), matureAccepted = BigInt(v.matureAcceptedFormUnits);
|
|
227
|
+
if (accepted > exposed || accepted > BigInt(v.acceptedFormEvents) || matureAccepted > mature || matureAccepted > accepted)
|
|
228
|
+
fail(at, "accepted form units must remain within their exposed, event and mature cohorts");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
var measurement = union(
|
|
233
|
+
object({ schemaVersion: literal(1), mode: literal("legacy"), state: literal("unavailable"), reasonCode: literal("legacy_assignment_outcome_events_only") }),
|
|
234
|
+
object({ schemaVersion: literal(1), mode: literal("trusted_source"), state: literal("unavailable"), reasonCode: literal("measurement_scope_required") }),
|
|
235
|
+
observedMeasurement
|
|
236
|
+
);
|
|
237
|
+
measurement.schema;
|
|
238
|
+
var scopeOption = object({
|
|
239
|
+
campaignId: measurementUuid,
|
|
240
|
+
campaignVersionId: measurementUuid,
|
|
241
|
+
campaignDefinitionRevision: integer(1),
|
|
242
|
+
acceptedAt: measurementInstant,
|
|
243
|
+
site: measurementSite,
|
|
244
|
+
experimentId: measurementUuid,
|
|
245
|
+
experimentRevision: integer(1),
|
|
246
|
+
goalRevision: measurementRevision,
|
|
247
|
+
goalLabel: nullable(text(200)),
|
|
248
|
+
assignmentExtent: nullable(object({ firstAssignedAt: measurementInstant, lastAssignedAt: measurementInstant })),
|
|
249
|
+
suggestedCohort: nullable(object({ assignedFrom: measurementInstant, assignedTo: measurementInstant }))
|
|
250
|
+
});
|
|
251
|
+
var scopeOptions = refine(object({ observedAt: measurementInstant, items: list(scopeOption, 100), nextCursor: nullable(text(4096)) }), (value, p) => {
|
|
252
|
+
const observed = Date.parse(value.observedAt), tuples = /* @__PURE__ */ new Set();
|
|
253
|
+
for (const [index, item] of value.items.entries()) {
|
|
254
|
+
const at = `${p}.items[${index}]`, accepted = Date.parse(item.acceptedAt);
|
|
255
|
+
const tuple = JSON.stringify([
|
|
256
|
+
item.campaignId.toLowerCase(),
|
|
257
|
+
item.campaignVersionId.toLowerCase(),
|
|
258
|
+
item.site,
|
|
259
|
+
item.experimentId.toLowerCase(),
|
|
260
|
+
item.experimentRevision,
|
|
261
|
+
item.goalRevision
|
|
262
|
+
]);
|
|
263
|
+
if (tuples.has(tuple)) fail(at, "original scope tuples must be unique");
|
|
264
|
+
tuples.add(tuple);
|
|
265
|
+
if (accepted > observed) fail(at + ".acceptedAt", "must not follow the native observation");
|
|
266
|
+
if (item.assignmentExtent) {
|
|
267
|
+
const first = Date.parse(item.assignmentExtent.firstAssignedAt), last = Date.parse(item.assignmentExtent.lastAssignedAt);
|
|
268
|
+
if (first > last || last > observed) fail(at + ".assignmentExtent", "requires first <= last <= native observation");
|
|
269
|
+
}
|
|
270
|
+
if (item.suggestedCohort) {
|
|
271
|
+
const from = Date.parse(item.suggestedCohort.assignedFrom), to = Date.parse(item.suggestedCohort.assignedTo);
|
|
272
|
+
if (from !== accepted || from >= to || to > observed) fail(at + ".suggestedCohort", "requires original acceptance start and increasing cohort ending by native observation");
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
scopeOptions.schema;
|
|
277
|
+
|
|
278
|
+
// src/contracts/campaign-normalize.ts
|
|
279
|
+
function normalizeCampaignSlug(input) {
|
|
280
|
+
if (!input || typeof input !== "string") return null;
|
|
281
|
+
const slug = input.trim().toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
282
|
+
return slug || null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/contracts/resonance.ts
|
|
286
|
+
var RESONANCE_SCHEMA_VERSION = 1;
|
|
287
|
+
var MONEY_EXPONENT = 6;
|
|
288
|
+
var MAX_MONEY_MICROS = "999999999999999999999999999999";
|
|
289
|
+
var ResonanceValidationError = class extends Error {
|
|
290
|
+
constructor(path2, reason) {
|
|
291
|
+
super(`${path2}: ${reason}`);
|
|
292
|
+
this.path = path2;
|
|
293
|
+
this.name = "ResonanceValidationError";
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
var fail2 = (path2, reason) => {
|
|
297
|
+
throw new ResonanceValidationError(path2, reason);
|
|
298
|
+
};
|
|
299
|
+
var { field: field2, text: text2, choice: choice2, literal: literal2, integer: integer2, bool, optional: optional2, nullable: nullable2, list: list2, object: object2, union: union2, refine: refine2 } = createWireSchema(fail2, (error) => error instanceof ResonanceValidationError);
|
|
300
|
+
var uuid = text2(36, /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
|
|
301
|
+
var digest = text2(64, /^[a-f0-9]{64}$/);
|
|
302
|
+
var revision = text2(128, /^[a-zA-Z0-9._:-]+$/);
|
|
303
|
+
var instant = refine2(text2(30, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/), (v, p) => {
|
|
304
|
+
if (!Number.isFinite(Date.parse(v)) || new Date(v).toISOString().replace(".000Z", "Z") !== v.replace(".000Z", "Z")) fail2(p, "invalid UTC instant");
|
|
305
|
+
});
|
|
306
|
+
var timeZone = refine2(text2(80), (v, p) => {
|
|
307
|
+
try {
|
|
308
|
+
new Intl.DateTimeFormat("en", { timeZone: v });
|
|
309
|
+
} catch {
|
|
310
|
+
fail2(p, "invalid IANA timezone");
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
var site = refine2(text2(253), (v, p) => {
|
|
314
|
+
if (!isNormalizedSiteHost(v) || isBlockedPublicUrlHost(v)) fail2(p, "must be a normalized public site host");
|
|
315
|
+
});
|
|
316
|
+
var path = text2(2048, /^\/(?!\/)[^?#\s\\]*$/);
|
|
317
|
+
var decimal = text2(30, /^(?:0|[1-9][0-9]{0,29})$/);
|
|
318
|
+
var operationKey = text2(200, /^[a-zA-Z0-9._:-]+$/);
|
|
319
|
+
var objectiveCountingUnit = choice2("unique_source", "unique_qualified_contact", "net_order");
|
|
320
|
+
var RESONANCE_CURRENCIES = ["AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BRL", "BSD", "BTN", "BWP", "BYN", "BZD", "CAD", "CDF", "CHF", "CLP", "CNY", "COP", "CRC", "CUC", "CUP", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "INR", "IQD", "IRR", "ISK", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRU", "MUR", "MVR", "MWK", "MXN", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLE", "SLL", "SOS", "SRD", "SSP", "STN", "SVC", "SYP", "SZL", "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VES", "VND", "VUV", "WST", "XAF", "XCD", "XCG", "XDR", "XOF", "XPF", "XSU", "YER", "ZAR", "ZMW", "ZWG", "ZWL"];
|
|
321
|
+
var currency = choice2(...RESONANCE_CURRENCIES);
|
|
322
|
+
var money = object2({ currency, amountMicros: decimal });
|
|
323
|
+
var parseMoney = (value) => money.read(value, "money");
|
|
324
|
+
function addMoney(a, b) {
|
|
325
|
+
a = parseMoney(a);
|
|
326
|
+
b = parseMoney(b);
|
|
327
|
+
if (a.currency !== b.currency) fail2("money.currency", "currency mismatch; no implicit FX");
|
|
328
|
+
return parseMoney({ currency: a.currency, amountMicros: (BigInt(a.amountMicros) + BigInt(b.amountMicros)).toString() });
|
|
329
|
+
}
|
|
330
|
+
function moneyToProviderUnits(input, account) {
|
|
331
|
+
const m = parseMoney(input);
|
|
332
|
+
currency.read(account.currency, "account.currency");
|
|
333
|
+
decimal.read(account.maxUnits, "account.maxUnits");
|
|
334
|
+
if (m.currency !== account.currency) fail2("money.currency", "currency mismatch");
|
|
335
|
+
if (![0, 2, 3, 6].includes(account.unitExponent)) fail2("account.unitExponent", "unsupported provider exponent");
|
|
336
|
+
const factor = 10n ** BigInt(6 - account.unitExponent), amount = BigInt(m.amountMicros);
|
|
337
|
+
if (amount % factor !== 0n) fail2("money.amountMicros", "not exactly representable in provider units");
|
|
338
|
+
const units = amount / factor;
|
|
339
|
+
if (units > BigInt(account.maxUnits)) fail2("money.amountMicros", "provider overflow");
|
|
340
|
+
return units.toString();
|
|
341
|
+
}
|
|
342
|
+
function moneyFromProviderUnits(units, account) {
|
|
343
|
+
decimal.read(units, "provider.units");
|
|
344
|
+
if (![0, 2, 3, 6].includes(account.unitExponent)) fail2("account.unitExponent", "unsupported provider exponent");
|
|
345
|
+
return parseMoney({ currency: account.currency, amountMicros: (BigInt(units) * 10n ** BigInt(6 - account.unitExponent)).toString() });
|
|
346
|
+
}
|
|
347
|
+
var MONEY_JSON_SCHEMA = money.schema;
|
|
348
|
+
var sourceMarker = text2(1536, /^rsm1\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]{43}$/);
|
|
349
|
+
var parseResonanceSourceMarker = (v) => sourceMarker.read(v, "sourceMarker");
|
|
350
|
+
var RESONANCE_SOURCE_MARKER_JSON_SCHEMA = sourceMarker.schema;
|
|
351
|
+
var scope = { projectId: uuid, site };
|
|
352
|
+
var resource = object2({ ...scope, module: choice2("funnel", "broadcast", "outreach", "experiment", "files", "business_brain", "publication", "crm", "commerce", "forms", "booking", "prepared_plan"), id: uuid, revision, digest });
|
|
353
|
+
var source = object2({ ...scope, kind: choice2("accepted_form", "qualified_lead", "completed_booking", "net_paid_order", "approved_application"), id: uuid, revision });
|
|
354
|
+
var qualificationFields = { schemaVersion: literal2(1), semantics: literal2("current_at_observation"), positiveStageKeys: list2(revision, 500), finalNegativeStageKeys: list2(revision, 500), pendingStageKeys: list2(revision, 500), existingContactPolicy: literal2("include_source_cohort") };
|
|
355
|
+
var checkQualificationPartitions = (v, p) => {
|
|
356
|
+
unique([...v.positiveStageKeys, ...v.finalNegativeStageKeys, ...v.pendingStageKeys], p + ".stagePartitions");
|
|
357
|
+
};
|
|
358
|
+
var qualificationRule = refine2(object2({ ...qualificationFields, attributionModel: literal2(RESONANCE_ATTRIBUTION_MODEL) }), checkQualificationPartitions);
|
|
359
|
+
var parseResonanceQualificationRule = (v) => qualificationRule.read(v, "qualificationRule");
|
|
360
|
+
var RESONANCE_QUALIFICATION_RULE_JSON_SCHEMA = qualificationRule.schema;
|
|
361
|
+
var resolvedQualificationRule = refine2(object2({
|
|
362
|
+
...qualificationFields,
|
|
363
|
+
ref: resource,
|
|
364
|
+
crmVariant: text2(128),
|
|
365
|
+
ladderSnapshot: list2(object2({ id: nullable2(uuid), stage_key: revision, semantic_key: nullable2(revision), is_won: bool, is_lost: bool }), 500, 1),
|
|
366
|
+
eligibleFormIds: list2(uuid, 500, 1),
|
|
367
|
+
attribution: object2({ model: literal2(RESONANCE_ATTRIBUTION_MODEL), resolverRevision: literal2(1) }),
|
|
368
|
+
cohort: object2({ startAt: instant, endAt: instant, maturityDelaySeconds: integer2(0, 31536e3) })
|
|
369
|
+
}), (v, p) => {
|
|
370
|
+
checkQualificationPartitions(v, p);
|
|
371
|
+
if (v.ref.module !== "crm" || v.ref.id !== v.ref.projectId) fail2(p + ".ref", "must reference the actual CRM project configuration");
|
|
372
|
+
unique(v.eligibleFormIds, p + ".eligibleFormIds");
|
|
373
|
+
unique(v.ladderSnapshot.flatMap((x) => x.id ? [x.id] : []), p + ".ladderSnapshot.id");
|
|
374
|
+
unique(v.ladderSnapshot.map((x) => x.stage_key), p + ".ladderSnapshot.stage_key");
|
|
375
|
+
const partitions = [...v.positiveStageKeys, ...v.finalNegativeStageKeys, ...v.pendingStageKeys], keys = new Set(v.ladderSnapshot.map((x) => x.stage_key));
|
|
376
|
+
if (partitions.length !== keys.size || partitions.some((k) => !keys.has(k))) fail2(p + ".stagePartitions", "must exhaust the actual native ladder");
|
|
377
|
+
if (Date.parse(v.cohort.endAt) <= Date.parse(v.cohort.startAt)) fail2(p + ".cohort", "invalid cohort interval");
|
|
378
|
+
});
|
|
379
|
+
var parseResonanceResolvedQualificationRule = (v) => resolvedQualificationRule.read(v, "resolvedQualificationRule");
|
|
380
|
+
var RESONANCE_RESOLVED_QUALIFICATION_RULE_JSON_SCHEMA = resolvedQualificationRule.schema;
|
|
381
|
+
var objective = refine2(object2({ source, measurementRevision: revision, countingUnit: objectiveCountingUnit, attributionWindowDays: integer2(1, 365), maturityDelaySeconds: integer2(0, 31536e3), qualificationRuleRef: nullable2(resource), qualificationRule: optional2(qualificationRule) }), (v, p) => {
|
|
382
|
+
if (v.qualificationRule && v.source.kind !== "qualified_lead") fail2(p + ".qualificationRule", "requires the native qualified-lead source");
|
|
383
|
+
if (v.qualificationRule && v.qualificationRuleRef && (v.qualificationRuleRef.module !== "crm" || v.qualificationRuleRef.id !== v.source.projectId)) fail2(p + ".qualificationRuleRef", "must reference actual CRM project configuration");
|
|
384
|
+
});
|
|
385
|
+
var creative = object2({ id: uuid, assetRef: resource, preparedArtifactRef: nullable2(resource), headline: text2(160), body: text2(1e4), callToAction: text2(80), cropDigest: digest });
|
|
386
|
+
var destination = union2(
|
|
387
|
+
object2({ kind: literal2("funnel"), id: uuid, ...scope, path, funnelRef: resource, formSlug: text2(80, /^[a-z0-9]+(?:-[a-z0-9]+)*$/), installationRevision: revision }),
|
|
388
|
+
object2({ kind: literal2("existing_page"), id: uuid, ...scope, path, pageRevision: revision, evidenceDigest: digest })
|
|
389
|
+
);
|
|
390
|
+
var audience = object2({ ...scope, id: uuid, revision, policyRevision: revision, retentionDays: integer2(1, 540), exclusionRuleIds: list2(uuid, 50), consentPurpose: literal2("ad_personalization") });
|
|
391
|
+
var experiment = object2({ ...scope, id: uuid, revision: integer2(1), digest });
|
|
392
|
+
var budget = refine2(object2({ total: money, dailyAllocationCeiling: nullable2(money), reserveMargin: money, startAt: instant, endAt: instant, timeZone, maxChange: money, maxChangeBasisPoints: integer2(0, 1e4), cooldownSeconds: integer2(0, 31536e3) }), (v, p) => {
|
|
393
|
+
if (Date.parse(v.endAt) <= Date.parse(v.startAt)) fail2(p, "end must follow start");
|
|
394
|
+
for (const m of [v.reserveMargin, v.maxChange, ...v.dailyAllocationCeiling ? [v.dailyAllocationCeiling] : []]) if (m.currency !== v.total.currency) fail2(p, "currency mismatch");
|
|
395
|
+
if (BigInt(v.reserveMargin.amountMicros) > BigInt(v.total.amountMicros)) fail2(p, "reserve exceeds total");
|
|
396
|
+
});
|
|
397
|
+
var matureOptimizationPolicy = object2({
|
|
398
|
+
schemaVersion: literal2(1),
|
|
399
|
+
sourceRef: nullable2(resource),
|
|
400
|
+
decisionKind: literal2("observational_allocation"),
|
|
401
|
+
eligibleGoal: literal2("qualified_lead"),
|
|
402
|
+
algorithm: literal2("mature_cpql_conserved_transfer_v1"),
|
|
403
|
+
minimumMatureExposures: integer2(1),
|
|
404
|
+
minimumMatureOutcomes: integer2(1),
|
|
405
|
+
minimumCoverageBasisPoints: integer2(0, 1e4),
|
|
406
|
+
maxEvidenceAgeSeconds: integer2(1),
|
|
407
|
+
maturityDelaySeconds: integer2(0),
|
|
408
|
+
cooldownSeconds: integer2(0),
|
|
409
|
+
donorTransferBasisPoints: integer2(1, 1e4),
|
|
410
|
+
maxUncertaintyBasisPoints: integer2(0, 1e4),
|
|
411
|
+
uncertaintyMethod: literal2("observational_cpql_missing_outcome_bound_v1"),
|
|
412
|
+
attributionModel: choice2("first_party_source", "first_touch", "last_touch", "provider_reported", RESONANCE_ATTRIBUTION_MODEL),
|
|
413
|
+
requiredChecks: list2(choice2("tracking", "dedupe", "qualification", "uncertainty", "assignment", "power"), 6, 1)
|
|
414
|
+
});
|
|
415
|
+
function uniqueList(item, max, min = 0) {
|
|
416
|
+
const base = list2(item, max, min);
|
|
417
|
+
return field2({ ...base.schema, uniqueItems: true }, (v, p) => {
|
|
418
|
+
const r = base.read(v, p);
|
|
419
|
+
unique(r, p);
|
|
420
|
+
return r;
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
var optimizationCheck = choice2("tracking", "dedupe", "qualification", "uncertainty", "assignment", "power");
|
|
424
|
+
var finiteCandidateAction = choice2("materialize_and_activate", "activate_held", "pause_candidate");
|
|
425
|
+
var finiteStopRule = choice2("exploration_cap", "flight_end", "policy_or_mandate_expiry", "kill", "candidate_identity_or_settings_drift", "source_or_consent_invalid", "required_tracking_unavailable_or_stale", "unknown_effect");
|
|
426
|
+
var positiveMicrosPattern = /^[1-9][0-9]*(?![\s\S])/;
|
|
427
|
+
var positiveMoneyShape = field2({ allOf: [money.schema, { properties: { amountMicros: { pattern: positiveMicrosPattern.source } } }] }, (v, p) => {
|
|
428
|
+
const m = money.read(v, p);
|
|
429
|
+
if (!positiveMicrosPattern.test(m.amountMicros)) fail2(p, "requires positive canonical money");
|
|
430
|
+
return m;
|
|
431
|
+
});
|
|
432
|
+
var finiteMetaCandidatePolicy = object2({
|
|
433
|
+
schemaVersion: literal2(1),
|
|
434
|
+
algorithm: literal2("finite_meta_candidates_v1"),
|
|
435
|
+
decisionKind: literal2("preapproved_candidate_operation"),
|
|
436
|
+
eligibleGoal: literal2("qualified_lead"),
|
|
437
|
+
sourceRef: nullable2(resource),
|
|
438
|
+
candidateChannelIds: uniqueList(uuid, 100, 1),
|
|
439
|
+
selection: object2({ kind: literal2("fixed_reviewed_order"), candidateOrder: uniqueList(uuid, 100, 1), eligibilitySchedule: list2(object2({ channelId: uuid, notBefore: instant, notAfter: instant }), 100, 1) }),
|
|
440
|
+
allowedActions: uniqueList(finiteCandidateAction, 3, 1),
|
|
441
|
+
maxConcurrentCandidates: integer2(1, 100),
|
|
442
|
+
maxActivationsPerCandidate: literal2(1),
|
|
443
|
+
totalExplorationCap: positiveMoneyShape,
|
|
444
|
+
measurementGuard: object2({ sourceRef: source, qualificationRuleRef: resource, attributionModel: literal2(RESONANCE_ATTRIBUTION_MODEL), maturityDelaySeconds: integer2(0, 31536e3), minimumCoverageBasisPoints: integer2(0, 1e4), maxEvidenceAgeSeconds: integer2(1), requiredChecks: uniqueList(optimizationCheck, 6, 1) }),
|
|
445
|
+
performanceDecisionPolicy: literal2(null),
|
|
446
|
+
stopRules: uniqueList(finiteStopRule, 8, 8),
|
|
447
|
+
expiresAt: instant
|
|
448
|
+
});
|
|
449
|
+
var optimizationPolicy = union2(matureOptimizationPolicy, finiteMetaCandidatePolicy);
|
|
450
|
+
function parseResonanceOptimizationPolicy(v) {
|
|
451
|
+
const r = optimizationPolicy.read(v, "optimizationPolicy");
|
|
452
|
+
if (r.algorithm === "mature_cpql_conserved_transfer_v1") {
|
|
453
|
+
unique(r.requiredChecks, "optimizationPolicy.requiredChecks");
|
|
454
|
+
return r;
|
|
455
|
+
}
|
|
456
|
+
const p = "optimizationPolicy", ids = r.candidateChannelIds, guard = r.measurementGuard;
|
|
457
|
+
if (r.selection.candidateOrder.length !== ids.length || r.selection.candidateOrder.some((id) => !ids.includes(id))) fail2(p + ".selection.candidateOrder", "must be the exact candidate permutation");
|
|
458
|
+
unique(r.selection.eligibilitySchedule.map((s) => s.channelId), p + ".selection.eligibilitySchedule");
|
|
459
|
+
if (r.selection.eligibilitySchedule.length !== ids.length || r.selection.eligibilitySchedule.some((s) => !ids.includes(s.channelId))) fail2(p + ".selection.eligibilitySchedule", "must cover exactly the original candidates");
|
|
460
|
+
if (r.selection.eligibilitySchedule.some((s) => Date.parse(s.notAfter) <= Date.parse(s.notBefore) || Date.parse(s.notAfter) > Date.parse(r.expiresAt))) fail2(p + ".selection.eligibilitySchedule", "invalid policy selection window");
|
|
461
|
+
if (r.maxConcurrentCandidates > ids.length) fail2(p + ".maxConcurrentCandidates", "exceeds finite candidate count");
|
|
462
|
+
if (BigInt(r.totalExplorationCap.amountMicros) === 0n) fail2(p + ".totalExplorationCap", "requires a positive reviewed ceiling");
|
|
463
|
+
if (guard.sourceRef.kind !== r.eligibleGoal || guard.qualificationRuleRef.module !== "crm" || guard.qualificationRuleRef.id !== guard.sourceRef.projectId || guard.qualificationRuleRef.projectId !== guard.sourceRef.projectId || guard.qualificationRuleRef.site !== guard.sourceRef.site) fail2(p + ".measurementGuard", "requires the original qualified source and CRM witness scope");
|
|
464
|
+
scoped(r, guard.sourceRef.projectId, [guard.sourceRef.site], p);
|
|
465
|
+
return r;
|
|
466
|
+
}
|
|
467
|
+
var RESONANCE_OPTIMIZATION_POLICY_JSON_SCHEMA = optimizationPolicy.schema;
|
|
468
|
+
var optimization = object2({ enabled: bool, policy: optimizationPolicy, stopLoss: object2({ confirmedSpend: money, unreconciledExposure: money }) });
|
|
469
|
+
var channelBase = { id: uuid, ...scope, destinationId: uuid, creativeIds: list2(uuid, 50, 1) };
|
|
470
|
+
var search = object2({ ...channelBase, localCampaignKey: text2(80), kind: literal2("google_search"), provider: literal2("google_ads"), accountBindingId: uuid, capabilityRevision: revision, allocation: money, budgetMode: choice2("campaign_total", "average_daily"), keywords: list2(object2({ text: text2(80), match: choice2("exact", "phrase", "broad") }), 200, 1), negativeKeywords: list2(text2(80), 200), bidding: union2(object2({ strategy: literal2("maximize_conversions") }), object2({ strategy: literal2("target_cpa"), target: money })), locationIds: list2(text2(80), 100, 1), network: object2({ googleSearch: literal2(true), searchPartners: bool, displayExpansion: literal2(false) }), geoPresence: choice2("presence", "presence_or_interest"), excludedLocationIds: list2(text2(80), 100), adGroups: list2(object2({ localKey: text2(80), ads: list2(object2({ localKey: text2(80), creativeId: uuid, headlines: list2(text2(30), 15, 3), descriptions: list2(text2(90), 4, 2) }), 20, 1) }), 100, 1), assetAutomation: literal2("disabled") });
|
|
471
|
+
var metaAgeConstraint = union2(refine2(object2({ mode: literal2("bounded"), min: integer2(1, 120), max: integer2(1, 120) }), (v, p) => {
|
|
472
|
+
if (v.min > v.max) fail2(p, "minimum age exceeds maximum");
|
|
473
|
+
}), object2({ mode: literal2("omitted") }), object2({ mode: literal2("provider_managed") }));
|
|
474
|
+
var meta = object2({ ...channelBase, localCampaignKey: text2(80), kind: literal2("meta_retargeting"), provider: literal2("meta_ads"), accountBindingId: uuid, capabilityRevision: revision, allocation: money, budgetMode: choice2("lifetime", "daily"), audienceRuleRefs: list2(audience, 50, 1), objective: choice2("OUTCOME_LEADS", "OUTCOME_SALES", "OUTCOME_TRAFFIC"), pageRef: object2({ connectionId: uuid, externalId: text2(160), revision }), instagramRef: nullable2(object2({ connectionId: uuid, externalId: text2(160), revision })), datasetRef: object2({ id: text2(160), revision }), conversionGoalRef: resource, optimization: choice2("landing_page_views", "lead", "purchase"), billing: literal2("impressions"), bid: union2(object2({ strategy: literal2("lowest_cost") }), object2({ strategy: literal2("cost_cap"), cap: money })), localAdSetKey: text2(80), localAdKey: text2(80), budgetOwner: choice2("campaign", "adset"), specialCategories: list2(choice2("housing", "employment", "financial_products_services"), 3), countryCodes: list2(text2(2, /^[A-Z]{2}$/), 30, 1), placement: literal2("automatic"), ageConstraint: optional2(metaAgeConstraint) });
|
|
475
|
+
var owned = object2({ ...channelBase, kind: choice2("funnel", "broadcast", "outreach"), resourceRef: resource });
|
|
476
|
+
var channel = union2(search, meta, owned);
|
|
477
|
+
var definitionFields = { schemaVersion: literal2(1), projectId: uuid, name: text2(160), slug: refine2(text2(80), (v, p) => {
|
|
478
|
+
if (normalizeCampaignSlug(v) !== v) fail2(p, "must be canonical campaign slug");
|
|
479
|
+
}), sites: list2(site, 50, 1), objective, brandRef: resource, creativeRefs: list2(creative, 100, 1), destinations: list2(destination, 100, 1), channels: list2(channel, 100, 1), experimentRefs: list2(experiment, 50), budget, optimization };
|
|
480
|
+
var definition = object2(definitionFields);
|
|
481
|
+
var draft = object2({ schemaVersion: literal2(1), projectId: uuid, name: text2(160, void 0, 0), slug: definitionFields.slug, sites: list2(site, 50), objective: optional2(objective), brandRef: optional2(resource), creativeRefs: optional2(list2(creative, 100)), destinations: optional2(list2(destination, 100)), channels: optional2(list2(channel, 100)), experimentRefs: optional2(list2(experiment, 50)), budget: optional2(budget), optimization: optional2(optimization) });
|
|
482
|
+
function scoped(value, projectId, sites, at) {
|
|
483
|
+
if (!value || typeof value !== "object") return;
|
|
484
|
+
if (Array.isArray(value)) {
|
|
485
|
+
value.forEach((x, i) => scoped(x, projectId, sites, `${at}[${i}]`));
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const v = value;
|
|
489
|
+
if ("projectId" in v && v.projectId !== projectId) fail2(at, "cross-project reference");
|
|
490
|
+
if ("site" in v && !sites.includes(v.site)) fail2(at, "site outside campaign scope");
|
|
491
|
+
for (const [k, x] of Object.entries(v)) scoped(x, projectId, sites, `${at}.${k}`);
|
|
492
|
+
}
|
|
493
|
+
function unique(values, at) {
|
|
494
|
+
if (new Set(values).size !== values.length) fail2(at, "duplicate identity");
|
|
495
|
+
}
|
|
496
|
+
function checkDefinition(v) {
|
|
497
|
+
unique(v.sites, "campaign.sites");
|
|
498
|
+
scoped(v, v.projectId, v.sites, "campaign");
|
|
499
|
+
parseResonanceOptimizationPolicy(v.optimization.policy);
|
|
500
|
+
if (v.optimization.enabled && v.objective.source.kind !== v.optimization.policy.eligibleGoal) fail2("campaign.optimization.policy", "policy is not eligible for this goal");
|
|
501
|
+
if ([v.optimization.stopLoss.confirmedSpend, v.optimization.stopLoss.unreconciledExposure].some((m) => m.currency !== v.budget.total.currency)) fail2("campaign.optimization.stopLoss", "currency mismatch");
|
|
502
|
+
unique(v.channels.map((c) => c.id), "campaign.channels");
|
|
503
|
+
unique(v.creativeRefs.map((c) => c.id), "campaign.creativeRefs");
|
|
504
|
+
unique(v.destinations.map((d) => d.id), "campaign.destinations");
|
|
505
|
+
if (!["business_brain", "publication"].includes(v.brandRef.module)) fail2("campaign.brandRef", "must reference existing brand owner");
|
|
506
|
+
for (const c of v.creativeRefs) if (c.assetRef.module !== "files" || c.preparedArtifactRef && !["files", "broadcast"].includes(c.preparedArtifactRef.module)) fail2("campaign.creativeRefs", "unsupported asset owner");
|
|
507
|
+
for (const d of v.destinations) if (d.kind === "funnel" && d.funnelRef.module !== "funnel") fail2("campaign.destinations", "must reference funnel owner");
|
|
508
|
+
for (const c of v.channels) {
|
|
509
|
+
const dest = v.destinations.find((d) => d.id === c.destinationId) ?? fail2("campaign.channels.destinationId", "must reference a destination ID");
|
|
510
|
+
if (dest.site !== c.site) fail2("campaign.channels.destinationId", "destination site mismatch");
|
|
511
|
+
if (c.creativeIds.some((id) => !v.creativeRefs.some((x) => x.id === id))) fail2("campaign.channels.creativeIds", "unknown creative");
|
|
512
|
+
if ("bid" in c && "cap" in c.bid && c.bid.cap.currency !== v.budget.total.currency) fail2("campaign.channels.bid", "currency mismatch");
|
|
513
|
+
if ("adGroups" in c) {
|
|
514
|
+
for (const g of c.adGroups) for (const a of g.ads) if (!c.creativeIds.includes(a.creativeId)) fail2("campaign.channels.adGroups", "ad creative outside channel");
|
|
515
|
+
}
|
|
516
|
+
if ("resourceRef" in c && c.resourceRef.module !== c.kind) fail2("campaign.channels.resourceRef", "module owner mismatch");
|
|
517
|
+
if ("bidding" in c && "target" in c.bidding && c.bidding.target.currency !== v.budget.total.currency) fail2("campaign.channels.bidding", "currency mismatch");
|
|
518
|
+
if ("allocation" in c && c.allocation.currency !== v.budget.total.currency) fail2("campaign.channels.allocation", "currency mismatch");
|
|
519
|
+
}
|
|
520
|
+
const policy = v.optimization.policy;
|
|
521
|
+
if (policy.algorithm === "finite_meta_candidates_v1") {
|
|
522
|
+
const guard = policy.measurementGuard, goal = v.objective;
|
|
523
|
+
if (canonical(guard.sourceRef) !== canonical(goal.source) || !goal.qualificationRuleRef || canonical(guard.qualificationRuleRef) !== canonical(goal.qualificationRuleRef) || guard.maturityDelaySeconds !== goal.maturityDelaySeconds || goal.countingUnit !== "unique_qualified_contact") fail2("campaign.optimization.policy.measurementGuard", "must match the original qualified objective");
|
|
524
|
+
if (policy.totalExplorationCap.currency !== v.budget.total.currency || compareMoney(policy.totalExplorationCap, v.budget.total) > 0) fail2("campaign.optimization.policy.totalExplorationCap", "outside original budget ceiling");
|
|
525
|
+
if (Date.parse(policy.expiresAt) > Date.parse(v.budget.endAt) || policy.selection.eligibilitySchedule.some((s) => Date.parse(s.notBefore) < Date.parse(v.budget.startAt) || Date.parse(s.notAfter) > Date.parse(v.budget.endAt))) fail2("campaign.optimization.policy.selection", "selection windows must stay inside the original flight");
|
|
526
|
+
for (const id of policy.candidateChannelIds) {
|
|
527
|
+
const c = v.channels.find((c2) => c2.id === id);
|
|
528
|
+
if (!c || c.kind !== "meta_retargeting" || c.budgetMode !== "lifetime" || c.budgetOwner !== "adset" || BigInt(c.allocation.amountMicros) === 0n) fail2("campaign.optimization.policy.candidateChannelIds", "requires an original independently capped Meta adset channel");
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function parseResonanceCampaignDefinition(v) {
|
|
533
|
+
const result = definition.read(v, "campaign");
|
|
534
|
+
checkDefinition(result);
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
function parseResonanceCampaignDraft(v) {
|
|
538
|
+
const result = draft.read(v, "draft");
|
|
539
|
+
unique(result.sites, "draft.sites");
|
|
540
|
+
scoped(result, result.projectId, result.sites, "draft");
|
|
541
|
+
return result;
|
|
542
|
+
}
|
|
543
|
+
var RESONANCE_CAMPAIGN_DEFINITION_JSON_SCHEMA = definition.schema;
|
|
544
|
+
var RESONANCE_CAMPAIGN_DRAFT_JSON_SCHEMA = draft.schema;
|
|
545
|
+
var capability = object2({ id: uuid, projectId: uuid, connectionId: uuid, provider: choice2("google_ads", "meta_ads"), externalAccountId: text2(160), currency, timeZone, connectionRevision: revision, capabilityRevision: revision, observedAt: instant, expiresAt: instant, advertiserState: choice2("eligible", "blocked", "unknown"), operations: list2(object2({ operation: choice2("discover", "prepare", "launch", "update", "pause", "resume", "finish", "reconcile", "measure", "audience", "conversion", "experiment"), state: choice2("supported", "blocked", "unknown"), reasonCodes: list2(text2(80), 30), prerequisites: list2(text2(100), 30) }), 20), profiles: list2(object2({ profile: choice2("google_search", "meta_retargeting"), state: choice2("supported", "blocked", "unknown"), reasonCodes: list2(text2(80), 30), budgetModes: list2(choice2("campaign_total", "average_daily", "lifetime", "daily"), 4), providerApiVersion: text2(30) }), 2) });
|
|
546
|
+
var parseResonanceAccountCapability = (v) => {
|
|
547
|
+
const r = capability.read(v, "account");
|
|
548
|
+
if (Date.parse(r.expiresAt) <= Date.parse(r.observedAt)) fail2("account.expiresAt", "must follow observation");
|
|
549
|
+
return r;
|
|
550
|
+
};
|
|
551
|
+
var mandateRef = object2({ ledgerActionId: uuid, campaignId: uuid, preparedPlanId: uuid, reviewDigest: digest, mandateDigest: digest, expectedKillRevision: integer2(0) });
|
|
552
|
+
var parseResonanceMandateRef = (v) => mandateRef.read(v, "mandateRef");
|
|
553
|
+
var websiteVisitMaxAgeMs = 3e5;
|
|
554
|
+
var RESONANCE_WEBSITE_VISIT_PROFILE = Object.freeze({
|
|
555
|
+
profile: "sonor_sdk_page_view_fresh_v1",
|
|
556
|
+
source: "sdk_reported_first_party_cookie",
|
|
557
|
+
assurance: "sdk_reported",
|
|
558
|
+
matchingFields: Object.freeze(["fbp", "fbc"]),
|
|
559
|
+
maxCaptureAgeMs: websiteVisitMaxAgeMs,
|
|
560
|
+
maxDispatchAgeMs: websiteVisitMaxAgeMs,
|
|
561
|
+
maxFutureSkewMs: 0,
|
|
562
|
+
browserServerPairing: "unsupported"
|
|
563
|
+
});
|
|
564
|
+
var resonanceWebsiteVisitProfileDigestInput = () => canonical(RESONANCE_WEBSITE_VISIT_PROFILE);
|
|
565
|
+
var measurementRuleRef = object2({ id: uuid, revision });
|
|
566
|
+
var measurementConfigRef = object2({ configRevision: revision, policyRevision: revision, digest });
|
|
567
|
+
var matchingProfileRef = object2({ id: uuid, revision: integer2(1), digest });
|
|
568
|
+
var mandateAllowedOperations = list2(choice2("launch", "update", "resume", "optimize", "deliver_conversion"), 5, 1);
|
|
569
|
+
var websiteVisitFeed = object2({ profile: literal2(RESONANCE_WEBSITE_VISIT_PROFILE.profile), channels: list2(object2({
|
|
570
|
+
channelId: uuid,
|
|
571
|
+
measurementConfigRef,
|
|
572
|
+
resourceRuleRefs: list2(measurementRuleRef, 500, 1),
|
|
573
|
+
matchingProfileRef
|
|
574
|
+
}), 100, 1) });
|
|
575
|
+
var RESONANCE_WEBSITE_VISIT_FEED_JSON_SCHEMA = websiteVisitFeed.schema;
|
|
576
|
+
var mandateScope = object2({ projectId: uuid, campaignId: uuid, preparedPlanId: uuid, reviewDigest: digest, accountBindingIds: list2(uuid, 100), sites: list2(site, 50, 1), budget, creativeRefs: list2(creative, 100, 1), audienceRuleRefs: list2(audience, 100), goal: objective, allowedOperations: mandateAllowedOperations, allowedTransformationRef: nullable2(resource), expiresAt: instant, websiteVisitFeed: optional2(websiteVisitFeed) });
|
|
577
|
+
function parseResonanceMandateScope(v) {
|
|
578
|
+
const r = mandateScope.read(v, "mandateScope");
|
|
579
|
+
scoped(r, r.projectId, r.sites, "mandateScope");
|
|
580
|
+
if (r.websiteVisitFeed) {
|
|
581
|
+
unique(r.websiteVisitFeed.channels.map((c) => c.channelId), "mandateScope.websiteVisitFeed.channels");
|
|
582
|
+
for (const c of r.websiteVisitFeed.channels) unique(c.resourceRuleRefs.map((rule) => rule.id), "mandateScope.websiteVisitFeed.resourceRuleRefs");
|
|
583
|
+
}
|
|
584
|
+
return r;
|
|
585
|
+
}
|
|
586
|
+
var websiteVisitTimes = object2({ occurredAt: instant, receivedAt: instant });
|
|
587
|
+
function resonanceWebsiteVisitEventTime(value, context) {
|
|
588
|
+
const input = websiteVisitTimes.read(value, "websiteVisitTime");
|
|
589
|
+
const actual = object2({ mandate: mandateScope, approvedAt: instant, now: instant }).read(context, "websiteVisitTime.context");
|
|
590
|
+
const grant = parseResonanceMandateScope(actual.mandate);
|
|
591
|
+
if (!grant.websiteVisitFeed) fail2("websiteVisitTime.context.mandate.websiteVisitFeed", "requires an explicit original feed selection");
|
|
592
|
+
const occurred = Date.parse(input.occurredAt), received = Date.parse(input.receivedAt), now = Date.parse(actual.now);
|
|
593
|
+
const policy = RESONANCE_WEBSITE_VISIT_PROFILE;
|
|
594
|
+
if (received < occurred - policy.maxFutureSkewMs || received - occurred > policy.maxCaptureAgeMs || now < received || now < occurred - policy.maxFutureSkewMs || now - occurred > policy.maxDispatchAgeMs) fail2("websiteVisitTime", "outside the local original observation freshness profile");
|
|
595
|
+
const start = Math.max(Date.parse(grant.budget.startAt), Date.parse(actual.approvedAt)), end = Math.min(Date.parse(grant.budget.endAt), Date.parse(grant.expiresAt));
|
|
596
|
+
if (occurred < start || received < start || occurred >= end || received >= end || now >= end) fail2("websiteVisitTime", "outside the original approval and dispatch window");
|
|
597
|
+
return Math.floor(occurred / 1e3);
|
|
598
|
+
}
|
|
599
|
+
var RESONANCE_GOOGLE_SIGNAL_SEMANTICS = "qualified_at_observation_v1";
|
|
600
|
+
var googleSignalSemantics = literal2(RESONANCE_GOOGLE_SIGNAL_SEMANTICS);
|
|
601
|
+
var RESONANCE_GOOGLE_SOURCE_CATEGORIES = Object.freeze({ accepted_form: "SUBMIT_LEAD_FORM", qualified_lead: "QUALIFIED_LEAD", completed_booking: "BOOK_APPOINTMENT", net_paid_order: "PURCHASE", approved_application: "CONVERTED_LEAD" });
|
|
602
|
+
var googleSemanticsInput = object2({ sourceKind: choice2(...Object.keys(RESONANCE_GOOGLE_SOURCE_CATEGORIES)), counting: choice2("one", "every"), clickWindowDays: integer2(1, 90) });
|
|
603
|
+
function googleConversionActionSemantics(input) {
|
|
604
|
+
const v = googleSemanticsInput.read(input, "googleConversionActionSemantics");
|
|
605
|
+
return { type: "UPLOAD_CLICKS", category: RESONANCE_GOOGLE_SOURCE_CATEGORIES[v.sourceKind], countingType: v.counting === "one" ? "ONE_PER_CLICK" : "MANY_PER_CLICK", clickThroughLookbackWindowDays: v.clickWindowDays, primaryForGoal: false };
|
|
606
|
+
}
|
|
607
|
+
var googleSetupPolicyInput = object2({ source, countingUnit: objectiveCountingUnit, attributionWindowDays: integer2(1, 365) });
|
|
608
|
+
function googleUploadClicksSetupSemantics(input) {
|
|
609
|
+
const v = googleSetupPolicyInput.read(input, "googleSetupPolicy");
|
|
610
|
+
if (v.source.kind !== "qualified_lead" || v.countingUnit !== "unique_qualified_contact") fail2("googleSetupPolicy", "unsupported_counting_policy");
|
|
611
|
+
if (v.attributionWindowDays > 90) fail2("googleSetupPolicy.attributionWindowDays", "unsupported_attribution_window");
|
|
612
|
+
return { ...googleConversionActionSemantics({ sourceKind: v.source.kind, counting: "one", clickWindowDays: v.attributionWindowDays }), category: RESONANCE_GOOGLE_SOURCE_CATEGORIES.qualified_lead, countingType: "ONE_PER_CLICK" };
|
|
613
|
+
}
|
|
614
|
+
var googleGoalChoiceFields = {
|
|
615
|
+
externalId: text2(160, /^customers\/[0-9]+\/customConversionGoals\/[0-9]+$/),
|
|
616
|
+
actionIds: list2(text2(160, /^customers\/[0-9]+\/conversionActions\/[0-9]+$/), 100, 1)
|
|
617
|
+
};
|
|
618
|
+
var googleDeclarationChoiceFields = {
|
|
619
|
+
statement: literal2("DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING"),
|
|
620
|
+
category: choice2("general", "housing", "employment", "financial_products_services")
|
|
621
|
+
};
|
|
622
|
+
function normalizeGoogleGoal(value, at) {
|
|
623
|
+
unique(value.actionIds, at + ".actionIds");
|
|
624
|
+
value.actionIds.sort();
|
|
625
|
+
return value;
|
|
626
|
+
}
|
|
627
|
+
var googleSetupSpec = object2({
|
|
628
|
+
schemaVersion: literal2(1),
|
|
629
|
+
kind: literal2("google_upload_clicks_custom_goal_v1"),
|
|
630
|
+
signalSemantics: googleSignalSemantics,
|
|
631
|
+
goalLocalKey: literal2("setup:custom-goal"),
|
|
632
|
+
requestedGoalName: text2(160),
|
|
633
|
+
action: object2({
|
|
634
|
+
localKey: literal2("setup:conversion-action"),
|
|
635
|
+
requestedActionName: text2(160),
|
|
636
|
+
type: literal2("UPLOAD_CLICKS"),
|
|
637
|
+
category: literal2(RESONANCE_GOOGLE_SOURCE_CATEGORIES.qualified_lead),
|
|
638
|
+
countingType: literal2("ONE_PER_CLICK"),
|
|
639
|
+
clickThroughLookbackWindowDays: integer2(1, 90),
|
|
640
|
+
primaryForGoal: literal2(false)
|
|
641
|
+
}),
|
|
642
|
+
goalActionKey: literal2("setup:conversion-action"),
|
|
643
|
+
goalStrategy: literal2("exclusive_custom_goal_v1")
|
|
644
|
+
});
|
|
645
|
+
var parseResonanceGoogleUploadClicksCustomGoalSpec = (value) => googleSetupSpec.read(value, "googleSetupSpec");
|
|
646
|
+
var resonanceGoogleSetupDigestInput = (value) => canonical(parseResonanceGoogleUploadClicksCustomGoalSpec(value));
|
|
647
|
+
var googleSaveCommonFields = { schemaVersion: literal2(1), expectedCampaignRevision: integer2(1), expectedRevision: integer2(0), idempotencyKey: operationKey, signalSemantics: googleSignalSemantics, declaration: object2(googleDeclarationChoiceFields) };
|
|
648
|
+
var googleSave = union2(
|
|
649
|
+
object2({ ...googleSaveCommonFields, mode: literal2("existing"), nativeGoal: object2(googleGoalChoiceFields) }),
|
|
650
|
+
object2({ ...googleSaveCommonFields, mode: literal2("admitted_setup"), setupIntent: object2({ requestedGoalName: text2(160), requestedActionName: text2(160) }) })
|
|
651
|
+
);
|
|
652
|
+
function parseResonanceGoogleConfigurationSaveCommand(value) {
|
|
653
|
+
const result = googleSave.read(value, "googleConfigurationSave");
|
|
654
|
+
if (result.mode === "existing") normalizeGoogleGoal(result.nativeGoal, "googleConfigurationSave.nativeGoal");
|
|
655
|
+
return result;
|
|
656
|
+
}
|
|
657
|
+
var googleGoalDraft = object2({ ...googleGoalChoiceFields, name: nullable2(text2(200, void 0, 0)), revision: nullable2(digest) });
|
|
658
|
+
var googleGoalReviewed = object2({ ...googleGoalChoiceFields, name: text2(200, void 0, 0), revision: digest });
|
|
659
|
+
var googleDeclared = object2({
|
|
660
|
+
...googleDeclarationChoiceFields,
|
|
661
|
+
creativeDigest: digest,
|
|
662
|
+
locationIds: list2(text2(80), 100, 1),
|
|
663
|
+
excludedLocationIds: list2(text2(80), 100),
|
|
664
|
+
declaredByActorId: uuid,
|
|
665
|
+
declaredAt: instant,
|
|
666
|
+
declaredForCampaignRevision: integer2(1)
|
|
667
|
+
});
|
|
668
|
+
var googleConfigurationFields = {
|
|
669
|
+
projectId: uuid,
|
|
670
|
+
campaignId: uuid,
|
|
671
|
+
channelId: uuid,
|
|
672
|
+
site,
|
|
673
|
+
accountBindingId: uuid,
|
|
674
|
+
configurationRevision: integer2(1),
|
|
675
|
+
configurationDigest: digest,
|
|
676
|
+
signalSemantics: googleSignalSemantics,
|
|
677
|
+
source,
|
|
678
|
+
measurementRevision: revision,
|
|
679
|
+
countingUnit: objectiveCountingUnit,
|
|
680
|
+
attributionWindowDays: integer2(1, 365),
|
|
681
|
+
declaration: googleDeclared
|
|
682
|
+
};
|
|
683
|
+
var googleExistingChoiceFields = { mode: literal2("existing"), nativeGoal: googleGoalDraft };
|
|
684
|
+
var googleSetupChoiceFields = { mode: literal2("admitted_setup"), setupSpec: googleSetupSpec, setupDigest: digest };
|
|
685
|
+
function checkGoogleConfiguration(value, at) {
|
|
686
|
+
if (value.source.projectId !== value.projectId || value.source.site !== value.site) fail2(at + ".source", "source scope mismatch");
|
|
687
|
+
unique(value.declaration.locationIds, at + ".declaration.locationIds");
|
|
688
|
+
unique(value.declaration.excludedLocationIds, at + ".declaration.excludedLocationIds");
|
|
689
|
+
value.declaration.locationIds.sort();
|
|
690
|
+
value.declaration.excludedLocationIds.sort();
|
|
691
|
+
const expected = googleUploadClicksSetupSemantics({ source: value.source, countingUnit: value.countingUnit, attributionWindowDays: value.attributionWindowDays });
|
|
692
|
+
if (value.mode === "existing") {
|
|
693
|
+
normalizeGoogleGoal(value.nativeGoal, at + ".nativeGoal");
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (canonical({ type: value.setupSpec.action.type, category: value.setupSpec.action.category, countingType: value.setupSpec.action.countingType, clickThroughLookbackWindowDays: value.setupSpec.action.clickThroughLookbackWindowDays, primaryForGoal: value.setupSpec.action.primaryForGoal }) !== canonical(expected)) fail2(at + ".setupSpec.action", "canonical setup semantics mismatch");
|
|
697
|
+
}
|
|
698
|
+
var googleSnapshotBase = union2(object2({ ...googleConfigurationFields, ...googleExistingChoiceFields }), object2({ ...googleConfigurationFields, ...googleSetupChoiceFields }));
|
|
699
|
+
var googleConfigurationSnapshot = refine2(googleSnapshotBase, checkGoogleConfiguration);
|
|
700
|
+
var googleConfigurationReview = refine2(union2(object2({ ...googleConfigurationFields, mode: literal2("existing"), nativeGoal: googleGoalReviewed }), object2({ ...googleConfigurationFields, ...googleSetupChoiceFields })), checkGoogleConfiguration);
|
|
701
|
+
var parseResonanceGoogleConfigurationSnapshot = (value) => googleConfigurationSnapshot.read(value, "googleConfiguration");
|
|
702
|
+
var parseResonanceGoogleConfigurationReview = (value) => googleConfigurationReview.read(value, "googleConfigurationReview");
|
|
703
|
+
var { configurationRevision: _configurationRevisionField, ...googleViewContentFields } = googleConfigurationFields;
|
|
704
|
+
var googleReadinessFields = { ready: bool, activationReadiness: choice2("blocked", "pending_setup", "requires_final_verification", "verified"), reasonCodes: list2(text2(200), 100) };
|
|
705
|
+
var googleViewFields = { ...googleViewContentFields, revision: integer2(1), ...googleReadinessFields };
|
|
706
|
+
var googleViewBase = union2(object2({ ...googleViewFields, ...googleExistingChoiceFields }), object2({ ...googleViewFields, ...googleSetupChoiceFields }));
|
|
707
|
+
function checkGoogleView(value, at) {
|
|
708
|
+
checkGoogleConfiguration(value, at);
|
|
709
|
+
if (!value.ready && value.activationReadiness !== "blocked") fail2(at + ".activationReadiness", "unready configuration must be blocked");
|
|
710
|
+
if (value.mode === "existing") {
|
|
711
|
+
if (value.ready && (value.nativeGoal.name === null || value.nativeGoal.revision === null)) fail2(at + ".ready", "unavailable native evidence cannot be ready");
|
|
712
|
+
if (value.ready && value.activationReadiness !== "requires_final_verification" && value.activationReadiness !== "verified") fail2(at + ".activationReadiness", "existing native setup needs current verification");
|
|
713
|
+
} else if (value.ready && value.activationReadiness !== "pending_setup") fail2(at + ".activationReadiness", "reviewed setup intent is not native activation");
|
|
714
|
+
}
|
|
715
|
+
var googleConfigurationView = refine2(googleViewBase, checkGoogleView);
|
|
716
|
+
var googleSaveResultFields = { operationId: uuid, replayed: bool, campaignRevision: integer2(1) };
|
|
717
|
+
var googleConfigurationSaveResponse = refine2(union2(object2({ ...googleViewFields, ...googleExistingChoiceFields, ...googleSaveResultFields }), object2({ ...googleViewFields, ...googleSetupChoiceFields, ...googleSaveResultFields })), checkGoogleView);
|
|
718
|
+
var parseResonanceGoogleConfigurationView = (value) => googleConfigurationView.read(value, "googleConfigurationView");
|
|
719
|
+
var parseResonanceGoogleConfigurationSaveResponse = (value) => googleConfigurationSaveResponse.read(value, "googleConfigurationSaveResponse");
|
|
720
|
+
function projectResonanceGoogleConfiguration(value, readiness) {
|
|
721
|
+
const snapshot = parseResonanceGoogleConfigurationSnapshot(value);
|
|
722
|
+
const metadata = googleReadiness().read(readiness, "googleConfigurationReadiness");
|
|
723
|
+
const { configurationRevision, ...fields2 } = snapshot;
|
|
724
|
+
const view2 = parseResonanceGoogleConfigurationView({ ...fields2, revision: configurationRevision, ...metadata });
|
|
725
|
+
return { view: view2, review: view2.ready ? parseResonanceGoogleConfigurationReview(snapshot) : null };
|
|
726
|
+
}
|
|
727
|
+
function googleReadiness() {
|
|
728
|
+
return object2(googleReadinessFields);
|
|
729
|
+
}
|
|
730
|
+
var googleConfigurationDigestValue = refine2(union2(object2({ ...googleConfigurationFields, ...googleExistingChoiceFields, configurationDigest: optional2(digest) }), object2({ ...googleConfigurationFields, ...googleSetupChoiceFields, configurationDigest: optional2(digest) })), checkGoogleConfiguration);
|
|
731
|
+
function resonanceGoogleConfigurationDigestInput(value) {
|
|
732
|
+
const { configurationDigest: _self, ...content } = googleConfigurationDigestValue.read(value, "googleConfigurationDigestInput");
|
|
733
|
+
return canonical(content);
|
|
734
|
+
}
|
|
735
|
+
var RESONANCE_GOOGLE_SETUP_SPEC_JSON_SCHEMA = googleSetupSpec.schema;
|
|
736
|
+
var RESONANCE_GOOGLE_CONFIGURATION_SAVE_COMMAND_JSON_SCHEMA = googleSave.schema;
|
|
737
|
+
var RESONANCE_GOOGLE_CONFIGURATION_SNAPSHOT_JSON_SCHEMA = googleConfigurationSnapshot.schema;
|
|
738
|
+
var RESONANCE_GOOGLE_CONFIGURATION_REVIEW_JSON_SCHEMA = googleConfigurationReview.schema;
|
|
739
|
+
var RESONANCE_GOOGLE_CONFIGURATION_VIEW_JSON_SCHEMA = googleConfigurationView.schema;
|
|
740
|
+
var RESONANCE_GOOGLE_CONFIGURATION_SAVE_RESPONSE_JSON_SCHEMA = googleConfigurationSaveResponse.schema;
|
|
741
|
+
var preparedManifestRef = object2({ id: uuid, digest, providerVersion: text2(30), revision });
|
|
742
|
+
var preview = object2({ channelId: uuid, site, accountBindingId: nullable2(uuid), accountName: text2(200), accountTimeZone: timeZone, destinationUrl: text2(4096), attributionMarker: optional2(object2({ parameter: literal2(RESONANCE_SOURCE_MARKER_PARAMETER), value: sourceMarker })), orderedCreatives: list2(creative, 50, 1), audienceRuleRefs: list2(audience, 100), allocation: nullable2(money), capabilityRevision: revision, snapshotDigest: digest, manifestRef: preparedManifestRef, conversionRevision: revision, policyRevision: revision, googleConfiguration: nullable2(googleConfigurationReview) });
|
|
743
|
+
var prepared = object2({ schemaVersion: literal2(1), id: uuid, campaignId: uuid, projectId: uuid, expectedRevision: integer2(1), definitionDigest: digest, reviewDigest: digest, definition, resolvedEvidencePolicy: optimizationPolicy, resolvedQualificationRule: nullable2(resolvedQualificationRule), accounts: list2(capability, 100), channelPreviews: list2(preview, 100, 1), preparedAt: instant, expiresAt: instant, blockingReasons: list2(text2(200), 100) });
|
|
744
|
+
function parseResonancePreparedPlan(v) {
|
|
745
|
+
const r = prepared.read(v, "preparedPlan");
|
|
746
|
+
checkDefinition(r.definition);
|
|
747
|
+
scoped(r, r.projectId, r.definition.sites, "preparedPlan");
|
|
748
|
+
if (Date.parse(r.expiresAt) <= Date.parse(r.preparedAt)) fail2("preparedPlan.expiresAt", "must follow preparation");
|
|
749
|
+
const goal = r.definition.objective, witness = r.resolvedQualificationRule;
|
|
750
|
+
if (goal.source.kind === "qualified_lead") {
|
|
751
|
+
if (!goal.qualificationRule || !goal.qualificationRuleRef || !witness) fail2("preparedPlan.resolvedQualificationRule", "qualified-lead preparation requires actual configured native witness");
|
|
752
|
+
const w = witness, selected = goal.qualificationRule;
|
|
753
|
+
if (goal.countingUnit !== "unique_qualified_contact" || canonical(w.ref) !== canonical(goal.qualificationRuleRef) || w.eligibleFormIds.length !== 1 || w.eligibleFormIds[0] !== goal.source.id || w.cohort.maturityDelaySeconds !== goal.maturityDelaySeconds) fail2("preparedPlan.resolvedQualificationRule", "source, counting unit, reference or maturity mismatch");
|
|
754
|
+
for (const key of ["schemaVersion", "semantics", "positiveStageKeys", "finalNegativeStageKeys", "pendingStageKeys", "existingContactPolicy"]) if (canonical(w[key]) !== canonical(selected[key])) fail2("preparedPlan.resolvedQualificationRule", "selected qualification snapshot mismatch");
|
|
755
|
+
} else if (witness !== null) fail2("preparedPlan.resolvedQualificationRule", "nonqualified objectives require explicit null");
|
|
756
|
+
if (canonical(r.resolvedEvidencePolicy) !== canonical(r.definition.optimization.policy)) fail2("preparedPlan.resolvedEvidencePolicy", "policy snapshot mismatch");
|
|
757
|
+
parseResonanceOptimizationPolicy(r.resolvedEvidencePolicy);
|
|
758
|
+
if (r.resolvedEvidencePolicy.algorithm === "finite_meta_candidates_v1" && Date.parse(r.resolvedEvidencePolicy.expiresAt) <= Date.parse(r.preparedAt)) fail2("preparedPlan.resolvedEvidencePolicy.expiresAt", "finite policy expired before preparation");
|
|
759
|
+
unique(r.accounts.map((a) => a.id), "preparedPlan.accounts");
|
|
760
|
+
unique(r.channelPreviews.map((p) => p.channelId), "preparedPlan.channelPreviews");
|
|
761
|
+
if (r.channelPreviews.length !== r.definition.channels.length) fail2("preparedPlan.channelPreviews", "must cover every channel");
|
|
762
|
+
for (const a of r.accounts) parseResonanceAccountCapability(a);
|
|
763
|
+
for (const p of r.channelPreviews) {
|
|
764
|
+
const c = r.definition.channels.find((c2) => c2.id === p.channelId) ?? fail2("preparedPlan.channelPreviews", "unknown channel");
|
|
765
|
+
if (c.site !== p.site) fail2("preparedPlan.channelPreviews", "channel scope mismatch");
|
|
766
|
+
const google = p.googleConfiguration;
|
|
767
|
+
if (c.kind === "google_search") {
|
|
768
|
+
if (!google) fail2("preparedPlan.channelPreviews.googleConfiguration", "Google requires the actual reviewed configuration");
|
|
769
|
+
const g = google;
|
|
770
|
+
if (g.projectId !== r.projectId || g.campaignId !== r.campaignId || g.channelId !== c.id || g.site !== c.site || g.accountBindingId !== c.accountBindingId) fail2("preparedPlan.channelPreviews.googleConfiguration", "configuration scope mismatch");
|
|
771
|
+
if (canonical(g.source) !== canonical(goal.source) || g.measurementRevision !== goal.measurementRevision || g.countingUnit !== goal.countingUnit || g.attributionWindowDays !== goal.attributionWindowDays) fail2("preparedPlan.channelPreviews.googleConfiguration.source", "objective measurement mismatch");
|
|
772
|
+
unique(c.locationIds, "preparedPlan.channelPreviews.googleConfiguration.locationIds");
|
|
773
|
+
unique(c.excludedLocationIds, "preparedPlan.channelPreviews.googleConfiguration.excludedLocationIds");
|
|
774
|
+
if (canonical(g.declaration.locationIds) !== canonical([...c.locationIds].sort()) || canonical(g.declaration.excludedLocationIds) !== canonical([...c.excludedLocationIds].sort())) fail2("preparedPlan.channelPreviews.googleConfiguration.declaration", "region scope mismatch");
|
|
775
|
+
if (g.declaration.declaredForCampaignRevision > r.expectedRevision || Date.parse(g.declaration.declaredAt) > Date.parse(r.preparedAt)) fail2("preparedPlan.channelPreviews.googleConfiguration.declaration", "declaration follows preparation scope");
|
|
776
|
+
} else if (google !== null) fail2("preparedPlan.channelPreviews.googleConfiguration", "non-Google channels require explicit null");
|
|
777
|
+
const d = r.definition.destinations.find((d2) => d2.id === c.destinationId);
|
|
778
|
+
const expectedUrl = p.attributionMarker ? new URL(`https://${p.site}${d.path}`) : null;
|
|
779
|
+
if (expectedUrl && p.attributionMarker) expectedUrl.searchParams.set(RESONANCE_SOURCE_MARKER_PARAMETER, p.attributionMarker.value);
|
|
780
|
+
if (!sameSite(hostFromUrl(p.destinationUrl), p.site) || p.destinationUrl !== (expectedUrl?.toString() ?? `https://${p.site}${d.path}`)) fail2("preparedPlan.channelPreviews.destinationUrl", "must be exact HTTPS destination and reviewed source marker on the owned site");
|
|
781
|
+
if (canonical(p.orderedCreatives) !== canonical(c.creativeIds.map((id) => r.definition.creativeRefs.find((x) => x.id === id)))) fail2("preparedPlan.channelPreviews.orderedCreatives", "creative order/content mismatch");
|
|
782
|
+
if ("provider" in c) {
|
|
783
|
+
if (p.accountBindingId !== c.accountBindingId || !r.accounts.some((a) => a.id === c.accountBindingId)) fail2("preparedPlan.channelPreviews.accountBindingId", "account binding mismatch");
|
|
784
|
+
if (canonical(p.allocation) !== canonical(c.allocation)) fail2("preparedPlan.channelPreviews.allocation", "allocation mismatch");
|
|
785
|
+
} else if (p.accountBindingId !== null || p.allocation !== null) fail2("preparedPlan.channelPreviews", "nonpaid child cannot select paid account/allocation");
|
|
786
|
+
}
|
|
787
|
+
return r;
|
|
788
|
+
}
|
|
789
|
+
var commandBase = { schemaVersion: literal2(1), campaignId: uuid, expectedRevision: integer2(1), idempotencyKey: operationKey, channelIds: list2(uuid, 100) };
|
|
790
|
+
var effect = object2({ ...commandBase, operation: choice2("launch", "update", "resume"), preparedPlanId: uuid, reviewDigest: digest, mandateRef, expectedRemoteSnapshots: list2(object2({ channelId: uuid, digest }), 100) });
|
|
791
|
+
var optimize = object2({ ...commandBase, operation: literal2("optimize"), preparedPlanId: uuid, reviewDigest: digest, mandateRef, decisionRef: object2({ id: uuid, revision, evidenceDigest: digest }), expectedRemoteSnapshots: list2(object2({ channelId: uuid, digest }), 100) });
|
|
792
|
+
var safety = object2({ ...commandBase, operation: choice2("pause", "finish", "reconcile") });
|
|
793
|
+
var deliverConversion = object2({ ...commandBase, operation: literal2("deliver_conversion"), channelIds: list2(uuid, 1, 1), preparedPlanId: uuid, reviewDigest: digest, mandateRef, conversionId: uuid });
|
|
794
|
+
var deliverWebsiteVisit = object2({ ...commandBase, operation: literal2("deliver_website_visit"), channelIds: list2(uuid, 1, 1), preparedPlanId: uuid, reviewDigest: digest, mandateRef, occurrenceId: uuid });
|
|
795
|
+
var command = union2(effect, optimize, safety, deliverConversion, deliverWebsiteVisit);
|
|
796
|
+
function parseResonanceCommand(v, context) {
|
|
797
|
+
const r = command.read(v, "command");
|
|
798
|
+
unique(r.channelIds, "command.channelIds");
|
|
799
|
+
if ("mandateRef" in r && (r.mandateRef.campaignId !== r.campaignId || r.mandateRef.preparedPlanId !== r.preparedPlanId || r.mandateRef.reviewDigest !== r.reviewDigest)) fail2("command.mandateRef", "snapshot mismatch");
|
|
800
|
+
if ((r.operation === "deliver_conversion" || r.operation === "deliver_website_visit") && context) {
|
|
801
|
+
const plan = parseResonancePreparedPlan(context.preparedPlan), grant = parseResonanceMandateScope(context.mandate);
|
|
802
|
+
const now = Date.parse(instant.read(context.now, "command.context.now"));
|
|
803
|
+
if (r.expectedRevision !== plan.expectedRevision) fail2("command.expectedRevision", "requires the original prepared definition revision");
|
|
804
|
+
if (r.campaignId !== plan.campaignId || r.preparedPlanId !== plan.id || r.reviewDigest !== plan.reviewDigest || grant.projectId !== plan.projectId || grant.campaignId !== plan.campaignId || grant.preparedPlanId !== plan.id || grant.reviewDigest !== plan.reviewDigest) fail2("command.context", "requires the exact original plan and mandate scope");
|
|
805
|
+
if (canonical(grant.goal) !== canonical(plan.definition.objective) || canonical(grant.budget) !== canonical(plan.definition.budget)) fail2("command.context.mandate", "original goal or budget mismatch");
|
|
806
|
+
const channel2 = plan.definition.channels.find((c) => c.id === r.channelIds[0]);
|
|
807
|
+
if (r.operation === "deliver_conversion") {
|
|
808
|
+
if (!grant.allowedOperations.includes("deliver_conversion")) fail2("command.context.mandate.allowedOperations", "requires the explicit original delivery grant");
|
|
809
|
+
if (!channel2 || channel2.kind !== "google_search") fail2("command.channelIds", "requires one original Google channel");
|
|
810
|
+
const c = channel2;
|
|
811
|
+
const goal = plan.definition.objective, witness = plan.resolvedQualificationRule;
|
|
812
|
+
if (goal.source.kind !== "qualified_lead" || goal.countingUnit !== "unique_qualified_contact" || !witness || witness.eligibleFormIds.length !== 1 || witness.eligibleFormIds[0] !== goal.source.id || goal.source.site !== c.site) fail2("command.context.goal", "requires the original single managed-form qualified source");
|
|
813
|
+
const account = plan.accounts.find((a) => a.id === c.accountBindingId);
|
|
814
|
+
if (!account || account.provider !== "google_ads" || !grant.accountBindingIds.includes(c.accountBindingId) || !grant.sites.includes(c.site)) fail2("command.context.account", "requires the original granted Google account and site");
|
|
815
|
+
const configuration = plan.channelPreviews.find((p) => p.channelId === c.id)?.googleConfiguration;
|
|
816
|
+
if (!configuration || configuration.signalSemantics !== RESONANCE_GOOGLE_SIGNAL_SEMANTICS) fail2("command.context.googleConfiguration", "requires the reviewed qualified-at-observation profile");
|
|
817
|
+
const g = configuration;
|
|
818
|
+
if (g.mode === "existing" && (g.nativeGoal.actionIds.length !== 1 || g.nativeGoal.actionIds[0].split("/")[1] !== account.externalAccountId || g.nativeGoal.externalId.split("/")[1] !== account.externalAccountId)) fail2("command.context.googleConfiguration.nativeGoal", "requires one action owned by the original account; cross-account delivery is unsupported");
|
|
819
|
+
if (g.mode === "admitted_setup" && (g.setupSpec.goalStrategy !== "exclusive_custom_goal_v1" || g.setupSpec.goalActionKey !== g.setupSpec.action.localKey)) fail2("command.context.googleConfiguration.setupSpec", "requires the original single-action setup intent");
|
|
820
|
+
} else {
|
|
821
|
+
const selected = grant.websiteVisitFeed?.channels.find((c2) => c2.channelId === r.channelIds[0]);
|
|
822
|
+
if (!selected) fail2("command.context.mandate.websiteVisitFeed", "requires the explicit original feed channel");
|
|
823
|
+
if (!channel2 || channel2.kind !== "meta_retargeting") fail2("command.channelIds", "requires one original Meta channel");
|
|
824
|
+
const c = channel2;
|
|
825
|
+
const account = plan.accounts.find((a) => a.id === c.accountBindingId);
|
|
826
|
+
if (!account || account.provider !== "meta_ads" || !grant.accountBindingIds.includes(c.accountBindingId) || !grant.sites.includes(c.site)) fail2("command.context.account", "requires the original granted Meta account and site");
|
|
827
|
+
if (c.audienceRuleRefs.some((rule) => !grant.audienceRuleRefs.some((original) => canonical(original) === canonical(rule)))) fail2("command.context.audienceRuleRefs", "requires the original granted audience rules");
|
|
828
|
+
}
|
|
829
|
+
if (now < Date.parse(grant.budget.startAt) || now >= Date.parse(grant.budget.endAt) || now >= Date.parse(grant.expiresAt)) fail2("command.context.now", "outside the original mandate and budget dispatch window");
|
|
830
|
+
}
|
|
831
|
+
return r;
|
|
832
|
+
}
|
|
833
|
+
var RESONANCE_COMMAND_JSON_SCHEMA = command.schema;
|
|
834
|
+
var step = object2({ id: uuid, channelId: uuid, state: choice2("planned", "admitted", "confirmed", "failed", "uncertain"), providerRequestId: nullable2(text2(200)), admission: choice2("not_dispatched", "accepted", "rejected", "unknown"), configuredStatus: nullable2(text2(80)), effectiveStatus: nullable2(text2(80)), reviewStatus: nullable2(text2(80)), retryAfter: nullable2(instant), errorSubcode: nullable2(text2(80)), errorFieldPath: nullable2(text2(200)), resourceRefs: list2(object2({ type: text2(80), externalId: text2(200) }), 100), evidenceDigest: nullable2(digest), errorCode: nullable2(text2(80)), observedAt: nullable2(instant) });
|
|
835
|
+
var receipt = object2({ schemaVersion: literal2(1), id: uuid, projectId: uuid, campaignId: uuid, preparedPlanId: nullable2(uuid), effectKey: text2(200), requestDigest: digest, ledgerActionId: uuid, actorId: text2(160), state: choice2("pending", "applying", "accepted", "failed", "needs_reconciliation"), steps: list2(step, 1e3), reservedExposure: money, unreconciledExposure: money, providerSemanticsNotice: list2(text2(500), 30), nextActions: list2(choice2("read", "reconcile", "retry_same_operation", "pause", "escalate"), 5) });
|
|
836
|
+
function parseResonanceReceipt(v) {
|
|
837
|
+
const r = receipt.read(v, "receipt");
|
|
838
|
+
if (r.reservedExposure.currency !== r.unreconciledExposure.currency) fail2("receipt", "currency mismatch");
|
|
839
|
+
const uncertain = r.steps.some((s) => s.state === "uncertain" || s.admission === "unknown");
|
|
840
|
+
if (uncertain && r.state !== "needs_reconciliation") fail2("receipt.state", "uncertain child requires reconciliation state");
|
|
841
|
+
if ((uncertain || r.state === "needs_reconciliation") && r.nextActions.includes("retry_same_operation")) fail2("receipt.nextActions", "unknown admission must reconcile before retry");
|
|
842
|
+
return r;
|
|
843
|
+
}
|
|
844
|
+
var lifecycle = choice2("not_materialized", "pending", "partially_live", "live", "pausing", "paused", "finished", "needs_attention", "unknown");
|
|
845
|
+
var view = object2({ id: uuid, projectId: uuid, managementMode: choice2("tracking_only", "managed"), revision: integer2(0), definition: nullable2(draft), acceptedPlanId: nullable2(uuid), desiredLifecycle: choice2("draft", "running", "paused", "finished"), effectiveLifecycle: lifecycle, blockingReasons: list2(text2(200), 100), activeOperation: nullable2(receipt), unresolvedExposure: nullable2(money) });
|
|
846
|
+
function parseResonanceCampaignView(v) {
|
|
847
|
+
const r = view.read(v, "campaignView");
|
|
848
|
+
if (r.managementMode === "managed" && r.revision < 1) fail2("campaignView.revision", "managed campaigns require revision >=1");
|
|
849
|
+
if (r.definition) parseResonanceCampaignDraft(r.definition);
|
|
850
|
+
if (r.activeOperation) parseResonanceReceipt(r.activeOperation);
|
|
851
|
+
if (r.definition && r.definition.projectId !== r.projectId) fail2("campaignView.definition", "cross-project reference");
|
|
852
|
+
if (r.activeOperation && (r.activeOperation.projectId !== r.projectId || r.activeOperation.campaignId !== r.id)) fail2("campaignView.activeOperation", "scope mismatch");
|
|
853
|
+
return r;
|
|
854
|
+
}
|
|
855
|
+
var metricValue = union2(object2({ kind: literal2("count"), count: decimal }), object2({ kind: literal2("money"), money }), object2({ kind: literal2("ratio"), numerator: decimal, denominator: refine2(decimal, (v, p) => {
|
|
856
|
+
if (v === "0") fail2(p, "zero denominator");
|
|
857
|
+
}) }));
|
|
858
|
+
var metricBase = { ...scope, campaignId: uuid, name: choice2("impressions", "clicks", "spend", "accepted_goals", "qualified_goals", "revenue", "conversion_rate"), source: choice2("first_party", "provider", "experiment"), sourceRef: revision, goalRevision: nullable2(revision), countingUnit: choice2("unique_source", "unique_qualified_contact", "net_order", "provider_aggregate"), windowStart: instant, windowEnd: instant, timeZone, attributionModel: nullable2(text2(100)), asOf: instant, dataThrough: nullable2(instant), maturityDelaySeconds: integer2(0), coverageBasisPoints: nullable2(integer2(0, 1e4)), eligibleExposures: nullable2(decimal), matureOutcomes: nullable2(decimal), native: nullable2(object2({ unit: text2(80), amount: decimal, currency: nullable2(currency), unitExponent: integer2(0, 9), providerVersion: text2(30) })) };
|
|
859
|
+
var metric = union2(object2({ ...metricBase, state: literal2("observed"), value: metricValue }), object2({ ...metricBase, state: choice2("unknown", "insufficient", "delayed", "unavailable"), value: literal2(null), reasonCode: text2(80) }));
|
|
860
|
+
function parseResonanceMetricObservation(v) {
|
|
861
|
+
const r = metric.read(v, "metric");
|
|
862
|
+
if (["accepted_goals", "qualified_goals", "revenue", "conversion_rate"].includes(r.name) && r.goalRevision === null) fail2("metric.goalRevision", "goal measurement requires explicit revision");
|
|
863
|
+
if (Date.parse(r.windowEnd) <= Date.parse(r.windowStart)) fail2("metric.window", "invalid window");
|
|
864
|
+
if (r.state === "observed") {
|
|
865
|
+
const expected = r.name === "spend" || r.name === "revenue" ? "money" : r.name === "conversion_rate" ? "ratio" : "count";
|
|
866
|
+
if (r.value.kind !== expected) fail2("metric.value", "metric unit mismatch");
|
|
867
|
+
}
|
|
868
|
+
return r;
|
|
869
|
+
}
|
|
870
|
+
var purpose = choice2("analytics_storage", "ad_storage", "ad_user_data", "ad_personalization");
|
|
871
|
+
var consent = object2({ ...scope, receiptId: uuid, visitorNamespace: text2(160), configRevision: revision, policyRevision: revision, consentRevision: integer2(1), state: choice2("unknown", "granted", "denied", "withdrawn"), purpose, observedAt: instant, evidenceRef: revision });
|
|
872
|
+
var parseResonanceConsentSnapshot = (v) => consent.read(v, "consent");
|
|
873
|
+
var exposure = object2({ ...scope, experimentId: uuid, experimentRevision: integer2(1), assignmentId: uuid, variantKey: text2(80), exposureId: uuid, occurredAt: instant });
|
|
874
|
+
var conversion = object2({ schemaVersion: literal2(1), ...scope, eventId: uuid, sourceRef: source, campaignId: nullable2(uuid), goalRevision: revision, occurredAt: instant, receivedAt: instant, value: union2(object2({ state: literal2("observed"), money }), object2({ state: choice2("unknown", "unavailable"), money: literal2(null), reasonCode: text2(80) })), consent: list2(consent, 4), experiment: nullable2(exposure) });
|
|
875
|
+
function parseResonanceConversionEnvelope(v) {
|
|
876
|
+
const r = conversion.read(v, "conversion");
|
|
877
|
+
unique(r.consent.map((c) => c.purpose), "conversion.consent");
|
|
878
|
+
scoped(r, r.projectId, [r.site], "conversion");
|
|
879
|
+
if (Date.parse(r.receivedAt) < Date.parse(r.occurredAt)) fail2("conversion.receivedAt", "precedes occurrence");
|
|
880
|
+
return r;
|
|
881
|
+
}
|
|
882
|
+
var RESONANCE_CONVERSION_JSON_SCHEMA = conversion.schema;
|
|
883
|
+
var RESONANCE_PREPARED_PLAN_JSON_SCHEMA = prepared.schema;
|
|
884
|
+
var RESONANCE_ACCOUNT_CAPABILITY_JSON_SCHEMA = capability.schema;
|
|
885
|
+
var RESONANCE_MANDATE_SCOPE_JSON_SCHEMA = mandateScope.schema;
|
|
886
|
+
var RESONANCE_MANDATE_REF_JSON_SCHEMA = mandateRef.schema;
|
|
887
|
+
var RESONANCE_RECEIPT_JSON_SCHEMA = receipt.schema;
|
|
888
|
+
var RESONANCE_CAMPAIGN_VIEW_JSON_SCHEMA = view.schema;
|
|
889
|
+
var RESONANCE_METRIC_JSON_SCHEMA = metric.schema;
|
|
890
|
+
var RESONANCE_CONSENT_JSON_SCHEMA = consent.schema;
|
|
891
|
+
var parseResonanceExperimentExposure = (v) => exposure.read(v, "exposure");
|
|
892
|
+
var mandateTarget = object2({ project_id: uuid, campaign_id: uuid });
|
|
893
|
+
var mandateApproval = object2({ schemaVersion: literal2(1), expectedRevision: integer2(1), preparedPlanId: uuid, reviewDigest: digest, mandateDigest: digest, mandate: mandateScope });
|
|
894
|
+
var RESONANCE_MANDATE_APPROVAL_JSON_SCHEMA = mandateApproval.schema;
|
|
895
|
+
var RESONANCE_MANDATE_TARGET_JSON_SCHEMA = mandateTarget.schema;
|
|
896
|
+
var RESONANCE_MANDATE_ACTION_KIND = "resonance_mandate";
|
|
897
|
+
function parseResonanceMandateApprovalPayload(v, target) {
|
|
898
|
+
const r = mandateApproval.read(v, "mandateApproval"), t = mandateTarget.read(target, "target_ref");
|
|
899
|
+
parseResonanceMandateScope(r.mandate);
|
|
900
|
+
if (r.mandate.projectId !== t.project_id || r.mandate.campaignId !== t.campaign_id || r.preparedPlanId !== r.mandate.preparedPlanId || r.reviewDigest !== r.mandate.reviewDigest) fail2("mandateApproval", "target or snapshot mismatch");
|
|
901
|
+
return r;
|
|
902
|
+
}
|
|
903
|
+
function canonical(value) {
|
|
904
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
905
|
+
if (Array.isArray(value)) return "[" + value.map(canonical).join(",") + "]";
|
|
906
|
+
return "{" + Object.keys(value).sort().map((k) => JSON.stringify(k) + ":" + canonical(value[k])).join(",") + "}";
|
|
907
|
+
}
|
|
908
|
+
var resonanceMandateDigestInput = (v) => canonical(parseResonanceMandateScope(v));
|
|
909
|
+
var resonanceCommandDigestInput = (v) => canonical(parseResonanceCommand(v));
|
|
910
|
+
var parseResonanceMandateTarget = (v) => mandateTarget.read(v, "target_ref");
|
|
911
|
+
var prepareCommand = object2({ expectedRevision: integer2(1), idempotencyKey: operationKey });
|
|
912
|
+
var parseResonancePrepareCommand = (v) => prepareCommand.read(v, "prepare");
|
|
913
|
+
var parseResonanceCommandPrepareRequest = parseResonancePrepareCommand;
|
|
914
|
+
var commandPreparation = object2({ command: nullable2(command), covered: bool, approvalRequirement: nullable2(choice2("existing_mandate", "current_owner")), reasonCodes: list2(text2(200), 100) });
|
|
915
|
+
function parseResonanceCommandPrepareResponse(v) {
|
|
916
|
+
const r = commandPreparation.read(v, "commandPreparation");
|
|
917
|
+
if (r.command) parseResonanceCommand(r.command);
|
|
918
|
+
return r;
|
|
919
|
+
}
|
|
920
|
+
var RESONANCE_COMMAND_PREPARE_REQUEST_JSON_SCHEMA = prepareCommand.schema;
|
|
921
|
+
var RESONANCE_COMMAND_PREPARE_RESPONSE_JSON_SCHEMA = commandPreparation.schema;
|
|
922
|
+
function resonancePlanIssues(input, at) {
|
|
923
|
+
const plan = parseResonancePreparedPlan(input), issues = [];
|
|
924
|
+
instant.read(at, "asOf");
|
|
925
|
+
if (Date.parse(plan.expiresAt) <= Date.parse(at) || Date.parse(plan.preparedAt) > Date.parse(at)) issues.push({ path: "preparedPlan", code: "plan_stale" });
|
|
926
|
+
for (const reason of plan.blockingReasons) issues.push({ path: "preparedPlan", code: reason });
|
|
927
|
+
if (plan.definition.optimization.enabled) {
|
|
928
|
+
const goal = plan.definition.objective;
|
|
929
|
+
if (!goal.qualificationRule || !goal.qualificationRuleRef || !goal.qualificationRule.positiveStageKeys.length) issues.push({ path: "objective.qualificationRule", code: "native_qualification_rule_missing" });
|
|
930
|
+
const attributionModel = plan.resolvedEvidencePolicy.algorithm === "finite_meta_candidates_v1" ? plan.resolvedEvidencePolicy.measurementGuard.attributionModel : plan.resolvedEvidencePolicy.attributionModel;
|
|
931
|
+
if (plan.resolvedEvidencePolicy.algorithm === "finite_meta_candidates_v1" && Date.parse(plan.resolvedEvidencePolicy.expiresAt) <= Date.parse(at)) issues.push({ path: "resolvedEvidencePolicy.expiresAt", code: "finite_policy_expired" });
|
|
932
|
+
if (attributionModel !== RESONANCE_ATTRIBUTION_MODEL) issues.push({ path: "resolvedEvidencePolicy.attributionModel", code: "verified_last_touch_model_required" });
|
|
933
|
+
for (const preview2 of plan.channelPreviews) if (!preview2.attributionMarker) issues.push({ path: "channelPreviews." + preview2.channelId, code: "approved_source_marker_missing" });
|
|
934
|
+
}
|
|
935
|
+
for (const channel2 of plan.definition.channels) {
|
|
936
|
+
if (!("provider" in channel2)) continue;
|
|
937
|
+
const account = plan.accounts.find((a) => a.id === channel2.accountBindingId);
|
|
938
|
+
const path2 = "channels." + channel2.id;
|
|
939
|
+
if (!account) {
|
|
940
|
+
issues.push({ path: path2, code: "account_missing" });
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
if (account.provider !== channel2.provider || account.projectId !== plan.projectId) issues.push({ path: path2, code: "account_scope_mismatch" });
|
|
944
|
+
if (account.currency !== channel2.allocation.currency) issues.push({ path: path2, code: "currency_mismatch" });
|
|
945
|
+
if (account.capabilityRevision !== channel2.capabilityRevision || Date.parse(account.expiresAt) <= Date.parse(at) || Date.parse(account.observedAt) > Date.parse(at)) issues.push({ path: path2, code: "capability_stale" });
|
|
946
|
+
if (account.advertiserState !== "eligible" || account.profiles.find((p) => p.profile === channel2.kind)?.state !== "supported" || account.operations.find((o) => o.operation === "launch")?.state !== "supported") issues.push({ path: path2, code: "capability_not_supported" });
|
|
947
|
+
if (!account.profiles.find((p) => p.profile === channel2.kind)?.budgetModes.includes(channel2.budgetMode)) issues.push({ path: path2, code: "budget_mode_not_supported" });
|
|
948
|
+
}
|
|
949
|
+
return issues;
|
|
950
|
+
}
|
|
951
|
+
function compareMoney(a, b) {
|
|
952
|
+
a = parseMoney(a);
|
|
953
|
+
b = parseMoney(b);
|
|
954
|
+
if (a.currency !== b.currency) fail2("money.currency", "currency mismatch");
|
|
955
|
+
const left = BigInt(a.amountMicros), right = BigInt(b.amountMicros);
|
|
956
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
957
|
+
}
|
|
958
|
+
function subtractMoney(a, b) {
|
|
959
|
+
if (compareMoney(a, b) < 0) fail2("money.amountMicros", "negative result");
|
|
960
|
+
return parseMoney({ currency: a.currency, amountMicros: (BigInt(a.amountMicros) - BigInt(b.amountMicros)).toString() });
|
|
961
|
+
}
|
|
962
|
+
function formatMoney(input) {
|
|
963
|
+
const m = parseMoney(input), n = m.amountMicros.padStart(7, "0");
|
|
964
|
+
const fraction = n.slice(-6).replace(/0+$/, "");
|
|
965
|
+
return m.currency + " " + n.slice(0, -6) + (fraction ? "." + fraction : "");
|
|
966
|
+
}
|
|
967
|
+
var saveCommand = object2({ definition: draft, expectedRevision: optional2(integer2(0)), idempotencyKey: operationKey });
|
|
968
|
+
function parseResonanceCampaignSaveCommand(v, mode = "update") {
|
|
969
|
+
const r = saveCommand.read(v, "save");
|
|
970
|
+
parseResonanceCampaignDraft(r.definition);
|
|
971
|
+
if (mode !== "create" && mode !== "update") fail2("save.mode", "unsupported mode");
|
|
972
|
+
if (mode === "update" && (r.expectedRevision === void 0 || r.expectedRevision < 1)) fail2("save.expectedRevision", "required for update");
|
|
973
|
+
return r;
|
|
974
|
+
}
|
|
975
|
+
var acceptCommand = object2({ expectedRevision: integer2(1), ledgerActionId: uuid });
|
|
976
|
+
var parseResonanceAcceptCommand = (v) => acceptCommand.read(v, "accept");
|
|
977
|
+
var bindingCommand = object2({ connectionId: uuid, provider: choice2("google_ads", "meta_ads"), externalAccountId: text2(160), expectedConnectionRevision: revision, idempotencyKey: operationKey });
|
|
978
|
+
var parseResonanceBindingCommand = (v) => bindingCommand.read(v, "binding");
|
|
979
|
+
var RESONANCE_SAVE_COMMAND_JSON_SCHEMA = saveCommand.schema;
|
|
980
|
+
var RESONANCE_ACCEPT_COMMAND_JSON_SCHEMA = acceptCommand.schema;
|
|
981
|
+
var RESONANCE_BINDING_COMMAND_JSON_SCHEMA = bindingCommand.schema;
|
|
982
|
+
var RESONANCE_CPQL_UNCERTAINTY_METHOD = "observational_cpql_missing_outcome_bound_v1";
|
|
983
|
+
var allocationHorizon = union2(
|
|
984
|
+
refine2(object2({ kind: literal2("finite_period"), startAt: instant, endAt: instant, timeZone, sourceRef: revision }), (r, p) => {
|
|
985
|
+
if (Date.parse(r.endAt) <= Date.parse(r.startAt)) fail2(p, "invalid finite horizon");
|
|
986
|
+
}),
|
|
987
|
+
object2({ kind: literal2("daily_average"), timeZone, sourceRef: revision })
|
|
988
|
+
);
|
|
989
|
+
var uncertaintySource = object2({ method: literal2(RESONANCE_CPQL_UNCERTAINTY_METHOD), eligibleContacts: decimal, fullyResolvedContacts: decimal, qualifiedContacts: decimal, verifiedSpend: money, sourceRevision: revision, qualificationRuleRef: resource, universeComplete: literal2(true), spendPeriodComplete: literal2(true) });
|
|
990
|
+
function cpqlMissingOutcomeBound(input) {
|
|
991
|
+
const n = BigInt(decimal.read(input.eligibleContacts, "bound.eligibleContacts"));
|
|
992
|
+
const c = BigInt(decimal.read(input.fullyResolvedContacts, "bound.fullyResolvedContacts"));
|
|
993
|
+
const k = BigInt(decimal.read(input.qualifiedContacts, "bound.qualifiedContacts"));
|
|
994
|
+
if (c > n || k > c) fail2("bound", "inconsistent unique-contact source universe");
|
|
995
|
+
if (n === 0n || k === 0n || parseMoney(input.verifiedSpend).amountMicros === "0") return null;
|
|
996
|
+
const u = n - c, denominator = 2n * k + u;
|
|
997
|
+
return Number((10000n * u + denominator - 1n) / denominator);
|
|
998
|
+
}
|
|
999
|
+
var matureOptimizationEvidence = object2({
|
|
1000
|
+
id: uuid,
|
|
1001
|
+
revision,
|
|
1002
|
+
evidenceDigest: digest,
|
|
1003
|
+
projectId: uuid,
|
|
1004
|
+
campaignId: uuid,
|
|
1005
|
+
preparedPlanId: uuid,
|
|
1006
|
+
reviewDigest: digest,
|
|
1007
|
+
observedAt: instant,
|
|
1008
|
+
windowStart: instant,
|
|
1009
|
+
windowEnd: instant,
|
|
1010
|
+
dataThrough: nullable2(instant),
|
|
1011
|
+
lastAdjustmentAt: nullable2(instant),
|
|
1012
|
+
currentKillRevision: integer2(0),
|
|
1013
|
+
goalRevision: revision,
|
|
1014
|
+
objective,
|
|
1015
|
+
policyDigest: digest,
|
|
1016
|
+
resolvedPolicy: matureOptimizationPolicy,
|
|
1017
|
+
trackingState: choice2("healthy", "blocked", "unknown"),
|
|
1018
|
+
coverageBasisPoints: nullable2(integer2(0, 1e4)),
|
|
1019
|
+
assignmentCheck: choice2("pass", "failed", "unknown", "not_applicable"),
|
|
1020
|
+
powerCheck: choice2("pass", "failed", "unknown", "not_applicable"),
|
|
1021
|
+
channels: list2(object2({ channelId: uuid, site, accountBindingId: uuid, trackingState: choice2("healthy", "blocked", "unknown"), coverageBasisPoints: nullable2(integer2(0, 1e4)), coverageFamily: choice2("crm_attribution", "provider_import", "experiment_exposure"), coverageDefinitionRef: resource, coveredNumerator: nullable2(decimal), eligibleDenominator: nullable2(decimal), uncertaintyBasisPoints: nullable2(integer2(0, 1e4)), uncertaintySource: nullable2(uncertaintySource), dedupeCheck: choice2("pass", "failed", "unknown", "not_applicable"), qualificationCheck: choice2("pass", "failed", "unknown", "not_applicable"), reasonCodes: list2(text2(80), 50), capabilityRevision: revision, budgetMode: choice2("campaign_total", "average_daily", "lifetime", "daily"), allocationHorizon: nullable2(allocationHorizon), matureThrough: nullable2(instant), dataThrough: nullable2(instant), goalRevision: revision, countingUnit: choice2("unique_source", "unique_qualified_contact", "net_order"), attributionModel: text2(100), attributionWindowDays: integer2(1, 365), policyRevision: revision, currentAllocation: money, remoteSnapshotDigest: digest, observations: list2(metric, 100), providerUnitExponent: refine2(integer2(0, 6), (v, p) => {
|
|
1022
|
+
if (![0, 2, 3, 6].includes(v)) fail2(p, "unsupported provider exponent");
|
|
1023
|
+
}), providerMaxUnits: decimal }), 100, 1)
|
|
1024
|
+
});
|
|
1025
|
+
function parseMatureOptimizationEvidence(v) {
|
|
1026
|
+
const r = matureOptimizationEvidence.read(v, "optimizationEvidence");
|
|
1027
|
+
if (r.objective.measurementRevision !== r.goalRevision) fail2("optimizationEvidence.goalRevision", "goal snapshot mismatch");
|
|
1028
|
+
if (Date.parse(r.windowEnd) <= Date.parse(r.windowStart) || r.dataThrough !== null && Date.parse(r.dataThrough) > Date.parse(r.observedAt)) fail2("optimizationEvidence.window", "invalid observed window");
|
|
1029
|
+
const sites = r.channels.map((c) => c.site);
|
|
1030
|
+
scoped(r, r.projectId, sites, "optimizationEvidence");
|
|
1031
|
+
unique(r.channels.map((c) => c.channelId), "optimizationEvidence.channels");
|
|
1032
|
+
const currency2 = r.channels[0].currentAllocation.currency;
|
|
1033
|
+
for (const c of r.channels) {
|
|
1034
|
+
if (c.uncertaintySource === null) {
|
|
1035
|
+
if (c.uncertaintyBasisPoints !== null) fail2("optimizationEvidence.channels.uncertainty", "no bound without source witness");
|
|
1036
|
+
} else if (c.uncertaintySource.verifiedSpend.currency !== c.currentAllocation.currency || c.uncertaintyBasisPoints !== cpqlMissingOutcomeBound(c.uncertaintySource)) fail2("optimizationEvidence.channels.uncertainty", "source bound mismatch");
|
|
1037
|
+
if (c.allocationHorizon !== null && (["campaign_total", "lifetime"].includes(c.budgetMode) ? c.allocationHorizon.kind !== "finite_period" : c.allocationHorizon.kind !== "daily_average")) fail2("optimizationEvidence.channels.allocationHorizon", "unapproved budget period normalization");
|
|
1038
|
+
if (c.coveredNumerator === null || c.eligibleDenominator === null || c.eligibleDenominator === "0") {
|
|
1039
|
+
if (c.coverageBasisPoints !== null) fail2("optimizationEvidence.channels.coverage", "unknown denominator means unknown coverage");
|
|
1040
|
+
} else {
|
|
1041
|
+
const numerator = BigInt(c.coveredNumerator), denominator = BigInt(c.eligibleDenominator);
|
|
1042
|
+
if (numerator > denominator || c.coverageBasisPoints !== Number(numerator * 10000n / denominator)) fail2("optimizationEvidence.channels.coverage", "coverage ratio mismatch");
|
|
1043
|
+
}
|
|
1044
|
+
if (r.resolvedPolicy.sourceRef === null && c.policyRevision !== r.policyDigest) fail2("optimizationEvidence.channels.policyRevision", "inline policy digest mismatch");
|
|
1045
|
+
if (c.goalRevision !== r.goalRevision || c.countingUnit !== r.objective.countingUnit || c.attributionWindowDays !== r.objective.attributionWindowDays) fail2("optimizationEvidence.channels", "measurement semantics mismatch");
|
|
1046
|
+
if (c.currentAllocation.currency !== currency2) fail2("optimizationEvidence.channels", "currency mismatch");
|
|
1047
|
+
moneyToProviderUnits(c.currentAllocation, { currency: currency2, unitExponent: c.providerUnitExponent, maxUnits: c.providerMaxUnits });
|
|
1048
|
+
for (const observation of c.observations) {
|
|
1049
|
+
parseResonanceMetricObservation(observation);
|
|
1050
|
+
if (observation.campaignId !== r.campaignId || observation.site !== c.site) fail2("optimizationEvidence.channels.observations", "scope mismatch");
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return r;
|
|
1054
|
+
}
|
|
1055
|
+
var optimizationEvidenceRefFields = { id: uuid, revision, evidenceDigest: digest };
|
|
1056
|
+
var optimizationEvidenceRef = object2(optimizationEvidenceRefFields);
|
|
1057
|
+
var finiteOwnMeasurement = union2(
|
|
1058
|
+
object2({ state: literal2("unobserved"), observations: literal2(null), reasonCodes: list2(text2(80), 50) }),
|
|
1059
|
+
object2({ state: choice2("pending", "blocked"), observations: nullable2(list2(metric, 100)), reasonCodes: list2(text2(80), 50) }),
|
|
1060
|
+
object2({ state: literal2("mature"), observations: list2(metric, 100, 1), reasonCodes: list2(text2(80), 50) })
|
|
1061
|
+
);
|
|
1062
|
+
var finiteCandidateObservation = object2({
|
|
1063
|
+
channelId: uuid,
|
|
1064
|
+
site,
|
|
1065
|
+
accountBindingId: uuid,
|
|
1066
|
+
graphDigest: digest,
|
|
1067
|
+
manifestRef: preparedManifestRef,
|
|
1068
|
+
cap: positiveMoneyShape,
|
|
1069
|
+
flight: object2({ startAt: instant, endAt: instant, timeZone }),
|
|
1070
|
+
lifecycle: choice2("not_materialized", "paused", "active", "stopped", "unknown"),
|
|
1071
|
+
nativeStateEvidenceRef: nullable2(optimizationEvidenceRef),
|
|
1072
|
+
commitmentEvidenceRef: nullable2(object2({ ...optimizationEvidenceRefFields, commitmentOperationId: uuid })),
|
|
1073
|
+
activationsObserved: integer2(0, 1),
|
|
1074
|
+
trackingState: choice2("healthy", "blocked", "unknown"),
|
|
1075
|
+
coverageBasisPoints: nullable2(integer2(0, 1e4)),
|
|
1076
|
+
dedupeCheck: choice2("pass", "failed", "unknown", "not_applicable"),
|
|
1077
|
+
qualificationCheck: choice2("pass", "failed", "unknown", "not_applicable"),
|
|
1078
|
+
reasonCodes: list2(text2(80), 50),
|
|
1079
|
+
ownMeasurement: finiteOwnMeasurement
|
|
1080
|
+
});
|
|
1081
|
+
var finiteOptimizationEvidence = object2({
|
|
1082
|
+
id: uuid,
|
|
1083
|
+
revision,
|
|
1084
|
+
evidenceDigest: digest,
|
|
1085
|
+
projectId: uuid,
|
|
1086
|
+
campaignId: uuid,
|
|
1087
|
+
preparedPlanId: uuid,
|
|
1088
|
+
reviewDigest: digest,
|
|
1089
|
+
observedAt: instant,
|
|
1090
|
+
windowStart: instant,
|
|
1091
|
+
windowEnd: instant,
|
|
1092
|
+
dataThrough: nullable2(instant),
|
|
1093
|
+
lastAdjustmentAt: nullable2(instant),
|
|
1094
|
+
currentKillRevision: integer2(0),
|
|
1095
|
+
goalRevision: revision,
|
|
1096
|
+
objective,
|
|
1097
|
+
policyDigest: digest,
|
|
1098
|
+
resolvedPolicy: finiteMetaCandidatePolicy,
|
|
1099
|
+
trackingState: choice2("healthy", "blocked", "unknown"),
|
|
1100
|
+
coverageBasisPoints: nullable2(integer2(0, 1e4)),
|
|
1101
|
+
assignmentCheck: choice2("pass", "failed", "unknown", "not_applicable"),
|
|
1102
|
+
powerCheck: choice2("pass", "failed", "unknown", "not_applicable"),
|
|
1103
|
+
evidenceKind: literal2("finite_candidate_lifecycle_v1"),
|
|
1104
|
+
inventoryState: choice2("complete", "incomplete", "unknown"),
|
|
1105
|
+
candidates: list2(finiteCandidateObservation, 100, 1)
|
|
1106
|
+
});
|
|
1107
|
+
var optimizationEvidence = union2(matureOptimizationEvidence, finiteOptimizationEvidence);
|
|
1108
|
+
function parseResonanceOptimizationEvidence(v, preparedPlan) {
|
|
1109
|
+
const r = optimizationEvidence.read(v, "optimizationEvidence");
|
|
1110
|
+
if (preparedPlan !== void 0) checkOptimizationEvidencePlan(r, parseResonancePreparedPlan(preparedPlan));
|
|
1111
|
+
if (!("evidenceKind" in r)) return parseMatureOptimizationEvidence(r);
|
|
1112
|
+
const p = "optimizationEvidence", policy = parseResonanceOptimizationPolicy(r.resolvedPolicy);
|
|
1113
|
+
if (r.objective.measurementRevision !== r.goalRevision || r.objective.countingUnit !== "unique_qualified_contact" || canonical(policy.measurementGuard.sourceRef) !== canonical(r.objective.source) || canonical(policy.measurementGuard.qualificationRuleRef) !== canonical(r.objective.qualificationRuleRef) || policy.measurementGuard.maturityDelaySeconds !== r.objective.maturityDelaySeconds) fail2(p + ".objective", "finite policy must retain the original qualified objective");
|
|
1114
|
+
if (Date.parse(r.windowEnd) <= Date.parse(r.windowStart) || r.dataThrough !== null && Date.parse(r.dataThrough) > Date.parse(r.observedAt)) fail2(p + ".window", "invalid observed window");
|
|
1115
|
+
unique(r.candidates.map((c) => c.channelId), p + ".candidates");
|
|
1116
|
+
if (r.candidates.length !== policy.candidateChannelIds.length || r.candidates.some((c) => !policy.candidateChannelIds.includes(c.channelId))) fail2(p + ".candidates", "must cover exactly the original finite policy");
|
|
1117
|
+
scoped(r, r.projectId, r.candidates.map((c) => c.site), p);
|
|
1118
|
+
for (const c of r.candidates) {
|
|
1119
|
+
if (c.graphDigest !== c.manifestRef.digest) fail2(p + ".candidates.graphDigest", "must match the actual original manifest graph digest");
|
|
1120
|
+
if (c.cap.currency !== policy.totalExplorationCap.currency || BigInt(c.cap.amountMicros) === 0n) fail2(p + ".candidates.cap", "requires the original positive cap currency");
|
|
1121
|
+
if (Date.parse(c.flight.endAt) <= Date.parse(c.flight.startAt)) fail2(p + ".candidates.flight", "invalid original flight");
|
|
1122
|
+
const schedule = policy.selection.eligibilitySchedule.find((s) => s.channelId === c.channelId);
|
|
1123
|
+
if (Date.parse(schedule.notBefore) < Date.parse(c.flight.startAt) || Date.parse(schedule.notAfter) > Date.parse(c.flight.endAt)) fail2(p + ".candidates.flight", "selection must stay inside original flight");
|
|
1124
|
+
if (c.lifecycle === "not_materialized" && (c.commitmentEvidenceRef !== null || c.activationsObserved !== 0)) fail2(p + ".candidates", "absence cannot borrow a commitment or prior activation");
|
|
1125
|
+
if (["active", "stopped"].includes(c.lifecycle) && c.activationsObserved !== 1) fail2(p + ".candidates.activationsObserved", "active/stopped state requires its original activation");
|
|
1126
|
+
if (c.lifecycle === "not_materialized" && c.ownMeasurement.observations !== null || c.activationsObserved === 0 && c.ownMeasurement.state === "mature") fail2(p + ".candidates.ownMeasurement", "cold/absent candidates cannot borrow mature observations");
|
|
1127
|
+
if (c.ownMeasurement.state === "mature" && c.ownMeasurement.observations.some((o) => o.state !== "observed")) fail2(p + ".candidates.ownMeasurement", "mature observations must be observed");
|
|
1128
|
+
for (const o of c.ownMeasurement.observations ?? []) {
|
|
1129
|
+
parseResonanceMetricObservation(o);
|
|
1130
|
+
if (o.projectId !== r.projectId || o.campaignId !== r.campaignId || o.site !== c.site) fail2(p + ".candidates.ownMeasurement", "foreign candidate measurement scope");
|
|
1131
|
+
if (Date.parse(o.asOf) > Date.parse(r.observedAt) || Date.parse(o.windowStart) < Date.parse(r.windowStart) || Date.parse(o.windowEnd) > Date.parse(r.windowEnd) || o.dataThrough !== null && Date.parse(o.dataThrough) > Date.parse(r.observedAt)) fail2(p + ".candidates.ownMeasurement", "outside the actual candidate evidence window");
|
|
1132
|
+
const aggregate = o.source === "provider" && ["spend", "clicks", "impressions"].includes(o.name);
|
|
1133
|
+
if (aggregate ? o.countingUnit !== "provider_aggregate" || o.attributionModel !== "provider_reported" || o.goalRevision !== null && o.goalRevision !== r.goalRevision : o.source !== "first_party" || o.goalRevision !== r.goalRevision || o.countingUnit !== r.objective.countingUnit || o.attributionModel !== policy.measurementGuard.attributionModel) fail2(p + ".candidates.ownMeasurement", "preserve actual provider aggregate versus first-party cohort provenance");
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return r;
|
|
1137
|
+
}
|
|
1138
|
+
function checkOptimizationEvidencePlan(e, plan) {
|
|
1139
|
+
const p = "optimizationEvidence";
|
|
1140
|
+
if (e.projectId !== plan.projectId || e.campaignId !== plan.campaignId || e.preparedPlanId !== plan.id || e.reviewDigest !== plan.reviewDigest || canonical(e.objective) !== canonical(plan.definition.objective) || canonical(e.resolvedPolicy) !== canonical(plan.resolvedEvidencePolicy)) fail2(p + ".preparedPlanId", "requires the exact original prepared scope and policy");
|
|
1141
|
+
if (!("evidenceKind" in e)) return;
|
|
1142
|
+
for (const observed of e.candidates) {
|
|
1143
|
+
const original = plan.definition.channels.find((c) => c.id === observed.channelId), preview2 = plan.channelPreviews.find((c) => c.channelId === observed.channelId);
|
|
1144
|
+
if (!original || original.kind !== "meta_retargeting" || !preview2 || observed.site !== original.site || observed.accountBindingId !== original.accountBindingId || canonical(observed.cap) !== canonical(original.allocation) || canonical(observed.manifestRef) !== canonical(preview2.manifestRef)) fail2(p + ".candidates", "candidate projection differs from original channel and manifest");
|
|
1145
|
+
if (canonical(observed.flight) !== canonical({ startAt: plan.definition.budget.startAt, endAt: plan.definition.budget.endAt, timeZone: plan.definition.budget.timeZone })) fail2(p + ".candidates.flight", "must retain the original budget flight");
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
var matureOptimizationDecision = object2({
|
|
1149
|
+
id: uuid,
|
|
1150
|
+
revision,
|
|
1151
|
+
projectId: uuid,
|
|
1152
|
+
campaignId: uuid,
|
|
1153
|
+
preparedPlanId: uuid,
|
|
1154
|
+
reviewDigest: digest,
|
|
1155
|
+
evidenceRef: optimizationEvidenceRef,
|
|
1156
|
+
policyDigest: digest,
|
|
1157
|
+
requestDigest: digest,
|
|
1158
|
+
expiresAt: instant,
|
|
1159
|
+
algorithmRevision: revision,
|
|
1160
|
+
sourceRefs: list2(revision, 200),
|
|
1161
|
+
state: choice2("actionable", "noop", "blocked"),
|
|
1162
|
+
decisionKind: choice2("observational_allocation", "experimental_promotion"),
|
|
1163
|
+
executionOrder: literal2("reduce_then_increase"),
|
|
1164
|
+
reasonCodes: list2(text2(80), 50),
|
|
1165
|
+
adjustments: list2(object2({ channelId: uuid, site, budgetMode: choice2("campaign_total", "average_daily", "lifetime", "daily"), providerUnitExponent: refine2(integer2(0, 6), (v, p) => {
|
|
1166
|
+
if (![0, 2, 3, 6].includes(v)) fail2(p, "unsupported provider exponent");
|
|
1167
|
+
}), providerMaxUnits: decimal, before: money, after: money, remoteSnapshotDigest: digest }), 100),
|
|
1168
|
+
createdAt: instant
|
|
1169
|
+
});
|
|
1170
|
+
var finiteCandidateActionFields = { channelId: uuid, graphDigest: digest, nativeStateEvidenceRef: optimizationEvidenceRef };
|
|
1171
|
+
var finiteCandidateActionBody = union2(
|
|
1172
|
+
object2({ action: literal2("materialize_and_activate"), ...finiteCandidateActionFields }),
|
|
1173
|
+
object2({ action: literal2("activate_held"), ...finiteCandidateActionFields, commitmentOperationId: uuid }),
|
|
1174
|
+
object2({ action: literal2("pause_candidate"), ...finiteCandidateActionFields })
|
|
1175
|
+
);
|
|
1176
|
+
var finiteDecisionFields = {
|
|
1177
|
+
id: uuid,
|
|
1178
|
+
revision,
|
|
1179
|
+
projectId: uuid,
|
|
1180
|
+
campaignId: uuid,
|
|
1181
|
+
preparedPlanId: uuid,
|
|
1182
|
+
reviewDigest: digest,
|
|
1183
|
+
evidenceRef: optimizationEvidenceRef,
|
|
1184
|
+
policyDigest: digest,
|
|
1185
|
+
requestDigest: digest,
|
|
1186
|
+
expiresAt: instant,
|
|
1187
|
+
createdAt: instant,
|
|
1188
|
+
algorithmRevision: literal2("finite_meta_candidates_v1"),
|
|
1189
|
+
sourceRefs: list2(revision, 200),
|
|
1190
|
+
decisionKind: literal2("preapproved_candidate_operation"),
|
|
1191
|
+
reasonCodes: list2(text2(80), 50, 1)
|
|
1192
|
+
};
|
|
1193
|
+
var finiteOptimizationDecision = union2(
|
|
1194
|
+
object2({ ...finiteDecisionFields, state: literal2("actionable"), candidateActions: list2(finiteCandidateActionBody, 1, 1) }),
|
|
1195
|
+
object2({ ...finiteDecisionFields, state: choice2("noop", "blocked"), candidateActions: list2(finiteCandidateActionBody, 0) })
|
|
1196
|
+
);
|
|
1197
|
+
var optimizationDecision = union2(matureOptimizationDecision, finiteOptimizationDecision);
|
|
1198
|
+
function parseResonanceOptimizationDecision(v, evidence, preparedPlan) {
|
|
1199
|
+
const r = optimizationDecision.read(v, "optimizationDecision");
|
|
1200
|
+
if (Date.parse(r.expiresAt) <= Date.parse(r.createdAt)) fail2("optimizationDecision.expiresAt", "must follow decision");
|
|
1201
|
+
if (preparedPlan !== void 0 && evidence === void 0) fail2("optimizationDecision.evidenceRef", "plan binding requires actual evidence");
|
|
1202
|
+
if (evidence !== void 0) checkOptimizationDecisionEvidence(r, parseResonanceOptimizationEvidence(evidence, preparedPlan));
|
|
1203
|
+
if (r.decisionKind === "preapproved_candidate_operation") return r;
|
|
1204
|
+
unique(r.adjustments.map((c) => c.channelId), "optimizationDecision.adjustments");
|
|
1205
|
+
scoped(r, r.projectId, r.adjustments.map((c) => c.site), "optimizationDecision");
|
|
1206
|
+
if (r.state !== "actionable" && r.adjustments.length) fail2("optimizationDecision.adjustments", "no effects for blocked/noop decision");
|
|
1207
|
+
if (r.state === "actionable" && !r.adjustments.length) fail2("optimizationDecision.adjustments", "action requires exact adjustments");
|
|
1208
|
+
for (const a of r.adjustments) {
|
|
1209
|
+
compareMoney(a.before, a.after);
|
|
1210
|
+
moneyToProviderUnits(a.before, { currency: a.before.currency, unitExponent: a.providerUnitExponent, maxUnits: a.providerMaxUnits });
|
|
1211
|
+
moneyToProviderUnits(a.after, { currency: a.after.currency, unitExponent: a.providerUnitExponent, maxUnits: a.providerMaxUnits });
|
|
1212
|
+
if (r.adjustments[0].before.currency !== a.before.currency) fail2("optimizationDecision.adjustments", "currency mismatch");
|
|
1213
|
+
}
|
|
1214
|
+
return r;
|
|
1215
|
+
}
|
|
1216
|
+
function checkOptimizationDecisionEvidence(d, e) {
|
|
1217
|
+
const p = "optimizationDecision";
|
|
1218
|
+
if (d.projectId !== e.projectId || d.campaignId !== e.campaignId || d.preparedPlanId !== e.preparedPlanId || d.reviewDigest !== e.reviewDigest || d.policyDigest !== e.policyDigest || canonical(d.evidenceRef) !== canonical({ id: e.id, revision: e.revision, evidenceDigest: e.evidenceDigest })) fail2(p + ".evidenceRef", "must bind the exact original evidence and review");
|
|
1219
|
+
if (Date.parse(d.createdAt) < Date.parse(e.observedAt)) fail2(p + ".createdAt", "precedes actual evidence");
|
|
1220
|
+
if (d.decisionKind !== "preapproved_candidate_operation") {
|
|
1221
|
+
if ("evidenceKind" in e) fail2(p + ".decisionKind", "mature decisions cannot borrow finite lifecycle evidence");
|
|
1222
|
+
if (d.algorithmRevision === "finite_meta_candidates_v1") fail2(p + ".algorithmRevision", "mature decisions cannot use the finite algorithm");
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
if (!("evidenceKind" in e)) fail2(p + ".decisionKind", "finite actions require finite lifecycle evidence");
|
|
1226
|
+
const finite = e, policy = finite.resolvedPolicy;
|
|
1227
|
+
if (Date.parse(d.createdAt) - Date.parse(finite.observedAt) > policy.measurementGuard.maxEvidenceAgeSeconds * 1e3 || Date.parse(d.expiresAt) > Date.parse(policy.expiresAt)) fail2(p + ".expiresAt", "outside current policy/evidence freshness");
|
|
1228
|
+
for (const a of d.candidateActions) {
|
|
1229
|
+
const c = finite.candidates.find((c2) => c2.channelId === a.channelId) ?? fail2(p + ".candidateActions", "unknown original candidate");
|
|
1230
|
+
if (!policy.allowedActions.includes(a.action) || c.graphDigest !== a.graphDigest || c.nativeStateEvidenceRef === null || canonical(c.nativeStateEvidenceRef) !== canonical(a.nativeStateEvidenceRef)) fail2(p + ".candidateActions", "outside original candidate action/state evidence");
|
|
1231
|
+
if (c.lifecycle === "unknown") fail2(p + ".candidateActions", "unknown state requires reconciliation");
|
|
1232
|
+
if (a.action === "pause_candidate") {
|
|
1233
|
+
if (c.lifecycle !== "active") fail2(p + ".candidateActions", "pause requires the actual active original candidate");
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
if (finite.inventoryState !== "complete" || finite.candidates.some((c2) => c2.lifecycle === "unknown") || c.activationsObserved !== 0 || finite.candidates.filter((c2) => c2.lifecycle === "active").length >= policy.maxConcurrentCandidates) fail2(p + ".candidateActions", "unknown inventory, prior activation or concurrency blocks activation");
|
|
1237
|
+
const firstEligible = policy.selection.candidateOrder.find((id) => {
|
|
1238
|
+
const candidate = finite.candidates.find((c2) => c2.channelId === id), window = policy.selection.eligibilitySchedule.find((s) => s.channelId === id);
|
|
1239
|
+
const permittedLifecycle = candidate.lifecycle === "not_materialized" && policy.allowedActions.includes("materialize_and_activate") && candidate.nativeStateEvidenceRef !== null || candidate.lifecycle === "paused" && policy.allowedActions.includes("activate_held") && candidate.commitmentEvidenceRef !== null && candidate.nativeStateEvidenceRef !== null;
|
|
1240
|
+
return permittedLifecycle && candidate.activationsObserved === 0 && Date.parse(d.createdAt) >= Date.parse(window.notBefore) && Date.parse(d.createdAt) < Date.parse(window.notAfter);
|
|
1241
|
+
});
|
|
1242
|
+
if (firstEligible !== c.channelId) fail2(p + ".candidateActions", "must retain the original fixed candidate order");
|
|
1243
|
+
const schedule = policy.selection.eligibilitySchedule.find((s) => s.channelId === c.channelId);
|
|
1244
|
+
if (Date.parse(d.createdAt) < Date.parse(schedule.notBefore) || Date.parse(d.createdAt) >= Date.parse(schedule.notAfter)) fail2(p + ".candidateActions", "outside original selection window");
|
|
1245
|
+
if (a.action === "materialize_and_activate" && (c.lifecycle !== "not_materialized" || c.commitmentEvidenceRef !== null)) fail2(p + ".candidateActions", "new create requires original absence, not a borrowed hold");
|
|
1246
|
+
if (a.action === "activate_held" && (c.lifecycle !== "paused" || !c.commitmentEvidenceRef || c.commitmentEvidenceRef.commitmentOperationId !== a.commitmentOperationId)) fail2(p + ".candidateActions", "held activation requires the exact original commitment reference");
|
|
1247
|
+
if (compareMoney(c.cap, policy.totalExplorationCap) > 0) fail2(p + ".candidateActions", "original full cap exceeds reviewed exploration ceiling");
|
|
1248
|
+
if (finite.trackingState !== "healthy" || c.trackingState !== "healthy" || c.coverageBasisPoints === null || c.coverageBasisPoints < policy.measurementGuard.minimumCoverageBasisPoints) fail2(p + ".candidateActions", "current required tracking coverage unavailable");
|
|
1249
|
+
for (const check of policy.measurementGuard.requiredChecks) {
|
|
1250
|
+
if (check === "dedupe" && c.dedupeCheck !== "pass" || check === "qualification" && c.qualificationCheck !== "pass") fail2(p + ".candidateActions", "required source check unavailable");
|
|
1251
|
+
if (["uncertainty", "assignment", "power"].includes(check)) fail2(p + ".candidateActions", "fixed exploration has no adopted measured/assignment decision policy");
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
var RESONANCE_OPTIMIZATION_EVIDENCE_JSON_SCHEMA = optimizationEvidence.schema;
|
|
1256
|
+
var RESONANCE_OPTIMIZATION_DECISION_JSON_SCHEMA = optimizationDecision.schema;
|
|
1257
|
+
var publicDestination = union2(
|
|
1258
|
+
object2({ provider: literal2("google_ads"), id: uuid, tagId: text2(40, /^AW-[0-9]+$/) }),
|
|
1259
|
+
object2({ provider: literal2("meta_ads"), id: uuid, pixelId: text2(40, /^[0-9]+$/) })
|
|
1260
|
+
);
|
|
1261
|
+
var interestResource = object2({ ...scope, type: choice2("page", "article", "product", "listing"), id: text2(160, /^[a-zA-Z0-9._:-]+$/), revision });
|
|
1262
|
+
var measurementConfig = object2({
|
|
1263
|
+
schemaVersion: literal2(1),
|
|
1264
|
+
...scope,
|
|
1265
|
+
configRevision: revision,
|
|
1266
|
+
policyRevision: revision,
|
|
1267
|
+
expiresAt: instant,
|
|
1268
|
+
enabled: bool,
|
|
1269
|
+
consentPolicy: object2({ mode: literal2("basic"), requiredPurposes: list2(purpose, 4, 1) }),
|
|
1270
|
+
destinations: list2(publicDestination, 10),
|
|
1271
|
+
resourceRules: list2(object2({ id: uuid, revision, site, resource: interestResource, path, interestKey: text2(100, /^[a-zA-Z0-9._:-]+$/) }), 500)
|
|
1272
|
+
});
|
|
1273
|
+
function parseResonanceMeasurementConfig(v) {
|
|
1274
|
+
const r = measurementConfig.read(v, "measurementConfig");
|
|
1275
|
+
scoped(r, r.projectId, [r.site], "measurementConfig");
|
|
1276
|
+
unique(r.destinations.map((d) => d.id), "measurementConfig.destinations");
|
|
1277
|
+
unique(r.consentPolicy.requiredPurposes, "measurementConfig.consentPolicy.requiredPurposes");
|
|
1278
|
+
unique(r.resourceRules.map((rule) => rule.id), "measurementConfig.resourceRules");
|
|
1279
|
+
if (r.destinations.length && ["ad_storage", "ad_user_data", "ad_personalization"].some((p) => !r.consentPolicy.requiredPurposes.includes(p))) fail2("measurementConfig.consentPolicy", "provider minimum purposes are mandatory");
|
|
1280
|
+
return r;
|
|
1281
|
+
}
|
|
1282
|
+
function resonanceMeasurementConfigDigestInput(value) {
|
|
1283
|
+
const { expiresAt: _expiresAt, ...semantic } = parseResonanceMeasurementConfig(value);
|
|
1284
|
+
return canonical(semantic);
|
|
1285
|
+
}
|
|
1286
|
+
var clickIdentifierPattern = /^[^\s\u0000-\u001f\u007f-\u009f]+(?![\s\S])/;
|
|
1287
|
+
var clickIdentifier = field2({ type: "string", minLength: 1, maxLength: 1024, pattern: clickIdentifierPattern.source }, (v, p) => typeof v === "string" && v.length <= 2048 && [...v].length <= 1024 && clickIdentifierPattern.test(v) ? v : fail2(p, "invalid click identifier"));
|
|
1288
|
+
var clickIdentifiersShape = union2(object2({ gclid: clickIdentifier }), object2({ gbraid: clickIdentifier }), object2({ wbraid: clickIdentifier }));
|
|
1289
|
+
var clickIdentifiers = field2(clickIdentifiersShape.schema, clickIdentifiersShape.read);
|
|
1290
|
+
var parseResonanceClickIdentifiers = (v) => clickIdentifiers.read(v, "clickIdentifiers");
|
|
1291
|
+
var RESONANCE_CLICK_IDENTIFIERS_JSON_SCHEMA = clickIdentifiers.schema;
|
|
1292
|
+
var observedPathControl = /[\s\u0000-\u001f\u007f-\u009f]/;
|
|
1293
|
+
var observedPagePath = field2({ ...path.schema, allOf: [{ not: { pattern: observedPathControl.source } }] }, (v, p) => {
|
|
1294
|
+
const result = path.read(v, p);
|
|
1295
|
+
if (observedPathControl.test(result)) fail2(p, "invalid observed path");
|
|
1296
|
+
return result;
|
|
1297
|
+
});
|
|
1298
|
+
var metaCookieShape = object2({ source: literal2(RESONANCE_WEBSITE_VISIT_PROFILE.source), fbp: optional2(clickIdentifier), fbc: optional2(clickIdentifier) });
|
|
1299
|
+
var metaCookieObservation = field2({ ...metaCookieShape.schema, anyOf: [{ required: ["fbp"] }, { required: ["fbc"] }] }, (v, p) => {
|
|
1300
|
+
const result = metaCookieShape.read(v, p);
|
|
1301
|
+
if (!result.fbp && !result.fbc) fail2(p, "requires an actual reported cookie value");
|
|
1302
|
+
return result;
|
|
1303
|
+
});
|
|
1304
|
+
var occurrenceShape = object2({
|
|
1305
|
+
schemaVersion: literal2(1),
|
|
1306
|
+
...scope,
|
|
1307
|
+
configRevision: revision,
|
|
1308
|
+
eventId: uuid,
|
|
1309
|
+
navigationId: uuid,
|
|
1310
|
+
occurredAt: instant,
|
|
1311
|
+
kind: choice2("page_view", "content_interest", "experiment_exposure"),
|
|
1312
|
+
sourceMarker: optional2(sourceMarker),
|
|
1313
|
+
resourceRef: nullable2(interestResource),
|
|
1314
|
+
campaignPlanId: nullable2(uuid),
|
|
1315
|
+
experiment: nullable2(exposure),
|
|
1316
|
+
clickIdentifiers: optional2(clickIdentifiers),
|
|
1317
|
+
pagePath: optional2(observedPagePath),
|
|
1318
|
+
metaCookieObservation: optional2(metaCookieObservation),
|
|
1319
|
+
consentReceiptRefs: list2(object2({ purpose, receiptId: uuid, consentRevision: integer2(1) }), 4)
|
|
1320
|
+
});
|
|
1321
|
+
var occurrence = field2({ ...occurrenceShape.schema, allOf: [
|
|
1322
|
+
{ if: { required: ["clickIdentifiers"] }, then: { properties: { kind: { const: "page_view" } } } },
|
|
1323
|
+
{ if: { required: ["pagePath"] }, then: { properties: { kind: { enum: ["page_view", "experiment_exposure"] } } } },
|
|
1324
|
+
{ if: { required: ["metaCookieObservation"] }, then: { required: ["pagePath"], properties: { kind: { const: "page_view" }, resourceRef: interestResource.schema } } }
|
|
1325
|
+
] }, (v, p) => {
|
|
1326
|
+
const r = occurrenceShape.read(v, p);
|
|
1327
|
+
if (r.clickIdentifiers && r.kind !== "page_view") fail2(p + ".clickIdentifiers", "requires page_view");
|
|
1328
|
+
if (r.pagePath !== void 0 && r.kind !== "page_view" && r.kind !== "experiment_exposure") fail2(p + ".pagePath", "requires page_view or experiment_exposure");
|
|
1329
|
+
if (r.metaCookieObservation && (r.kind !== "page_view" || r.pagePath === void 0 || !r.resourceRef)) fail2(p + ".metaCookieObservation", "requires the same page_view path and resource");
|
|
1330
|
+
return r;
|
|
1331
|
+
});
|
|
1332
|
+
function parseResonanceMeasurementOccurrence(v) {
|
|
1333
|
+
const r = occurrence.read(v, "occurrence");
|
|
1334
|
+
scoped(r, r.projectId, [r.site], "occurrence");
|
|
1335
|
+
unique(r.consentReceiptRefs.map((c) => c.purpose), "occurrence.consentReceiptRefs");
|
|
1336
|
+
if (r.kind === "content_interest" && !r.resourceRef) fail2("occurrence.resourceRef", "content interest requires resource");
|
|
1337
|
+
if (r.kind === "experiment_exposure" && !r.experiment) fail2("occurrence.experiment", "exposure requires assignment context");
|
|
1338
|
+
return r;
|
|
1339
|
+
}
|
|
1340
|
+
var consentUpdate = object2({ schemaVersion: literal2(1), ...scope, configRevision: revision, policyRevision: revision, expectedConsentRevision: integer2(0), occurredAt: instant, idempotencyKey: operationKey, source: choice2("cmp", "user_preference"), purposes: list2(object2({ purpose, state: choice2("unknown", "granted", "denied", "withdrawn") }), 4, 1) });
|
|
1341
|
+
function parseResonanceConsentUpdate(v) {
|
|
1342
|
+
const r = consentUpdate.read(v, "consentUpdate");
|
|
1343
|
+
unique(r.purposes.map((p) => p.purpose), "consentUpdate.purposes");
|
|
1344
|
+
return r;
|
|
1345
|
+
}
|
|
1346
|
+
var RESONANCE_MEASUREMENT_CONFIG_JSON_SCHEMA = measurementConfig.schema;
|
|
1347
|
+
var RESONANCE_MEASUREMENT_OCCURRENCE_JSON_SCHEMA = occurrence.schema;
|
|
1348
|
+
var RESONANCE_CONSENT_UPDATE_JSON_SCHEMA = consentUpdate.schema;
|
|
1349
|
+
function resonanceOptimizationEvidenceDigestInput(v) {
|
|
1350
|
+
const { evidenceDigest: _digest, ...body } = parseResonanceOptimizationEvidence(v);
|
|
1351
|
+
return canonical(body);
|
|
1352
|
+
}
|
|
1353
|
+
function resonanceOptimizationDecisionDigestInput(v) {
|
|
1354
|
+
const { requestDigest: _digest, ...body } = parseResonanceOptimizationDecision(v);
|
|
1355
|
+
return canonical(body);
|
|
1356
|
+
}
|
|
1357
|
+
var consentResult = object2({ subjectProof: text2(200, /^[a-zA-Z0-9_-]{32,}$/), expiresAt: instant, currentConsentRevision: integer2(1), receipts: list2(consent, 4, 4) });
|
|
1358
|
+
function parseResonanceConsentResponse(v) {
|
|
1359
|
+
const r = consentResult.read(v, "consentResponse"), first = r.receipts[0];
|
|
1360
|
+
if (r.receipts.some((c) => c.consentRevision !== r.currentConsentRevision)) fail2("consentResponse.receipts", "global consent revision mismatch");
|
|
1361
|
+
unique(r.receipts.map((c) => c.purpose), "consentResult.receipts");
|
|
1362
|
+
for (const c of r.receipts) if (c.projectId !== first.projectId || c.site !== first.site || c.configRevision !== first.configRevision || c.policyRevision !== first.policyRevision || c.visitorNamespace !== first.visitorNamespace) fail2("consentResult.receipts", "mixed consent subjects or scope");
|
|
1363
|
+
return r;
|
|
1364
|
+
}
|
|
1365
|
+
var occurrenceResult = object2({ eventId: uuid, accepted: literal2(true), replayed: bool, exposureId: nullable2(uuid) });
|
|
1366
|
+
var parseResonanceOccurrenceResponse = (v) => occurrenceResult.read(v, "occurrenceResult");
|
|
1367
|
+
var RESONANCE_CONSENT_RESPONSE_JSON_SCHEMA = consentResult.schema;
|
|
1368
|
+
var RESONANCE_OCCURRENCE_RESPONSE_JSON_SCHEMA = occurrenceResult.schema;
|
|
1369
|
+
var resonanceOptimizationPolicyDigestInput = (v) => canonical(parseResonanceOptimizationPolicy(v));
|
|
1370
|
+
var subjectRenew = object2({ schemaVersion: literal2(1), ...scope, expectedConsentRevision: integer2(1), configRevision: revision, policyRevision: revision });
|
|
1371
|
+
var parseResonanceSubjectRenewCommand = (v) => subjectRenew.read(v, "subjectRenew");
|
|
1372
|
+
var RESONANCE_SUBJECT_RENEW_COMMAND_JSON_SCHEMA = subjectRenew.schema;
|
|
1373
|
+
var parseResonanceResourceRef = (v) => resource.read(v, "resourceRef");
|
|
1374
|
+
var parseResonanceAudienceRuleRef = (v) => audience.read(v, "audienceRuleRef");
|
|
1375
|
+
var parseResonanceExperimentRef = (v) => experiment.read(v, "experimentRef");
|
|
1376
|
+
var RESONANCE_RESOURCE_REF_JSON_SCHEMA = resource.schema;
|
|
1377
|
+
var RESONANCE_AUDIENCE_RULE_REF_JSON_SCHEMA = audience.schema;
|
|
1378
|
+
var RESONANCE_EXPERIMENT_REF_JSON_SCHEMA = experiment.schema;
|
|
1379
|
+
var audienceDraft = object2({
|
|
1380
|
+
schemaVersion: literal2(1),
|
|
1381
|
+
expectedRevision: optional2(integer2(1)),
|
|
1382
|
+
idempotencyKey: operationKey,
|
|
1383
|
+
definition: object2({ name: text2(160), site, accountBindingId: uuid, resourceRuleId: uuid, excludedResourceRuleIds: list2(uuid, 50), retentionDays: integer2(1, 540), policyRevision: revision, currentConsentPolicyRevision: revision })
|
|
1384
|
+
});
|
|
1385
|
+
function parseResonanceAudienceDraftSaveCommand(v) {
|
|
1386
|
+
const r = audienceDraft.read(v, "audienceDraftSave");
|
|
1387
|
+
unique(r.definition.excludedResourceRuleIds, "audienceDraftSave.definition.excludedResourceRuleIds");
|
|
1388
|
+
return r;
|
|
1389
|
+
}
|
|
1390
|
+
var RESONANCE_AUDIENCE_DRAFT_SAVE_COMMAND_JSON_SCHEMA = audienceDraft.schema;
|
|
1391
|
+
var managedExperimentBase = { expectedRevision: integer2(1), idempotencyKey: operationKey };
|
|
1392
|
+
var experimentCreateField = field2(SIGNAL_EXPERIMENT_CREATE_JSON_SCHEMA, (v, p) => {
|
|
1393
|
+
try {
|
|
1394
|
+
return parseSignalExperimentCreate(v);
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
return fail2(p, error instanceof Error ? error.message : "invalid experiment");
|
|
1397
|
+
}
|
|
1398
|
+
});
|
|
1399
|
+
var experimentUpdateField = field2(SIGNAL_EXPERIMENT_UPDATE_JSON_SCHEMA, (v, p) => {
|
|
1400
|
+
try {
|
|
1401
|
+
return parseSignalExperimentUpdate(v);
|
|
1402
|
+
} catch (error) {
|
|
1403
|
+
return fail2(p, error instanceof Error ? error.message : "invalid experiment");
|
|
1404
|
+
}
|
|
1405
|
+
});
|
|
1406
|
+
var managedExperimentCreate = object2({ ...managedExperimentBase, experiment: experimentCreateField });
|
|
1407
|
+
var managedExperimentUpdate = object2({ ...managedExperimentBase, experiment: experimentUpdateField });
|
|
1408
|
+
var managedExperimentLifecycle = object2(managedExperimentBase);
|
|
1409
|
+
var managedExperimentComplete = object2({ ...managedExperimentBase, winnerVariant: text2(80) });
|
|
1410
|
+
var managedExperimentLink = object2({ ...managedExperimentBase, experimentId: uuid });
|
|
1411
|
+
var parseResonanceExperimentCreateCommand = (v) => managedExperimentCreate.read(v, "experimentCreateCommand");
|
|
1412
|
+
var parseResonanceExperimentUpdateCommand = (v) => managedExperimentUpdate.read(v, "experimentUpdateCommand");
|
|
1413
|
+
var parseResonanceExperimentLifecycleCommand = (v) => managedExperimentLifecycle.read(v, "experimentLifecycleCommand");
|
|
1414
|
+
var parseResonanceExperimentCompleteCommand = (v) => managedExperimentComplete.read(v, "experimentCompleteCommand");
|
|
1415
|
+
var parseResonanceExperimentLinkCommand = (v) => managedExperimentLink.read(v, "experimentLinkCommand");
|
|
1416
|
+
var RESONANCE_EXPERIMENT_CREATE_COMMAND_JSON_SCHEMA = managedExperimentCreate.schema;
|
|
1417
|
+
var RESONANCE_EXPERIMENT_UPDATE_COMMAND_JSON_SCHEMA = managedExperimentUpdate.schema;
|
|
1418
|
+
var RESONANCE_EXPERIMENT_LIFECYCLE_COMMAND_JSON_SCHEMA = managedExperimentLifecycle.schema;
|
|
1419
|
+
var RESONANCE_EXPERIMENT_COMPLETE_COMMAND_JSON_SCHEMA = managedExperimentComplete.schema;
|
|
1420
|
+
var RESONANCE_EXPERIMENT_LINK_COMMAND_JSON_SCHEMA = managedExperimentLink.schema;
|
|
1421
|
+
var nativePageRevision = field2({ ...instant.schema, pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z(?![\\s\\S])" }, (v, p) => {
|
|
1422
|
+
const result = instant.read(v, p);
|
|
1423
|
+
if (new Date(result).toISOString() !== result) fail2(p, "requires canonical UTC milliseconds");
|
|
1424
|
+
return result;
|
|
1425
|
+
});
|
|
1426
|
+
var positiveRevision = text2(128, /^[1-9][0-9]*(?![\s\S])/);
|
|
1427
|
+
var friendlyName = field2({ type: "string", minLength: 0, maxLength: 200 }, (v, p) => typeof v === "string" && [...v].length <= 200 ? v : fail2(p, "invalid friendly name"));
|
|
1428
|
+
var setupConsentPolicyShape = object2({ mode: literal2("basic"), requiredPurposes: uniqueList(purpose, 4, 3) });
|
|
1429
|
+
var setupConsentPolicy = field2({ ...setupConsentPolicyShape.schema, allOf: [{ properties: { requiredPurposes: { allOf: ["ad_storage", "ad_user_data", "ad_personalization"].map((value) => ({ contains: { const: value } })) } } }] }, (input, p) => {
|
|
1430
|
+
const v = setupConsentPolicyShape.read(input, p);
|
|
1431
|
+
for (const required of ["ad_storage", "ad_user_data", "ad_personalization"]) if (!v.requiredPurposes.includes(required)) fail2(p, "all advertising purpose requirements are mandatory");
|
|
1432
|
+
return v;
|
|
1433
|
+
});
|
|
1434
|
+
var measurementSave = refine2(object2({
|
|
1435
|
+
schemaVersion: literal2(1),
|
|
1436
|
+
site,
|
|
1437
|
+
expectedCampaignRevision: integer2(1),
|
|
1438
|
+
expectedConfigurationRevision: integer2(0),
|
|
1439
|
+
idempotencyKey: operationKey,
|
|
1440
|
+
enabled: bool,
|
|
1441
|
+
consentPolicy: setupConsentPolicy,
|
|
1442
|
+
pages: list2(object2({ pageId: uuid, observedRevision: nativePageRevision, observedDigest: digest }), 500, 1)
|
|
1443
|
+
}), (v, p) => unique(v.pages.map((page2) => page2.pageId), p + ".pages"));
|
|
1444
|
+
var parseResonanceMeasurementConfigurationSaveCommand = (v) => measurementSave.read(v, "measurementConfigurationSave");
|
|
1445
|
+
var resonanceMeasurementConfigurationSaveDigestInput = (v) => canonical(parseResonanceMeasurementConfigurationSaveCommand(v));
|
|
1446
|
+
var measurementPage = object2({ pageId: uuid, label: friendlyName, path: observedPagePath, pageRevision: nativePageRevision, evidenceDigest: digest, resourceRuleId: uuid, resourceRuleRevision: digest });
|
|
1447
|
+
var measurementSavedFields = { configurationRevision: integer2(1), configurationDigest: digest, configuration: measurementConfig, saveOperationId: uuid, pages: list2(measurementPage, 500, 1) };
|
|
1448
|
+
object2(measurementSavedFields);
|
|
1449
|
+
function checkMeasurementSaved(v, p) {
|
|
1450
|
+
const config = parseResonanceMeasurementConfig(v.configuration);
|
|
1451
|
+
if (config.projectId !== v.projectId || config.site !== v.site) fail2(p + ".configuration", "configuration scope mismatch");
|
|
1452
|
+
positiveRevision.read(config.configRevision, p + ".configuration.configRevision");
|
|
1453
|
+
positiveRevision.read(config.policyRevision, p + ".configuration.policyRevision");
|
|
1454
|
+
setupConsentPolicy.read(config.consentPolicy, p + ".configuration.consentPolicy");
|
|
1455
|
+
unique(v.pages.map((page2) => page2.pageId), p + ".pages");
|
|
1456
|
+
if (v.pages.length !== config.resourceRules.length) fail2(p + ".pages", "must cover the exact native page rules");
|
|
1457
|
+
for (const page2 of v.pages) {
|
|
1458
|
+
const rule = config.resourceRules.find((rule2) => rule2.id === page2.resourceRuleId);
|
|
1459
|
+
if (page2.resourceRuleId !== page2.pageId || page2.resourceRuleRevision !== page2.evidenceDigest || !rule || rule.revision !== page2.evidenceDigest || rule.path !== page2.path || rule.interestKey !== page2.pageId || rule.resource.type !== "page" || rule.resource.id !== page2.pageId || rule.resource.revision !== page2.pageRevision) fail2(p + ".pages", "native page/resource/rule version mismatch");
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
var measurementCurrent = refine2(union2(
|
|
1463
|
+
object2({ schemaVersion: literal2(1), kind: literal2("unconfigured"), ...scope, configurationRevision: literal2(0) }),
|
|
1464
|
+
object2({ schemaVersion: literal2(1), kind: literal2("configured"), ...scope, ...measurementSavedFields, reasonCodes: uniqueList(text2(200), 100) })
|
|
1465
|
+
), (v, p) => {
|
|
1466
|
+
if (v.kind === "configured") checkMeasurementSaved(v, p);
|
|
1467
|
+
});
|
|
1468
|
+
var measurementSnapshotFields = { schemaVersion: literal2(1), ...scope, campaignId: uuid, operationId: uuid, configurationRevision: integer2(1), configurationDigest: digest, configuration: measurementConfig, pages: list2(measurementPage, 500, 1) };
|
|
1469
|
+
var measurementSnapshotShape = object2(measurementSnapshotFields);
|
|
1470
|
+
function checkMeasurementSnapshot(v, p) {
|
|
1471
|
+
checkMeasurementSaved({ ...v, saveOperationId: v.operationId }, p);
|
|
1472
|
+
}
|
|
1473
|
+
var measurementSnapshot = refine2(measurementSnapshotShape, checkMeasurementSnapshot);
|
|
1474
|
+
var measurementSaveResponse = refine2(object2({ ...measurementSnapshotFields, replayed: bool }), checkMeasurementSnapshot);
|
|
1475
|
+
var parseResonanceMeasurementConfigurationCurrent = (v) => measurementCurrent.read(v, "measurementConfigurationCurrent");
|
|
1476
|
+
var parseResonanceMeasurementConfigurationSnapshot = (v) => measurementSnapshot.read(v, "measurementConfigurationSnapshot");
|
|
1477
|
+
var parseResonanceMeasurementConfigurationSaveResponse = (v) => measurementSaveResponse.read(v, "measurementConfigurationSaveResponse");
|
|
1478
|
+
var measurementProjectionContext = object2({ projectId: uuid, current: measurementCurrent, pages: list2(object2({ pageId: uuid, label: friendlyName, path: observedPagePath, pageRevision: nativePageRevision, evidenceDigest: digest }), 500, 1), expiresAt: instant });
|
|
1479
|
+
function projectResonanceMeasurementConfiguration(value, context) {
|
|
1480
|
+
const command2 = parseResonanceMeasurementConfigurationSaveCommand(value), actual = measurementProjectionContext.read(context, "measurementConfigurationProjection");
|
|
1481
|
+
const current = parseResonanceMeasurementConfigurationCurrent(actual.current);
|
|
1482
|
+
if (current.projectId !== actual.projectId || current.site !== command2.site || current.configurationRevision !== command2.expectedConfigurationRevision) fail2("measurementConfigurationProjection.current", "scope or configuration CAS mismatch");
|
|
1483
|
+
unique(actual.pages.map((page2) => page2.pageId), "measurementConfigurationProjection.pages");
|
|
1484
|
+
if (actual.pages.length !== command2.pages.length || actual.pages.some((page2) => !command2.pages.some((selected) => selected.pageId === page2.pageId && selected.observedRevision === page2.pageRevision && selected.observedDigest === page2.evidenceDigest))) fail2("measurementConfigurationProjection.pages", "requires the exact selected current native page evidence");
|
|
1485
|
+
const pages = [...actual.pages].sort((a, b) => a.pageId < b.pageId ? -1 : a.pageId > b.pageId ? 1 : 0).map((page2) => ({ ...page2, resourceRuleId: page2.pageId, resourceRuleRevision: page2.evidenceDigest }));
|
|
1486
|
+
const consentPolicy = { ...command2.consentPolicy, requiredPurposes: [...command2.consentPolicy.requiredPurposes].sort() };
|
|
1487
|
+
const previous = current.kind === "configured" ? current.configuration : null;
|
|
1488
|
+
const next = {
|
|
1489
|
+
schemaVersion: 1,
|
|
1490
|
+
projectId: actual.projectId,
|
|
1491
|
+
site: command2.site,
|
|
1492
|
+
configRevision: previous?.configRevision ?? "1",
|
|
1493
|
+
policyRevision: previous?.policyRevision ?? "1",
|
|
1494
|
+
expiresAt: actual.expiresAt,
|
|
1495
|
+
enabled: command2.enabled,
|
|
1496
|
+
consentPolicy,
|
|
1497
|
+
destinations: previous?.destinations ?? [],
|
|
1498
|
+
resourceRules: pages.map((page2) => ({ id: page2.pageId, revision: page2.evidenceDigest, site: command2.site, resource: { projectId: actual.projectId, site: command2.site, type: "page", id: page2.pageId, revision: page2.pageRevision }, path: page2.path, interestKey: page2.pageId }))
|
|
1499
|
+
};
|
|
1500
|
+
if (previous) {
|
|
1501
|
+
if (canonical({ ...previous.consentPolicy, requiredPurposes: [...previous.consentPolicy.requiredPurposes].sort() }) !== canonical(consentPolicy)) next.policyRevision = (BigInt(previous.policyRevision) + 1n).toString();
|
|
1502
|
+
if (resonanceMeasurementConfigDigestInput(previous) !== resonanceMeasurementConfigDigestInput(next)) next.configRevision = (BigInt(previous.configRevision) + 1n).toString();
|
|
1503
|
+
}
|
|
1504
|
+
const configuration = parseResonanceMeasurementConfig(next);
|
|
1505
|
+
positiveRevision.read(configuration.configRevision, "measurementConfigurationProjection.configRevision");
|
|
1506
|
+
positiveRevision.read(configuration.policyRevision, "measurementConfigurationProjection.policyRevision");
|
|
1507
|
+
return { configuration, pages, configurationDigestInput: resonanceMeasurementConfigDigestInput(configuration) };
|
|
1508
|
+
}
|
|
1509
|
+
var RESONANCE_MEASUREMENT_CONFIGURATION_SAVE_COMMAND_JSON_SCHEMA = measurementSave.schema;
|
|
1510
|
+
var RESONANCE_MEASUREMENT_CONFIGURATION_CURRENT_JSON_SCHEMA = measurementCurrent.schema;
|
|
1511
|
+
var RESONANCE_MEASUREMENT_CONFIGURATION_SNAPSHOT_JSON_SCHEMA = measurementSnapshot.schema;
|
|
1512
|
+
var RESONANCE_MEASUREMENT_CONFIGURATION_SAVE_RESPONSE_JSON_SCHEMA = measurementSaveResponse.schema;
|
|
1513
|
+
var nativeAudienceRef = object2({ externalId: text2(160, /^[0-9]+(?![\s\S])/), revision: digest });
|
|
1514
|
+
var metaSave = refine2(object2({
|
|
1515
|
+
schemaVersion: literal2(1),
|
|
1516
|
+
expectedCampaignRevision: integer2(1),
|
|
1517
|
+
expectedConfigurationRevision: integer2(0),
|
|
1518
|
+
idempotencyKey: operationKey,
|
|
1519
|
+
audiences: list2(union2(object2({ audienceRuleId: uuid, mode: literal2("existing"), nativeAudienceRef }), object2({ audienceRuleId: uuid, mode: literal2("admitted_setup") })), 50, 1)
|
|
1520
|
+
}), (v, p) => unique(v.audiences.map((a) => a.audienceRuleId), p + ".audiences"));
|
|
1521
|
+
var parseResonanceMetaConfigurationSaveCommand = (v) => metaSave.read(v, "metaConfigurationSave");
|
|
1522
|
+
var resonanceMetaConfigurationSaveDigestInput = (v) => canonical(parseResonanceMetaConfigurationSaveCommand(v));
|
|
1523
|
+
var metaResolvedAudience = object2({ audienceRuleRef: audience, name: friendlyName, resourceRuleRef: measurementRuleRef, excludedResourceRuleRefs: list2(measurementRuleRef, 50), selection: union2(
|
|
1524
|
+
object2({ mode: literal2("existing"), nativeAudienceRef, nativeAudienceName: nullable2(friendlyName) }),
|
|
1525
|
+
object2({ mode: literal2("admitted_setup"), setup: object2({ kind: literal2("meta_website_audience_v1"), localOutputKey: operationKey, specDigest: digest }) })
|
|
1526
|
+
) });
|
|
1527
|
+
var metaMaterialFields = {
|
|
1528
|
+
schemaVersion: literal2(1),
|
|
1529
|
+
projectId: uuid,
|
|
1530
|
+
campaignId: uuid,
|
|
1531
|
+
channelId: uuid,
|
|
1532
|
+
site,
|
|
1533
|
+
campaignRevision: integer2(1),
|
|
1534
|
+
reviewedDefinitionDigest: digest,
|
|
1535
|
+
configurationRevision: integer2(1),
|
|
1536
|
+
saveOperationId: uuid,
|
|
1537
|
+
channel: meta,
|
|
1538
|
+
account: object2({ id: uuid, externalId: text2(160), name: nullable2(friendlyName) }),
|
|
1539
|
+
datasetName: nullable2(friendlyName),
|
|
1540
|
+
pageName: nullable2(friendlyName),
|
|
1541
|
+
instagramName: nullable2(friendlyName),
|
|
1542
|
+
measurementConfigRef,
|
|
1543
|
+
matchingProfileRef,
|
|
1544
|
+
rules: list2(object2({ id: uuid, revision, resource: interestResource, path: observedPagePath, label: friendlyName }), 500, 1),
|
|
1545
|
+
audiences: list2(metaResolvedAudience, 50, 1)
|
|
1546
|
+
};
|
|
1547
|
+
object2(metaMaterialFields);
|
|
1548
|
+
function checkMetaMaterial(v, p) {
|
|
1549
|
+
scoped(v, v.projectId, [v.site], p);
|
|
1550
|
+
if (v.channel.id !== v.channelId || v.channel.accountBindingId !== v.account.id || v.matchingProfileRef.id !== v.saveOperationId || v.matchingProfileRef.revision !== v.configurationRevision) fail2(p, "original channel/account/profile receipt mismatch");
|
|
1551
|
+
if (!v.channel.instagramRef && v.instagramName !== null) fail2(p + ".instagramName", "absent original Instagram identity requires null");
|
|
1552
|
+
unique(v.rules.map((rule) => rule.id), p + ".rules");
|
|
1553
|
+
unique(v.audiences.map((a) => a.audienceRuleRef.id), p + ".audiences");
|
|
1554
|
+
unique(v.channel.audienceRuleRefs.map((a) => a.id), p + ".channel.audienceRuleRefs");
|
|
1555
|
+
if (v.audiences.length !== v.channel.audienceRuleRefs.length) fail2(p + ".audiences", "must cover exactly the original logical audiences");
|
|
1556
|
+
for (const a of v.audiences) {
|
|
1557
|
+
if (!v.channel.audienceRuleRefs.some((ref) => canonical(ref) === canonical(a.audienceRuleRef))) fail2(p + ".audiences", "original logical audience version mismatch");
|
|
1558
|
+
unique(a.excludedResourceRuleRefs.map((ref) => ref.id), p + ".audiences.excludedResourceRuleRefs");
|
|
1559
|
+
unique(a.audienceRuleRef.exclusionRuleIds, p + ".audiences.audienceRuleRef.exclusionRuleIds");
|
|
1560
|
+
if (canonical([...a.audienceRuleRef.exclusionRuleIds].sort()) !== canonical(a.excludedResourceRuleRefs.map((ref) => ref.id).sort()) || a.excludedResourceRuleRefs.some((ref) => ref.id === a.resourceRuleRef.id)) fail2(p + ".audiences", "positive/excluded resource scope mismatch");
|
|
1561
|
+
for (const selected of [a.resourceRuleRef, ...a.excludedResourceRuleRefs]) if (!v.rules.some((rule) => rule.id === selected.id && rule.revision === selected.revision)) fail2(p + ".audiences", "resource rule version mismatch");
|
|
1562
|
+
if (a.selection.mode === "admitted_setup" && a.selection.setup.localOutputKey !== `audience:${a.audienceRuleRef.id}:${a.audienceRuleRef.revision}`) fail2(p + ".audiences.selection.setup.localOutputKey", "must retain the actual original SDK creator key");
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
var metaSnapshot = refine2(object2({ ...metaMaterialFields, configurationDigest: digest }), checkMetaMaterial);
|
|
1566
|
+
var parseResonanceMetaConfigurationSnapshot = (v) => metaSnapshot.read(v, "metaConfigurationSnapshot");
|
|
1567
|
+
var metaDigestValue = refine2(object2({ ...metaMaterialFields, configurationDigest: optional2(digest) }), checkMetaMaterial);
|
|
1568
|
+
function resonanceMetaConfigurationDigestInput(v) {
|
|
1569
|
+
const { configurationDigest: _self, ...material } = metaDigestValue.read(v, "metaConfigurationDigestInput");
|
|
1570
|
+
return canonical(material);
|
|
1571
|
+
}
|
|
1572
|
+
var metaCurrentMetadata = object2({ currentCampaignRevision: integer2(1), currentDefinitionDigest: digest, observedAt: instant, expiresAt: nullable2(instant), reasonCodes: uniqueList(text2(200), 100), pendingRequirements: uniqueList(text2(200), 100) });
|
|
1573
|
+
function metaReviewable(snapshot, metadata) {
|
|
1574
|
+
return metadata.currentCampaignRevision >= snapshot.campaignRevision && metadata.currentDefinitionDigest === snapshot.reviewedDefinitionDigest && metadata.reasonCodes.length === 0 && metadata.expiresAt !== null && Date.parse(metadata.expiresAt) > Date.parse(metadata.observedAt);
|
|
1575
|
+
}
|
|
1576
|
+
var metaView = refine2(union2(
|
|
1577
|
+
object2({ schemaVersion: literal2(1), kind: literal2("unconfigured"), projectId: uuid, campaignId: uuid, channelId: uuid, configurationRevision: literal2(0) }),
|
|
1578
|
+
object2({
|
|
1579
|
+
schemaVersion: literal2(1),
|
|
1580
|
+
kind: literal2("configured"),
|
|
1581
|
+
projectId: uuid,
|
|
1582
|
+
campaignId: uuid,
|
|
1583
|
+
channelId: uuid,
|
|
1584
|
+
configurationRevision: integer2(1),
|
|
1585
|
+
snapshot: metaSnapshot,
|
|
1586
|
+
currentCampaignRevision: integer2(1),
|
|
1587
|
+
currentDefinitionDigest: digest,
|
|
1588
|
+
observedAt: instant,
|
|
1589
|
+
expiresAt: nullable2(instant),
|
|
1590
|
+
readyForReview: bool,
|
|
1591
|
+
reasonCodes: uniqueList(text2(200), 100),
|
|
1592
|
+
pendingRequirements: uniqueList(text2(200), 100)
|
|
1593
|
+
})
|
|
1594
|
+
), (v, p) => {
|
|
1595
|
+
if (v.kind !== "configured") return;
|
|
1596
|
+
const s = parseResonanceMetaConfigurationSnapshot(v.snapshot);
|
|
1597
|
+
if (v.projectId !== s.projectId || v.campaignId !== s.campaignId || v.channelId !== s.channelId || v.configurationRevision !== s.configurationRevision) fail2(p, "snapshot identity or configuration CAS mismatch");
|
|
1598
|
+
if (v.readyForReview !== metaReviewable(s, v)) fail2(p + ".readyForReview", "must reflect current review prerequisites only");
|
|
1599
|
+
});
|
|
1600
|
+
var parseResonanceMetaConfigurationView = (v) => metaView.read(v, "metaConfigurationView");
|
|
1601
|
+
var metaSaveResponse = object2({ schemaVersion: literal2(1), snapshot: metaSnapshot, replayed: bool });
|
|
1602
|
+
var parseResonanceMetaConfigurationSaveResponse = (v) => metaSaveResponse.read(v, "metaConfigurationSaveResponse");
|
|
1603
|
+
function projectResonanceMetaConfiguration(v, currentMetadata) {
|
|
1604
|
+
const snapshot = parseResonanceMetaConfigurationSnapshot(v), metadata = metaCurrentMetadata.read(currentMetadata, "metaConfigurationCurrentMetadata");
|
|
1605
|
+
const readyForReview = metaReviewable(snapshot, metadata);
|
|
1606
|
+
const view2 = parseResonanceMetaConfigurationView({ schemaVersion: 1, kind: "configured", projectId: snapshot.projectId, campaignId: snapshot.campaignId, channelId: snapshot.channelId, configurationRevision: snapshot.configurationRevision, snapshot, ...metadata, readyForReview });
|
|
1607
|
+
return { view: view2, review: readyForReview ? snapshot : null };
|
|
1608
|
+
}
|
|
1609
|
+
var RESONANCE_META_CONFIGURATION_SAVE_COMMAND_JSON_SCHEMA = metaSave.schema;
|
|
1610
|
+
var RESONANCE_META_CONFIGURATION_SNAPSHOT_JSON_SCHEMA = metaSnapshot.schema;
|
|
1611
|
+
var RESONANCE_META_CONFIGURATION_VIEW_JSON_SCHEMA = metaView.schema;
|
|
1612
|
+
var RESONANCE_META_CONFIGURATION_SAVE_RESPONSE_JSON_SCHEMA = metaSaveResponse.schema;
|
|
1613
|
+
var feedSelection = refine2(object2({ channels: list2(object2({ channelId: uuid, expectedConfigurationRevision: integer2(1), resourceRuleRefs: list2(measurementRuleRef, 500, 1) }), 100, 1) }), (v, p) => {
|
|
1614
|
+
unique(v.channels.map((c) => c.channelId), p + ".channels");
|
|
1615
|
+
for (const c of v.channels) unique(c.resourceRuleRefs.map((ref) => ref.id), p + ".resourceRuleRefs");
|
|
1616
|
+
});
|
|
1617
|
+
var mandateDraftRequest = object2({ expectedRevision: integer2(1), allowedOperations: mandateAllowedOperations, expiresAt: instant, websiteVisitFeedSelection: optional2(feedSelection) });
|
|
1618
|
+
var parseResonanceMandateDraftRequest = (v) => mandateDraftRequest.read(v, "mandateDraftRequest");
|
|
1619
|
+
var RESONANCE_MANDATE_DRAFT_REQUEST_JSON_SCHEMA = mandateDraftRequest.schema;
|
|
1620
|
+
var feedReview = object2({ schemaVersion: literal2(1), approval: mandateApproval, mandateRef, configurations: list2(metaSnapshot, 100), current: object2({ state: choice2("not_enabled", "eligible", "invalidated", "unavailable"), checkedAt: instant, expiresAt: nullable2(instant), reasonCodes: uniqueList(text2(200), 100) }) });
|
|
1621
|
+
function parseResonanceWebsiteVisitFeedReview(v, context) {
|
|
1622
|
+
const r = feedReview.read(v, "websiteVisitFeedReview"), original = r.approval, grant = parseResonanceMandateScope(original.mandate), ref = r.mandateRef, p = "websiteVisitFeedReview";
|
|
1623
|
+
parseResonanceMandateApprovalPayload(original, { project_id: grant.projectId, campaign_id: grant.campaignId });
|
|
1624
|
+
if (ref.campaignId !== grant.campaignId || ref.preparedPlanId !== original.preparedPlanId || ref.reviewDigest !== original.reviewDigest || ref.mandateDigest !== original.mandateDigest) fail2(p + ".mandateRef", "requires the same original accepted mandate");
|
|
1625
|
+
unique(r.configurations.map((c) => c.channelId), p + ".configurations");
|
|
1626
|
+
const feed = grant.websiteVisitFeed;
|
|
1627
|
+
if (!feed) {
|
|
1628
|
+
if (r.configurations.length || r.current.state !== "not_enabled") fail2(p, "absent original feed requires not_enabled and no configurations");
|
|
1629
|
+
} else {
|
|
1630
|
+
if (r.current.state === "not_enabled" || r.configurations.length !== feed.channels.length) fail2(p, "must cover the exact original feed channels");
|
|
1631
|
+
for (const selected of feed.channels) {
|
|
1632
|
+
const config = r.configurations.find((c) => c.channelId === selected.channelId);
|
|
1633
|
+
if (!config || config.projectId !== grant.projectId || config.campaignId !== grant.campaignId || !grant.sites.includes(config.site) || !grant.accountBindingIds.includes(config.account.id) || config.campaignRevision > original.expectedRevision || canonical(config.measurementConfigRef) !== canonical(selected.measurementConfigRef) || canonical(config.matchingProfileRef) !== canonical(selected.matchingProfileRef)) fail2(p + ".configurations", "original configuration/profile/scope mismatch");
|
|
1634
|
+
const saved = config;
|
|
1635
|
+
for (const a of saved.channel.audienceRuleRefs) if (!grant.audienceRuleRefs.some((ref2) => canonical(ref2) === canonical(a))) fail2(p + ".configurations", "original logical audience grant mismatch");
|
|
1636
|
+
const positive = saved.audiences.map((a) => a.resourceRuleRef);
|
|
1637
|
+
for (const rule of selected.resourceRuleRefs) if (!positive.some((ref2) => ref2.id === rule.id && ref2.revision === rule.revision)) fail2(p + ".configurations", "feed requires an original positive resource rule, never excluded-only");
|
|
1638
|
+
}
|
|
1639
|
+
if (r.current.state === "eligible" && (r.current.reasonCodes.length || r.current.expiresAt === null || Date.parse(r.current.expiresAt) <= Date.parse(r.current.checkedAt))) fail2(p + ".current", "eligible review requires an observed nonexpired horizon and no blockers");
|
|
1640
|
+
}
|
|
1641
|
+
if (context) {
|
|
1642
|
+
const actual = object2({ preparedPlan: prepared }).read(context, p + ".context"), plan = parseResonancePreparedPlan(actual.preparedPlan);
|
|
1643
|
+
if (plan.id !== original.preparedPlanId || plan.campaignId !== grant.campaignId || plan.projectId !== grant.projectId || plan.reviewDigest !== original.reviewDigest || plan.expectedRevision !== original.expectedRevision || canonical(plan.definition.budget) !== canonical(grant.budget) || canonical(plan.definition.objective) !== canonical(grant.goal)) fail2(p + ".context", "requires the original prepared plan and mandate material");
|
|
1644
|
+
for (const config of r.configurations) if (config.reviewedDefinitionDigest !== plan.definitionDigest || !plan.definition.channels.some((c) => canonical(c) === canonical(config.channel))) fail2(p + ".context", "original channel or reviewed definition mismatch");
|
|
1645
|
+
}
|
|
1646
|
+
return r;
|
|
1647
|
+
}
|
|
1648
|
+
var RESONANCE_WEBSITE_VISIT_FEED_REVIEW_JSON_SCHEMA = feedReview.schema;
|
|
1649
|
+
|
|
1650
|
+
export { MAX_MONEY_MICROS, MONEY_EXPONENT, MONEY_JSON_SCHEMA, RESONANCE_ACCEPT_COMMAND_JSON_SCHEMA, RESONANCE_ACCOUNT_CAPABILITY_JSON_SCHEMA, RESONANCE_ATTRIBUTION_MODEL, RESONANCE_AUDIENCE_DRAFT_SAVE_COMMAND_JSON_SCHEMA, RESONANCE_AUDIENCE_RULE_REF_JSON_SCHEMA, RESONANCE_BINDING_COMMAND_JSON_SCHEMA, RESONANCE_CAMPAIGN_DEFINITION_JSON_SCHEMA, RESONANCE_CAMPAIGN_DRAFT_JSON_SCHEMA, RESONANCE_CAMPAIGN_VIEW_JSON_SCHEMA, RESONANCE_CLICK_IDENTIFIERS_JSON_SCHEMA, RESONANCE_COMMAND_JSON_SCHEMA, RESONANCE_COMMAND_PREPARE_REQUEST_JSON_SCHEMA, RESONANCE_COMMAND_PREPARE_RESPONSE_JSON_SCHEMA, RESONANCE_CONSENT_JSON_SCHEMA, RESONANCE_CONSENT_RESPONSE_JSON_SCHEMA, RESONANCE_CONSENT_UPDATE_JSON_SCHEMA, RESONANCE_CONVERSION_JSON_SCHEMA, RESONANCE_CPQL_UNCERTAINTY_METHOD, RESONANCE_CURRENCIES, RESONANCE_EXPERIMENT_COMPLETE_COMMAND_JSON_SCHEMA, RESONANCE_EXPERIMENT_CREATE_COMMAND_JSON_SCHEMA, RESONANCE_EXPERIMENT_LIFECYCLE_COMMAND_JSON_SCHEMA, RESONANCE_EXPERIMENT_LINK_COMMAND_JSON_SCHEMA, RESONANCE_EXPERIMENT_REF_JSON_SCHEMA, RESONANCE_EXPERIMENT_UPDATE_COMMAND_JSON_SCHEMA, RESONANCE_GOOGLE_CONFIGURATION_REVIEW_JSON_SCHEMA, RESONANCE_GOOGLE_CONFIGURATION_SAVE_COMMAND_JSON_SCHEMA, RESONANCE_GOOGLE_CONFIGURATION_SAVE_RESPONSE_JSON_SCHEMA, RESONANCE_GOOGLE_CONFIGURATION_SNAPSHOT_JSON_SCHEMA, RESONANCE_GOOGLE_CONFIGURATION_VIEW_JSON_SCHEMA, RESONANCE_GOOGLE_SETUP_SPEC_JSON_SCHEMA, RESONANCE_GOOGLE_SIGNAL_SEMANTICS, RESONANCE_GOOGLE_SOURCE_CATEGORIES, RESONANCE_MANDATE_ACTION_KIND, RESONANCE_MANDATE_APPROVAL_JSON_SCHEMA, RESONANCE_MANDATE_DRAFT_REQUEST_JSON_SCHEMA, RESONANCE_MANDATE_REF_JSON_SCHEMA, RESONANCE_MANDATE_SCOPE_JSON_SCHEMA, RESONANCE_MANDATE_TARGET_JSON_SCHEMA, RESONANCE_MEASUREMENT_CONFIGURATION_CURRENT_JSON_SCHEMA, RESONANCE_MEASUREMENT_CONFIGURATION_SAVE_COMMAND_JSON_SCHEMA, RESONANCE_MEASUREMENT_CONFIGURATION_SAVE_RESPONSE_JSON_SCHEMA, RESONANCE_MEASUREMENT_CONFIGURATION_SNAPSHOT_JSON_SCHEMA, RESONANCE_MEASUREMENT_CONFIG_JSON_SCHEMA, RESONANCE_MEASUREMENT_OCCURRENCE_JSON_SCHEMA, RESONANCE_META_CONFIGURATION_SAVE_COMMAND_JSON_SCHEMA, RESONANCE_META_CONFIGURATION_SAVE_RESPONSE_JSON_SCHEMA, RESONANCE_META_CONFIGURATION_SNAPSHOT_JSON_SCHEMA, RESONANCE_META_CONFIGURATION_VIEW_JSON_SCHEMA, RESONANCE_METRIC_JSON_SCHEMA, RESONANCE_OCCURRENCE_RESPONSE_JSON_SCHEMA, RESONANCE_OPTIMIZATION_DECISION_JSON_SCHEMA, RESONANCE_OPTIMIZATION_EVIDENCE_JSON_SCHEMA, RESONANCE_OPTIMIZATION_POLICY_JSON_SCHEMA, RESONANCE_PREPARED_PLAN_JSON_SCHEMA, RESONANCE_QUALIFICATION_RULE_JSON_SCHEMA, RESONANCE_RECEIPT_JSON_SCHEMA, RESONANCE_RESOLVED_QUALIFICATION_RULE_JSON_SCHEMA, RESONANCE_RESOURCE_REF_JSON_SCHEMA, RESONANCE_SAVE_COMMAND_JSON_SCHEMA, RESONANCE_SCHEMA_VERSION, RESONANCE_SOURCE_MARKER_JSON_SCHEMA, RESONANCE_SOURCE_MARKER_PARAMETER, RESONANCE_SUBJECT_HEADER, RESONANCE_SUBJECT_RENEW_COMMAND_JSON_SCHEMA, RESONANCE_WEBSITE_VISIT_FEED_JSON_SCHEMA, RESONANCE_WEBSITE_VISIT_FEED_REVIEW_JSON_SCHEMA, RESONANCE_WEBSITE_VISIT_PROFILE, ResonanceValidationError, addMoney, compareMoney, cpqlMissingOutcomeBound, formatMoney, googleConversionActionSemantics, googleUploadClicksSetupSemantics, moneyFromProviderUnits, moneyToProviderUnits, parseMoney, parseResonanceAcceptCommand, parseResonanceAccountCapability, parseResonanceAudienceDraftSaveCommand, parseResonanceAudienceRuleRef, parseResonanceBindingCommand, parseResonanceCampaignDefinition, parseResonanceCampaignDraft, parseResonanceCampaignSaveCommand, parseResonanceCampaignView, parseResonanceClickIdentifiers, parseResonanceCommand, parseResonanceCommandPrepareRequest, parseResonanceCommandPrepareResponse, parseResonanceConsentResponse, parseResonanceConsentSnapshot, parseResonanceConsentUpdate, parseResonanceConversionEnvelope, parseResonanceExperimentCompleteCommand, parseResonanceExperimentCreateCommand, parseResonanceExperimentExposure, parseResonanceExperimentLifecycleCommand, parseResonanceExperimentLinkCommand, parseResonanceExperimentRef, parseResonanceExperimentUpdateCommand, parseResonanceGoogleConfigurationReview, parseResonanceGoogleConfigurationSaveCommand, parseResonanceGoogleConfigurationSaveResponse, parseResonanceGoogleConfigurationSnapshot, parseResonanceGoogleConfigurationView, parseResonanceGoogleUploadClicksCustomGoalSpec, parseResonanceMandateApprovalPayload, parseResonanceMandateDraftRequest, parseResonanceMandateRef, parseResonanceMandateScope, parseResonanceMandateTarget, parseResonanceMeasurementConfig, parseResonanceMeasurementConfigurationCurrent, parseResonanceMeasurementConfigurationSaveCommand, parseResonanceMeasurementConfigurationSaveResponse, parseResonanceMeasurementConfigurationSnapshot, parseResonanceMeasurementOccurrence, parseResonanceMetaConfigurationSaveCommand, parseResonanceMetaConfigurationSaveResponse, parseResonanceMetaConfigurationSnapshot, parseResonanceMetaConfigurationView, parseResonanceMetricObservation, parseResonanceOccurrenceResponse, parseResonanceOptimizationDecision, parseResonanceOptimizationEvidence, parseResonanceOptimizationPolicy, parseResonancePrepareCommand, parseResonancePreparedPlan, parseResonanceQualificationRule, parseResonanceReceipt, parseResonanceResolvedQualificationRule, parseResonanceResourceRef, parseResonanceSourceMarker, parseResonanceSubjectRenewCommand, parseResonanceWebsiteVisitFeedReview, projectResonanceGoogleConfiguration, projectResonanceMeasurementConfiguration, projectResonanceMetaConfiguration, resonanceCommandDigestInput, resonanceGoogleConfigurationDigestInput, resonanceGoogleSetupDigestInput, resonanceMandateDigestInput, resonanceMeasurementConfigDigestInput, resonanceMeasurementConfigurationSaveDigestInput, resonanceMetaConfigurationDigestInput, resonanceMetaConfigurationSaveDigestInput, resonanceOptimizationDecisionDigestInput, resonanceOptimizationEvidenceDigestInput, resonanceOptimizationPolicyDigestInput, resonancePlanIssues, resonanceWebsiteVisitEventTime, resonanceWebsiteVisitProfileDigestInput, subtractMoney };
|