@revturbine/cli 0.21.1 → 0.21.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +614 -548
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -2284,10 +2284,20 @@ var DEFAULT_TEMPLATE_COMPONENT_TYPES = {
2284
2284
  agent_connector: "agent",
2285
2285
  custom_in_app: "custom"
2286
2286
  };
2287
+ var LEGACY_TEMPLATE_ALIASES = Object.freeze({
2288
+ plans_pricing_ctas: "plans_page_ctas",
2289
+ plans_pricing_full: "plans_page_full",
2290
+ credit_balance_counter: "credit_counter",
2291
+ banner_placement: "banner",
2292
+ modal_overlay: "modal_optional",
2293
+ toast_message: "toast",
2294
+ push_notification: "push"
2295
+ });
2287
2296
  var BUILT_IN_TEMPLATE_COMPONENT_TYPES = Object.freeze({
2288
2297
  ...DEFAULT_TEMPLATE_COMPONENT_TYPES,
2289
- modal_overlay: "modal",
2290
- banner_placement: "banner",
2298
+ ...Object.fromEntries(
2299
+ Object.entries(LEGACY_TEMPLATE_ALIASES).map(([legacy, id11]) => [legacy, DEFAULT_TEMPLATE_COMPONENT_TYPES[id11]])
2300
+ ),
2291
2301
  full_page: "full_page"
2292
2302
  });
2293
2303
  var ENTITLEMENT_STATUS_VALUES = ["allowed", "limited", "denied"];
@@ -21713,9 +21723,9 @@ var SimulationRunReceiptSchema = z68.strictObject({
21713
21723
 
21714
21724
  // src/schema/validators.snapshot.mjs
21715
21725
  import { z as z69 } from "zod";
21716
- import { z as z92 } from "zod";
21717
21726
  import { z as z310 } from "zod";
21718
21727
  import { z as z210 } from "zod";
21728
+ import { z as z92 } from "zod";
21719
21729
  import { z as z410 } from "zod";
21720
21730
  import { z as z610 } from "zod";
21721
21731
  import { z as z510 } from "zod";
@@ -21897,6 +21907,15 @@ var CATALOG = {
21897
21907
  message: "This payload names a placement that does not exist.",
21898
21908
  specRef: "config-validation.md \xA75.9 (BL-0180, D-31)"
21899
21909
  },
21910
+ // payload surface → template (BUG-1345): a template id that is neither
21911
+ // built-in (incl. legacy aliases) nor authored in `surface_templates` has no
21912
+ // rendering type, so every SDK skips that payload at load.
21913
+ "VAL-PLC-11": {
21914
+ id: "VAL-PLC-11",
21915
+ severity: "error_launch",
21916
+ message: "This payload uses a surface template that does not exist.",
21917
+ specRef: "config-validation.md \xA75.2 (BUG-1345)"
21918
+ },
21900
21919
  "VAL-PLN-08": {
21901
21920
  id: "VAL-PLN-08",
21902
21921
  severity: "error_launch",
@@ -21978,193 +21997,465 @@ function categoryBucket(category) {
21978
21997
  if (normalized.includes("retention") || normalized.includes("winback") || normalized.includes("churn")) return 4;
21979
21998
  return 99;
21980
21999
  }
21981
- function hasCreditResetPeriod2(fields) {
21982
- return fields.reset_period !== void 0 && fields.reset_period !== null;
21983
- }
21984
- function allowancePeriod2(kind, fields, billingPeriod) {
21985
- if (kind === "seat") return "seat";
21986
- const raw = kind === "credits" ? fields.reset_period : fields.period_scope;
21987
- if (raw === "billing_period" || raw === "per_billing_period") {
21988
- if (billingPeriod === "monthly") return "month";
21989
- if (billingPeriod === "annual") return "year";
21990
- return void 0;
22000
+ var HANDLE_PATTERN2 = /^[a-z0-9._]{1,100}$/;
22001
+ var SchemaPersistence2 = {
22002
+ Persisted: "persisted",
22003
+ Transient: "transient"
22004
+ };
22005
+ var SchemaExposure2 = {
22006
+ Internal: "internal",
22007
+ External: "external"
22008
+ };
22009
+ var DataClassification2 = {
22010
+ Pii: { "x-revturbine-data-classification": "pii" },
22011
+ Financial: { "x-revturbine-data-classification": "financial" },
22012
+ Unrestricted: { "x-revturbine-data-classification": "unrestricted" },
22013
+ /**
22014
+ * Machine-generated evaluation exhaust — supersession records, entitlement
22015
+ * grants, placement decision traces. Carries no personal or financial data
22016
+ * itself, but describes what the engine decided about a user, so it is not
22017
+ * `Unrestricted` either. Applied at the MODEL level (plan 182 REQ-7); it was
22018
+ * already in use as a raw string literal on four schemas before being
22019
+ * declared here, which left `DataClassificationValue` unable to represent it.
22020
+ */
22021
+ Operational: { "x-revturbine-data-classification": "operational" }
22022
+ };
22023
+ var SCHEMA_EXPOSURE_META_KEY2 = "x-revturbine-schema-exposure";
22024
+ var READ_ONLY_META_KEY2 = "readOnly";
22025
+ var DECISION_ONLY_META_KEY2 = "x-revturbine-decision-only";
22026
+ var DecisionOnly2 = { [DECISION_ONLY_META_KEY2]: true };
22027
+ var ClientSafe2 = { [SCHEMA_EXPOSURE_META_KEY2]: SchemaExposure2.External };
22028
+ var ServerOnly2 = { [SCHEMA_EXPOSURE_META_KEY2]: SchemaExposure2.Internal };
22029
+ function toWritableSchema2(schema) {
22030
+ const writableShape = {};
22031
+ for (const [fieldName, fieldSchema] of Object.entries(schema.shape)) {
22032
+ if (typeof fieldSchema !== "object" || fieldSchema === null) {
22033
+ continue;
22034
+ }
22035
+ const isReadOnly = fieldSchema.meta()?.[READ_ONLY_META_KEY2] === true;
22036
+ if (!isReadOnly) {
22037
+ writableShape[fieldName] = fieldSchema;
22038
+ }
21991
22039
  }
21992
- if (typeof raw !== "string" || raw === "") return void 0;
21993
- if (raw === "per_6_hours" || raw === "six_hours") return "six_hours";
21994
- return raw.startsWith("per_") ? raw.slice(4) : raw;
22040
+ return z210.object(writableShape);
21995
22041
  }
21996
- function assertCompatibleAllowances2(a, b, aBillingPeriod, bBillingPeriod) {
21997
- const aPeriod = allowancePeriod2(a.kind, a.fields, aBillingPeriod);
21998
- const bPeriod = allowancePeriod2(b.kind, b.fields, bBillingPeriod);
21999
- if (a.kind !== b.kind || aPeriod === void 0 || aPeriod !== bPeriod || (a.fields.allocation ?? "account") !== (b.fields.allocation ?? "account") || (a.fields.instance ?? "") !== (b.fields.instance ?? "") || (a.fields.seat_type_id ?? "") !== (b.fields.seat_type_id ?? "")) {
22000
- throw new Error(`Incompatible purchased allowances: ${a.ruleId} and ${b.ruleId} require matching periods and scopes`);
22042
+ function toCreateSchema2(schema) {
22043
+ const writable = toWritableSchema2(schema);
22044
+ const handleField = writable.shape["handle"];
22045
+ if (handleField && typeof handleField.regex === "function") {
22046
+ return writable.extend({ handle: handleField.regex(HANDLE_PATTERN2) });
22001
22047
  }
22048
+ return writable;
22002
22049
  }
22003
- var HANDLE_PATTERN2 = /^[a-z0-9._]{1,100}$/;
22004
- var BUILTIN_SEGMENT_HANDLE_PREFIX2 = "rt.";
22005
- var BUILTIN_TRAIT_KEY_PREFIX2 = "rt_";
22006
- var MAX_HANDLE_LENGTH = 100;
22007
- function dimension2(key4, name, legacyAbbr, values, opts = {}) {
22008
- const legacyPrefix = `seg:${legacyAbbr}:`;
22009
- return {
22010
- key: key4,
22011
- name,
22012
- traitKey: `${BUILTIN_TRAIT_KEY_PREFIX2}${key4}`,
22013
- dimensionId: `${BUILTIN_SEGMENT_HANDLE_PREFIX2}${key4}`,
22014
- vocabulary: opts.vocabulary ?? "closed",
22015
- values: values.map(([value, valueName, legacyValue]) => ({
22016
- value,
22017
- name: valueName,
22018
- legacyPickerId: `${legacyPrefix}${legacyValue ?? value}`
22019
- })),
22020
- ...opts.fallback !== void 0 ? { fallback: opts.fallback } : {},
22021
- legacyPrefix
22022
- };
22023
- }
22024
- var BUILTIN_DIMENSIONS2 = [
22025
- dimension2("registration_state", "Registration State", "reg", [
22026
- ["unregistered", "Unregistered"],
22027
- ["registered", "Registered"]
22028
- ]),
22029
- dimension2(
22030
- "activity_level",
22031
- "Activity State",
22032
- "act",
22033
- [
22034
- ["new", "New"],
22035
- ["high", "High Activity"],
22036
- ["medium", "Medium Activity"],
22037
- ["low", "Low Activity"],
22038
- ["inactive", "Inactive"]
22039
- ],
22040
- { fallback: "new" }
22041
- ),
22042
- dimension2(
22043
- "subscription_state",
22044
- "Subscription State",
22045
- "sub",
22046
- [
22047
- ["none", "None"],
22048
- ["trial", "Trial"],
22049
- ["paid", "Paid"],
22050
- ["cancelled", "Cancelled"]
22051
- ],
22052
- { fallback: "none" }
22053
- ),
22054
- dimension2(
22055
- "trial_type",
22056
- "Trial Type",
22057
- "trial",
22058
- [
22059
- ["none", "Not in trial"],
22060
- ["free_trial", "Free trial", "free"],
22061
- ["reverse_trial", "Reverse trial", "reverse"]
22062
- ],
22063
- { fallback: "none" }
22064
- ),
22065
- dimension2("seat_type", "Seat Type", "seat", [], { vocabulary: "seat_type" }),
22066
- dimension2("buyer_role", "Buyer Role", "role", [
22067
- ["buyer", "Buyer"],
22068
- ["non_buyer", "Non-buyer"]
22069
- ]),
22070
- dimension2(
22071
- "email_type",
22072
- "Email Type",
22073
- "email",
22074
- [
22075
- ["business", "Business email"],
22076
- ["personal", "Personal email"],
22077
- ["unknown", "Unknown"]
22078
- ],
22079
- { fallback: "unknown" }
22080
- ),
22081
- dimension2(
22082
- "billing_health",
22083
- "Billing Health",
22084
- "bill",
22085
- [
22086
- ["no_billing", "No billing", "none"],
22087
- ["good_standing", "Good standing", "good"],
22088
- ["trial_payment_method_attached", "Trial \u2014 payment method attached", "trial_pm"],
22089
- ["payment_method_missing", "Payment method missing", "pm_missing"],
22090
- ["payment_failed", "Payment failed", "failed"],
22091
- ["payment_overdue", "Payment overdue", "overdue"],
22092
- ["cancelled", "Cancelled"]
22093
- ],
22094
- { fallback: "no_billing" }
22095
- ),
22096
- dimension2("region", "Country / Region", "geo", [
22097
- ["us_canada", "US / Canada"],
22098
- ["europe", "Europe"],
22099
- ["rest_of_world", "Rest of World", "row"]
22100
- ]),
22101
- dimension2(
22102
- "device_type",
22103
- "Device Type",
22104
- "dev",
22105
- [
22106
- ["desktop", "Desktop"],
22107
- ["mobile", "Mobile"],
22108
- ["tablet", "Tablet"],
22109
- ["unknown", "Unknown"]
22110
- ],
22111
- { fallback: "unknown" }
22112
- )
22113
- ];
22114
- var BY_KEY2 = new Map(
22115
- BUILTIN_DIMENSIONS2.map((d) => [d.key, d])
22050
+ var { Unrestricted: Unrestricted39 } = DataClassification2;
22051
+ var { Transient: Transient32, Persisted: Persisted24 } = SchemaPersistence2;
22052
+ var { Internal: Internal29, External: External15 } = SchemaExposure2;
22053
+ var PaginationParamsSchema2 = z310.object({
22054
+ page: z310.coerce.number().int().min(1).default(1).meta(Unrestricted39),
22055
+ per_page: z310.coerce.number().int().min(1).max(100).default(25).meta(Unrestricted39)
22056
+ }).meta(
22057
+ {
22058
+ id: "PaginationParams",
22059
+ "x-revturbine-schema-persistence": Transient32,
22060
+ "x-revturbine-schema-exposure": External15
22061
+ }
22116
22062
  );
22117
- function isBuiltinDimensionKey(key4) {
22118
- return BY_KEY2.has(key4);
22119
- }
22120
- function getBuiltinDimension(key4) {
22121
- const found = BY_KEY2.get(key4);
22122
- if (!found) throw new Error(`Unknown built-in dimension '${key4}'`);
22123
- return found;
22124
- }
22125
- function builtinSegmentHandle2(key4, value) {
22126
- return `${BUILTIN_SEGMENT_HANDLE_PREFIX2}${key4}.${value}`;
22127
- }
22128
- function isReservedSegmentHandle(handle) {
22129
- return handle.startsWith(BUILTIN_SEGMENT_HANDLE_PREFIX2);
22130
- }
22131
- function isBuiltinDimensionValue(key4, value) {
22132
- const dim = getBuiltinDimension(key4);
22133
- if (dim.vocabulary === "closed") return dim.values.some((v) => v.value === value);
22134
- return HANDLE_PATTERN2.test(value) && builtinSegmentHandle2(key4, value).length <= MAX_HANDLE_LENGTH;
22135
- }
22136
- function parseBuiltinSegmentHandle(handle) {
22137
- if (!isReservedSegmentHandle(handle)) return void 0;
22138
- const rest = handle.slice(BUILTIN_SEGMENT_HANDLE_PREFIX2.length);
22139
- const dot = rest.indexOf(".");
22140
- if (dot <= 0) return void 0;
22141
- const key4 = rest.slice(0, dot);
22142
- const value = rest.slice(dot + 1);
22143
- if (!isBuiltinDimensionKey(key4) || !isBuiltinDimensionValue(key4, value)) return void 0;
22144
- return { key: key4, value };
22145
- }
22146
- function isGeneratedBuiltinSegment(segment) {
22147
- if (typeof segment.handle !== "string") return false;
22148
- const parsed = parseBuiltinSegmentHandle(segment.handle);
22149
- if (!parsed) return false;
22150
- const dim = getBuiltinDimension(parsed.key);
22151
- if (segment.dimension_id !== dim.dimensionId) return false;
22152
- if (segment.experiment_handle !== void 0 && segment.experiment_handle !== null) return false;
22153
- const predicates = segment.predicates;
22154
- if (!Array.isArray(predicates) || predicates.length !== 1) return false;
22155
- const [p] = predicates;
22156
- if (!p || typeof p !== "object") return false;
22157
- const { field: field2, operator, value } = p;
22158
- return field2 === dim.traitKey && operator === "eq" && value === parsed.value;
22159
- }
22160
- var LEGACY_BY_ID2 = new Map(
22161
- BUILTIN_DIMENSIONS2.flatMap(
22162
- (d) => d.values.map((v) => [v.legacyPickerId, builtinSegmentHandle2(d.key, v.value)])
22163
- )
22063
+ var TimestampFields2 = z310.object({
22064
+ created_at: z310.string().datetime().meta({ ...Unrestricted39, readOnly: true }),
22065
+ updated_at: z310.string().datetime().meta({ ...Unrestricted39, readOnly: true })
22066
+ }).meta(
22067
+ {
22068
+ id: "null",
22069
+ "x-revturbine-schema-persistence": Transient32,
22070
+ "x-revturbine-schema-exposure": External15
22071
+ }
22164
22072
  );
22165
- function identity2(row) {
22166
- return String(row.unique_handle ?? row.handle ?? row.id ?? "");
22167
- }
22073
+ var IdField2 = z310.object({
22074
+ id: z310.string().min(1).meta({ ...Unrestricted39, readOnly: true })
22075
+ }).meta(
22076
+ {
22077
+ id: "null",
22078
+ "x-revturbine-schema-persistence": Transient32,
22079
+ "x-revturbine-schema-exposure": External15
22080
+ }
22081
+ );
22082
+ var TenantIdField2 = z310.object({
22083
+ tenant_id: z310.string().min(1, "tenant_id is required").meta({ ...Unrestricted39, readOnly: true })
22084
+ }).meta(
22085
+ {
22086
+ id: "null",
22087
+ "x-revturbine-schema-persistence": Transient32,
22088
+ "x-revturbine-schema-exposure": Internal29
22089
+ }
22090
+ );
22091
+ var BillingCadenceSchema2 = z310.enum([
22092
+ "monthly",
22093
+ "annual",
22094
+ "quarterly",
22095
+ "one_time",
22096
+ "usage_based"
22097
+ ]).meta(
22098
+ {
22099
+ id: "BillingCadence",
22100
+ "x-revturbine-schema-persistence": Transient32,
22101
+ "x-revturbine-schema-exposure": External15
22102
+ }
22103
+ );
22104
+ var COMPONENT_TYPE_VALUES2 = [
22105
+ "banner",
22106
+ "modal",
22107
+ "tooltip",
22108
+ "sidebar",
22109
+ "inline",
22110
+ "toast",
22111
+ "fullscreen",
22112
+ "email",
22113
+ "sms",
22114
+ "push",
22115
+ "in_page",
22116
+ "button",
22117
+ "full_page",
22118
+ "agent",
22119
+ "cli",
22120
+ "custom"
22121
+ ];
22122
+ var ComponentTypeSchema2 = z310.enum(COMPONENT_TYPE_VALUES2).meta({ id: "ComponentType", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": External15 });
22123
+ var DEFAULT_TEMPLATE_IDS2 = [
22124
+ "button",
22125
+ "plans_page_ctas",
22126
+ "plans_page_full",
22127
+ "inline_gate_message",
22128
+ "tooltip",
22129
+ "in_page_card",
22130
+ "usage_counter",
22131
+ "credit_counter",
22132
+ "trial_counter",
22133
+ "banner",
22134
+ "modal_optional",
22135
+ "modal_blocking",
22136
+ "toast",
22137
+ "email",
22138
+ "sms",
22139
+ "push",
22140
+ "cli",
22141
+ "agent_connector",
22142
+ "custom_in_app"
22143
+ ];
22144
+ var DefaultTemplateIdsSchema2 = z310.enum(DEFAULT_TEMPLATE_IDS2).meta({ id: "DefaultTemplateIds", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": Internal29 });
22145
+ var DEFAULT_TEMPLATE_COMPONENT_TYPES2 = {
22146
+ button: "button",
22147
+ plans_page_ctas: "in_page",
22148
+ plans_page_full: "full_page",
22149
+ inline_gate_message: "inline",
22150
+ tooltip: "tooltip",
22151
+ in_page_card: "in_page",
22152
+ usage_counter: "in_page",
22153
+ credit_counter: "in_page",
22154
+ trial_counter: "in_page",
22155
+ banner: "banner",
22156
+ modal_optional: "modal",
22157
+ modal_blocking: "modal",
22158
+ toast: "toast",
22159
+ email: "email",
22160
+ sms: "sms",
22161
+ push: "push",
22162
+ cli: "cli",
22163
+ agent_connector: "agent",
22164
+ custom_in_app: "custom"
22165
+ };
22166
+ var LEGACY_TEMPLATE_ALIASES2 = Object.freeze({
22167
+ plans_pricing_ctas: "plans_page_ctas",
22168
+ plans_pricing_full: "plans_page_full",
22169
+ credit_balance_counter: "credit_counter",
22170
+ banner_placement: "banner",
22171
+ modal_overlay: "modal_optional",
22172
+ toast_message: "toast",
22173
+ push_notification: "push"
22174
+ });
22175
+ var BUILT_IN_TEMPLATE_COMPONENT_TYPES2 = Object.freeze({
22176
+ ...DEFAULT_TEMPLATE_COMPONENT_TYPES2,
22177
+ ...Object.fromEntries(
22178
+ Object.entries(LEGACY_TEMPLATE_ALIASES2).map(([legacy, id11]) => [legacy, DEFAULT_TEMPLATE_COMPONENT_TYPES2[id11]])
22179
+ ),
22180
+ full_page: "full_page"
22181
+ });
22182
+ var ENTITLEMENT_STATUS_VALUES2 = ["allowed", "limited", "denied"];
22183
+ var PresentationOutcomeSchema2 = z310.enum(["presented", "clicked", "converted", "dismissed", "reminded", "suppressed"]).meta(
22184
+ { id: "PresentationOutcome", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": External15 }
22185
+ );
22186
+ var EntitlementTypeSchema2 = z310.enum([
22187
+ "feature",
22188
+ "capability_tier",
22189
+ "usage_limit",
22190
+ "price_per_unit",
22191
+ "rate_limit",
22192
+ "credits",
22193
+ "seat"
22194
+ ]).meta(
22195
+ {
22196
+ id: "EntitlementType",
22197
+ "x-revturbine-schema-persistence": Transient32,
22198
+ "x-revturbine-schema-exposure": External15
22199
+ }
22200
+ );
22201
+ var CurrencySchema2 = z310.string().regex(/^[a-z]{3}$/, "Currency must be a lowercase ISO 4217 code").default("usd").meta(
22202
+ {
22203
+ id: "Currency",
22204
+ "x-revturbine-schema-persistence": Transient32,
22205
+ "x-revturbine-schema-exposure": External15
22206
+ }
22207
+ );
22208
+ var FeatureFlagValueSchema2 = z310.union([
22209
+ z310.boolean(),
22210
+ z310.number(),
22211
+ z310.string()
22212
+ ]).meta(
22213
+ {
22214
+ id: "FeatureFlagValue",
22215
+ "x-revturbine-schema-persistence": Transient32,
22216
+ "x-revturbine-schema-exposure": Internal29
22217
+ }
22218
+ );
22219
+ var NameField2 = z310.string().min(1).max(200);
22220
+ var HandleField2 = z310.string().min(1).max(100);
22221
+ var DescriptionField2 = z310.string().max(500).optional();
22222
+ var MetadataField2 = z310.record(z310.string(), z310.unknown()).default({});
22223
+ var ThresholdPercentField2 = z310.number().int().min(10).max(100).multipleOf(10);
22224
+ var NullableDatetimeField2 = z310.string().datetime().nullable().default(null);
22225
+ var AnchorFields2 = z310.object({
22226
+ environment_id: z310.string().min(1).default("production").meta({ ...Unrestricted39, readOnly: true })
22227
+ }).meta({ id: "null", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": Internal29 });
22228
+ var VersionFields2 = z310.object({
22229
+ // Which playbook version (the review/deploy unit — was `change_set_id`) staged
22230
+ // this ledger row; null once it is the deployed live row.
22231
+ playbook_version_id: z310.string().nullable().default(null).meta(Unrestricted39),
22232
+ is_current: z310.boolean().default(true).meta({ ...Unrestricted39, readOnly: true }),
22233
+ is_deleted: z310.boolean().default(false).meta({ ...Unrestricted39, readOnly: true }),
22234
+ delete_date: z310.string().datetime().nullable().default(null).meta({ ...Unrestricted39, readOnly: true }),
22235
+ // Monotonic version number within a lineage (v1, v2, v3 …); `base_sequence` is
22236
+ // the live sequence this version was forked from (null for the first version),
22237
+ // so a stale draft is detected when live has moved past it.
22238
+ sequence: z310.number().int().min(1).default(1).meta({ ...Unrestricted39, readOnly: true }),
22239
+ base_sequence: z310.number().int().nullable().default(null).meta({ ...Unrestricted39, readOnly: true })
22240
+ }).meta({ id: "null", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": Internal29 });
22241
+ var AnchorBaseSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).extend({
22242
+ handle: HandleField2.meta({ ...Unrestricted39, readOnly: true }),
22243
+ active: z310.boolean().default(true).meta({ ...Unrestricted39, readOnly: true })
22244
+ });
22245
+ function makeAnchor2(id11) {
22246
+ return AnchorBaseSchema2.meta({
22247
+ id: id11,
22248
+ "x-revturbine-schema-persistence": Persisted24,
22249
+ "x-revturbine-schema-exposure": Internal29
22250
+ });
22251
+ }
22252
+ var SeveritySchema22 = z310.enum(["info", "warning", "critical"]).meta({ id: "Severity", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": Internal29 });
22253
+ var CtaActionTypeSchema2 = z310.enum([
22254
+ "open_checkout",
22255
+ "view_plans",
22256
+ "book_demo",
22257
+ "contact_sales",
22258
+ "complete_onboarding",
22259
+ "invite_teammate",
22260
+ "refer_friend",
22261
+ "verify_work_email",
22262
+ "update_payment_method",
22263
+ "enable_auto_renewal",
22264
+ "manage_subscription",
22265
+ "switch_billing_period",
22266
+ "extend_trial",
22267
+ "open_rt_placement",
22268
+ "dismiss",
22269
+ "snooze",
22270
+ "custom"
22271
+ ]).meta({ id: "CtaActionType", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": External15 });
22272
+ var ObjectiveField2 = HandleField2.optional();
22273
+ function hasCreditResetPeriod2(fields) {
22274
+ return fields.reset_period !== void 0 && fields.reset_period !== null;
22275
+ }
22276
+ function allowancePeriod2(kind, fields, billingPeriod) {
22277
+ if (kind === "seat") return "seat";
22278
+ const raw = kind === "credits" ? fields.reset_period : fields.period_scope;
22279
+ if (raw === "billing_period" || raw === "per_billing_period") {
22280
+ if (billingPeriod === "monthly") return "month";
22281
+ if (billingPeriod === "annual") return "year";
22282
+ return void 0;
22283
+ }
22284
+ if (typeof raw !== "string" || raw === "") return void 0;
22285
+ if (raw === "per_6_hours" || raw === "six_hours") return "six_hours";
22286
+ return raw.startsWith("per_") ? raw.slice(4) : raw;
22287
+ }
22288
+ function assertCompatibleAllowances2(a, b, aBillingPeriod, bBillingPeriod) {
22289
+ const aPeriod = allowancePeriod2(a.kind, a.fields, aBillingPeriod);
22290
+ const bPeriod = allowancePeriod2(b.kind, b.fields, bBillingPeriod);
22291
+ if (a.kind !== b.kind || aPeriod === void 0 || aPeriod !== bPeriod || (a.fields.allocation ?? "account") !== (b.fields.allocation ?? "account") || (a.fields.instance ?? "") !== (b.fields.instance ?? "") || (a.fields.seat_type_id ?? "") !== (b.fields.seat_type_id ?? "")) {
22292
+ throw new Error(`Incompatible purchased allowances: ${a.ruleId} and ${b.ruleId} require matching periods and scopes`);
22293
+ }
22294
+ }
22295
+ var BUILTIN_SEGMENT_HANDLE_PREFIX2 = "rt.";
22296
+ var BUILTIN_TRAIT_KEY_PREFIX2 = "rt_";
22297
+ var MAX_HANDLE_LENGTH = 100;
22298
+ function dimension2(key4, name, legacyAbbr, values, opts = {}) {
22299
+ const legacyPrefix = `seg:${legacyAbbr}:`;
22300
+ return {
22301
+ key: key4,
22302
+ name,
22303
+ traitKey: `${BUILTIN_TRAIT_KEY_PREFIX2}${key4}`,
22304
+ dimensionId: `${BUILTIN_SEGMENT_HANDLE_PREFIX2}${key4}`,
22305
+ vocabulary: opts.vocabulary ?? "closed",
22306
+ values: values.map(([value, valueName, legacyValue]) => ({
22307
+ value,
22308
+ name: valueName,
22309
+ legacyPickerId: `${legacyPrefix}${legacyValue ?? value}`
22310
+ })),
22311
+ ...opts.fallback !== void 0 ? { fallback: opts.fallback } : {},
22312
+ legacyPrefix
22313
+ };
22314
+ }
22315
+ var BUILTIN_DIMENSIONS2 = [
22316
+ dimension2("registration_state", "Registration State", "reg", [
22317
+ ["unregistered", "Unregistered"],
22318
+ ["registered", "Registered"]
22319
+ ]),
22320
+ dimension2(
22321
+ "activity_level",
22322
+ "Activity State",
22323
+ "act",
22324
+ [
22325
+ ["new", "New"],
22326
+ ["high", "High Activity"],
22327
+ ["medium", "Medium Activity"],
22328
+ ["low", "Low Activity"],
22329
+ ["inactive", "Inactive"]
22330
+ ],
22331
+ { fallback: "new" }
22332
+ ),
22333
+ dimension2(
22334
+ "subscription_state",
22335
+ "Subscription State",
22336
+ "sub",
22337
+ [
22338
+ ["none", "None"],
22339
+ ["trial", "Trial"],
22340
+ ["paid", "Paid"],
22341
+ ["cancelled", "Cancelled"]
22342
+ ],
22343
+ { fallback: "none" }
22344
+ ),
22345
+ dimension2(
22346
+ "trial_type",
22347
+ "Trial Type",
22348
+ "trial",
22349
+ [
22350
+ ["none", "Not in trial"],
22351
+ ["free_trial", "Free trial", "free"],
22352
+ ["reverse_trial", "Reverse trial", "reverse"]
22353
+ ],
22354
+ { fallback: "none" }
22355
+ ),
22356
+ dimension2("seat_type", "Seat Type", "seat", [], { vocabulary: "seat_type" }),
22357
+ dimension2("buyer_role", "Buyer Role", "role", [
22358
+ ["buyer", "Buyer"],
22359
+ ["non_buyer", "Non-buyer"]
22360
+ ]),
22361
+ dimension2(
22362
+ "email_type",
22363
+ "Email Type",
22364
+ "email",
22365
+ [
22366
+ ["business", "Business email"],
22367
+ ["personal", "Personal email"],
22368
+ ["unknown", "Unknown"]
22369
+ ],
22370
+ { fallback: "unknown" }
22371
+ ),
22372
+ dimension2(
22373
+ "billing_health",
22374
+ "Billing Health",
22375
+ "bill",
22376
+ [
22377
+ ["no_billing", "No billing", "none"],
22378
+ ["good_standing", "Good standing", "good"],
22379
+ ["trial_payment_method_attached", "Trial \u2014 payment method attached", "trial_pm"],
22380
+ ["payment_method_missing", "Payment method missing", "pm_missing"],
22381
+ ["payment_failed", "Payment failed", "failed"],
22382
+ ["payment_overdue", "Payment overdue", "overdue"],
22383
+ ["cancelled", "Cancelled"]
22384
+ ],
22385
+ { fallback: "no_billing" }
22386
+ ),
22387
+ dimension2("region", "Country / Region", "geo", [
22388
+ ["us_canada", "US / Canada"],
22389
+ ["europe", "Europe"],
22390
+ ["rest_of_world", "Rest of World", "row"]
22391
+ ]),
22392
+ dimension2(
22393
+ "device_type",
22394
+ "Device Type",
22395
+ "dev",
22396
+ [
22397
+ ["desktop", "Desktop"],
22398
+ ["mobile", "Mobile"],
22399
+ ["tablet", "Tablet"],
22400
+ ["unknown", "Unknown"]
22401
+ ],
22402
+ { fallback: "unknown" }
22403
+ )
22404
+ ];
22405
+ var BY_KEY2 = new Map(
22406
+ BUILTIN_DIMENSIONS2.map((d) => [d.key, d])
22407
+ );
22408
+ function isBuiltinDimensionKey(key4) {
22409
+ return BY_KEY2.has(key4);
22410
+ }
22411
+ function getBuiltinDimension(key4) {
22412
+ const found = BY_KEY2.get(key4);
22413
+ if (!found) throw new Error(`Unknown built-in dimension '${key4}'`);
22414
+ return found;
22415
+ }
22416
+ function builtinSegmentHandle2(key4, value) {
22417
+ return `${BUILTIN_SEGMENT_HANDLE_PREFIX2}${key4}.${value}`;
22418
+ }
22419
+ function isReservedSegmentHandle(handle) {
22420
+ return handle.startsWith(BUILTIN_SEGMENT_HANDLE_PREFIX2);
22421
+ }
22422
+ function isBuiltinDimensionValue(key4, value) {
22423
+ const dim = getBuiltinDimension(key4);
22424
+ if (dim.vocabulary === "closed") return dim.values.some((v) => v.value === value);
22425
+ return HANDLE_PATTERN2.test(value) && builtinSegmentHandle2(key4, value).length <= MAX_HANDLE_LENGTH;
22426
+ }
22427
+ function parseBuiltinSegmentHandle(handle) {
22428
+ if (!isReservedSegmentHandle(handle)) return void 0;
22429
+ const rest = handle.slice(BUILTIN_SEGMENT_HANDLE_PREFIX2.length);
22430
+ const dot = rest.indexOf(".");
22431
+ if (dot <= 0) return void 0;
22432
+ const key4 = rest.slice(0, dot);
22433
+ const value = rest.slice(dot + 1);
22434
+ if (!isBuiltinDimensionKey(key4) || !isBuiltinDimensionValue(key4, value)) return void 0;
22435
+ return { key: key4, value };
22436
+ }
22437
+ function isGeneratedBuiltinSegment(segment) {
22438
+ if (typeof segment.handle !== "string") return false;
22439
+ const parsed = parseBuiltinSegmentHandle(segment.handle);
22440
+ if (!parsed) return false;
22441
+ const dim = getBuiltinDimension(parsed.key);
22442
+ if (segment.dimension_id !== dim.dimensionId) return false;
22443
+ if (segment.experiment_handle !== void 0 && segment.experiment_handle !== null) return false;
22444
+ const predicates = segment.predicates;
22445
+ if (!Array.isArray(predicates) || predicates.length !== 1) return false;
22446
+ const [p] = predicates;
22447
+ if (!p || typeof p !== "object") return false;
22448
+ const { field: field2, operator, value } = p;
22449
+ return field2 === dim.traitKey && operator === "eq" && value === parsed.value;
22450
+ }
22451
+ var LEGACY_BY_ID2 = new Map(
22452
+ BUILTIN_DIMENSIONS2.flatMap(
22453
+ (d) => d.values.map((v) => [v.legacyPickerId, builtinSegmentHandle2(d.key, v.value)])
22454
+ )
22455
+ );
22456
+ function identity2(row) {
22457
+ return String(row.unique_handle ?? row.handle ?? row.id ?? "");
22458
+ }
22168
22459
  function matchesIdentity2(row, key4) {
22169
22460
  return typeof key4 === "string" && [row.unique_handle, row.handle, row.id].includes(key4);
22170
22461
  }
@@ -22289,6 +22580,7 @@ function runSemanticRules(graph, opts = {}) {
22289
22580
  ...checkLimitRuleEnforcement(graph),
22290
22581
  ...checkPublicVariationCollisions(graph),
22291
22582
  ...checkPayloadCtaOverflow(graph),
22583
+ ...checkPayloadTemplateRefs(graph),
22292
22584
  ...checkPlacementAuthoring(graph),
22293
22585
  ...checkTrialRuleWidestAudience(graph),
22294
22586
  ...checkSegmentExperimentRefs(graph),
@@ -22752,6 +23044,42 @@ function checkPayloadCtaOverflow(graph) {
22752
23044
  }
22753
23045
  return findings;
22754
23046
  }
23047
+ function checkPayloadTemplateRefs(graph) {
23048
+ const known = new Set(Object.keys(BUILT_IN_TEMPLATE_COMPONENT_TYPES2));
23049
+ for (const template of graph.surface_templates ?? []) {
23050
+ if (typeof template.id !== "string" || !template.id) return [];
23051
+ known.add(template.id);
23052
+ }
23053
+ const findings = [];
23054
+ const check = (payloadId, surfaces, path3) => {
23055
+ if (!Array.isArray(surfaces)) return;
23056
+ surfaces.forEach((surface, index) => {
23057
+ if (!isRecord3(surface) || typeof surface.template_id !== "string") return;
23058
+ if (known.has(surface.template_id)) return;
23059
+ findings.push(finding("VAL-PLC-11", {
23060
+ object_type: "placement_payload",
23061
+ object_id: payloadId,
23062
+ field: "template_id",
23063
+ path: [...path3, "surfaces", index, "template_id"],
23064
+ studio: "placements"
23065
+ }, {
23066
+ message: `Payload '${payloadId}' uses surface template '${surface.template_id}', which is neither built in nor defined in surface_templates. SDKs skip this payload.`,
23067
+ detail: "Use a built-in template id, or add a surface_templates entry with this id and a surface_type."
23068
+ }));
23069
+ });
23070
+ };
23071
+ (graph.placements ?? []).forEach((placement, placementIndex) => {
23072
+ const payloads = Array.isArray(placement.payloads) ? placement.payloads : [];
23073
+ payloads.forEach((payload, payloadIndex) => {
23074
+ if (!isRecord3(payload)) return;
23075
+ check(String(payload.id ?? placement.id ?? ""), payload.surfaces, ["placements", placementIndex, "payloads", payloadIndex]);
23076
+ });
23077
+ });
23078
+ (graph.placement_payloads ?? []).forEach((payload, index) => {
23079
+ check(String(payload.payload_id ?? payload.id ?? ""), payload.surfaces, ["placement_payloads", index]);
23080
+ });
23081
+ return findings;
23082
+ }
22755
23083
  function checkTrialRuleWidestAudience(graph) {
22756
23084
  const findings = [];
22757
23085
  for (const rule of graph.free_trial_rules ?? []) {
@@ -23022,374 +23350,112 @@ function checkPublicVariationCollisions(graph) {
23022
23350
  ...collectPublicCollisions(graph.addon_variations, "addon_variation", "addon_id")
23023
23351
  ];
23024
23352
  }
23025
- function collectPublicCollisions(rows, objectType, parentField) {
23026
- if (!rows?.length) return [];
23027
- const parentHandleField = parentField === "plan_id" ? "plan_handle" : "addon_handle";
23028
- const publicByTuple = /* @__PURE__ */ new Map();
23029
- for (const row of rows) {
23030
- if (row.is_current === false) continue;
23031
- if (String(row.visibility ?? "public") !== "public") continue;
23032
- const parentRef = row[parentField] ?? row[parentHandleField];
23033
- if (parentRef == null || String(parentRef) === "") continue;
23034
- const segmentRef = row.segment_id ?? row.segment_handle;
23035
- const tenant = String(row.tenant_id ?? "");
23036
- const parent2 = String(parentRef);
23037
- const period = String(row.billing_period ?? "");
23038
- const segment = segmentRef == null ? "_default" : String(segmentRef);
23039
- const key4 = `${tenant} ${parent2} ${period} ${segment}`;
23040
- const id11 = String(row.handle ?? row.id ?? "");
23041
- const bucket = publicByTuple.get(key4);
23042
- if (bucket) bucket.ids.push(id11);
23043
- else publicByTuple.set(key4, { ids: [id11], parent: parent2, period, segment });
23044
- }
23045
- const findings = [];
23046
- for (const bucket of publicByTuple.values()) {
23047
- if (bucket.ids.length < 2) continue;
23048
- const allIds = [...bucket.ids].sort();
23049
- const segmentLabel = bucket.segment === "_default" ? "no segment" : `segment ${bucket.segment}`;
23050
- const parentLabel = parentField === "plan_id" ? "plan" : "add-on";
23051
- for (const id11 of bucket.ids) {
23052
- findings.push(
23053
- finding(
23054
- "VAL-PLN-06",
23055
- {
23056
- object_type: objectType,
23057
- object_id: id11,
23058
- field: "visibility",
23059
- studio: "plans-entitlements"
23060
- },
23061
- {
23062
- message: `Variation '${id11}' is one of ${bucket.ids.length} marked Public for the same ${parentLabel}, billing period '${bucket.period}', and ${segmentLabel} \u2014 only one may be Public.`,
23063
- detail: `Set all but one of [${allIds.join(", ")}] to Unlisted or Legacy before deploying.`
23064
- }
23065
- )
23066
- );
23067
- }
23068
- }
23069
- return findings;
23070
- }
23071
- function fieldLabel(path3) {
23072
- if (!path3 || path3.length === 0) return "This value";
23073
- return String(path3[path3.length - 1]);
23074
- }
23075
- function messageForZodIssue(issue) {
23076
- const field2 = fieldLabel(issue.path);
23077
- switch (issue.code) {
23078
- case "invalid_type":
23079
- return issue.input === void 0 ? `${field2} is required.` : `${field2} must be a ${issue.expected ?? "valid value"}.`;
23080
- case "invalid_value": {
23081
- const values = (issue.values ?? []).filter(
23082
- (v) => ["string", "number", "boolean"].includes(typeof v)
23083
- );
23084
- return values.length > 0 ? `${field2} must be one of: ${values.map((v) => `'${String(v)}'`).join(", ")}.` : `${field2} must be one of the allowed values.`;
23085
- }
23086
- case "invalid_format":
23087
- return `${field2} isn't in a valid format.`;
23088
- case "too_small":
23089
- return `${field2} is too small.`;
23090
- case "too_big":
23091
- return `${field2} is too large.`;
23092
- case "unrecognized_keys":
23093
- return `${field2} isn't a recognized setting.`;
23094
- default:
23095
- return issue.message ?? `${field2} is invalid.`;
23096
- }
23097
- }
23098
- function targetRefForIssue(issue, basePath) {
23099
- const path3 = [...basePath, ...issue.path ?? []].map(
23100
- (seg2) => typeof seg2 === "number" ? seg2 : String(seg2)
23101
- );
23102
- return { path: path3 };
23103
- }
23104
- function zodErrorToFindings(error2, opts = {}) {
23105
- const basePath = opts.basePath ?? [];
23106
- return error2.issues.map((issue) => ({
23107
- code: issue.code ?? "invalid",
23108
- severity: "error_draft",
23109
- targetRef: targetRefForIssue(issue, basePath),
23110
- message: messageForZodIssue(issue),
23111
- ...opts.specRef ? { specRef: opts.specRef } : {}
23112
- }));
23113
- }
23114
- function spotlights(finding2, focus) {
23115
- if (!focus) return false;
23116
- const { object_type, object_id } = finding2.targetRef;
23117
- if (focus.object_id != null && object_id !== focus.object_id) return false;
23118
- if (focus.object_type != null && object_type !== focus.object_type) return false;
23119
- return focus.object_id != null || focus.object_type != null;
23120
- }
23121
- function evaluate(graph, opts = {}) {
23122
- const findings = [];
23123
- if (opts.structuralErrors) {
23124
- const errors2 = Array.isArray(opts.structuralErrors) ? opts.structuralErrors : [opts.structuralErrors];
23125
- for (const error2 of errors2) findings.push(...zodErrorToFindings(error2));
23126
- }
23127
- findings.push(...runSemanticRules(graph, { knownMetricIds: opts.knownMetricIds }));
23128
- if (!opts.focus) return findings;
23129
- return findings.map((f) => spotlights(f, opts.focus) ? { ...f, spotlight: true } : f);
23130
- }
23131
- var SchemaPersistence2 = {
23132
- Persisted: "persisted",
23133
- Transient: "transient"
23134
- };
23135
- var SchemaExposure2 = {
23136
- Internal: "internal",
23137
- External: "external"
23138
- };
23139
- var DataClassification2 = {
23140
- Pii: { "x-revturbine-data-classification": "pii" },
23141
- Financial: { "x-revturbine-data-classification": "financial" },
23142
- Unrestricted: { "x-revturbine-data-classification": "unrestricted" },
23143
- /**
23144
- * Machine-generated evaluation exhaust — supersession records, entitlement
23145
- * grants, placement decision traces. Carries no personal or financial data
23146
- * itself, but describes what the engine decided about a user, so it is not
23147
- * `Unrestricted` either. Applied at the MODEL level (plan 182 REQ-7); it was
23148
- * already in use as a raw string literal on four schemas before being
23149
- * declared here, which left `DataClassificationValue` unable to represent it.
23150
- */
23151
- Operational: { "x-revturbine-data-classification": "operational" }
23152
- };
23153
- var SCHEMA_EXPOSURE_META_KEY2 = "x-revturbine-schema-exposure";
23154
- var READ_ONLY_META_KEY2 = "readOnly";
23155
- var DECISION_ONLY_META_KEY2 = "x-revturbine-decision-only";
23156
- var DecisionOnly2 = { [DECISION_ONLY_META_KEY2]: true };
23157
- var ClientSafe2 = { [SCHEMA_EXPOSURE_META_KEY2]: SchemaExposure2.External };
23158
- var ServerOnly2 = { [SCHEMA_EXPOSURE_META_KEY2]: SchemaExposure2.Internal };
23159
- function toWritableSchema2(schema) {
23160
- const writableShape = {};
23161
- for (const [fieldName, fieldSchema] of Object.entries(schema.shape)) {
23162
- if (typeof fieldSchema !== "object" || fieldSchema === null) {
23163
- continue;
23164
- }
23165
- const isReadOnly = fieldSchema.meta()?.[READ_ONLY_META_KEY2] === true;
23166
- if (!isReadOnly) {
23167
- writableShape[fieldName] = fieldSchema;
23168
- }
23169
- }
23170
- return z210.object(writableShape);
23171
- }
23172
- function toCreateSchema2(schema) {
23173
- const writable = toWritableSchema2(schema);
23174
- const handleField = writable.shape["handle"];
23175
- if (handleField && typeof handleField.regex === "function") {
23176
- return writable.extend({ handle: handleField.regex(HANDLE_PATTERN2) });
23177
- }
23178
- return writable;
23179
- }
23180
- var { Unrestricted: Unrestricted39 } = DataClassification2;
23181
- var { Transient: Transient32, Persisted: Persisted24 } = SchemaPersistence2;
23182
- var { Internal: Internal29, External: External15 } = SchemaExposure2;
23183
- var PaginationParamsSchema2 = z310.object({
23184
- page: z310.coerce.number().int().min(1).default(1).meta(Unrestricted39),
23185
- per_page: z310.coerce.number().int().min(1).max(100).default(25).meta(Unrestricted39)
23186
- }).meta(
23187
- {
23188
- id: "PaginationParams",
23189
- "x-revturbine-schema-persistence": Transient32,
23190
- "x-revturbine-schema-exposure": External15
23191
- }
23192
- );
23193
- var TimestampFields2 = z310.object({
23194
- created_at: z310.string().datetime().meta({ ...Unrestricted39, readOnly: true }),
23195
- updated_at: z310.string().datetime().meta({ ...Unrestricted39, readOnly: true })
23196
- }).meta(
23197
- {
23198
- id: "null",
23199
- "x-revturbine-schema-persistence": Transient32,
23200
- "x-revturbine-schema-exposure": External15
23201
- }
23202
- );
23203
- var IdField2 = z310.object({
23204
- id: z310.string().min(1).meta({ ...Unrestricted39, readOnly: true })
23205
- }).meta(
23206
- {
23207
- id: "null",
23208
- "x-revturbine-schema-persistence": Transient32,
23209
- "x-revturbine-schema-exposure": External15
23210
- }
23211
- );
23212
- var TenantIdField2 = z310.object({
23213
- tenant_id: z310.string().min(1, "tenant_id is required").meta({ ...Unrestricted39, readOnly: true })
23214
- }).meta(
23215
- {
23216
- id: "null",
23217
- "x-revturbine-schema-persistence": Transient32,
23218
- "x-revturbine-schema-exposure": Internal29
23219
- }
23220
- );
23221
- var BillingCadenceSchema2 = z310.enum([
23222
- "monthly",
23223
- "annual",
23224
- "quarterly",
23225
- "one_time",
23226
- "usage_based"
23227
- ]).meta(
23228
- {
23229
- id: "BillingCadence",
23230
- "x-revturbine-schema-persistence": Transient32,
23231
- "x-revturbine-schema-exposure": External15
23353
+ function collectPublicCollisions(rows, objectType, parentField) {
23354
+ if (!rows?.length) return [];
23355
+ const parentHandleField = parentField === "plan_id" ? "plan_handle" : "addon_handle";
23356
+ const publicByTuple = /* @__PURE__ */ new Map();
23357
+ for (const row of rows) {
23358
+ if (row.is_current === false) continue;
23359
+ if (String(row.visibility ?? "public") !== "public") continue;
23360
+ const parentRef = row[parentField] ?? row[parentHandleField];
23361
+ if (parentRef == null || String(parentRef) === "") continue;
23362
+ const segmentRef = row.segment_id ?? row.segment_handle;
23363
+ const tenant = String(row.tenant_id ?? "");
23364
+ const parent2 = String(parentRef);
23365
+ const period = String(row.billing_period ?? "");
23366
+ const segment = segmentRef == null ? "_default" : String(segmentRef);
23367
+ const key4 = `${tenant} ${parent2} ${period} ${segment}`;
23368
+ const id11 = String(row.handle ?? row.id ?? "");
23369
+ const bucket = publicByTuple.get(key4);
23370
+ if (bucket) bucket.ids.push(id11);
23371
+ else publicByTuple.set(key4, { ids: [id11], parent: parent2, period, segment });
23232
23372
  }
23233
- );
23234
- var COMPONENT_TYPE_VALUES2 = [
23235
- "banner",
23236
- "modal",
23237
- "tooltip",
23238
- "sidebar",
23239
- "inline",
23240
- "toast",
23241
- "fullscreen",
23242
- "email",
23243
- "sms",
23244
- "push",
23245
- "in_page",
23246
- "button",
23247
- "full_page",
23248
- "agent",
23249
- "cli",
23250
- "custom"
23251
- ];
23252
- var ComponentTypeSchema2 = z310.enum(COMPONENT_TYPE_VALUES2).meta({ id: "ComponentType", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": External15 });
23253
- var DEFAULT_TEMPLATE_IDS2 = [
23254
- "button",
23255
- "plans_page_ctas",
23256
- "plans_page_full",
23257
- "inline_gate_message",
23258
- "tooltip",
23259
- "in_page_card",
23260
- "usage_counter",
23261
- "credit_counter",
23262
- "trial_counter",
23263
- "banner",
23264
- "modal_optional",
23265
- "modal_blocking",
23266
- "toast",
23267
- "email",
23268
- "sms",
23269
- "push",
23270
- "cli",
23271
- "agent_connector",
23272
- "custom_in_app"
23273
- ];
23274
- var DefaultTemplateIdsSchema2 = z310.enum(DEFAULT_TEMPLATE_IDS2).meta({ id: "DefaultTemplateIds", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": Internal29 });
23275
- var DEFAULT_TEMPLATE_COMPONENT_TYPES2 = {
23276
- button: "button",
23277
- plans_page_ctas: "in_page",
23278
- plans_page_full: "full_page",
23279
- inline_gate_message: "inline",
23280
- tooltip: "tooltip",
23281
- in_page_card: "in_page",
23282
- usage_counter: "in_page",
23283
- credit_counter: "in_page",
23284
- trial_counter: "in_page",
23285
- banner: "banner",
23286
- modal_optional: "modal",
23287
- modal_blocking: "modal",
23288
- toast: "toast",
23289
- email: "email",
23290
- sms: "sms",
23291
- push: "push",
23292
- cli: "cli",
23293
- agent_connector: "agent",
23294
- custom_in_app: "custom"
23295
- };
23296
- var BUILT_IN_TEMPLATE_COMPONENT_TYPES2 = Object.freeze({
23297
- ...DEFAULT_TEMPLATE_COMPONENT_TYPES2,
23298
- modal_overlay: "modal",
23299
- banner_placement: "banner",
23300
- full_page: "full_page"
23301
- });
23302
- var ENTITLEMENT_STATUS_VALUES2 = ["allowed", "limited", "denied"];
23303
- var PresentationOutcomeSchema2 = z310.enum(["presented", "clicked", "converted", "dismissed", "reminded", "suppressed"]).meta(
23304
- { id: "PresentationOutcome", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": External15 }
23305
- );
23306
- var EntitlementTypeSchema2 = z310.enum([
23307
- "feature",
23308
- "capability_tier",
23309
- "usage_limit",
23310
- "price_per_unit",
23311
- "rate_limit",
23312
- "credits",
23313
- "seat"
23314
- ]).meta(
23315
- {
23316
- id: "EntitlementType",
23317
- "x-revturbine-schema-persistence": Transient32,
23318
- "x-revturbine-schema-exposure": External15
23373
+ const findings = [];
23374
+ for (const bucket of publicByTuple.values()) {
23375
+ if (bucket.ids.length < 2) continue;
23376
+ const allIds = [...bucket.ids].sort();
23377
+ const segmentLabel = bucket.segment === "_default" ? "no segment" : `segment ${bucket.segment}`;
23378
+ const parentLabel = parentField === "plan_id" ? "plan" : "add-on";
23379
+ for (const id11 of bucket.ids) {
23380
+ findings.push(
23381
+ finding(
23382
+ "VAL-PLN-06",
23383
+ {
23384
+ object_type: objectType,
23385
+ object_id: id11,
23386
+ field: "visibility",
23387
+ studio: "plans-entitlements"
23388
+ },
23389
+ {
23390
+ message: `Variation '${id11}' is one of ${bucket.ids.length} marked Public for the same ${parentLabel}, billing period '${bucket.period}', and ${segmentLabel} \u2014 only one may be Public.`,
23391
+ detail: `Set all but one of [${allIds.join(", ")}] to Unlisted or Legacy before deploying.`
23392
+ }
23393
+ )
23394
+ );
23395
+ }
23319
23396
  }
23320
- );
23321
- var CurrencySchema2 = z310.string().regex(/^[a-z]{3}$/, "Currency must be a lowercase ISO 4217 code").default("usd").meta(
23322
- {
23323
- id: "Currency",
23324
- "x-revturbine-schema-persistence": Transient32,
23325
- "x-revturbine-schema-exposure": External15
23397
+ return findings;
23398
+ }
23399
+ function fieldLabel(path3) {
23400
+ if (!path3 || path3.length === 0) return "This value";
23401
+ return String(path3[path3.length - 1]);
23402
+ }
23403
+ function messageForZodIssue(issue) {
23404
+ const field2 = fieldLabel(issue.path);
23405
+ switch (issue.code) {
23406
+ case "invalid_type":
23407
+ return issue.input === void 0 ? `${field2} is required.` : `${field2} must be a ${issue.expected ?? "valid value"}.`;
23408
+ case "invalid_value": {
23409
+ const values = (issue.values ?? []).filter(
23410
+ (v) => ["string", "number", "boolean"].includes(typeof v)
23411
+ );
23412
+ return values.length > 0 ? `${field2} must be one of: ${values.map((v) => `'${String(v)}'`).join(", ")}.` : `${field2} must be one of the allowed values.`;
23413
+ }
23414
+ case "invalid_format":
23415
+ return `${field2} isn't in a valid format.`;
23416
+ case "too_small":
23417
+ return `${field2} is too small.`;
23418
+ case "too_big":
23419
+ return `${field2} is too large.`;
23420
+ case "unrecognized_keys":
23421
+ return `${field2} isn't a recognized setting.`;
23422
+ default:
23423
+ return issue.message ?? `${field2} is invalid.`;
23326
23424
  }
23327
- );
23328
- var FeatureFlagValueSchema2 = z310.union([
23329
- z310.boolean(),
23330
- z310.number(),
23331
- z310.string()
23332
- ]).meta(
23333
- {
23334
- id: "FeatureFlagValue",
23335
- "x-revturbine-schema-persistence": Transient32,
23336
- "x-revturbine-schema-exposure": Internal29
23425
+ }
23426
+ function targetRefForIssue(issue, basePath) {
23427
+ const path3 = [...basePath, ...issue.path ?? []].map(
23428
+ (seg2) => typeof seg2 === "number" ? seg2 : String(seg2)
23429
+ );
23430
+ return { path: path3 };
23431
+ }
23432
+ function zodErrorToFindings(error2, opts = {}) {
23433
+ const basePath = opts.basePath ?? [];
23434
+ return error2.issues.map((issue) => ({
23435
+ code: issue.code ?? "invalid",
23436
+ severity: "error_draft",
23437
+ targetRef: targetRefForIssue(issue, basePath),
23438
+ message: messageForZodIssue(issue),
23439
+ ...opts.specRef ? { specRef: opts.specRef } : {}
23440
+ }));
23441
+ }
23442
+ function spotlights(finding2, focus) {
23443
+ if (!focus) return false;
23444
+ const { object_type, object_id } = finding2.targetRef;
23445
+ if (focus.object_id != null && object_id !== focus.object_id) return false;
23446
+ if (focus.object_type != null && object_type !== focus.object_type) return false;
23447
+ return focus.object_id != null || focus.object_type != null;
23448
+ }
23449
+ function evaluate(graph, opts = {}) {
23450
+ const findings = [];
23451
+ if (opts.structuralErrors) {
23452
+ const errors2 = Array.isArray(opts.structuralErrors) ? opts.structuralErrors : [opts.structuralErrors];
23453
+ for (const error2 of errors2) findings.push(...zodErrorToFindings(error2));
23337
23454
  }
23338
- );
23339
- var NameField2 = z310.string().min(1).max(200);
23340
- var HandleField2 = z310.string().min(1).max(100);
23341
- var DescriptionField2 = z310.string().max(500).optional();
23342
- var MetadataField2 = z310.record(z310.string(), z310.unknown()).default({});
23343
- var ThresholdPercentField2 = z310.number().int().min(10).max(100).multipleOf(10);
23344
- var NullableDatetimeField2 = z310.string().datetime().nullable().default(null);
23345
- var AnchorFields2 = z310.object({
23346
- environment_id: z310.string().min(1).default("production").meta({ ...Unrestricted39, readOnly: true })
23347
- }).meta({ id: "null", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": Internal29 });
23348
- var VersionFields2 = z310.object({
23349
- // Which playbook version (the review/deploy unit — was `change_set_id`) staged
23350
- // this ledger row; null once it is the deployed live row.
23351
- playbook_version_id: z310.string().nullable().default(null).meta(Unrestricted39),
23352
- is_current: z310.boolean().default(true).meta({ ...Unrestricted39, readOnly: true }),
23353
- is_deleted: z310.boolean().default(false).meta({ ...Unrestricted39, readOnly: true }),
23354
- delete_date: z310.string().datetime().nullable().default(null).meta({ ...Unrestricted39, readOnly: true }),
23355
- // Monotonic version number within a lineage (v1, v2, v3 …); `base_sequence` is
23356
- // the live sequence this version was forked from (null for the first version),
23357
- // so a stale draft is detected when live has moved past it.
23358
- sequence: z310.number().int().min(1).default(1).meta({ ...Unrestricted39, readOnly: true }),
23359
- base_sequence: z310.number().int().nullable().default(null).meta({ ...Unrestricted39, readOnly: true })
23360
- }).meta({ id: "null", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": Internal29 });
23361
- var AnchorBaseSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).extend({
23362
- handle: HandleField2.meta({ ...Unrestricted39, readOnly: true }),
23363
- active: z310.boolean().default(true).meta({ ...Unrestricted39, readOnly: true })
23364
- });
23365
- function makeAnchor2(id11) {
23366
- return AnchorBaseSchema2.meta({
23367
- id: id11,
23368
- "x-revturbine-schema-persistence": Persisted24,
23369
- "x-revturbine-schema-exposure": Internal29
23370
- });
23455
+ findings.push(...runSemanticRules(graph, { knownMetricIds: opts.knownMetricIds }));
23456
+ if (!opts.focus) return findings;
23457
+ return findings.map((f) => spotlights(f, opts.focus) ? { ...f, spotlight: true } : f);
23371
23458
  }
23372
- var SeveritySchema22 = z310.enum(["info", "warning", "critical"]).meta({ id: "Severity", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": Internal29 });
23373
- var CtaActionTypeSchema2 = z310.enum([
23374
- "open_checkout",
23375
- "view_plans",
23376
- "book_demo",
23377
- "contact_sales",
23378
- "complete_onboarding",
23379
- "invite_teammate",
23380
- "refer_friend",
23381
- "verify_work_email",
23382
- "update_payment_method",
23383
- "enable_auto_renewal",
23384
- "manage_subscription",
23385
- "switch_billing_period",
23386
- "extend_trial",
23387
- "open_rt_placement",
23388
- "dismiss",
23389
- "snooze",
23390
- "custom"
23391
- ]).meta({ id: "CtaActionType", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": External15 });
23392
- var ObjectiveField2 = HandleField2.optional();
23393
23459
  var IdentityKind2 = {
23394
23460
  /** Author-given, human-meaningful handle (plans, entitlements, segments, …). */
23395
23461
  Named: "named",
@@ -25970,7 +26036,7 @@ function repairDeprecatedFields(config, schema = PlaybookObjectSchema2) {
25970
26036
  }
25971
26037
 
25972
26038
  // src/schema/version.ts
25973
- var SCHEMA_VERSION = "0.1.522";
26039
+ var SCHEMA_VERSION = "0.1.524";
25974
26040
 
25975
26041
  // src/lib/credentials.ts
25976
26042
  import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revturbine/cli",
3
- "version": "0.21.1",
3
+ "version": "0.21.2",
4
4
  "description": "revturbine — validate RevTurbine Playbooks and ship them to a RevTurbine instance through the playbook-version lifecycle (draft → Release).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -30,7 +30,7 @@
30
30
  "verify": "npm run typecheck && npm run lint && npm run check:schema && npm run test"
31
31
  },
32
32
  "dependencies": {
33
- "@revturbine/sdk": ">=0.10.9 <1",
33
+ "@revturbine/sdk": ">=0.12.5 <1",
34
34
  "commander": "^15.0.0",
35
35
  "semver": "^7.8.5",
36
36
  "zod": "4.4.3"