@revturbine/cli 0.9.1 → 0.11.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.
Files changed (4) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1 -0
  3. package/dist/cli.js +3123 -344
  4. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -52,9 +52,9 @@ var DataClassification = {
52
52
  Unrestricted: { "x-revturbine-data-classification": "unrestricted" }
53
53
  };
54
54
  var READ_ONLY_META_KEY = "readOnly";
55
- function toWritableSchema(schema2) {
55
+ function toWritableSchema(schema) {
56
56
  const writableShape = {};
57
- for (const [fieldName, fieldSchema] of Object.entries(schema2.shape)) {
57
+ for (const [fieldName, fieldSchema] of Object.entries(schema.shape)) {
58
58
  if (typeof fieldSchema !== "object" || fieldSchema === null) {
59
59
  continue;
60
60
  }
@@ -65,8 +65,8 @@ function toWritableSchema(schema2) {
65
65
  }
66
66
  return z.object(writableShape);
67
67
  }
68
- function toCreateSchema(schema2) {
69
- const writable = toWritableSchema(schema2);
68
+ function toCreateSchema(schema) {
69
+ const writable = toWritableSchema(schema);
70
70
  const handleField = writable.shape["handle"];
71
71
  if (handleField && typeof handleField.regex === "function") {
72
72
  return writable.extend({ handle: handleField.regex(HANDLE_PATTERN) });
@@ -375,6 +375,11 @@ var PlanSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(Ancho
375
375
  handle: HandleField.meta(Unrestricted2),
376
376
  tier_position: z5.number().int().min(0).default(0).meta(Unrestricted2),
377
377
  sort_order: z5.number().int().default(0).meta(Unrestricted2),
378
+ // Plan-level visibility default (plan 91 Part B). Distinct from the
379
+ // per-variation `PlanVariationSchema.visibility`: a free/custom tier with no
380
+ // priced variation can still be unlisted/legacy. Persisted so it round-trips
381
+ // (plan 146 found it was declared on the portable config but had no column).
382
+ visibility: PlanVisibilitySchema.default("public").meta(Unrestricted2),
378
383
  metadata: MetadataField.meta(Unrestricted2)
379
384
  }).meta(
380
385
  {
@@ -415,6 +420,9 @@ var AddOnSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(Anch
415
420
  name: NameField.meta(Unrestricted2),
416
421
  handle: HandleField.meta(Unrestricted2),
417
422
  sort_order: z5.number().int().default(0).meta(Unrestricted2),
423
+ // Add-on visibility default — same rationale as PlanSchema (plan 91 Part B);
424
+ // metadata, not price, so it lives on the add-on independent of variations.
425
+ visibility: PlanVisibilitySchema.default("public").meta(Unrestricted2),
418
426
  metadata: MetadataField.meta(Unrestricted2)
419
427
  }).meta(
420
428
  {
@@ -862,97 +870,9 @@ var EntitlementRuleTargetSchema = z6.object({
862
870
  }).meta(
863
871
  { id: "EntitlementRuleTarget", "x-revturbine-schema-persistence": Transient3, "x-revturbine-schema-exposure": External3 }
864
872
  );
865
- var EntitlementRulePeriodUnitSchema = z6.enum(["month", "day", "week", "quarter", "year", "billing_period", "on_purchase", "hour", "six_hours"]).meta(
873
+ var EntitlementRulePeriodUnitSchema = z6.enum(["month", "day", "week", "quarter", "year", "billing_period", "hour", "six_hours"]).meta(
866
874
  { id: "EntitlementRulePeriodUnit", "x-revturbine-schema-persistence": Transient3, "x-revturbine-schema-exposure": External3 }
867
875
  );
868
- var LimitValueField = z6.union([z6.number(), z6.literal("unlimited")]).nullable().optional();
869
- var EntitlementTypeFieldsFeatureSchema = z6.looseObject({
870
- kind: z6.literal("feature").meta(Unrestricted3),
871
- enabled: z6.boolean().optional().meta(Unrestricted3)
872
- }).meta(
873
- { id: "EntitlementTypeFieldsFeature", "x-revturbine-schema-persistence": Transient3, "x-revturbine-schema-exposure": External3 }
874
- );
875
- var EntitlementTypeFieldsCapabilityTierSchema = z6.looseObject({
876
- kind: z6.literal("capability_tier").meta(Unrestricted3),
877
- tier_name: z6.string().optional().meta(Unrestricted3),
878
- tier_description: z6.string().optional().meta(Unrestricted3)
879
- }).meta(
880
- { id: "EntitlementTypeFieldsCapabilityTier", "x-revturbine-schema-persistence": Transient3, "x-revturbine-schema-exposure": External3 }
881
- );
882
- var EntitlementTypeFieldsUsageLimitSchema = z6.looseObject({
883
- kind: z6.literal("usage_limit").meta(Unrestricted3),
884
- limit_value: LimitValueField.meta(Unrestricted3),
885
- unit: z6.string().optional().meta(Unrestricted3),
886
- period: UsagePeriodScopeSchema.optional().meta(Unrestricted3),
887
- enforcement: EnforcementModeSchema.optional().meta(Unrestricted3)
888
- }).meta(
889
- { id: "EntitlementTypeFieldsUsageLimit", "x-revturbine-schema-persistence": Transient3, "x-revturbine-schema-exposure": External3 }
890
- );
891
- var EntitlementTypeFieldsPricePerUnitSchema = z6.looseObject({
892
- kind: z6.literal("price_per_unit").meta(Unrestricted3),
893
- amount_cents: z6.number().optional().meta(Unrestricted3),
894
- currency: z6.string().optional().meta(Unrestricted3),
895
- unit: z6.string().optional().meta(Unrestricted3),
896
- period: UsagePeriodScopeSchema.optional().meta(Unrestricted3)
897
- }).meta(
898
- { id: "EntitlementTypeFieldsPricePerUnit", "x-revturbine-schema-persistence": Transient3, "x-revturbine-schema-exposure": External3 }
899
- );
900
- var EntitlementTypeFieldsRateLimitSchema = z6.looseObject({
901
- kind: z6.literal("rate_limit").meta(Unrestricted3),
902
- rate_value: z6.number().optional().meta(Unrestricted3),
903
- period_scope: UsagePeriodScopeSchema.optional().meta(Unrestricted3),
904
- enforcement: EnforcementModeSchema.optional().meta(Unrestricted3)
905
- }).meta(
906
- { id: "EntitlementTypeFieldsRateLimit", "x-revturbine-schema-persistence": Transient3, "x-revturbine-schema-exposure": External3 }
907
- );
908
- var EntitlementTypeFieldsCreditsSchema = z6.looseObject({
909
- kind: z6.literal("credits").meta(Unrestricted3),
910
- allowance: LimitValueField.meta(Unrestricted3),
911
- initial_grant: z6.number().optional().meta(Unrestricted3),
912
- unit: z6.string().optional().meta(Unrestricted3),
913
- // Wire cadence uses the `per_*` vocabulary; the persisted `reset_period`
914
- // column uses the bare vocabulary (`month`). Both are declared so the
915
- // translation gap is visible to the contract instead of silent (plan 109).
916
- period: UsagePeriodScopeSchema.optional().meta(Unrestricted3),
917
- reset_period: EntitlementRulePeriodUnitSchema.optional().meta(Unrestricted3),
918
- rollover: z6.boolean().optional().meta(Unrestricted3),
919
- // `max_balance` — the maximum balance allowed at any time — is the ONLY
920
- // credits cap in the model (Kent, 2026-07-22). The older `max_rollover`
921
- // (how much unused balance carried into the next period) was folded away
922
- // when plan 28 REQ-13 renamed the column, and authored configs have since
923
- // been migrated off it. Do not re-add it: it has no column, IR field, or
924
- // bundle slot — plan 37 retired its FlatBuffer slot to a -1 sentinel — so
925
- // declaring it would promise a round-trip the model cannot honour.
926
- max_balance: LimitValueField.meta(Unrestricted3),
927
- top_up_available: z6.boolean().optional().meta(Unrestricted3)
928
- }).meta(
929
- { id: "EntitlementTypeFieldsCredits", "x-revturbine-schema-persistence": Transient3, "x-revturbine-schema-exposure": External3 }
930
- );
931
- var EntitlementTypeFieldsSeatSchema = z6.looseObject({
932
- kind: z6.literal("seat").meta(Unrestricted3),
933
- included_seats: z6.union([z6.number(), z6.literal("unlimited")]).optional().meta(Unrestricted3),
934
- // Authored configs already carry `null` here; plan 72 treats null as the
935
- // canonical "unlimited". The 999999 sentinel is an export-side artifact.
936
- max_seats: z6.union([z6.number(), z6.literal("unlimited")]).nullable().optional().meta(Unrestricted3),
937
- seat_type: z6.string().optional().meta(Unrestricted3)
938
- }).meta(
939
- { id: "EntitlementTypeFieldsSeat", "x-revturbine-schema-persistence": Transient3, "x-revturbine-schema-exposure": External3 }
940
- );
941
- var EntitlementTypeFieldsUntypedSchema = z6.looseObject({}).meta(
942
- { id: "EntitlementTypeFieldsUntyped", "x-revturbine-schema-persistence": Transient3, "x-revturbine-schema-exposure": External3 }
943
- );
944
- var ENTITLEMENT_TYPE_FIELDS_VARIANTS = [
945
- EntitlementTypeFieldsFeatureSchema,
946
- EntitlementTypeFieldsCapabilityTierSchema,
947
- EntitlementTypeFieldsUsageLimitSchema,
948
- EntitlementTypeFieldsPricePerUnitSchema,
949
- EntitlementTypeFieldsRateLimitSchema,
950
- EntitlementTypeFieldsCreditsSchema,
951
- EntitlementTypeFieldsSeatSchema
952
- ];
953
- var EntitlementTypeFieldsSchema = z6.union([...ENTITLEMENT_TYPE_FIELDS_VARIANTS, EntitlementTypeFieldsUntypedSchema]).meta(
954
- { id: "EntitlementTypeFields", "x-revturbine-schema-persistence": Transient3, "x-revturbine-schema-exposure": External3 }
955
- );
956
876
  var EntitlementSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
957
877
  anchor_id: z6.string().min(1).meta({ ...Unrestricted3, readOnly: true }),
958
878
  name: NameField.meta(Unrestricted3),
@@ -995,8 +915,11 @@ var EntitlementRuleSchema = IdField.merge(TimestampFields).merge(TenantIdField).
995
915
  period_scope: UsagePeriodScopeSchema.optional().meta(Unrestricted3),
996
916
  // Optional instance label, surfaced when `period_scope = 'per_instance'` (F-1).
997
917
  instance: z6.string().max(100).optional().meta(Unrestricted3),
998
- // Credits reset cadence ("refills every"). `billing_period` resolves at
999
- // runtime to the customer's Variation billing period; structural guard below.
918
+ // Credits reset cadence ("refills every"): governs only the per-period
919
+ // `allowance_value` refill. `billing_period` resolves at runtime to the
920
+ // customer's Variation billing period; structural guard below. Absent means
921
+ // one-time only — an `initial_grant` with no recurring refill (plan 147
922
+ // REQ-6; `on_purchase` retired from the enum).
1000
923
  reset_period: EntitlementRulePeriodUnitSchema.optional().meta(Unrestricted3),
1001
924
  // Type-specific fields (populated based on entitlement type)
1002
925
  limit_value: z6.union([z6.number(), z6.literal("unlimited")]).optional().meta(Unrestricted3),
@@ -1020,7 +943,30 @@ var EntitlementRuleSchema = IdField.merge(TimestampFields).merge(TenantIdField).
1020
943
  seat_type_id: z6.string().optional().meta(Unrestricted3),
1021
944
  included_count: z6.union([z6.number().int(), z6.literal("unlimited")]).optional().meta(Unrestricted3),
1022
945
  at_limit_behavior: z6.enum(["hard_cap", "auto_upgrade_at_renewal"]).optional().meta(Unrestricted3),
1023
- stripe_metered_price_id: z6.string().optional().meta(Unrestricted3)
946
+ stripe_metered_price_id: z6.string().optional().meta(Unrestricted3),
947
+ // ── Plan 147: columns promoted from the deleted portable `type_fields` union
948
+ // so the flat wire projection round-trips (REQ-3 persist verdicts). The wire
949
+ // (RevTurbineConfigEntitlementRulesItemSchema) mirrors these one-to-one under
950
+ // their canonical names; web derives `kind`/`unit`/`tier_*` from the parent
951
+ // entitlement instead of persisting them.
952
+ //
953
+ // feature enable/disable: a present-but-disabled feature rule is a real DENY
954
+ // (`entitlement-check.ts` reads `enabled !== false`), distinct from "no rule".
955
+ // Optional (absent = enabled) matches that evaluator semantic — replaces the
956
+ // hard-coded `true` the export used to emit.
957
+ enabled: z6.boolean().optional().meta(Unrestricted3),
958
+ // How this rule partitions usage across the identity hierarchy. Rule-level in
959
+ // the IR (`encode.ts` maps `r.allocation`); may default from the parent
960
+ // entitlement's `allocation` when unset.
961
+ allocation: UsageAllocationSchema.optional().meta(Unrestricted3),
962
+ // Seat ceiling. null = unlimited (plan 72); the 999999 export sentinel maps
963
+ // back to null at the compile boundary.
964
+ max_seats: z6.union([z6.number(), z6.literal("unlimited")]).nullable().optional().meta(Unrestricted3),
965
+ // price_per_unit content-rendering fields (OQ-7: persisted-and-rendered, NOT
966
+ // evaluated — no IR/evaluator branch). `unit` derives from the entitlement;
967
+ // `period` maps onto the existing `period_scope` column.
968
+ amount_cents: z6.number().int().optional().meta(Unrestricted3),
969
+ currency: z6.string().optional().meta(Unrestricted3)
1024
970
  }).meta(
1025
971
  {
1026
972
  id: "EntitlementRule",
@@ -1050,6 +996,16 @@ var EntitlementRuleValidatedSchema = EntitlementRuleSchema.superRefine(
1050
996
  });
1051
997
  }
1052
998
  }
999
+ const allowance = rule.allowance_value;
1000
+ const hasRecurringAllowance = allowance === "unlimited" || typeof allowance === "number" && allowance > 0;
1001
+ if (hasRecurringAllowance && rule.reset_period == null) {
1002
+ ctx.addIssue({
1003
+ code: "custom",
1004
+ path: ["reset_period"],
1005
+ params: { code: "allowance_requires_reset_period" },
1006
+ message: "a non-zero credits allowance_value requires a reset_period (the per-period refill cadence)"
1007
+ });
1008
+ }
1053
1009
  }
1054
1010
  );
1055
1011
  var EntitlementRuleWarningCodeSchema = z6.enum(["one_time_period_mismatch"]).meta(
@@ -4111,10 +4067,39 @@ var RevTurbineConfigEntitlementRulesItemSchema = z19.object({
4111
4067
  // means "match all users" (replaces the singular `segment_id` field
4112
4068
  // and its 'all'/null sentinels).
4113
4069
  segment_ids: z19.array(z19.string()).default([]).meta(Unrestricted16),
4114
- // Typed per-kind wire shape (plan 141). Loose variants + an untyped tail,
4115
- // so every input that parsed as the former opaque record still parses and
4116
- // unmodelled keys survive; see EntitlementTypeFieldsSchema.
4117
- type_fields: EntitlementTypeFieldsSchema.default({}).meta(Unrestricted16),
4070
+ // ── Derived denormalizations from the parent entitlement (plan 147, OQ-6).
4071
+ // Resolved via `entitlement_id` on export; ignored on import (the entitlement
4072
+ // is authoritative). `readOnly` excluded from round-trip obligations: they
4073
+ // are computed, not authored, so requiring a sentinel to preserve them would
4074
+ // test derivation rather than authoring fidelity.
4075
+ kind: EntitlementTypeSchema.optional().meta({ ...Unrestricted16, readOnly: true }),
4076
+ unit: z19.string().optional().meta({ ...Unrestricted16, readOnly: true }),
4077
+ tier_name: z19.string().optional().meta({ ...Unrestricted16, readOnly: true }),
4078
+ tier_description: z19.string().optional().meta({ ...Unrestricted16, readOnly: true }),
4079
+ // ── Flat per-rule fields (plan 147, OQ-6) — single-sourced from the persisted
4080
+ // `EntitlementRuleSchema` under its canonical names (REQ-1/REQ-2), replacing
4081
+ // the deleted nested `type_fields` union. Null-stripped on export. The
4082
+ // evaluated subset lowers into `TypeFieldsIR`; the persisted-not-evaluated
4083
+ // fields (`amount_cents`/`currency`/`rate_value`/`period_scope`/…) round-trip
4084
+ // via web import/export for content rendering (OQ-7), not the bundle.
4085
+ ...EntitlementRuleSchema.pick({
4086
+ enabled: true,
4087
+ limit_value: true,
4088
+ enforcement: true,
4089
+ tier_value: true,
4090
+ period_scope: true,
4091
+ included_count: true,
4092
+ seat_type_id: true,
4093
+ initial_grant: true,
4094
+ allowance_value: true,
4095
+ rollover_enabled: true,
4096
+ max_balance: true,
4097
+ reset_period: true,
4098
+ max_seats: true,
4099
+ rate_value: true,
4100
+ amount_cents: true,
4101
+ currency: true
4102
+ }).shape,
4118
4103
  current_usage: z19.number().default(0).meta(Unrestricted16),
4119
4104
  /** How usage is partitioned across the identity hierarchy. */
4120
4105
  allocation: UsageAllocationSchema.optional().meta(Unrestricted16)
@@ -4347,23 +4332,7 @@ var RevTurbineConfigFreeTrialRuleItemSchema = IdField.merge(FreeTrialRuleCoreFie
4347
4332
  var RevTurbineConfigReverseTrialRuleItemSchema = IdField.merge(ReverseTrialRuleCoreFieldsSchema).meta(
4348
4333
  { id: "RevTurbineConfigReverseTrialRuleItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PENDING_PLAYBOOK_FACETS4 }
4349
4334
  );
4350
- var LegacyRevTurbineConfigSchema = z19.object({
4351
- version: z19.string().meta({ ...Unrestricted16, ...PLAYBOOK_VERSION_HEADER_FACETS }),
4352
- exported_at: z19.string().datetime().optional().meta({ ...Unrestricted16, ...PLAYBOOK_PROVENANCE_HEADER_FACETS, readOnly: true }),
4353
- // The @revt-eng/schema package version this config was generated with.
4354
- schema_version: z19.string().optional().meta({ ...Unrestricted16, ...PLAYBOOK_VERSION_HEADER_FACETS, readOnly: true }),
4355
- // The compiled-bundle wire-format SCHEMA_VERSION (core/bundle/ir.ts).
4356
- bundle_schema_version: z19.number().int().optional().meta({ ...Unrestricted16, ...PLAYBOOK_VERSION_HEADER_FACETS, readOnly: true }),
4357
- // The change set this export represents: the active change set by default,
4358
- // or a specific change set when one is requested. Null for an unscoped export.
4359
- change_set_id: z19.string().nullable().default(null).meta({ ...Unrestricted16, ...PLAYBOOK_PROVENANCE_HEADER_FACETS, readOnly: true }),
4360
- // Origin target identity (plan 131 TASK-10, cli.md "Target-aware, portable"):
4361
- // stamped by the server on export so a downloaded Config File records where
4362
- // it came from; upload tooling targets these by default and flags a tenant
4363
- // mismatch against the session before sending. Optional — hand-authored and
4364
- // pre-existing configs carry no target.
4365
- tenant_id: z19.string().optional().meta({ ...Unrestricted16, ...PLAYBOOK_TARGET_FACETS, readOnly: true }),
4366
- environment_id: z19.string().optional().meta({ ...Unrestricted16, ...PLAYBOOK_TARGET_FACETS, readOnly: true }),
4335
+ var PlaybookBodySchema = z19.object({
4367
4336
  plans: z19.array(RevTurbineConfigPlansItemSchema).meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4368
4337
  // Optional for back-compat: pre-plan-88 configs (and the live export until web
4369
4338
  // adopts the new @revt-eng/schema) omit it. Add-on definitions only; pricing
@@ -4441,19 +4410,33 @@ var LegacyRevTurbineConfigSchema = z19.object({
4441
4410
  enforcement_defaults: z19.array(RevTurbineConfigEnforcementDefaultsItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4442
4411
  placement_settings: z19.array(RevTurbineConfigPlacementSettingsItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4443
4412
  segment_dimensions: z19.array(RevTurbineConfigSegmentDimensionsItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4444
- meter_bindings: z19.array(RevTurbineConfigMeterBindingsItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 })
4413
+ meter_bindings: z19.array(RevTurbineConfigMeterBindingsItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4414
+ experiments: z19.array(z19.unknown()).max(0).optional().meta({
4415
+ ...Unrestricted16,
4416
+ ...PENDING_PLAYBOOK_FACETS4
4417
+ }),
4418
+ // Reserved like `experiments` above: claim the key now, ship the
4419
+ // semantics later. A Playbook will eventually pin the Signal Catalog
4420
+ // version its targeting was interpreted under, so a decision stays
4421
+ // reproducible after the event taxonomy moves on. Reserving costs one
4422
+ // schema change; adding a top-level Playbook key after the fact costs a
4423
+ // second breaking cascade through the IR, the CLI, demo data, and every
4424
+ // SDK port.
4425
+ //
4426
+ // Empty-object-only until a consumer exists — `.strict()` rejects a
4427
+ // populated `{ id, version }` rather than letting it round-trip as
4428
+ // config nothing reads. Catalog *definitions* stay server-side and must
4429
+ // never reach the browser Playbook; only the reference will live here.
4430
+ signal_catalog: z19.object({}).strict().optional().meta({
4431
+ ...Unrestricted16,
4432
+ ...PENDING_PLAYBOOK_FACETS4
4433
+ })
4445
4434
  }).meta(
4446
4435
  {
4447
- id: "RevTurbineConfig",
4436
+ id: "PlaybookBody",
4448
4437
  "x-revturbine-schema-persistence": Transient16,
4449
4438
  "x-revturbine-schema-exposure": External11,
4450
- ...PLAYBOOK_SDK_FACETS8,
4451
- ...schemaDeprecation({
4452
- since: "0.1.111",
4453
- replacement: "PlaybookSchema",
4454
- removeAfter: "one compatibility window",
4455
- reason: "The legacy wire conflates format and provenance under version and change_set_id."
4456
- })
4439
+ ...PLAYBOOK_SDK_FACETS8
4457
4440
  }
4458
4441
  );
4459
4442
  var PlaybookHeaderSchema = z19.object({
@@ -4476,12 +4459,17 @@ var PlaybookHeaderSchema = z19.object({
4476
4459
  ...PLAYBOOK_PROVENANCE_HEADER_FACETS,
4477
4460
  readOnly: true
4478
4461
  }),
4479
- tenant_id: z19.string().min(1).meta({
4462
+ // Origin target identity (plan 131 TASK-10). Optional so a hand-authored /
4463
+ // legacy `export-config.json` (which carries no target) parses unchanged —
4464
+ // the plan 147 TASK-1 header reconciliation: the one config schema must
4465
+ // absorb legacy files that predate target stamping. Stamped by the server
4466
+ // on export when present.
4467
+ tenant_id: z19.string().min(1).optional().meta({
4480
4468
  ...Unrestricted16,
4481
4469
  ...PLAYBOOK_TARGET_FACETS,
4482
4470
  readOnly: true
4483
4471
  }),
4484
- environment_id: z19.string().min(1).meta({
4472
+ environment_id: z19.string().min(1).optional().meta({
4485
4473
  ...Unrestricted16,
4486
4474
  ...PLAYBOOK_TARGET_FACETS,
4487
4475
  readOnly: true
@@ -4514,61 +4502,39 @@ var PlaybookHeaderSchema = z19.object({
4514
4502
  ...PLAYBOOK_PROVENANCE_HEADER_FACETS
4515
4503
  }
4516
4504
  );
4517
- var LEGACY_HEADER_KEYS = {
4518
- version: true,
4519
- exported_at: true,
4520
- schema_version: true,
4521
- bundle_schema_version: true,
4522
- change_set_id: true,
4523
- tenant_id: true,
4524
- environment_id: true
4525
- };
4526
- var PlaybookBodySchema = LegacyRevTurbineConfigSchema.omit(LEGACY_HEADER_KEYS).extend({
4527
- experiments: z19.array(z19.unknown()).max(0).optional().meta({
4528
- ...Unrestricted16,
4529
- ...PENDING_PLAYBOOK_FACETS4
4530
- }),
4531
- // Reserved like `experiments` above: claim the key now, ship the
4532
- // semantics later. A Playbook will eventually pin the Signal Catalog
4533
- // version its targeting was interpreted under, so a decision stays
4534
- // reproducible after the event taxonomy moves on. Reserving costs one
4535
- // schema change; adding a top-level Playbook key after the fact costs a
4536
- // second breaking cascade through the IR, the CLI, demo data, and every
4537
- // SDK port.
4538
- //
4539
- // Empty-object-only until a consumer exists — `.strict()` rejects a
4540
- // populated `{ id, version }` rather than letting it round-trip as
4541
- // config nothing reads. Catalog *definitions* stay server-side and must
4542
- // never reach the browser Playbook; only the reference will live here.
4543
- signal_catalog: z19.object({}).strict().optional().meta({
4544
- ...Unrestricted16,
4545
- ...PENDING_PLAYBOOK_FACETS4
4546
- })
4547
- // NOTE: plan_variations / addon_variations are NOT re-declared here — they
4548
- // now live on LegacyRevTurbineConfigSchema (plan 118) and flow through the
4549
- // `.omit(LEGACY_HEADER_KEYS)` above, so both the legacy and canonical shapes
4550
- // carry them from a single definition.
4551
- // NOTE: the authored-config projections (seat_types, enforcement_defaults,
4552
- // placement_settings, segment_dimensions, meter_bindings) are active SDK
4553
- // inputs and flow through from LegacyRevTurbineConfigSchema (they are not
4554
- // LEGACY_HEADER_KEYS, so `.omit` retains them). See plan 118 TASK-18.
4555
- }).meta(
4505
+ var PlaybookObjectSchema = PlaybookHeaderSchema.extend(PlaybookBodySchema.shape).meta(
4556
4506
  {
4557
- id: "PlaybookBody",
4558
4507
  "x-revturbine-schema-persistence": Transient16,
4559
4508
  "x-revturbine-schema-exposure": External11,
4560
4509
  ...PLAYBOOK_SDK_FACETS8
4561
4510
  }
4562
4511
  );
4563
- var PlaybookSchema = PlaybookHeaderSchema.extend(PlaybookBodySchema.shape).meta(
4564
- {
4565
- id: "Playbook",
4566
- "x-revturbine-schema-persistence": Transient16,
4567
- "x-revturbine-schema-exposure": External11,
4568
- ...PLAYBOOK_SDK_FACETS8
4512
+ function isRecord(value) {
4513
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4514
+ }
4515
+ function normalizeConfigHeaderInput(input) {
4516
+ if (!isRecord(input)) return input;
4517
+ const next = { ...input };
4518
+ if (!("artifact_type" in next)) {
4519
+ next.artifact_type = "playbook";
4569
4520
  }
4570
- );
4571
- var RevTurbineConfigSchema = LegacyRevTurbineConfigSchema;
4521
+ if ("version" in next) {
4522
+ if (!("format_version" in next)) next.format_version = next.version;
4523
+ delete next.version;
4524
+ }
4525
+ if ("change_set_id" in next) {
4526
+ if (!("playbook_version_id" in next)) next.playbook_version_id = next.change_set_id;
4527
+ delete next.change_set_id;
4528
+ }
4529
+ return next;
4530
+ }
4531
+ var PlaybookSchema = z19.preprocess(normalizeConfigHeaderInput, PlaybookObjectSchema).meta({
4532
+ id: "Playbook",
4533
+ "x-revturbine-schema-persistence": Transient16,
4534
+ "x-revturbine-schema-exposure": External11,
4535
+ ...PLAYBOOK_SDK_FACETS8
4536
+ });
4537
+ var RevTurbineConfigSchema = PlaybookSchema;
4572
4538
  var configPaths = {
4573
4539
  "/api/seat-type-anchors": {
4574
4540
  get: operation({
@@ -5793,7 +5759,15 @@ var AuthSsoProviderSchema = IdField.extend({
5793
5759
 
5794
5760
  // src/schema/validators.snapshot.mjs
5795
5761
  import { z as z27 } from "zod";
5762
+ import { z as z92 } from "zod";
5763
+ import { z as z32 } from "zod";
5796
5764
  import { z as z28 } from "zod";
5765
+ import { z as z42 } from "zod";
5766
+ import { z as z62 } from "zod";
5767
+ import { z as z52 } from "zod";
5768
+ import { z as z72 } from "zod";
5769
+ import { z as z82 } from "zod";
5770
+ import { z as z102 } from "zod";
5797
5771
  var SeveritySchema2 = z27.enum([
5798
5772
  "error_draft",
5799
5773
  "error_launch",
@@ -6128,153 +6102,2760 @@ function evaluate(graph, opts = {}) {
6128
6102
  if (!opts.focus) return findings;
6129
6103
  return findings.map((f) => spotlights(f, opts.focus) ? { ...f, spotlight: true } : f);
6130
6104
  }
6131
-
6132
- // src/schema/version.ts
6133
- var SCHEMA_VERSION = "0.1.138";
6134
-
6135
- // src/lib/credentials.ts
6136
- import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
6137
- import os from "os";
6138
- import path from "path";
6139
- function resolveConfigDir(cwd = process.cwd()) {
6140
- const override = process.env.REVTURBINE_CONFIG_DIR;
6141
- if (override) return { dir: path.resolve(override), source: "env" };
6142
- const scoped = findWorktreeConfigDir(cwd);
6143
- if (scoped) return { dir: scoped, source: "worktree" };
6144
- return { dir: path.join(os.homedir(), ".revturbine"), source: "global" };
6145
- }
6146
- function findWorktreeConfigDir(start) {
6147
- let dir = path.resolve(start);
6148
- for (; ; ) {
6149
- const candidate = path.join(dir, ".revturbine");
6150
- if (existsSync(candidate) && statSync(candidate).isDirectory()) return candidate;
6151
- const parent = path.dirname(dir);
6152
- if (parent === dir) return null;
6153
- dir = parent;
6154
- }
6155
- }
6156
- function configDir() {
6157
- return resolveConfigDir().dir;
6158
- }
6159
- function configFile() {
6160
- return path.join(configDir(), "credentials.json");
6161
- }
6162
- function normalizeBaseUrl(input) {
6163
- const s = input.trim();
6164
- if (/^https?:\/\//i.test(s)) return s.replace(/\/+$/, "");
6165
- const isLocal = /^(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(:\d+)?(\/|$)/i.test(s);
6166
- return `${isLocal ? "http" : "https"}://${s}`.replace(/\/+$/, "");
6167
- }
6168
- function urlKey(baseUrl) {
6169
- try {
6170
- return new URL(normalizeBaseUrl(baseUrl)).origin;
6171
- } catch {
6172
- return baseUrl.replace(/\/+$/, "");
6173
- }
6174
- }
6175
- function read() {
6176
- const file = configFile();
6177
- if (!existsSync(file)) return { version: 1, credentials: {} };
6178
- try {
6179
- const parsed = JSON.parse(readFileSync(file, "utf8"));
6180
- if (parsed && typeof parsed === "object" && parsed.credentials) {
6181
- return { version: 1, credentials: parsed.credentials };
6105
+ var HANDLE_PATTERN2 = /^[a-z0-9._]{1,100}$/;
6106
+ var SchemaPersistence2 = {
6107
+ Persisted: "persisted",
6108
+ Transient: "transient"
6109
+ };
6110
+ var SchemaExposure2 = {
6111
+ Internal: "internal",
6112
+ External: "external"
6113
+ };
6114
+ var DataClassification2 = {
6115
+ Pii: { "x-revturbine-data-classification": "pii" },
6116
+ Financial: { "x-revturbine-data-classification": "financial" },
6117
+ Unrestricted: { "x-revturbine-data-classification": "unrestricted" }
6118
+ };
6119
+ var READ_ONLY_META_KEY2 = "readOnly";
6120
+ function toWritableSchema2(schema) {
6121
+ const writableShape = {};
6122
+ for (const [fieldName, fieldSchema] of Object.entries(schema.shape)) {
6123
+ if (typeof fieldSchema !== "object" || fieldSchema === null) {
6124
+ continue;
6125
+ }
6126
+ const isReadOnly = fieldSchema.meta()?.[READ_ONLY_META_KEY2] === true;
6127
+ if (!isReadOnly) {
6128
+ writableShape[fieldName] = fieldSchema;
6182
6129
  }
6183
- } catch {
6184
6130
  }
6185
- return { version: 1, credentials: {} };
6131
+ return z28.object(writableShape);
6186
6132
  }
6187
- function write(data) {
6188
- const dir = configDir();
6189
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
6190
- writeFileSync(configFile(), `${JSON.stringify(data, null, 2)}
6191
- `, { mode: 384 });
6192
- try {
6193
- chmodSync(configFile(), 384);
6194
- } catch {
6133
+ function toCreateSchema2(schema) {
6134
+ const writable = toWritableSchema2(schema);
6135
+ const handleField = writable.shape["handle"];
6136
+ if (handleField && typeof handleField.regex === "function") {
6137
+ return writable.extend({ handle: handleField.regex(HANDLE_PATTERN2) });
6195
6138
  }
6139
+ return writable;
6196
6140
  }
6197
- function saveCredential(baseUrl, cred) {
6198
- const data = read();
6199
- data.credentials[urlKey(baseUrl)] = cred;
6200
- write(data);
6201
- }
6202
- function getCredential(baseUrl) {
6203
- return read().credentials[urlKey(baseUrl)] ?? null;
6204
- }
6205
- function removeCredential(baseUrl) {
6206
- const data = read();
6207
- const key = urlKey(baseUrl);
6208
- if (!(key in data.credentials)) return false;
6209
- delete data.credentials[key];
6210
- write(data);
6211
- return true;
6212
- }
6213
- function redactToken(token) {
6214
- if (token.length <= 8) return "rtk_\u2026";
6215
- return `${token.slice(0, 4)}\u2026${token.slice(-4)}`;
6216
- }
6217
-
6218
- // src/lib/device-auth.ts
6219
- import { spawn } from "child_process";
6220
- import os2 from "os";
6221
- function trimUrl(baseUrl) {
6222
- return baseUrl.replace(/\/+$/, "");
6223
- }
6224
- async function startDeviceAuth(baseUrl, label, fetchImpl) {
6225
- const res = await fetchImpl(`${trimUrl(baseUrl)}/api/cli/device/code`, {
6226
- method: "POST",
6227
- headers: { "Content-Type": "application/json" },
6228
- body: JSON.stringify(label ? { label } : {})
6229
- });
6230
- if (!res.ok) {
6231
- throw new Error(`Could not start device login (${res.status}). Is ${baseUrl} reachable?`);
6141
+ var { Unrestricted: Unrestricted24 } = DataClassification2;
6142
+ var { Transient: Transient22, Persisted: Persisted24 } = SchemaPersistence2;
6143
+ var { Internal: Internal20, External: External13 } = SchemaExposure2;
6144
+ var PaginationParamsSchema2 = z32.object({
6145
+ page: z32.coerce.number().int().min(1).default(1).meta(Unrestricted24),
6146
+ per_page: z32.coerce.number().int().min(1).max(100).default(25).meta(Unrestricted24)
6147
+ }).meta(
6148
+ {
6149
+ id: "PaginationParams",
6150
+ "x-revturbine-schema-persistence": Transient22,
6151
+ "x-revturbine-schema-exposure": External13
6232
6152
  }
6233
- return await res.json();
6234
- }
6235
- async function pollForToken(opts) {
6236
- const now = opts.now ?? (() => Date.now());
6237
- const startedAt = now();
6238
- let interval = opts.intervalSeconds;
6239
- for (; ; ) {
6240
- if (now() - startedAt > opts.expiresInSeconds * 1e3) {
6241
- throw new Error("Device code expired before authorization. Run --login again.");
6242
- }
6243
- await opts.sleep(interval * 1e3);
6244
- const res = await opts.fetchImpl(`${trimUrl(opts.baseUrl)}/api/cli/device/token`, {
6245
- method: "POST",
6246
- headers: { "Content-Type": "application/json" },
6247
- body: JSON.stringify({ device_code: opts.deviceCode })
6248
- });
6249
- if (res.ok) {
6250
- return await res.json();
6251
- }
6252
- const body = await res.json().catch(() => ({}));
6253
- switch (body.error) {
6254
- case "authorization_pending":
6255
- opts.onPending?.();
6256
- continue;
6257
- case "slow_down":
6258
- interval += 5;
6259
- continue;
6260
- case "access_denied":
6261
- throw new Error("Authorization was denied in the browser.");
6262
- case "expired_token":
6263
- throw new Error("Device code expired. Run --login again.");
6264
- default:
6265
- throw new Error(`Authorization failed: ${body.error ?? `HTTP ${res.status}`}`);
6266
- }
6153
+ );
6154
+ var TimestampFields2 = z32.object({
6155
+ created_at: z32.string().datetime().meta({ ...Unrestricted24, readOnly: true }),
6156
+ updated_at: z32.string().datetime().meta({ ...Unrestricted24, readOnly: true })
6157
+ }).meta(
6158
+ {
6159
+ id: "null",
6160
+ "x-revturbine-schema-persistence": Transient22,
6161
+ "x-revturbine-schema-exposure": External13
6267
6162
  }
6268
- }
6269
- function openBrowser(url) {
6270
- try {
6271
- const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
6272
- const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
6273
- const child = spawn(cmd, args, { stdio: "ignore", detached: true });
6274
- child.on("error", () => {
6275
- });
6276
- child.unref();
6277
- } catch {
6163
+ );
6164
+ var IdField2 = z32.object({
6165
+ id: z32.string().min(1).meta({ ...Unrestricted24, readOnly: true })
6166
+ }).meta(
6167
+ {
6168
+ id: "null",
6169
+ "x-revturbine-schema-persistence": Transient22,
6170
+ "x-revturbine-schema-exposure": External13
6171
+ }
6172
+ );
6173
+ var TenantIdField2 = z32.object({
6174
+ tenant_id: z32.string().min(1, "tenant_id is required").meta({ ...Unrestricted24, readOnly: true })
6175
+ }).meta(
6176
+ {
6177
+ id: "null",
6178
+ "x-revturbine-schema-persistence": Transient22,
6179
+ "x-revturbine-schema-exposure": Internal20
6180
+ }
6181
+ );
6182
+ var BillingCadenceSchema2 = z32.enum([
6183
+ "monthly",
6184
+ "annual",
6185
+ "quarterly",
6186
+ "one_time",
6187
+ "usage_based"
6188
+ ]).meta(
6189
+ {
6190
+ id: "BillingCadence",
6191
+ "x-revturbine-schema-persistence": Transient22,
6192
+ "x-revturbine-schema-exposure": External13
6193
+ }
6194
+ );
6195
+ var SurfaceTypeSchema2 = z32.enum([
6196
+ "banner",
6197
+ "modal",
6198
+ "tooltip",
6199
+ "sidebar",
6200
+ "inline",
6201
+ "toast",
6202
+ "fullscreen",
6203
+ "email",
6204
+ "sms",
6205
+ "push",
6206
+ "in_page",
6207
+ "button",
6208
+ "full_page",
6209
+ "agent",
6210
+ "cli",
6211
+ "custom"
6212
+ ]).meta(
6213
+ { id: "SurfaceType", "x-revturbine-schema-persistence": Transient22, "x-revturbine-schema-exposure": External13 }
6214
+ );
6215
+ var EntitlementTypeSchema2 = z32.enum([
6216
+ "feature",
6217
+ "capability_tier",
6218
+ "usage_limit",
6219
+ "price_per_unit",
6220
+ "rate_limit",
6221
+ "credits",
6222
+ "seat"
6223
+ ]).meta(
6224
+ {
6225
+ id: "EntitlementType",
6226
+ "x-revturbine-schema-persistence": Transient22,
6227
+ "x-revturbine-schema-exposure": External13
6228
+ }
6229
+ );
6230
+ var CurrencySchema2 = z32.enum(["usd", "eur", "gbp"]).default("usd").meta(
6231
+ {
6232
+ id: "Currency",
6233
+ "x-revturbine-schema-persistence": Transient22,
6234
+ "x-revturbine-schema-exposure": External13
6235
+ }
6236
+ );
6237
+ var FeatureFlagValueSchema2 = z32.union([
6238
+ z32.boolean(),
6239
+ z32.number(),
6240
+ z32.string()
6241
+ ]).meta(
6242
+ {
6243
+ id: "FeatureFlagValue",
6244
+ "x-revturbine-schema-persistence": Transient22,
6245
+ "x-revturbine-schema-exposure": Internal20
6246
+ }
6247
+ );
6248
+ var NameField2 = z32.string().min(1).max(200);
6249
+ var HandleField2 = z32.string().min(1).max(100);
6250
+ var DescriptionField2 = z32.string().max(500).optional();
6251
+ var MetadataField2 = z32.record(z32.string(), z32.unknown()).default({});
6252
+ var ThresholdPercentField2 = z32.number().int().min(10).max(100).multipleOf(10);
6253
+ var NullableDatetimeField2 = z32.string().datetime().nullable().default(null);
6254
+ var AnchorFields2 = z32.object({
6255
+ environment_id: z32.string().min(1).default("production").meta({ ...Unrestricted24, readOnly: true })
6256
+ }).meta({ id: "null", "x-revturbine-schema-persistence": Transient22, "x-revturbine-schema-exposure": Internal20 });
6257
+ var VersionFields2 = z32.object({
6258
+ // Which playbook version (the review/deploy unit — was `change_set_id`) staged
6259
+ // this ledger row; null once it is the deployed live row.
6260
+ playbook_version_id: z32.string().nullable().default(null).meta(Unrestricted24),
6261
+ is_current: z32.boolean().default(true).meta({ ...Unrestricted24, readOnly: true }),
6262
+ is_deleted: z32.boolean().default(false).meta({ ...Unrestricted24, readOnly: true }),
6263
+ delete_date: z32.string().datetime().nullable().default(null).meta({ ...Unrestricted24, readOnly: true }),
6264
+ // Monotonic version number within a lineage (v1, v2, v3 …); `base_sequence` is
6265
+ // the live sequence this version was forked from (null for the first version),
6266
+ // so a stale draft is detected when live has moved past it.
6267
+ sequence: z32.number().int().min(1).default(1).meta({ ...Unrestricted24, readOnly: true }),
6268
+ base_sequence: z32.number().int().nullable().default(null).meta({ ...Unrestricted24, readOnly: true })
6269
+ }).meta({ id: "null", "x-revturbine-schema-persistence": Transient22, "x-revturbine-schema-exposure": Internal20 });
6270
+ var AnchorBaseSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).extend({
6271
+ handle: HandleField2.meta({ ...Unrestricted24, readOnly: true }),
6272
+ active: z32.boolean().default(true).meta({ ...Unrestricted24, readOnly: true })
6273
+ });
6274
+ function makeAnchor2(id) {
6275
+ return AnchorBaseSchema2.meta({
6276
+ id,
6277
+ "x-revturbine-schema-persistence": Persisted24,
6278
+ "x-revturbine-schema-exposure": Internal20
6279
+ });
6280
+ }
6281
+ var SeveritySchema22 = z32.enum(["info", "warning", "critical"]).meta({ id: "Severity", "x-revturbine-schema-persistence": Transient22, "x-revturbine-schema-exposure": Internal20 });
6282
+ var StudioSurfaceTypeSchema2 = z32.enum([
6283
+ "button",
6284
+ "plans_page_ctas",
6285
+ "plans_page_full",
6286
+ "inline_gate_message",
6287
+ "tooltip",
6288
+ "in_page_card",
6289
+ "usage_counter",
6290
+ "credit_counter",
6291
+ "trial_counter",
6292
+ "banner",
6293
+ "modal_optional",
6294
+ "modal_blocking",
6295
+ "toast",
6296
+ "email",
6297
+ "sms",
6298
+ "push",
6299
+ "cli",
6300
+ "agent_connector",
6301
+ "custom_in_app"
6302
+ ]).meta({ id: "StudioSurfaceType", "x-revturbine-schema-persistence": Transient22, "x-revturbine-schema-exposure": Internal20 });
6303
+ var CtaActionTypeSchema2 = z32.enum([
6304
+ "open_checkout",
6305
+ "view_plans",
6306
+ "book_demo",
6307
+ "contact_sales",
6308
+ "complete_onboarding",
6309
+ "invite_teammate",
6310
+ "refer_friend",
6311
+ "verify_work_email",
6312
+ "update_payment_method",
6313
+ "enable_auto_renewal",
6314
+ "manage_subscription",
6315
+ "switch_billing_period",
6316
+ "extend_trial",
6317
+ "open_rt_placement",
6318
+ "dismiss",
6319
+ "snooze",
6320
+ "custom"
6321
+ ]).meta({ id: "CtaActionType", "x-revturbine-schema-persistence": Transient22, "x-revturbine-schema-exposure": External13 });
6322
+ var IdentityKind2 = {
6323
+ /** Author-given, human-meaningful handle (plans, entitlements, segments, …). */
6324
+ Named: "named",
6325
+ /** Opaque machine-minted slug (variations, entitlement_rules, payloads, …). */
6326
+ Minted: "minted"
6327
+ };
6328
+ var SCHEMA_IDENTITY_META_KEY2 = "x-revturbine-schema-identity";
6329
+ function namedIdentity2(handleField = "handle") {
6330
+ return { [SCHEMA_IDENTITY_META_KEY2]: { kind: IdentityKind2.Named, handleField } };
6331
+ }
6332
+ function mintedIdentity2(handleField = "handle") {
6333
+ return { [SCHEMA_IDENTITY_META_KEY2]: { kind: IdentityKind2.Minted, handleField } };
6334
+ }
6335
+ var SchemaContext2 = {
6336
+ Playbook: "playbook",
6337
+ Branding: "branding",
6338
+ Billing: "billing",
6339
+ Metering: "metering",
6340
+ CustomerOperations: "customer_operations",
6341
+ EventIngestion: "event_ingestion"
6342
+ };
6343
+ var SchemaSource2 = {
6344
+ Customer: "customer",
6345
+ Stripe: "stripe",
6346
+ CodeConstant: "code-constant",
6347
+ Runtime: "runtime"
6348
+ };
6349
+ var SCHEMA_CONTEXT_META_KEY2 = "x-revturbine-context";
6350
+ var SCHEMA_IN_CONFIG_META_KEY2 = "x-revturbine-in-config";
6351
+ var SCHEMA_SDK_INPUT_META_KEY2 = "x-revturbine-sdk-input";
6352
+ var SCHEMA_SOURCE_META_KEY2 = "x-revturbine-source";
6353
+ var SCHEMA_DEPRECATION_META_KEY2 = "x-revturbine-deprecation";
6354
+ var DEFAULT_SOURCE_BY_CONTEXT2 = {
6355
+ [SchemaContext2.Playbook]: SchemaSource2.Customer,
6356
+ [SchemaContext2.Branding]: SchemaSource2.Customer,
6357
+ [SchemaContext2.Billing]: SchemaSource2.Stripe,
6358
+ [SchemaContext2.Metering]: SchemaSource2.Customer,
6359
+ [SchemaContext2.CustomerOperations]: SchemaSource2.Runtime,
6360
+ [SchemaContext2.EventIngestion]: SchemaSource2.Runtime
6361
+ };
6362
+ function schemaFacets2(context, options) {
6363
+ return {
6364
+ [SCHEMA_CONTEXT_META_KEY2]: context,
6365
+ [SCHEMA_IN_CONFIG_META_KEY2]: options.inConfig ?? context === SchemaContext2.Playbook,
6366
+ [SCHEMA_SDK_INPUT_META_KEY2]: options.sdkInput,
6367
+ [SCHEMA_SOURCE_META_KEY2]: options.source ?? DEFAULT_SOURCE_BY_CONTEXT2[context]
6368
+ };
6369
+ }
6370
+ function schemaDeprecation2(declaration) {
6371
+ return {
6372
+ deprecated: true,
6373
+ [SCHEMA_DEPRECATION_META_KEY2]: {
6374
+ since: declaration.since,
6375
+ replacement: declaration.replacement,
6376
+ remove_after: declaration.removeAfter,
6377
+ reason: declaration.reason
6378
+ }
6379
+ };
6380
+ }
6381
+ function getSchemaDeprecation(schema) {
6382
+ const meta = schema.meta();
6383
+ if (meta?.deprecated !== true) return void 0;
6384
+ const value = meta[SCHEMA_DEPRECATION_META_KEY2];
6385
+ if (!value || typeof value !== "object") return void 0;
6386
+ const declaration = value;
6387
+ if (typeof declaration.since !== "string" || typeof declaration.replacement !== "string" || typeof declaration.remove_after !== "string" || typeof declaration.reason !== "string") {
6388
+ return void 0;
6389
+ }
6390
+ return {
6391
+ since: declaration.since,
6392
+ replacement: declaration.replacement,
6393
+ removeAfter: declaration.remove_after,
6394
+ reason: declaration.reason
6395
+ };
6396
+ }
6397
+ var ListEnvelope2 = (itemSchema) => z52.object({
6398
+ items: z52.array(itemSchema)
6399
+ });
6400
+ var ErrorEnvelope2 = z52.object({
6401
+ error: z52.string(),
6402
+ code: z52.string(),
6403
+ request_id: z52.string()
6404
+ });
6405
+ var operation2 = (op) => op;
6406
+ var ListQueryParamsSchema2 = z52.object({
6407
+ page: z52.number().int().min(1).default(1).optional(),
6408
+ per_page: z52.number().int().min(1).max(100).default(25).optional(),
6409
+ sort: z52.string().optional(),
6410
+ order: z52.enum(["asc", "desc"]).default("asc").optional(),
6411
+ include_deleted: z52.boolean().default(false).optional()
6412
+ });
6413
+ var { Unrestricted: Unrestricted25 } = DataClassification2;
6414
+ var { Persisted: Persisted25, Transient: Transient23 } = SchemaPersistence2;
6415
+ var { Internal: Internal22, External: External22 } = SchemaExposure2;
6416
+ var PLAYBOOK_SDK_FACETS9 = schemaFacets2(SchemaContext2.Playbook, { sdkInput: true });
6417
+ var PENDING_PLAYBOOK_FACETS5 = schemaFacets2(SchemaContext2.Playbook, {
6418
+ inConfig: false,
6419
+ sdkInput: false
6420
+ });
6421
+ var UsagePeriodScopeSchema2 = z62.enum(["per_month", "per_year", "per_billing_period", "lifetime", "concurrent", "per_instance", "per_second", "per_minute", "per_hour", "per_6_hours", "per_day", "per_week"]).meta(
6422
+ { id: "UsagePeriodScope", "x-revturbine-schema-persistence": Transient23, "x-revturbine-schema-exposure": External22 }
6423
+ );
6424
+ var UsageAllocationSchema2 = z62.enum(["account_pool", "per_instance", "per_user", "per_user_pooled"]).meta(
6425
+ { id: "UsageAllocation", "x-revturbine-schema-persistence": Transient23, "x-revturbine-schema-exposure": External22 }
6426
+ );
6427
+ var EntitlementGrantStatusSchema2 = z62.enum(["allowed", "limited", "denied"]).meta(
6428
+ { id: "EntitlementGrantStatus", "x-revturbine-schema-persistence": Transient23, "x-revturbine-schema-exposure": External22 }
6429
+ );
6430
+ var EntitlementGrantSourceSchema2 = z62.enum(["rule", "user_context", "override"]).meta(
6431
+ { id: "EntitlementGrantSource", "x-revturbine-schema-persistence": Transient23, "x-revturbine-schema-exposure": External22 }
6432
+ );
6433
+ var EnforcementModeSchema2 = z62.enum(["hard_block", "soft_block", "degrade", "allow_overage"]).meta(
6434
+ { id: "EnforcementMode", "x-revturbine-schema-persistence": Transient23, "x-revturbine-schema-exposure": External22 }
6435
+ );
6436
+ var EntitlementGrantSchema2 = z62.object({
6437
+ entitlement_id: z62.string().min(1).meta(Unrestricted25),
6438
+ entitlement_handle: z62.string().min(1).meta(Unrestricted25),
6439
+ status: EntitlementGrantStatusSchema2.meta(Unrestricted25),
6440
+ limit: z62.number().optional().meta(Unrestricted25),
6441
+ used: z62.number().optional().meta(Unrestricted25),
6442
+ allocation: UsageAllocationSchema2.optional().meta(Unrestricted25),
6443
+ enforcement: EnforcementModeSchema2.optional().meta(Unrestricted25),
6444
+ /** How this grant was derived. */
6445
+ source: EntitlementGrantSourceSchema2.optional().meta(Unrestricted25),
6446
+ // ── Derivation context (populated when source = 'rule') ──
6447
+ /** The plan that activated this rule-derived grant. */
6448
+ plan_id: z62.string().optional().meta(Unrestricted25),
6449
+ /** The segment that matched for this rule-derived grant. */
6450
+ segment_id: z62.string().optional().meta(Unrestricted25),
6451
+ /** The seat type that qualified this grant (when allocation is per-seat). */
6452
+ seat_type_id: z62.string().optional().meta(Unrestricted25),
6453
+ /** The entitlement rule id that produced this grant. */
6454
+ rule_id: z62.string().optional().meta(Unrestricted25)
6455
+ }).meta(
6456
+ {
6457
+ id: "EntitlementGrant",
6458
+ "x-revturbine-schema-persistence": Transient23,
6459
+ "x-revturbine-schema-exposure": External22,
6460
+ "x-revturbine-data-classification": "operational"
6461
+ }
6462
+ );
6463
+ var EntitlementGrantSetSchema2 = z62.object({
6464
+ account: z62.record(z62.string(), EntitlementGrantSchema2).optional().meta(Unrestricted25),
6465
+ instance: z62.record(z62.string(), EntitlementGrantSchema2).optional().meta(Unrestricted25),
6466
+ user: z62.record(z62.string(), EntitlementGrantSchema2).optional().meta(Unrestricted25)
6467
+ }).meta(
6468
+ {
6469
+ id: "EntitlementGrantSet",
6470
+ "x-revturbine-schema-persistence": Transient23,
6471
+ "x-revturbine-schema-exposure": External22,
6472
+ "x-revturbine-data-classification": "operational"
6473
+ }
6474
+ );
6475
+ var RuleVisibilitySchema2 = z62.enum(["public", "non_public"]).meta(
6476
+ { id: "RuleVisibility", "x-revturbine-schema-persistence": Transient23, "x-revturbine-schema-exposure": External22 }
6477
+ );
6478
+ var EntitlementRuleTargetKindSchema2 = z62.enum(["plan", "plan_variation", "addon", "addon_variation"]).meta(
6479
+ { id: "EntitlementRuleTargetKind", "x-revturbine-schema-persistence": Transient23, "x-revturbine-schema-exposure": External22 }
6480
+ );
6481
+ var EntitlementRuleTargetSchema2 = z62.object({
6482
+ kind: EntitlementRuleTargetKindSchema2.meta(Unrestricted25),
6483
+ id: z62.string().min(1).meta(Unrestricted25)
6484
+ }).meta(
6485
+ { id: "EntitlementRuleTarget", "x-revturbine-schema-persistence": Transient23, "x-revturbine-schema-exposure": External22 }
6486
+ );
6487
+ var EntitlementRulePeriodUnitSchema2 = z62.enum(["month", "day", "week", "quarter", "year", "billing_period", "hour", "six_hours"]).meta(
6488
+ { id: "EntitlementRulePeriodUnit", "x-revturbine-schema-persistence": Transient23, "x-revturbine-schema-exposure": External22 }
6489
+ );
6490
+ var EntitlementSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
6491
+ anchor_id: z62.string().min(1).meta({ ...Unrestricted25, readOnly: true }),
6492
+ name: NameField2.meta(Unrestricted25),
6493
+ handle: HandleField2.meta(Unrestricted25),
6494
+ customer_facing_description: z62.string().max(500).optional().meta(Unrestricted25),
6495
+ type: EntitlementTypeSchema2.meta(Unrestricted25),
6496
+ unit: z62.string().max(100).optional().meta(Unrestricted25),
6497
+ period_scope: UsagePeriodScopeSchema2.optional().meta(Unrestricted25),
6498
+ allocation: UsageAllocationSchema2.optional().meta(Unrestricted25),
6499
+ tier_definitions: z62.array(z62.object({
6500
+ name: z62.string(),
6501
+ handle: z62.string(),
6502
+ description: z62.string().optional()
6503
+ })).optional().meta(Unrestricted25),
6504
+ sort_order: z62.number().int().default(0).meta(Unrestricted25),
6505
+ metadata: MetadataField2.meta(Unrestricted25)
6506
+ }).meta(
6507
+ {
6508
+ id: "Entitlement",
6509
+ "x-revturbine-schema-persistence": Persisted25,
6510
+ "x-revturbine-schema-exposure": External22,
6511
+ ...PLAYBOOK_SDK_FACETS9,
6512
+ ...namedIdentity2()
6513
+ }
6514
+ );
6515
+ var EntitlementAnchorSchema2 = makeAnchor2("EntitlementAnchor");
6516
+ var EntitlementRuleSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
6517
+ anchor_id: z62.string().min(1).meta({ ...Unrestricted25, readOnly: true }),
6518
+ handle: HandleField2.meta({ ...Unrestricted25, readOnly: true }),
6519
+ entitlement_id: z62.string().min(1).meta(Unrestricted25),
6520
+ targets: z62.array(EntitlementRuleTargetSchema2).min(1).meta(Unrestricted25),
6521
+ // Segment scoping per spec §2.5: array of segment IDs interpreted with
6522
+ // intra-dimension OR + cross-dimension AND at evaluation time. The
6523
+ // dimensions registry resolves each ID → dimension. Empty array means
6524
+ // "match all users" (replaces the legacy 'all' sentinel).
6525
+ segment_ids: z62.array(z62.string()).default([]).meta(Unrestricted25),
6526
+ visibility: RuleVisibilitySchema2.default("public").meta(Unrestricted25),
6527
+ // Usage-Limit "measured over" window, rule-level (plan #55). Rate Limit
6528
+ // keeps its entitlement-level `period_scope`; this is the per-rule one.
6529
+ period_scope: UsagePeriodScopeSchema2.optional().meta(Unrestricted25),
6530
+ // Optional instance label, surfaced when `period_scope = 'per_instance'` (F-1).
6531
+ instance: z62.string().max(100).optional().meta(Unrestricted25),
6532
+ // Credits reset cadence ("refills every"): governs only the per-period
6533
+ // `allowance_value` refill. `billing_period` resolves at runtime to the
6534
+ // customer's Variation billing period; structural guard below. Absent means
6535
+ // one-time only — an `initial_grant` with no recurring refill (plan 147
6536
+ // REQ-6; `on_purchase` retired from the enum).
6537
+ reset_period: EntitlementRulePeriodUnitSchema2.optional().meta(Unrestricted25),
6538
+ // Type-specific fields (populated based on entitlement type)
6539
+ limit_value: z62.union([z62.number(), z62.literal("unlimited")]).optional().meta(Unrestricted25),
6540
+ enforcement: EnforcementModeSchema2.optional().meta(Unrestricted25),
6541
+ // Usage-warning emitter (plan 138 REQ-13): the percent at which this rule
6542
+ // emits a usage-warning crossing event. 10% increments so a placement's
6543
+ // `threshold_percent` (same field type) can align with — "should match one
6544
+ // of those global values" (placement-studio-ui.md §3.4). Optional; a rule
6545
+ // with no warning threshold emits none.
6546
+ warning_threshold_percent: ThresholdPercentField2.optional().meta(Unrestricted25),
6547
+ overage_price_ref: z62.string().optional().meta(Unrestricted25),
6548
+ grace_period_hours: z62.number().int().min(0).optional().meta(Unrestricted25),
6549
+ tier_value: z62.string().optional().meta(Unrestricted25),
6550
+ rate_value: z62.number().optional().meta(Unrestricted25),
6551
+ initial_grant: z62.number().optional().meta(Unrestricted25),
6552
+ allowance_value: z62.union([z62.number(), z62.literal("unlimited")]).optional().meta(Unrestricted25),
6553
+ rollover_enabled: z62.boolean().optional().meta(Unrestricted25),
6554
+ // Ceiling on total balance — refills clipped, add-on top-ups bypass.
6555
+ // NOT NULL JSONB at the DB level; JSON-null value means "no ceiling."
6556
+ max_balance: z62.union([z62.number(), z62.literal("unlimited")]).nullable().default(null).meta(Unrestricted25),
6557
+ seat_type_id: z62.string().optional().meta(Unrestricted25),
6558
+ included_count: z62.union([z62.number().int(), z62.literal("unlimited")]).optional().meta(Unrestricted25),
6559
+ at_limit_behavior: z62.enum(["hard_cap", "auto_upgrade_at_renewal"]).optional().meta(Unrestricted25),
6560
+ stripe_metered_price_id: z62.string().optional().meta(Unrestricted25),
6561
+ // ── Plan 147: columns promoted from the deleted portable `type_fields` union
6562
+ // so the flat wire projection round-trips (REQ-3 persist verdicts). The wire
6563
+ // (RevTurbineConfigEntitlementRulesItemSchema) mirrors these one-to-one under
6564
+ // their canonical names; web derives `kind`/`unit`/`tier_*` from the parent
6565
+ // entitlement instead of persisting them.
6566
+ //
6567
+ // feature enable/disable: a present-but-disabled feature rule is a real DENY
6568
+ // (`entitlement-check.ts` reads `enabled !== false`), distinct from "no rule".
6569
+ // Optional (absent = enabled) matches that evaluator semantic — replaces the
6570
+ // hard-coded `true` the export used to emit.
6571
+ enabled: z62.boolean().optional().meta(Unrestricted25),
6572
+ // How this rule partitions usage across the identity hierarchy. Rule-level in
6573
+ // the IR (`encode.ts` maps `r.allocation`); may default from the parent
6574
+ // entitlement's `allocation` when unset.
6575
+ allocation: UsageAllocationSchema2.optional().meta(Unrestricted25),
6576
+ // Seat ceiling. null = unlimited (plan 72); the 999999 export sentinel maps
6577
+ // back to null at the compile boundary.
6578
+ max_seats: z62.union([z62.number(), z62.literal("unlimited")]).nullable().optional().meta(Unrestricted25),
6579
+ // price_per_unit content-rendering fields (OQ-7: persisted-and-rendered, NOT
6580
+ // evaluated — no IR/evaluator branch). `unit` derives from the entitlement;
6581
+ // `period` maps onto the existing `period_scope` column.
6582
+ amount_cents: z62.number().int().optional().meta(Unrestricted25),
6583
+ currency: z62.string().optional().meta(Unrestricted25)
6584
+ }).meta(
6585
+ {
6586
+ id: "EntitlementRule",
6587
+ "x-revturbine-schema-persistence": Persisted25,
6588
+ "x-revturbine-schema-exposure": Internal22,
6589
+ ...PLAYBOOK_SDK_FACETS9,
6590
+ // Bucket 2: a rule IS its scope (entitlement + segments + targets); its
6591
+ // limit/enforcement is the mutable payload. Behaviour-only edits coalesce
6592
+ // as the same rule; a scope edit is a new rule identity (plan 121 REQ-3).
6593
+ // segment_ids/targets are jsonb arrays — the DB unique index normalizes them.
6594
+ ...mintedIdentity2()
6595
+ }
6596
+ );
6597
+ var EntitlementRuleAnchorSchema2 = makeAnchor2("EntitlementRuleAnchor");
6598
+ var EntitlementRuleValidatedSchema2 = EntitlementRuleSchema2.superRefine(
6599
+ (rule, ctx) => {
6600
+ if (rule.reset_period === "billing_period") {
6601
+ const nonVariationIdx = rule.targets.findIndex(
6602
+ (t) => t.kind !== "plan_variation" && t.kind !== "addon_variation"
6603
+ );
6604
+ if (nonVariationIdx !== -1) {
6605
+ ctx.addIssue({
6606
+ code: "custom",
6607
+ path: ["reset_period"],
6608
+ params: { code: "billing_period_target_mismatch" },
6609
+ message: "reset_period='billing_period' requires all targets to be plan_variation or addon_variation"
6610
+ });
6611
+ }
6612
+ }
6613
+ const allowance = rule.allowance_value;
6614
+ const hasRecurringAllowance = allowance === "unlimited" || typeof allowance === "number" && allowance > 0;
6615
+ if (hasRecurringAllowance && rule.reset_period == null) {
6616
+ ctx.addIssue({
6617
+ code: "custom",
6618
+ path: ["reset_period"],
6619
+ params: { code: "allowance_requires_reset_period" },
6620
+ message: "a non-zero credits allowance_value requires a reset_period (the per-period refill cadence)"
6621
+ });
6622
+ }
6623
+ }
6624
+ );
6625
+ var EntitlementRuleWarningCodeSchema2 = z62.enum(["one_time_period_mismatch"]).meta(
6626
+ { id: "EntitlementRuleWarningCode", "x-revturbine-schema-persistence": Transient23, "x-revturbine-schema-exposure": External22 }
6627
+ );
6628
+ var EntitlementRuleWarningSchema2 = z62.object({
6629
+ code: EntitlementRuleWarningCodeSchema2.meta(Unrestricted25),
6630
+ message: z62.string().min(1).meta(Unrestricted25),
6631
+ /** Optional dotted field path the warning relates to (e.g. `['targets']`). */
6632
+ path: z62.array(z62.union([z62.string(), z62.number().int()])).optional().meta(Unrestricted25)
6633
+ }).meta(
6634
+ { id: "EntitlementRuleWarning", "x-revturbine-schema-persistence": Transient23, "x-revturbine-schema-exposure": External22 }
6635
+ );
6636
+ var EntitlementRuleWriteResponseSchema2 = EntitlementRuleSchema2.extend({
6637
+ warnings: z62.array(EntitlementRuleWarningSchema2).optional().meta(Unrestricted25)
6638
+ }).meta(
6639
+ {
6640
+ id: "EntitlementRuleWriteResponse",
6641
+ "x-revturbine-schema-persistence": Transient23,
6642
+ "x-revturbine-schema-exposure": External22
6643
+ }
6644
+ );
6645
+ var EntitlementRuleVariantSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
6646
+ handle: HandleField2.meta({ ...Unrestricted25, readOnly: true }),
6647
+ rule_id: z62.string().min(1).meta(Unrestricted25),
6648
+ experiment_id: z62.string().min(1).meta(Unrestricted25),
6649
+ variant_name: z62.string().min(1).max(200).meta(Unrestricted25),
6650
+ is_control: z62.boolean().default(false).meta(Unrestricted25),
6651
+ override_fields: z62.record(z62.string(), z62.unknown()).default({}).meta(Unrestricted25)
6652
+ }).meta(
6653
+ {
6654
+ id: "EntitlementRuleVariant",
6655
+ "x-revturbine-schema-persistence": Persisted25,
6656
+ "x-revturbine-schema-exposure": Internal22,
6657
+ ...PENDING_PLAYBOOK_FACETS5,
6658
+ ...mintedIdentity2()
6659
+ }
6660
+ );
6661
+ var EntitlementWriteSchema2 = toWritableSchema2(EntitlementSchema2);
6662
+ var EntitlementRuleWriteSchema2 = toWritableSchema2(EntitlementRuleSchema2);
6663
+ var entitlementPaths2 = {
6664
+ "/api/entitlement-anchors": {
6665
+ get: operation2({
6666
+ operationId: "listEntitlementAnchors",
6667
+ requestParams: { query: ListQueryParamsSchema2 },
6668
+ summary: "List entitlement anchors (identity registry)",
6669
+ tags: ["entitlements"],
6670
+ responses: {
6671
+ "200": { description: "Entitlement anchor list", content: { "application/json": { schema: ListEnvelope2(EntitlementAnchorSchema2) } } },
6672
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
6673
+ },
6674
+ "x-revturbine-operation": { exposure: "internal", resource: "entitlement-anchors", persistence: { table: "entitlements", mode: "list" } }
6675
+ })
6676
+ },
6677
+ "/api/entitlement-rule-anchors": {
6678
+ get: operation2({
6679
+ operationId: "listEntitlementRuleAnchors",
6680
+ requestParams: { query: ListQueryParamsSchema2 },
6681
+ summary: "List entitlement rule anchors (identity registry)",
6682
+ tags: ["entitlements"],
6683
+ responses: {
6684
+ "200": { description: "Entitlement rule anchor list", content: { "application/json": { schema: ListEnvelope2(EntitlementRuleAnchorSchema2) } } },
6685
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
6686
+ },
6687
+ "x-revturbine-operation": { exposure: "internal", resource: "entitlement-rule-anchors", persistence: { table: "entitlementRules", mode: "list" } }
6688
+ })
6689
+ },
6690
+ "/api/entitlements": {
6691
+ get: operation2({
6692
+ operationId: "listEntitlements",
6693
+ requestParams: { query: ListQueryParamsSchema2 },
6694
+ summary: "List entitlements",
6695
+ tags: ["entitlements"],
6696
+ responses: { "200": { description: "Entitlement list", content: { "application/json": { schema: ListEnvelope2(EntitlementSchema2) } } } },
6697
+ "x-revturbine-operation": { exposure: "external", resource: "entitlements", persistence: { table: "entitlementVersions", mode: "list" } }
6698
+ }),
6699
+ post: operation2({
6700
+ operationId: "createEntitlement",
6701
+ summary: "Create entitlement",
6702
+ tags: ["entitlements"],
6703
+ requestBody: { required: true, content: { "application/json": { schema: toCreateSchema2(EntitlementSchema2) } } },
6704
+ responses: { "201": { description: "Created", content: { "application/json": { schema: EntitlementSchema2 } } } },
6705
+ "x-revturbine-operation": { exposure: "external", resource: "entitlements", persistence: { table: "entitlementVersions", mode: "create" } }
6706
+ })
6707
+ },
6708
+ "/api/entitlements/{entitlementId}": {
6709
+ get: operation2({
6710
+ operationId: "getEntitlement",
6711
+ requestParams: { path: z62.object({ entitlementId: z62.string() }) },
6712
+ summary: "Get entitlement",
6713
+ tags: ["entitlements"],
6714
+ responses: { "200": { description: "Entitlement detail", content: { "application/json": { schema: EntitlementSchema2 } } } },
6715
+ "x-revturbine-operation": { exposure: "external", resource: "entitlements", persistence: { table: "entitlementVersions", mode: "get" } }
6716
+ }),
6717
+ patch: operation2({
6718
+ operationId: "updateEntitlement",
6719
+ requestParams: { path: z62.object({ entitlementId: z62.string() }) },
6720
+ summary: "Update entitlement",
6721
+ tags: ["entitlements"],
6722
+ requestBody: { required: true, content: { "application/json": { schema: EntitlementWriteSchema2 } } },
6723
+ responses: { "200": { description: "Updated", content: { "application/json": { schema: EntitlementSchema2 } } } },
6724
+ "x-revturbine-operation": { exposure: "external", resource: "entitlements", persistence: { table: "entitlementVersions", mode: "update" } }
6725
+ }),
6726
+ delete: operation2({
6727
+ operationId: "deleteEntitlement",
6728
+ requestParams: { path: z62.object({ entitlementId: z62.string() }) },
6729
+ summary: "Delete entitlement",
6730
+ tags: ["entitlements"],
6731
+ responses: { "204": { description: "Deleted" }, default: { description: "Error", content: { "application/json": { schema: ErrorEnvelope2 } } } },
6732
+ "x-revturbine-operation": { exposure: "external", resource: "entitlements", persistence: { table: "entitlementVersions", mode: "delete" } }
6733
+ })
6734
+ },
6735
+ "/api/entitlements/{entitlementId}/rules": {
6736
+ get: operation2({
6737
+ operationId: "listEntitlementRules",
6738
+ requestParams: { path: z62.object({ entitlementId: z62.string() }), query: ListQueryParamsSchema2 },
6739
+ summary: "List rules for entitlement",
6740
+ tags: ["entitlements"],
6741
+ responses: { "200": { description: "Rule list", content: { "application/json": { schema: ListEnvelope2(EntitlementRuleSchema2) } } } },
6742
+ "x-revturbine-operation": { exposure: "external", resource: "entitlement-rules", persistence: { table: "entitlementRuleVersions", mode: "list" } }
6743
+ }),
6744
+ post: operation2({
6745
+ operationId: "createEntitlementRule",
6746
+ requestParams: { path: z62.object({ entitlementId: z62.string() }) },
6747
+ summary: "Create entitlement rule",
6748
+ tags: ["entitlements"],
6749
+ requestBody: { required: true, content: { "application/json": { schema: toCreateSchema2(EntitlementRuleSchema2) } } },
6750
+ responses: { "201": { description: "Created", content: { "application/json": { schema: EntitlementRuleWriteResponseSchema2 } } } },
6751
+ "x-revturbine-operation": { exposure: "external", resource: "entitlement-rules", persistence: { table: "entitlementRuleVersions", mode: "create" } }
6752
+ })
6753
+ },
6754
+ "/api/entitlement-rules/{ruleId}": {
6755
+ patch: operation2({
6756
+ operationId: "updateEntitlementRule",
6757
+ requestParams: { path: z62.object({ ruleId: z62.string() }) },
6758
+ summary: "Update entitlement rule",
6759
+ tags: ["entitlements"],
6760
+ requestBody: { required: true, content: { "application/json": { schema: EntitlementRuleWriteSchema2 } } },
6761
+ responses: { "200": { description: "Updated", content: { "application/json": { schema: EntitlementRuleWriteResponseSchema2 } } } },
6762
+ "x-revturbine-operation": { exposure: "external", resource: "entitlement-rules", persistence: { table: "entitlementRuleVersions", mode: "update" } }
6763
+ }),
6764
+ delete: operation2({
6765
+ operationId: "deleteEntitlementRule",
6766
+ requestParams: { path: z62.object({ ruleId: z62.string() }) },
6767
+ summary: "Delete entitlement rule",
6768
+ tags: ["entitlements"],
6769
+ responses: { "204": { description: "Deleted" } },
6770
+ "x-revturbine-operation": { exposure: "external", resource: "entitlement-rules", persistence: { table: "entitlementRuleVersions", mode: "delete" } }
6771
+ })
6772
+ },
6773
+ "/api/entitlement-rules/{ruleId}/duplicate": {
6774
+ post: operation2({
6775
+ operationId: "duplicateEntitlementRule",
6776
+ requestParams: { path: z62.object({ ruleId: z62.string() }) },
6777
+ summary: "Duplicate entitlement rule",
6778
+ tags: ["entitlements"],
6779
+ responses: { "201": { description: "Duplicated", content: { "application/json": { schema: EntitlementRuleWriteResponseSchema2 } } } },
6780
+ "x-revturbine-operation": { exposure: "external", resource: "entitlement-rules", persistence: { table: "entitlementRuleVersions", mode: "duplicate" } }
6781
+ })
6782
+ },
6783
+ "/api/entitlement-rules/{ruleId}/variants": {
6784
+ get: operation2({
6785
+ operationId: "listEntitlementRuleVariants",
6786
+ requestParams: { path: z62.object({ ruleId: z62.string() }), query: ListQueryParamsSchema2 },
6787
+ summary: "List A/B variants for rule",
6788
+ tags: ["entitlements"],
6789
+ responses: { "200": { description: "Variant list", content: { "application/json": { schema: ListEnvelope2(EntitlementRuleVariantSchema2) } } } },
6790
+ "x-revturbine-operation": { exposure: "external", resource: "entitlement-rule-variants", persistence: { table: "entitlementRuleVariants", mode: "list" } }
6791
+ }),
6792
+ post: operation2({
6793
+ operationId: "createEntitlementRuleVariant",
6794
+ requestParams: { path: z62.object({ ruleId: z62.string() }) },
6795
+ summary: "Create rule A/B variant",
6796
+ tags: ["entitlements"],
6797
+ requestBody: { required: true, content: { "application/json": { schema: toCreateSchema2(EntitlementRuleVariantSchema2) } } },
6798
+ responses: { "201": { description: "Created", content: { "application/json": { schema: EntitlementRuleVariantSchema2 } } } },
6799
+ "x-revturbine-operation": { exposure: "external", resource: "entitlement-rule-variants", persistence: { table: "entitlementRuleVariants", mode: "create" } }
6800
+ })
6801
+ }
6802
+ };
6803
+ var { Unrestricted: Unrestricted32 } = DataClassification2;
6804
+ var { Persisted: Persisted32, Transient: Transient32 } = SchemaPersistence2;
6805
+ var { Internal: Internal32 } = SchemaExposure2;
6806
+ var PLAYBOOK_SDK_FACETS22 = schemaFacets2(SchemaContext2.Playbook, { sdkInput: true });
6807
+ var PENDING_PLAYBOOK_SDK_FACETS5 = schemaFacets2(SchemaContext2.Playbook, {
6808
+ inConfig: false,
6809
+ sdkInput: true
6810
+ });
6811
+ var TrialStatusSchema2 = z72.enum(["not_started", "active", "expired", "converted", "cancelled"]).meta(
6812
+ { id: "TrialStatus", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": Internal32 }
6813
+ );
6814
+ var TrialLimitTypeSchema2 = z72.enum(["time", "usage"]).meta(
6815
+ { id: "TrialLimitType", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": Internal32 }
6816
+ );
6817
+ var FreeTrialRuleCoreFieldsSchema2 = z72.object({
6818
+ name: NameField2.meta(Unrestricted32),
6819
+ handle: HandleField2.meta(Unrestricted32),
6820
+ // plan_id null = "All plans" — see plans-entitlements-studio-ui.md §2.4.1.
6821
+ plan_id: z72.string().nullable().optional().meta(Unrestricted32),
6822
+ segment_id: z72.string().nullable().optional().meta(Unrestricted32),
6823
+ // Defaults to 'time' so every pre-existing rule keeps its current
6824
+ // duration-based semantics. Set to 'usage' to scope the trial by
6825
+ // consumption of `usage_entitlement_handle` up to
6826
+ // `usage_limit_value`; the time fields below are then ignored.
6827
+ trial_limit_type: TrialLimitTypeSchema2.default("time").meta(Unrestricted32),
6828
+ // Time-based: rule is skipped at runtime when null/blank. The
6829
+ // Default Trial Length global was removed (no fallback exists).
6830
+ duration_days: z72.number().int().min(1).max(365).nullable().optional().meta(Unrestricted32),
6831
+ grace_period_days: z72.number().int().min(0).default(0).meta(Unrestricted32),
6832
+ // Usage-based: the entitlement whose consumption gates the trial,
6833
+ // and the cap. Both required when `trial_limit_type === 'usage'`;
6834
+ // otherwise ignored. Cross-field validation is done at the API
6835
+ // boundary (web app's POST handler) rather than here so partial
6836
+ // drafts stay round-trippable.
6837
+ usage_entitlement_handle: z72.string().min(1).optional().meta(Unrestricted32),
6838
+ usage_limit_value: z72.number().int().min(1).optional().meta(Unrestricted32),
6839
+ require_payment_method: z72.boolean().default(false).meta(Unrestricted32),
6840
+ auto_convert: z72.boolean().default(true).meta(Unrestricted32),
6841
+ /**
6842
+ * Post-trial destination plans. At end of trial the control plane
6843
+ * places the user on either:
6844
+ * - `convert_to_plan_id` — typically a PAID plan; the user must
6845
+ * already have a payment method on file (require_payment_method
6846
+ * usually implies this). Used when `auto_convert: true` AND
6847
+ * the user has met any conversion preconditions.
6848
+ * - `fallback_plan_id` — typically a FREE plan (the company's
6849
+ * Free tier or equivalent "no plan"). Used when the user did
6850
+ * not convert (auto_convert: false, missing payment method,
6851
+ * declined upsell, etc.). When unset the user reverts to
6852
+ * "no plan" / pre-trial state.
6853
+ */
6854
+ convert_to_plan_id: z72.string().optional().meta(Unrestricted32),
6855
+ fallback_plan_id: z72.string().optional().meta(Unrestricted32),
6856
+ limit_per_customer: z72.number().int().min(1).default(1).meta(Unrestricted32),
6857
+ is_active: z72.boolean().default(true).meta(Unrestricted32),
6858
+ metadata: MetadataField2.meta(Unrestricted32)
6859
+ });
6860
+ var FreeTrialRuleSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
6861
+ anchor_id: z72.string().min(1).meta({ ...Unrestricted32, readOnly: true })
6862
+ }).merge(FreeTrialRuleCoreFieldsSchema2).meta(
6863
+ { id: "FreeTrialRule", "x-revturbine-schema-persistence": Persisted32, "x-revturbine-schema-exposure": Internal32, ...PLAYBOOK_SDK_FACETS22, ...namedIdentity2() }
6864
+ );
6865
+ var FreeTrialRuleAnchorSchema2 = makeAnchor2("FreeTrialRuleAnchor");
6866
+ var ReverseTrialStartPolicySchema2 = z72.enum(["signup", "first_premium_access", "manual"]).meta(
6867
+ { id: "ReverseTrialStartPolicy", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": Internal32 }
6868
+ );
6869
+ var ReverseTrialRuleCoreFieldsSchema2 = z72.object({
6870
+ name: NameField2.meta(Unrestricted32),
6871
+ handle: HandleField2.meta(Unrestricted32),
6872
+ premium_plan_id: z72.string().min(1).meta(Unrestricted32),
6873
+ fallback_plan_id: z72.string().min(1).meta(Unrestricted32),
6874
+ segment_id: z72.string().nullable().optional().meta(Unrestricted32),
6875
+ trial_limit_type: TrialLimitTypeSchema2.default("time").meta(Unrestricted32),
6876
+ duration_days: z72.number().int().min(1).max(365).nullable().optional().meta(Unrestricted32),
6877
+ usage_entitlement_handle: z72.string().min(1).optional().meta(Unrestricted32),
6878
+ usage_limit_value: z72.number().int().min(1).optional().meta(Unrestricted32),
6879
+ start_policy: ReverseTrialStartPolicySchema2.default("signup").meta(Unrestricted32),
6880
+ show_upgrade_prompt_at_day: z72.number().int().min(0).optional().meta(Unrestricted32),
6881
+ entitlements_during_trial: z72.array(z72.string()).default([]).meta(Unrestricted32),
6882
+ is_active: z72.boolean().default(true).meta(Unrestricted32),
6883
+ metadata: MetadataField2.meta(Unrestricted32)
6884
+ });
6885
+ var ReverseTrialRuleSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
6886
+ anchor_id: z72.string().min(1).meta({ ...Unrestricted32, readOnly: true })
6887
+ }).merge(ReverseTrialRuleCoreFieldsSchema2).meta(
6888
+ { id: "ReverseTrialRule", "x-revturbine-schema-persistence": Persisted32, "x-revturbine-schema-exposure": Internal32, ...PLAYBOOK_SDK_FACETS22, ...namedIdentity2() }
6889
+ );
6890
+ var ReverseTrialRuleAnchorSchema2 = makeAnchor2("ReverseTrialRuleAnchor");
6891
+ var TrialLimitPolicySchema2 = z72.enum(["1_per_lifetime", "1_per_plan", "1_per_year", "unlimited"]).meta(
6892
+ { id: "TrialLimitPolicy", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": Internal32 }
6893
+ );
6894
+ var TrialEligibilityScopeSchema2 = z72.enum(["per_customer", "per_email_domain"]).meta(
6895
+ { id: "TrialEligibilityScope", "x-revturbine-schema-persistence": Transient32, "x-revturbine-schema-exposure": Internal32 }
6896
+ );
6897
+ var FreeTrialSettingsSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).extend({
6898
+ trial_limit_policy: TrialLimitPolicySchema2.default("1_per_lifetime").meta(Unrestricted32),
6899
+ eligibility_scope: TrialEligibilityScopeSchema2.default("per_customer").meta(Unrestricted32)
6900
+ }).meta(
6901
+ { id: "FreeTrialSettings", "x-revturbine-schema-persistence": Persisted32, "x-revturbine-schema-exposure": Internal32, ...PENDING_PLAYBOOK_SDK_FACETS5 }
6902
+ );
6903
+ var ReverseTrialSettingsSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).extend({
6904
+ trial_limit_policy: TrialLimitPolicySchema2.default("1_per_lifetime").meta(Unrestricted32),
6905
+ eligibility_scope: TrialEligibilityScopeSchema2.default("per_customer").meta(Unrestricted32)
6906
+ }).meta(
6907
+ { id: "ReverseTrialSettings", "x-revturbine-schema-persistence": Persisted32, "x-revturbine-schema-exposure": Internal32, ...PENDING_PLAYBOOK_SDK_FACETS5 }
6908
+ );
6909
+ var TrialInstanceSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).extend({
6910
+ customer_id: z72.string().min(1).meta(Unrestricted32),
6911
+ rule_id: z72.string().min(1).meta(Unrestricted32),
6912
+ rule_type: z72.enum(["free_trial", "reverse_trial"]).meta(Unrestricted32),
6913
+ plan_id: z72.string().min(1).meta(Unrestricted32),
6914
+ status: TrialStatusSchema2.default("active").meta(Unrestricted32),
6915
+ started_at: z72.string().datetime().meta({ ...Unrestricted32, readOnly: true }),
6916
+ /**
6917
+ * Time-based expiry. Required for time-based trials; null for
6918
+ * pure usage-based trials (which expire when consumption crosses
6919
+ * `usage_limit_value` regardless of clock time).
6920
+ */
6921
+ expires_at: z72.string().datetime().nullable().optional().meta(Unrestricted32),
6922
+ /**
6923
+ * Snapshot of the rule's `trial_limit_type` at the moment the
6924
+ * instance was created. Persisted so subsequent changes to the
6925
+ * rule's mode don't retroactively alter the user's trial
6926
+ * semantics. Defaults to 'time' for backward compatibility with
6927
+ * pre-existing instances.
6928
+ */
6929
+ trial_limit_type: TrialLimitTypeSchema2.default("time").meta(Unrestricted32),
6930
+ /**
6931
+ * Snapshot of the rule's `usage_entitlement_handle` for
6932
+ * usage-based trials. Server queries the user's current
6933
+ * consumption of this entitlement to derive
6934
+ * `UserTrialStatus.usage_consumed` at read time.
6935
+ */
6936
+ usage_entitlement_handle: z72.string().min(1).optional().meta(Unrestricted32),
6937
+ /**
6938
+ * Snapshot of the rule's `usage_limit_value`. Persisted so
6939
+ * mid-trial limit changes on the rule don't shrink/expand a
6940
+ * user's in-flight trial.
6941
+ */
6942
+ usage_limit_value: z72.number().int().min(1).optional().meta(Unrestricted32),
6943
+ converted_at: NullableDatetimeField2.meta(Unrestricted32),
6944
+ cancelled_at: NullableDatetimeField2.meta(Unrestricted32),
6945
+ metadata: MetadataField2.meta(Unrestricted32)
6946
+ }).meta(
6947
+ { id: "TrialInstance", "x-revturbine-schema-persistence": Persisted32, "x-revturbine-schema-exposure": Internal32 }
6948
+ );
6949
+ var trialPaths2 = {
6950
+ "/api/free-trial-rule-anchors": {
6951
+ get: operation2({
6952
+ operationId: "listFreeTrialRuleAnchors",
6953
+ requestParams: { query: ListQueryParamsSchema2 },
6954
+ summary: "List free trial rule anchors (identity registry)",
6955
+ tags: ["trials"],
6956
+ responses: {
6957
+ "200": { description: "Free trial rule anchor list", content: { "application/json": { schema: ListEnvelope2(FreeTrialRuleAnchorSchema2) } } },
6958
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
6959
+ },
6960
+ "x-revturbine-operation": { exposure: "internal", resource: "free-trial-rule-anchors", persistence: { table: "freeTrialRules", mode: "list" } }
6961
+ })
6962
+ },
6963
+ "/api/trials/free-rules": {
6964
+ get: operation2({
6965
+ operationId: "listFreeTrialRules",
6966
+ requestParams: { query: ListQueryParamsSchema2 },
6967
+ summary: "List free trial rules",
6968
+ tags: ["trials"],
6969
+ responses: { "200": { description: "Free trial rule list", content: { "application/json": { schema: ListEnvelope2(FreeTrialRuleSchema2) } } } },
6970
+ "x-revturbine-operation": { exposure: "internal", resource: "trial-rules", persistence: { table: "freeTrialRuleVersions", mode: "list" } }
6971
+ }),
6972
+ post: operation2({
6973
+ operationId: "createFreeTrialRule",
6974
+ summary: "Create free trial rule",
6975
+ tags: ["trials"],
6976
+ requestBody: { required: true, content: { "application/json": { schema: FreeTrialRuleSchema2 } } },
6977
+ responses: { "201": { description: "Created", content: { "application/json": { schema: FreeTrialRuleSchema2 } } } },
6978
+ "x-revturbine-operation": { exposure: "internal", resource: "trial-rules", persistence: { table: "freeTrialRuleVersions", mode: "create" } }
6979
+ })
6980
+ },
6981
+ "/api/trials/free-rules/{ruleId}": {
6982
+ get: operation2({
6983
+ operationId: "getFreeTrialRule",
6984
+ requestParams: { path: z72.object({ ruleId: z72.string() }) },
6985
+ summary: "Get free trial rule",
6986
+ tags: ["trials"],
6987
+ responses: { "200": { description: "Free trial rule", content: { "application/json": { schema: FreeTrialRuleSchema2 } } } },
6988
+ "x-revturbine-operation": { exposure: "internal", resource: "trial-rules", persistence: { table: "freeTrialRuleVersions", mode: "get" } }
6989
+ }),
6990
+ patch: operation2({
6991
+ operationId: "updateFreeTrialRule",
6992
+ requestParams: { path: z72.object({ ruleId: z72.string() }) },
6993
+ summary: "Update free trial rule",
6994
+ tags: ["trials"],
6995
+ requestBody: { required: true, content: { "application/json": { schema: FreeTrialRuleSchema2.partial() } } },
6996
+ responses: { "200": { description: "Updated", content: { "application/json": { schema: FreeTrialRuleSchema2 } } } },
6997
+ "x-revturbine-operation": { exposure: "internal", resource: "trial-rules", persistence: { table: "freeTrialRuleVersions", mode: "update" } }
6998
+ }),
6999
+ delete: operation2({
7000
+ operationId: "deleteFreeTrialRule",
7001
+ requestParams: { path: z72.object({ ruleId: z72.string() }) },
7002
+ summary: "Delete free trial rule",
7003
+ tags: ["trials"],
7004
+ responses: { "204": { description: "Deleted" } },
7005
+ "x-revturbine-operation": { exposure: "internal", resource: "trial-rules", persistence: { table: "freeTrialRuleVersions", mode: "delete" } }
7006
+ })
7007
+ },
7008
+ "/api/reverse-trial-rule-anchors": {
7009
+ get: operation2({
7010
+ operationId: "listReverseTrialRuleAnchors",
7011
+ requestParams: { query: ListQueryParamsSchema2 },
7012
+ summary: "List reverse trial rule anchors (identity registry)",
7013
+ tags: ["trials"],
7014
+ responses: {
7015
+ "200": { description: "Reverse trial rule anchor list", content: { "application/json": { schema: ListEnvelope2(ReverseTrialRuleAnchorSchema2) } } },
7016
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
7017
+ },
7018
+ "x-revturbine-operation": { exposure: "internal", resource: "reverse-trial-rule-anchors", persistence: { table: "reverseTrialRules", mode: "list" } }
7019
+ })
7020
+ },
7021
+ "/api/trials/reverse-rules": {
7022
+ get: operation2({
7023
+ operationId: "listReverseTrialRules",
7024
+ requestParams: { query: ListQueryParamsSchema2 },
7025
+ summary: "List reverse trial rules",
7026
+ tags: ["trials"],
7027
+ responses: { "200": { description: "Reverse trial rule list", content: { "application/json": { schema: ListEnvelope2(ReverseTrialRuleSchema2) } } } },
7028
+ "x-revturbine-operation": { exposure: "internal", resource: "reverse-trial-rules", persistence: { table: "reverseTrialRuleVersions", mode: "list" } }
7029
+ }),
7030
+ post: operation2({
7031
+ operationId: "createReverseTrialRule",
7032
+ summary: "Create reverse trial rule",
7033
+ tags: ["trials"],
7034
+ requestBody: { required: true, content: { "application/json": { schema: ReverseTrialRuleSchema2 } } },
7035
+ responses: { "201": { description: "Created", content: { "application/json": { schema: ReverseTrialRuleSchema2 } } } },
7036
+ "x-revturbine-operation": { exposure: "internal", resource: "reverse-trial-rules", persistence: { table: "reverseTrialRuleVersions", mode: "create" } }
7037
+ })
7038
+ },
7039
+ "/api/trials/reverse-rules/{ruleId}": {
7040
+ get: operation2({
7041
+ operationId: "getReverseTrialRule",
7042
+ requestParams: { path: z72.object({ ruleId: z72.string() }) },
7043
+ summary: "Get reverse trial rule",
7044
+ tags: ["trials"],
7045
+ responses: { "200": { description: "Reverse trial rule", content: { "application/json": { schema: ReverseTrialRuleSchema2 } } } },
7046
+ "x-revturbine-operation": { exposure: "internal", resource: "reverse-trial-rules", persistence: { table: "reverseTrialRuleVersions", mode: "get" } }
7047
+ }),
7048
+ patch: operation2({
7049
+ operationId: "updateReverseTrialRule",
7050
+ requestParams: { path: z72.object({ ruleId: z72.string() }) },
7051
+ summary: "Update reverse trial rule",
7052
+ tags: ["trials"],
7053
+ requestBody: { required: true, content: { "application/json": { schema: ReverseTrialRuleSchema2.partial() } } },
7054
+ responses: { "200": { description: "Updated", content: { "application/json": { schema: ReverseTrialRuleSchema2 } } } },
7055
+ "x-revturbine-operation": { exposure: "internal", resource: "reverse-trial-rules", persistence: { table: "reverseTrialRuleVersions", mode: "update" } }
7056
+ }),
7057
+ delete: operation2({
7058
+ operationId: "deleteReverseTrialRule",
7059
+ requestParams: { path: z72.object({ ruleId: z72.string() }) },
7060
+ summary: "Delete reverse trial rule",
7061
+ tags: ["trials"],
7062
+ responses: { "204": { description: "Deleted" } },
7063
+ "x-revturbine-operation": { exposure: "internal", resource: "reverse-trial-rules", persistence: { table: "reverseTrialRuleVersions", mode: "delete" } }
7064
+ })
7065
+ },
7066
+ "/api/trials/free-settings": {
7067
+ get: operation2({
7068
+ operationId: "listFreeTrialSettings",
7069
+ requestParams: { query: ListQueryParamsSchema2 },
7070
+ summary: "List free trial settings (one row per tenant)",
7071
+ tags: ["trials"],
7072
+ responses: { "200": { description: "Free trial settings list", content: { "application/json": { schema: ListEnvelope2(FreeTrialSettingsSchema2) } } } },
7073
+ "x-revturbine-operation": { exposure: "internal", resource: "free-trial-settings", persistence: { table: "freeTrialSettings", mode: "list" } }
7074
+ }),
7075
+ post: operation2({
7076
+ operationId: "createFreeTrialSettings",
7077
+ summary: "Create free trial settings",
7078
+ tags: ["trials"],
7079
+ requestBody: { required: true, content: { "application/json": { schema: FreeTrialSettingsSchema2 } } },
7080
+ responses: { "201": { description: "Created", content: { "application/json": { schema: FreeTrialSettingsSchema2 } } } },
7081
+ "x-revturbine-operation": { exposure: "internal", resource: "free-trial-settings", persistence: { table: "freeTrialSettings", mode: "create" } }
7082
+ })
7083
+ },
7084
+ "/api/trials/free-settings/{settingsId}": {
7085
+ get: operation2({
7086
+ operationId: "getFreeTrialSettings",
7087
+ requestParams: { path: z72.object({ settingsId: z72.string() }) },
7088
+ summary: "Get free trial settings",
7089
+ tags: ["trials"],
7090
+ responses: { "200": { description: "Free trial settings", content: { "application/json": { schema: FreeTrialSettingsSchema2 } } } },
7091
+ "x-revturbine-operation": { exposure: "internal", resource: "free-trial-settings", persistence: { table: "freeTrialSettings", mode: "get" } }
7092
+ }),
7093
+ patch: operation2({
7094
+ operationId: "updateFreeTrialSettings",
7095
+ requestParams: { path: z72.object({ settingsId: z72.string() }) },
7096
+ summary: "Update free trial settings",
7097
+ tags: ["trials"],
7098
+ requestBody: { required: true, content: { "application/json": { schema: FreeTrialSettingsSchema2.partial() } } },
7099
+ responses: { "200": { description: "Updated", content: { "application/json": { schema: FreeTrialSettingsSchema2 } } } },
7100
+ "x-revturbine-operation": { exposure: "internal", resource: "free-trial-settings", persistence: { table: "freeTrialSettings", mode: "update" } }
7101
+ })
7102
+ },
7103
+ "/api/trials/reverse-settings": {
7104
+ get: operation2({
7105
+ operationId: "listReverseTrialSettings",
7106
+ requestParams: { query: ListQueryParamsSchema2 },
7107
+ summary: "List reverse trial settings (one row per tenant)",
7108
+ tags: ["trials"],
7109
+ responses: { "200": { description: "Reverse trial settings list", content: { "application/json": { schema: ListEnvelope2(ReverseTrialSettingsSchema2) } } } },
7110
+ "x-revturbine-operation": { exposure: "internal", resource: "reverse-trial-settings", persistence: { table: "reverseTrialSettings", mode: "list" } }
7111
+ }),
7112
+ post: operation2({
7113
+ operationId: "createReverseTrialSettings",
7114
+ summary: "Create reverse trial settings",
7115
+ tags: ["trials"],
7116
+ requestBody: { required: true, content: { "application/json": { schema: ReverseTrialSettingsSchema2 } } },
7117
+ responses: { "201": { description: "Created", content: { "application/json": { schema: ReverseTrialSettingsSchema2 } } } },
7118
+ "x-revturbine-operation": { exposure: "internal", resource: "reverse-trial-settings", persistence: { table: "reverseTrialSettings", mode: "create" } }
7119
+ })
7120
+ },
7121
+ "/api/trials/reverse-settings/{settingsId}": {
7122
+ get: operation2({
7123
+ operationId: "getReverseTrialSettings",
7124
+ requestParams: { path: z72.object({ settingsId: z72.string() }) },
7125
+ summary: "Get reverse trial settings",
7126
+ tags: ["trials"],
7127
+ responses: { "200": { description: "Reverse trial settings", content: { "application/json": { schema: ReverseTrialSettingsSchema2 } } } },
7128
+ "x-revturbine-operation": { exposure: "internal", resource: "reverse-trial-settings", persistence: { table: "reverseTrialSettings", mode: "get" } }
7129
+ }),
7130
+ patch: operation2({
7131
+ operationId: "updateReverseTrialSettings",
7132
+ requestParams: { path: z72.object({ settingsId: z72.string() }) },
7133
+ summary: "Update reverse trial settings",
7134
+ tags: ["trials"],
7135
+ requestBody: { required: true, content: { "application/json": { schema: ReverseTrialSettingsSchema2.partial() } } },
7136
+ responses: { "200": { description: "Updated", content: { "application/json": { schema: ReverseTrialSettingsSchema2 } } } },
7137
+ "x-revturbine-operation": { exposure: "internal", resource: "reverse-trial-settings", persistence: { table: "reverseTrialSettings", mode: "update" } }
7138
+ })
7139
+ },
7140
+ "/api/trials/instances": {
7141
+ get: operation2({
7142
+ operationId: "listTrialInstances",
7143
+ requestParams: { query: ListQueryParamsSchema2 },
7144
+ summary: "List trial instances",
7145
+ tags: ["trials"],
7146
+ responses: { "200": { description: "Trial instance list", content: { "application/json": { schema: ListEnvelope2(TrialInstanceSchema2) } } } },
7147
+ "x-revturbine-operation": { exposure: "internal", resource: "trial-instances", persistence: { table: "trialInstances", mode: "list" } }
7148
+ })
7149
+ },
7150
+ "/api/trials/instances/{instanceId}": {
7151
+ get: operation2({
7152
+ operationId: "getTrialInstance",
7153
+ requestParams: { path: z72.object({ instanceId: z72.string() }) },
7154
+ summary: "Get trial instance",
7155
+ tags: ["trials"],
7156
+ responses: { "200": { description: "Trial instance", content: { "application/json": { schema: TrialInstanceSchema2 } } } },
7157
+ "x-revturbine-operation": { exposure: "internal", resource: "trial-instances", persistence: { table: "trialInstances", mode: "get" } }
7158
+ })
7159
+ },
7160
+ "/api/trials/instances/{instanceId}/cancel": {
7161
+ post: operation2({
7162
+ operationId: "cancelTrialInstance",
7163
+ requestParams: { path: z72.object({ instanceId: z72.string() }) },
7164
+ summary: "Cancel an active trial",
7165
+ tags: ["trials"],
7166
+ responses: { "200": { description: "Cancelled", content: { "application/json": { schema: TrialInstanceSchema2 } } } },
7167
+ "x-revturbine-operation": { exposure: "internal", resource: "trial-instances", persistence: { table: "trialInstances", mode: "update" } }
7168
+ })
7169
+ },
7170
+ "/api/trials/instances/{instanceId}/convert": {
7171
+ post: operation2({
7172
+ operationId: "convertTrialInstance",
7173
+ requestParams: { path: z72.object({ instanceId: z72.string() }) },
7174
+ summary: "Convert trial to paid subscription",
7175
+ tags: ["trials"],
7176
+ requestBody: { required: true, content: { "application/json": { schema: z72.object({ plan_id: z72.string().optional() }) } } },
7177
+ responses: { "200": { description: "Converted", content: { "application/json": { schema: TrialInstanceSchema2 } } } },
7178
+ "x-revturbine-operation": { exposure: "internal", resource: "trial-instances", persistence: { table: "trialInstances", mode: "update" } }
7179
+ })
7180
+ }
7181
+ };
7182
+ var { Unrestricted: Unrestricted42, Financial: Financial4 } = DataClassification2;
7183
+ var { Persisted: Persisted42, Transient: Transient42 } = SchemaPersistence2;
7184
+ var { External: External32 } = SchemaExposure2;
7185
+ var PLAYBOOK_SDK_FACETS32 = schemaFacets2(SchemaContext2.Playbook, { sdkInput: true });
7186
+ var PLAYBOOK_AUTHORING_FACETS3 = schemaFacets2(SchemaContext2.Playbook, { sdkInput: false });
7187
+ var BILLING_FACETS3 = schemaFacets2(SchemaContext2.Billing, { sdkInput: false });
7188
+ var PlanVisibilitySchema2 = z82.enum(["public", "unlisted", "legacy"]).meta(
7189
+ {
7190
+ id: "PlanVisibility",
7191
+ "x-revturbine-schema-persistence": Transient42,
7192
+ "x-revturbine-schema-exposure": External32
7193
+ }
7194
+ );
7195
+ var PricingModelSchema2 = z82.enum(["flat", "per_unit", "tiered", "metered"]).meta(
7196
+ {
7197
+ id: "PricingModel",
7198
+ "x-revturbine-schema-persistence": Transient42,
7199
+ "x-revturbine-schema-exposure": External32
7200
+ }
7201
+ );
7202
+ var PriceSourceSchema2 = z82.enum(["stripe", "static"]).meta(
7203
+ {
7204
+ id: "PriceSource",
7205
+ "x-revturbine-schema-persistence": Transient42,
7206
+ "x-revturbine-schema-exposure": External32
7207
+ }
7208
+ );
7209
+ var PlanSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
7210
+ anchor_id: z82.string().min(1).meta({ ...Unrestricted42, readOnly: true }),
7211
+ name: NameField2.meta(Unrestricted42),
7212
+ handle: HandleField2.meta(Unrestricted42),
7213
+ tier_position: z82.number().int().min(0).default(0).meta(Unrestricted42),
7214
+ sort_order: z82.number().int().default(0).meta(Unrestricted42),
7215
+ // Plan-level visibility default (plan 91 Part B). Distinct from the
7216
+ // per-variation `PlanVariationSchema.visibility`: a free/custom tier with no
7217
+ // priced variation can still be unlisted/legacy. Persisted so it round-trips
7218
+ // (plan 146 found it was declared on the portable config but had no column).
7219
+ visibility: PlanVisibilitySchema2.default("public").meta(Unrestricted42),
7220
+ metadata: MetadataField2.meta(Unrestricted42)
7221
+ }).meta(
7222
+ {
7223
+ id: "Plan",
7224
+ "x-revturbine-schema-persistence": Persisted42,
7225
+ "x-revturbine-schema-exposure": External32,
7226
+ ...PLAYBOOK_SDK_FACETS32,
7227
+ ...namedIdentity2()
7228
+ }
7229
+ );
7230
+ var PlanAnchorSchema2 = makeAnchor2("PlanAnchor");
7231
+ var PlanVariationSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
7232
+ anchor_id: z82.string().min(1).meta({ ...Unrestricted42, readOnly: true }),
7233
+ handle: HandleField2.meta({ ...Unrestricted42, readOnly: true }),
7234
+ plan_id: z82.string().min(1).meta(Unrestricted42),
7235
+ billing_period: z82.enum(["monthly", "annual", "one_time", "custom"]).meta(Unrestricted42),
7236
+ segment_id: z82.string().nullable().default(null).meta(Unrestricted42),
7237
+ price_amount: z82.number().min(0).meta(Financial4),
7238
+ pricing_model: PricingModelSchema2.meta(Unrestricted42),
7239
+ visibility: PlanVisibilitySchema2.default("public").meta(Unrestricted42),
7240
+ // Soft reference → StripePrice.stripe_price_id (the backend Stripe-price mirror).
7241
+ // No DB foreign key: stripe_price_id lives on the append-only version tables and
7242
+ // a hard FK would block plan-122 price-deletion sync (plan 118 FK decision, devkit #472).
7243
+ stripe_price_id: z82.string().optional().meta(Unrestricted42),
7244
+ price_source: PriceSourceSchema2.default("static").meta(Unrestricted42)
7245
+ }).meta(
7246
+ {
7247
+ id: "PlanVariation",
7248
+ "x-revturbine-schema-persistence": Persisted42,
7249
+ "x-revturbine-schema-exposure": External32,
7250
+ ...BILLING_FACETS3,
7251
+ ...mintedIdentity2()
7252
+ }
7253
+ );
7254
+ var PlanVariationAnchorSchema2 = makeAnchor2("PlanVariationAnchor");
7255
+ var AddOnSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
7256
+ anchor_id: z82.string().min(1).meta({ ...Unrestricted42, readOnly: true }),
7257
+ name: NameField2.meta(Unrestricted42),
7258
+ handle: HandleField2.meta(Unrestricted42),
7259
+ sort_order: z82.number().int().default(0).meta(Unrestricted42),
7260
+ // Add-on visibility default — same rationale as PlanSchema (plan 91 Part B);
7261
+ // metadata, not price, so it lives on the add-on independent of variations.
7262
+ visibility: PlanVisibilitySchema2.default("public").meta(Unrestricted42),
7263
+ metadata: MetadataField2.meta(Unrestricted42)
7264
+ }).meta(
7265
+ {
7266
+ id: "AddOn",
7267
+ "x-revturbine-schema-persistence": Persisted42,
7268
+ "x-revturbine-schema-exposure": External32,
7269
+ ...PLAYBOOK_AUTHORING_FACETS3,
7270
+ ...namedIdentity2()
7271
+ }
7272
+ );
7273
+ var AddOnAnchorSchema2 = makeAnchor2("AddOnAnchor");
7274
+ var AddOnVariationSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
7275
+ anchor_id: z82.string().min(1).meta({ ...Unrestricted42, readOnly: true }),
7276
+ handle: HandleField2.meta({ ...Unrestricted42, readOnly: true }),
7277
+ addon_id: z82.string().min(1).meta(Unrestricted42),
7278
+ // 'one_time' is first-class — Stripe one-time Prices (credit packs,
7279
+ // expansion packages) bind here without coercion to 'custom'.
7280
+ billing_period: z82.enum(["monthly", "annual", "one_time", "custom"]).meta(Unrestricted42),
7281
+ segment_id: z82.string().nullable().default(null).meta(Unrestricted42),
7282
+ price_amount: z82.number().min(0).meta(Financial4),
7283
+ pricing_model: PricingModelSchema2.meta(Unrestricted42),
7284
+ visibility: PlanVisibilitySchema2.default("public").meta(Unrestricted42),
7285
+ // Soft reference → StripePrice.stripe_price_id (the backend Stripe-price mirror).
7286
+ // No DB foreign key: stripe_price_id lives on the append-only version tables and
7287
+ // a hard FK would block plan-122 price-deletion sync (plan 118 FK decision, devkit #472).
7288
+ stripe_price_id: z82.string().optional().meta(Unrestricted42),
7289
+ price_source: PriceSourceSchema2.default("static").meta(Unrestricted42)
7290
+ }).meta(
7291
+ {
7292
+ id: "AddOnVariation",
7293
+ "x-revturbine-schema-persistence": Persisted42,
7294
+ "x-revturbine-schema-exposure": External32,
7295
+ ...BILLING_FACETS3,
7296
+ ...mintedIdentity2()
7297
+ }
7298
+ );
7299
+ var AddOnVariationAnchorSchema2 = makeAnchor2("AddOnVariationAnchor");
7300
+ var StripePriceBillingPeriodSchema2 = z82.enum(["monthly", "annual", "one_time", "custom"]).meta({
7301
+ id: "StripePriceBillingPeriod",
7302
+ "x-revturbine-schema-persistence": Transient42,
7303
+ "x-revturbine-schema-exposure": External32
7304
+ });
7305
+ var StripePriceSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).extend({
7306
+ stripe_price_id: z82.string().min(1).meta(Unrestricted42),
7307
+ stripe_product_id: z82.string().min(1).meta(Unrestricted42),
7308
+ billing_period: StripePriceBillingPeriodSchema2.meta(Unrestricted42),
7309
+ unit_amount_cents: z82.number().int().min(0).nullable().default(null).meta(Financial4),
7310
+ currency: CurrencySchema2.meta(Financial4),
7311
+ pricing_model: PricingModelSchema2.meta(Unrestricted42),
7312
+ nickname: z82.string().nullable().default(null).meta(Unrestricted42),
7313
+ // Placeholder/seed price (demo or pre-integration tenants) vs a real
7314
+ // Stripe-synced mirror row. Migration backfills existing seed rows to true.
7315
+ is_mock: z82.boolean().default(false).meta(Unrestricted42),
7316
+ // Timestamp of the last successful sync from Stripe; null for rows that were
7317
+ // never sourced from a real Stripe Price (seeds/mocks).
7318
+ last_updated_from_stripe: NullableDatetimeField2.meta(Unrestricted42)
7319
+ }).meta({
7320
+ id: "StripePrice",
7321
+ "x-revturbine-schema-persistence": Persisted42,
7322
+ "x-revturbine-schema-exposure": External32,
7323
+ ...BILLING_FACETS3
7324
+ });
7325
+ var PlanWriteSchema2 = toWritableSchema2(PlanSchema2);
7326
+ var PlanVariationWriteSchema2 = toWritableSchema2(PlanVariationSchema2);
7327
+ var AddOnWriteSchema2 = toWritableSchema2(AddOnSchema2);
7328
+ var AddOnVariationWriteSchema2 = toWritableSchema2(AddOnVariationSchema2);
7329
+ var StripePriceWriteSchema2 = toWritableSchema2(StripePriceSchema2);
7330
+ var planPaths2 = {
7331
+ "/api/plan-anchors": {
7332
+ get: operation2({
7333
+ operationId: "listPlanAnchors",
7334
+ requestParams: { query: ListQueryParamsSchema2 },
7335
+ summary: "List plan anchors (identity registry)",
7336
+ tags: ["plans"],
7337
+ responses: {
7338
+ "200": { description: "Plan anchor list", content: { "application/json": { schema: ListEnvelope2(PlanAnchorSchema2) } } },
7339
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
7340
+ },
7341
+ "x-revturbine-operation": { exposure: "internal", resource: "plan-anchors", persistence: { table: "plans", mode: "list" } }
7342
+ })
7343
+ },
7344
+ "/api/plans": {
7345
+ get: operation2({
7346
+ operationId: "listPlans",
7347
+ requestParams: { query: ListQueryParamsSchema2 },
7348
+ summary: "List plans",
7349
+ tags: ["plans"],
7350
+ responses: {
7351
+ "200": { description: "Plan list response", content: { "application/json": { schema: ListEnvelope2(PlanSchema2) } } },
7352
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
7353
+ },
7354
+ "x-revturbine-operation": { exposure: "external", resource: "plans", persistence: { table: "planVersions", mode: "list" } }
7355
+ }),
7356
+ post: operation2({
7357
+ operationId: "createPlan",
7358
+ summary: "Create plan",
7359
+ tags: ["plans"],
7360
+ requestBody: { required: true, content: { "application/json": { schema: toCreateSchema2(PlanSchema2) } } },
7361
+ responses: {
7362
+ "201": { description: "Created plan", content: { "application/json": { schema: PlanSchema2 } } },
7363
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
7364
+ },
7365
+ "x-revturbine-operation": { exposure: "external", resource: "plans", persistence: { table: "planVersions", mode: "create" } }
7366
+ })
7367
+ },
7368
+ "/api/plans/{planId}": {
7369
+ get: operation2({
7370
+ operationId: "getPlan",
7371
+ requestParams: { path: z82.object({ planId: z82.string() }) },
7372
+ summary: "Get plan by ID",
7373
+ tags: ["plans"],
7374
+ responses: {
7375
+ "200": { description: "Plan detail", content: { "application/json": { schema: PlanSchema2 } } },
7376
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
7377
+ },
7378
+ "x-revturbine-operation": { exposure: "external", resource: "plans", persistence: { table: "planVersions", mode: "get" } }
7379
+ }),
7380
+ patch: operation2({
7381
+ operationId: "updatePlan",
7382
+ requestParams: { path: z82.object({ planId: z82.string() }) },
7383
+ summary: "Update plan",
7384
+ tags: ["plans"],
7385
+ requestBody: { required: true, content: { "application/json": { schema: PlanWriteSchema2 } } },
7386
+ responses: {
7387
+ "200": { description: "Updated plan", content: { "application/json": { schema: PlanSchema2 } } },
7388
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
7389
+ },
7390
+ "x-revturbine-operation": { exposure: "external", resource: "plans", persistence: { table: "planVersions", mode: "update" } }
7391
+ }),
7392
+ delete: operation2({
7393
+ operationId: "deletePlan",
7394
+ requestParams: { path: z82.object({ planId: z82.string() }) },
7395
+ summary: "Delete plan",
7396
+ tags: ["plans"],
7397
+ responses: {
7398
+ "204": { description: "Plan deleted" },
7399
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
7400
+ },
7401
+ "x-revturbine-operation": { exposure: "external", resource: "plans", persistence: { table: "planVersions", mode: "delete" } }
7402
+ })
7403
+ },
7404
+ "/api/plans/reorder": {
7405
+ post: operation2({
7406
+ operationId: "reorderPlans",
7407
+ summary: "Reorder plans",
7408
+ tags: ["plans"],
7409
+ requestBody: { required: true, content: { "application/json": { schema: z82.object({ ids: z82.array(z82.string()) }) } } },
7410
+ responses: { "200": { description: "Reordered" } },
7411
+ "x-revturbine-operation": { exposure: "external", resource: "plans", persistence: { table: "planVersions", mode: "reorder" } }
7412
+ })
7413
+ },
7414
+ "/api/plan-variation-anchors": {
7415
+ get: operation2({
7416
+ operationId: "listPlanVariationAnchors",
7417
+ requestParams: { query: ListQueryParamsSchema2 },
7418
+ summary: "List plan variation anchors (identity registry)",
7419
+ tags: ["plans"],
7420
+ responses: {
7421
+ "200": { description: "Plan variation anchor list", content: { "application/json": { schema: ListEnvelope2(PlanVariationAnchorSchema2) } } },
7422
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
7423
+ },
7424
+ "x-revturbine-operation": { exposure: "internal", resource: "plan-variation-anchors", persistence: { table: "planVariations", mode: "list" } }
7425
+ })
7426
+ },
7427
+ "/api/plans/{planId}/variations": {
7428
+ get: operation2({
7429
+ operationId: "listPlanVariations",
7430
+ requestParams: { path: z82.object({ planId: z82.string() }), query: ListQueryParamsSchema2 },
7431
+ summary: "List plan variations",
7432
+ tags: ["plans"],
7433
+ responses: { "200": { description: "Plan variations", content: { "application/json": { schema: ListEnvelope2(PlanVariationSchema2) } } } },
7434
+ "x-revturbine-operation": { exposure: "external", resource: "plan-variations", persistence: { table: "planVariationVersions", mode: "list" } }
7435
+ }),
7436
+ post: operation2({
7437
+ operationId: "createPlanVariation",
7438
+ requestParams: { path: z82.object({ planId: z82.string() }) },
7439
+ summary: "Create plan variation",
7440
+ tags: ["plans"],
7441
+ requestBody: { required: true, content: { "application/json": { schema: toCreateSchema2(PlanVariationSchema2) } } },
7442
+ responses: { "201": { description: "Created plan variation", content: { "application/json": { schema: PlanVariationSchema2 } } } },
7443
+ "x-revturbine-operation": { exposure: "external", resource: "plan-variations", persistence: { table: "planVariationVersions", mode: "create" } }
7444
+ })
7445
+ },
7446
+ "/api/plan-variations/{variationId}": {
7447
+ get: operation2({
7448
+ operationId: "getPlanVariation",
7449
+ requestParams: { path: z82.object({ variationId: z82.string() }) },
7450
+ summary: "Get plan variation by ID",
7451
+ tags: ["plans"],
7452
+ responses: { "200": { description: "Plan variation detail", content: { "application/json": { schema: PlanVariationSchema2 } } } },
7453
+ "x-revturbine-operation": { exposure: "external", resource: "plan-variations", persistence: { table: "planVariationVersions", mode: "get" } }
7454
+ }),
7455
+ patch: operation2({
7456
+ operationId: "updatePlanVariation",
7457
+ requestParams: { path: z82.object({ variationId: z82.string() }) },
7458
+ summary: "Update plan variation",
7459
+ tags: ["plans"],
7460
+ requestBody: { required: true, content: { "application/json": { schema: PlanVariationWriteSchema2 } } },
7461
+ responses: { "200": { description: "Updated plan variation", content: { "application/json": { schema: PlanVariationSchema2 } } } },
7462
+ "x-revturbine-operation": { exposure: "external", resource: "plan-variations", persistence: { table: "planVariationVersions", mode: "update" } }
7463
+ }),
7464
+ delete: operation2({
7465
+ operationId: "deletePlanVariation",
7466
+ requestParams: { path: z82.object({ variationId: z82.string() }) },
7467
+ summary: "Delete plan variation",
7468
+ tags: ["plans"],
7469
+ responses: { "204": { description: "Plan variation deleted" } },
7470
+ "x-revturbine-operation": { exposure: "external", resource: "plan-variations", persistence: { table: "planVariationVersions", mode: "delete" } }
7471
+ })
7472
+ },
7473
+ "/api/addon-anchors": {
7474
+ get: operation2({
7475
+ operationId: "listAddOnAnchors",
7476
+ requestParams: { query: ListQueryParamsSchema2 },
7477
+ summary: "List add-on anchors (identity registry)",
7478
+ tags: ["plans"],
7479
+ responses: {
7480
+ "200": { description: "Add-on anchor list", content: { "application/json": { schema: ListEnvelope2(AddOnAnchorSchema2) } } },
7481
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
7482
+ },
7483
+ "x-revturbine-operation": { exposure: "internal", resource: "addon-anchors", persistence: { table: "addons", mode: "list" } }
7484
+ })
7485
+ },
7486
+ "/api/addons": {
7487
+ get: operation2({
7488
+ operationId: "listAddOns",
7489
+ requestParams: { query: ListQueryParamsSchema2 },
7490
+ summary: "List add-ons",
7491
+ tags: ["plans"],
7492
+ responses: { "200": { description: "Add-on list", content: { "application/json": { schema: ListEnvelope2(AddOnSchema2) } } } },
7493
+ "x-revturbine-operation": { exposure: "external", resource: "addons", persistence: { table: "addonVersions", mode: "list" } }
7494
+ }),
7495
+ post: operation2({
7496
+ operationId: "createAddOn",
7497
+ summary: "Create add-on",
7498
+ tags: ["plans"],
7499
+ requestBody: { required: true, content: { "application/json": { schema: toCreateSchema2(AddOnSchema2) } } },
7500
+ responses: { "201": { description: "Created add-on", content: { "application/json": { schema: AddOnSchema2 } } } },
7501
+ "x-revturbine-operation": { exposure: "external", resource: "addons", persistence: { table: "addonVersions", mode: "create" } }
7502
+ })
7503
+ },
7504
+ "/api/addons/{addonId}": {
7505
+ get: operation2({
7506
+ operationId: "getAddOn",
7507
+ requestParams: { path: z82.object({ addonId: z82.string() }) },
7508
+ summary: "Get add-on by ID",
7509
+ tags: ["plans"],
7510
+ responses: { "200": { description: "Add-on detail", content: { "application/json": { schema: AddOnSchema2 } } } },
7511
+ "x-revturbine-operation": { exposure: "external", resource: "addons", persistence: { table: "addonVersions", mode: "get" } }
7512
+ }),
7513
+ patch: operation2({
7514
+ operationId: "updateAddOn",
7515
+ requestParams: { path: z82.object({ addonId: z82.string() }) },
7516
+ summary: "Update add-on",
7517
+ tags: ["plans"],
7518
+ requestBody: { required: true, content: { "application/json": { schema: AddOnWriteSchema2 } } },
7519
+ responses: { "200": { description: "Updated add-on", content: { "application/json": { schema: AddOnSchema2 } } } },
7520
+ "x-revturbine-operation": { exposure: "external", resource: "addons", persistence: { table: "addonVersions", mode: "update" } }
7521
+ }),
7522
+ delete: operation2({
7523
+ operationId: "deleteAddOn",
7524
+ requestParams: { path: z82.object({ addonId: z82.string() }) },
7525
+ summary: "Delete add-on",
7526
+ tags: ["plans"],
7527
+ responses: { "204": { description: "Add-on deleted" } },
7528
+ "x-revturbine-operation": { exposure: "external", resource: "addons", persistence: { table: "addonVersions", mode: "delete" } }
7529
+ })
7530
+ },
7531
+ "/api/addon-variation-anchors": {
7532
+ get: operation2({
7533
+ operationId: "listAddOnVariationAnchors",
7534
+ requestParams: { query: ListQueryParamsSchema2 },
7535
+ summary: "List add-on variation anchors (identity registry)",
7536
+ tags: ["plans"],
7537
+ responses: {
7538
+ "200": { description: "Add-on variation anchor list", content: { "application/json": { schema: ListEnvelope2(AddOnVariationAnchorSchema2) } } },
7539
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
7540
+ },
7541
+ "x-revturbine-operation": { exposure: "internal", resource: "addon-variation-anchors", persistence: { table: "addonVariations", mode: "list" } }
7542
+ })
7543
+ },
7544
+ "/api/addons/{addonId}/variations": {
7545
+ get: operation2({
7546
+ operationId: "listAddOnVariations",
7547
+ requestParams: { path: z82.object({ addonId: z82.string() }), query: ListQueryParamsSchema2 },
7548
+ summary: "List add-on variations",
7549
+ tags: ["plans"],
7550
+ responses: { "200": { description: "Add-on variations", content: { "application/json": { schema: ListEnvelope2(AddOnVariationSchema2) } } } },
7551
+ "x-revturbine-operation": { exposure: "external", resource: "addon-variations", persistence: { table: "addonVariationVersions", mode: "list" } }
7552
+ }),
7553
+ post: operation2({
7554
+ operationId: "createAddOnVariation",
7555
+ requestParams: { path: z82.object({ addonId: z82.string() }) },
7556
+ summary: "Create add-on variation",
7557
+ tags: ["plans"],
7558
+ requestBody: { required: true, content: { "application/json": { schema: toCreateSchema2(AddOnVariationSchema2) } } },
7559
+ responses: { "201": { description: "Created variation", content: { "application/json": { schema: AddOnVariationSchema2 } } } },
7560
+ "x-revturbine-operation": { exposure: "external", resource: "addon-variations", persistence: { table: "addonVariationVersions", mode: "create" } }
7561
+ })
7562
+ },
7563
+ "/api/addon-variations/{variationId}": {
7564
+ get: operation2({
7565
+ operationId: "getAddOnVariation",
7566
+ requestParams: { path: z82.object({ variationId: z82.string() }) },
7567
+ summary: "Get add-on variation by ID",
7568
+ tags: ["plans"],
7569
+ responses: { "200": { description: "Add-on variation detail", content: { "application/json": { schema: AddOnVariationSchema2 } } } },
7570
+ "x-revturbine-operation": { exposure: "external", resource: "addon-variations", persistence: { table: "addonVariationVersions", mode: "get" } }
7571
+ }),
7572
+ patch: operation2({
7573
+ operationId: "updateAddOnVariation",
7574
+ requestParams: { path: z82.object({ variationId: z82.string() }) },
7575
+ summary: "Update add-on variation",
7576
+ tags: ["plans"],
7577
+ requestBody: { required: true, content: { "application/json": { schema: AddOnVariationWriteSchema2 } } },
7578
+ responses: { "200": { description: "Updated variation", content: { "application/json": { schema: AddOnVariationSchema2 } } } },
7579
+ "x-revturbine-operation": { exposure: "external", resource: "addon-variations", persistence: { table: "addonVariationVersions", mode: "update" } }
7580
+ }),
7581
+ delete: operation2({
7582
+ operationId: "deleteAddOnVariation",
7583
+ requestParams: { path: z82.object({ variationId: z82.string() }) },
7584
+ summary: "Delete add-on variation",
7585
+ tags: ["plans"],
7586
+ responses: { "204": { description: "Variation deleted" } },
7587
+ "x-revturbine-operation": { exposure: "external", resource: "addon-variations", persistence: { table: "addonVariationVersions", mode: "delete" } }
7588
+ })
7589
+ },
7590
+ "/api/stripe-prices": {
7591
+ get: operation2({
7592
+ operationId: "listStripePrices",
7593
+ requestParams: { query: ListQueryParamsSchema2 },
7594
+ summary: "List Stripe prices",
7595
+ tags: ["plans"],
7596
+ responses: { "200": { description: "Stripe price list", content: { "application/json": { schema: ListEnvelope2(StripePriceSchema2) } } } },
7597
+ "x-revturbine-operation": { exposure: "external", resource: "stripe-prices", persistence: { table: "stripePrices", mode: "list" } }
7598
+ }),
7599
+ post: operation2({
7600
+ operationId: "createStripePrice",
7601
+ summary: "Create Stripe price",
7602
+ tags: ["plans"],
7603
+ requestBody: { required: true, content: { "application/json": { schema: toCreateSchema2(StripePriceSchema2) } } },
7604
+ responses: { "201": { description: "Created Stripe price", content: { "application/json": { schema: StripePriceSchema2 } } } },
7605
+ "x-revturbine-operation": { exposure: "external", resource: "stripe-prices", persistence: { table: "stripePrices", mode: "create" } }
7606
+ })
7607
+ },
7608
+ "/api/stripe-prices/{id}": {
7609
+ get: operation2({
7610
+ operationId: "getStripePrice",
7611
+ requestParams: { path: z82.object({ id: z82.string() }) },
7612
+ summary: "Get Stripe price by ID",
7613
+ tags: ["plans"],
7614
+ responses: { "200": { description: "Stripe price detail", content: { "application/json": { schema: StripePriceSchema2 } } } },
7615
+ "x-revturbine-operation": { exposure: "external", resource: "stripe-prices", persistence: { table: "stripePrices", mode: "get" } }
7616
+ }),
7617
+ patch: operation2({
7618
+ operationId: "updateStripePrice",
7619
+ requestParams: { path: z82.object({ id: z82.string() }) },
7620
+ summary: "Update Stripe price",
7621
+ tags: ["plans"],
7622
+ requestBody: { required: true, content: { "application/json": { schema: StripePriceWriteSchema2 } } },
7623
+ responses: { "200": { description: "Updated Stripe price", content: { "application/json": { schema: StripePriceSchema2 } } } },
7624
+ "x-revturbine-operation": { exposure: "external", resource: "stripe-prices", persistence: { table: "stripePrices", mode: "update" } }
7625
+ }),
7626
+ delete: operation2({
7627
+ operationId: "deleteStripePrice",
7628
+ requestParams: { path: z82.object({ id: z82.string() }) },
7629
+ summary: "Delete Stripe price",
7630
+ tags: ["plans"],
7631
+ responses: { "204": { description: "Stripe price deleted" } },
7632
+ "x-revturbine-operation": { exposure: "external", resource: "stripe-prices", persistence: { table: "stripePrices", mode: "delete" } }
7633
+ })
7634
+ }
7635
+ };
7636
+ var { Unrestricted: Unrestricted52 } = DataClassification2;
7637
+ var { Persisted: Persisted52, Transient: Transient52 } = SchemaPersistence2;
7638
+ var { Internal: Internal42, External: External42 } = SchemaExposure2;
7639
+ var PLAYBOOK_SDK_FACETS42 = schemaFacets2(SchemaContext2.Playbook, { sdkInput: true });
7640
+ var PLAYBOOK_AUTHORING_FACETS22 = schemaFacets2(SchemaContext2.Playbook, { sdkInput: false });
7641
+ var PENDING_PLAYBOOK_FACETS22 = schemaFacets2(SchemaContext2.Playbook, {
7642
+ inConfig: false,
7643
+ sdkInput: false
7644
+ });
7645
+ var PENDING_PLAYBOOK_SDK_FACETS22 = schemaFacets2(SchemaContext2.Playbook, {
7646
+ inConfig: false,
7647
+ sdkInput: true
7648
+ });
7649
+ var PLAYBOOK_VERSION_HEADER_FACETS2 = schemaFacets2(SchemaContext2.Playbook, {
7650
+ inConfig: false,
7651
+ sdkInput: true,
7652
+ source: SchemaSource2.CodeConstant
7653
+ });
7654
+ var PLAYBOOK_PROVENANCE_HEADER_FACETS2 = schemaFacets2(SchemaContext2.Playbook, {
7655
+ inConfig: false,
7656
+ sdkInput: true,
7657
+ source: SchemaSource2.Runtime
7658
+ });
7659
+ var PLAYBOOK_TARGET_FACETS2 = schemaFacets2(SchemaContext2.Playbook, {
7660
+ inConfig: false,
7661
+ sdkInput: false,
7662
+ source: SchemaSource2.Runtime
7663
+ });
7664
+ var BILLING_FACETS22 = schemaFacets2(SchemaContext2.Billing, { sdkInput: false });
7665
+ var METERING_FACETS2 = schemaFacets2(SchemaContext2.Metering, { sdkInput: false });
7666
+ var LEGACY_BRANDING_FACETS2 = schemaFacets2(SchemaContext2.Branding, {
7667
+ inConfig: true,
7668
+ sdkInput: true
7669
+ });
7670
+ var BRANDING_FACETS3 = schemaFacets2(SchemaContext2.Branding, { sdkInput: false });
7671
+ var PLAYBOOK_FORMAT_VERSION2 = "1.0.0";
7672
+ var SeatTypeSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
7673
+ anchor_id: z92.string().min(1).meta({ ...Unrestricted52, readOnly: true }),
7674
+ name: NameField2.meta(Unrestricted52),
7675
+ handle: HandleField2.meta(Unrestricted52),
7676
+ description: DescriptionField2.meta(Unrestricted52),
7677
+ is_default: z92.boolean().default(false).meta(Unrestricted52),
7678
+ entitlement_ids: z92.array(z92.string()).default([]).meta(Unrestricted52),
7679
+ metadata: MetadataField2.meta(Unrestricted52)
7680
+ }).meta(
7681
+ { id: "SeatType", "x-revturbine-schema-persistence": Persisted52, "x-revturbine-schema-exposure": Internal42, ...PENDING_PLAYBOOK_FACETS22, ...namedIdentity2() }
7682
+ );
7683
+ var SeatTypeAnchorSchema2 = makeAnchor2("SeatTypeAnchor");
7684
+ var PersonalizationTokenSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
7685
+ anchor_id: z92.string().min(1).meta({ ...Unrestricted52, readOnly: true }),
7686
+ handle: HandleField2.meta(Unrestricted52),
7687
+ label: z92.string().min(1).meta(Unrestricted52),
7688
+ description: z92.string().nullable().default(null).meta(Unrestricted52),
7689
+ category: z92.enum(["user", "plan", "usage", "trial", "billing", "promotion", "custom"]).meta(Unrestricted52),
7690
+ data_source: z92.string().nullable().default(null).meta(Unrestricted52),
7691
+ example_value: z92.string().nullable().default(null).meta(Unrestricted52),
7692
+ value_map: z92.record(z92.string(), z92.string()).default({}).meta(Unrestricted52),
7693
+ format: z92.enum(["string", "number", "currency", "percentage", "date"]).nullable().default(null).meta(Unrestricted52),
7694
+ metadata: MetadataField2.meta(Unrestricted52)
7695
+ }).meta(
7696
+ { id: "PersonalizationToken", "x-revturbine-schema-persistence": Persisted52, "x-revturbine-schema-exposure": Internal42, ...PENDING_PLAYBOOK_FACETS22, ...namedIdentity2() }
7697
+ );
7698
+ var PersonalizationTokenAnchorSchema2 = makeAnchor2("PersonalizationTokenAnchor");
7699
+ var OnboardingStateSchema2 = z92.enum(["not_started", "started", "details_submitted", "charges_enabled", "activated", "deauthorized"]).meta({ id: "OnboardingState", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": Internal42 });
7700
+ var StripeIntegrationConfigSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
7701
+ handle: HandleField2.meta({ ...Unrestricted52, readOnly: true }),
7702
+ stripe_account_id: z92.string().min(1).meta(Unrestricted52),
7703
+ live_mode: z92.boolean().default(false).meta(Unrestricted52),
7704
+ /** Funnel state for the Connect onboarding pipeline. */
7705
+ onboarding_state: OnboardingStateSchema2.default("not_started").meta({ ...Unrestricted52, readOnly: true }),
7706
+ /** Connect onboarding status — tracks whether hosted onboarding is complete. */
7707
+ onboarding_complete: z92.boolean().default(false).meta({ ...Unrestricted52, readOnly: true }),
7708
+ /** Whether the connected account can process charges (read from Stripe). */
7709
+ charges_enabled: z92.boolean().default(false).meta({ ...Unrestricted52, readOnly: true }),
7710
+ /** Whether the connected account has details submitted (read from Stripe). */
7711
+ details_submitted: z92.boolean().default(false).meta({ ...Unrestricted52, readOnly: true }),
7712
+ /** Whether the connected account can receive payouts (read from Stripe). */
7713
+ payouts_enabled: z92.boolean().default(false).meta({ ...Unrestricted52, readOnly: true }),
7714
+ webhook_secret_set: z92.boolean().default(false).meta({ ...Unrestricted52, readOnly: true }),
7715
+ sync_products: z92.boolean().default(true).meta(Unrestricted52),
7716
+ sync_prices: z92.boolean().default(true).meta(Unrestricted52),
7717
+ sync_subscriptions: z92.boolean().default(true).meta(Unrestricted52),
7718
+ sync_invoices: z92.boolean().default(false).meta(Unrestricted52),
7719
+ default_currency: z92.string().length(3).default("USD").meta(Unrestricted52),
7720
+ tax_behavior: z92.enum(["inclusive", "exclusive", "unspecified"]).default("unspecified").meta(Unrestricted52),
7721
+ /** ISO timestamp of the last successful full data sync from Stripe. */
7722
+ last_sync_at: z92.string().optional().meta({ ...Unrestricted52, readOnly: true }),
7723
+ metadata: MetadataField2.meta(Unrestricted52)
7724
+ }).meta(
7725
+ { id: "StripeIntegrationConfig", "x-revturbine-schema-persistence": Persisted52, "x-revturbine-schema-exposure": Internal42, ...BILLING_FACETS22, ...mintedIdentity2() }
7726
+ );
7727
+ var BrandingConfigSchema2 = z92.object({
7728
+ theme: z92.record(z92.string(), z92.unknown()).optional().meta(Unrestricted52),
7729
+ workspace_name: z92.string().optional().meta(Unrestricted52),
7730
+ logo_url: z92.string().optional().meta(Unrestricted52),
7731
+ support_email: z92.string().optional().meta(Unrestricted52)
7732
+ }).meta(
7733
+ {
7734
+ id: "BrandingConfig",
7735
+ "x-revturbine-schema-persistence": Transient52,
7736
+ "x-revturbine-schema-exposure": External42,
7737
+ ...BRANDING_FACETS3
7738
+ }
7739
+ );
7740
+ var MeteringConfigSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
7741
+ handle: HandleField2.meta({ ...Unrestricted52, readOnly: true }),
7742
+ entitlement_id: z92.string().min(1).meta(Unrestricted52),
7743
+ meter_key: z92.string().min(1).max(100).meta(Unrestricted52),
7744
+ aggregation_type: z92.enum(["sum", "count", "max", "last_value"]).default("sum").meta(Unrestricted52),
7745
+ reset_period: z92.enum(["none", "daily", "weekly", "monthly", "yearly"]).default("monthly").meta(Unrestricted52),
7746
+ stripe_meter_id: z92.string().nullable().default(null).meta(Unrestricted52),
7747
+ is_active: z92.boolean().default(true).meta(Unrestricted52),
7748
+ metadata: MetadataField2.meta(Unrestricted52)
7749
+ }).meta(
7750
+ { id: "MeteringConfig", "x-revturbine-schema-persistence": Persisted52, "x-revturbine-schema-exposure": Internal42, ...METERING_FACETS2, ...mintedIdentity2() }
7751
+ );
7752
+ var EnforcementActionSchema2 = z92.enum(["block", "warn", "downgrade", "throttle", "notify_admin", "custom"]).meta(
7753
+ { id: "EnforcementAction", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": Internal42 }
7754
+ );
7755
+ var UsageEnforcementSettingsSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
7756
+ anchor_id: z92.string().min(1).meta({ ...Unrestricted52, readOnly: true }),
7757
+ handle: HandleField2.meta({ ...Unrestricted52, readOnly: true }),
7758
+ entitlement_id: z92.string().min(1).meta(Unrestricted52),
7759
+ soft_limit_percent: z92.number().min(0).max(100).default(80).meta(Unrestricted52),
7760
+ hard_limit_percent: z92.number().min(0).max(100).default(100).meta(Unrestricted52),
7761
+ soft_limit_action: EnforcementActionSchema2.default("warn").meta(Unrestricted52),
7762
+ hard_limit_action: EnforcementActionSchema2.default("block").meta(Unrestricted52),
7763
+ grace_period_hours: z92.number().int().min(0).default(0).meta(Unrestricted52),
7764
+ notification_channels: z92.array(z92.enum(["email", "in_app", "webhook"])).default(["in_app"]).meta(Unrestricted52),
7765
+ is_active: z92.boolean().default(true).meta(Unrestricted52)
7766
+ }).meta(
7767
+ { id: "UsageEnforcementSettings", "x-revturbine-schema-persistence": Persisted52, "x-revturbine-schema-exposure": Internal42, ...PENDING_PLAYBOOK_SDK_FACETS22, ...mintedIdentity2() }
7768
+ );
7769
+ var UsageEnforcementSettingsAnchorSchema2 = makeAnchor2("UsageEnforcementSettingsAnchor");
7770
+ var PlacementSettingsCapRuleGroupItemSchema2 = z92.object({
7771
+ kind: z92.enum(["template", "slot"]).meta(Unrestricted52),
7772
+ id: z92.string().min(1).meta(Unrestricted52),
7773
+ label: z92.string().min(1).optional().meta(Unrestricted52)
7774
+ }).meta(
7775
+ { id: "PlacementSettingsCapRuleGroupItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": Internal42 }
7776
+ );
7777
+ var PlacementSettingsCapRuleSchema2 = z92.object({
7778
+ id: z92.string().min(1).meta(Unrestricted52),
7779
+ group: z92.array(PlacementSettingsCapRuleGroupItemSchema2).min(1).meta(Unrestricted52),
7780
+ cap: z92.object({
7781
+ count: z92.number().int().min(1).meta(Unrestricted52),
7782
+ period: z92.enum(["session", "day", "week", "month"]).meta(Unrestricted52)
7783
+ }).meta(Unrestricted52)
7784
+ }).meta(
7785
+ { id: "PlacementSettingsCapRule", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": Internal42 }
7786
+ );
7787
+ var PlacementTestModeSchema2 = z92.enum(["off", "test_users", "all_traffic"]).meta(
7788
+ { id: "PlacementTestMode", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": Internal42 }
7789
+ );
7790
+ var PlacementSettingsCapStateSchema2 = z92.object({
7791
+ capRules: z92.array(PlacementSettingsCapRuleSchema2).default([]).meta(Unrestricted52),
7792
+ sessionCooldownMinutes: z92.number().int().min(0).default(30).meta(Unrestricted52),
7793
+ testMode: PlacementTestModeSchema2.default("off").meta(Unrestricted52)
7794
+ }).meta(
7795
+ { id: "PlacementSettingsCapState", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": Internal42 }
7796
+ );
7797
+ var PlacementSettingsSchema2 = IdField2.merge(TimestampFields2).merge(TenantIdField2).merge(AnchorFields2).merge(VersionFields2).extend({
7798
+ anchor_id: z92.string().min(1).meta({ ...Unrestricted52, readOnly: true }),
7799
+ handle: HandleField2.meta({ ...Unrestricted52, readOnly: true }),
7800
+ global_frequency_cap: PlacementSettingsCapStateSchema2.nullable().default(null).meta(Unrestricted52),
7801
+ // Legacy companion column kept for migration continuity. The new
7802
+ // wrapper-object encoding above carries period information per
7803
+ // cap rule; this column is always null in v0.1.20+ writes.
7804
+ global_frequency_cap_period: z92.enum(["hour", "day", "week", "month", "session"]).nullable().default(null).meta(Unrestricted52),
7805
+ suppress_for_paid: z92.boolean().default(false).meta(Unrestricted52),
7806
+ suppress_for_trial: z92.boolean().default(false).meta(Unrestricted52),
7807
+ default_dismiss_cooldown_hours: z92.number().int().min(0).default(24).meta(Unrestricted52),
7808
+ allow_stacking: z92.boolean().default(false).meta(Unrestricted52),
7809
+ priority_collision_strategy: z92.enum(["highest_priority", "most_recent", "random"]).default("highest_priority").meta(Unrestricted52)
7810
+ }).meta(
7811
+ { id: "PlacementSettings", "x-revturbine-schema-persistence": Persisted52, "x-revturbine-schema-exposure": Internal42, ...PENDING_PLAYBOOK_SDK_FACETS22, ...mintedIdentity2() }
7812
+ );
7813
+ var PlacementSettingsAnchorSchema2 = makeAnchor2("PlacementSettingsAnchor");
7814
+ var RevTurbineConfigSegmentsItemPredicatesItemSchema2 = z92.object({
7815
+ field: z92.string().min(1).meta(Unrestricted52),
7816
+ operator: z92.enum(["eq", "neq", "gt", "lt", "gte", "lte", "contains", "in"]).meta(Unrestricted52),
7817
+ value: z92.string().meta(Unrestricted52)
7818
+ }).meta(
7819
+ { id: "RevTurbineConfigSegmentsItemPredicatesItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42 }
7820
+ );
7821
+ var RevTurbineConfigSegmentsItemSchema2 = z92.object({
7822
+ // Plan 120 TASK-4: the config carries the handle as its sole logical
7823
+ // identifier — the redundant config-level `id` is dropped. The physical
7824
+ // UUID primary key stays in the persisted (Drizzle) row, never the config.
7825
+ name: z92.string().min(1).meta(Unrestricted52),
7826
+ handle: z92.string().min(1).meta(Unrestricted52),
7827
+ predicates: z92.array(RevTurbineConfigSegmentsItemPredicatesItemSchema2).optional().meta(Unrestricted52),
7828
+ // Dimension this segment belongs to (plan #39 REQ-28 / Route A). Optional
7829
+ // for back-compat: pre-plan-39 RevTurbineConfigs and segments not yet
7830
+ // categorised lack it. The entitlement-rule evaluator uses this to
7831
+ // apply intra-dimension OR + cross-dimension AND per spec §2.5; when
7832
+ // missing across all of a rule's segment_ids, the evaluator falls
7833
+ // back to flat-OR (legacy single-segment behaviour).
7834
+ dimension_id: z92.string().optional().meta(Unrestricted52)
7835
+ }).meta(
7836
+ { id: "RevTurbineConfigSegmentsItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
7837
+ );
7838
+ var RevTurbineConfigPlansItemSchema2 = z92.object({
7839
+ // Plan 120 TASK-4: `unique_handle` is the sole logical identifier; the
7840
+ // redundant config-level `id` is dropped (physical UUID PK stays in the row).
7841
+ unique_handle: z92.string().min(1).meta(Unrestricted52),
7842
+ name: z92.string().min(1).meta(Unrestricted52),
7843
+ tier_position: z92.number().int().min(0).default(0).meta(Unrestricted52),
7844
+ sort_order: z92.number().int().min(0).default(0).meta(Unrestricted52),
7845
+ // Plan-level visibility (to_do/91 Part B). Lives on the plan, not a
7846
+ // priced variation, so a free/custom tier with no variation can still be
7847
+ // marked unlisted/legacy and round-trip. Variations may still carry their
7848
+ // own visibility for per-price overrides; this is the plan's default.
7849
+ visibility: PlanVisibilitySchema2.default("public").meta(Unrestricted52)
7850
+ }).meta(
7851
+ { id: "RevTurbineConfigPlansItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
7852
+ );
7853
+ var RevTurbineConfigAddonsItemSchema2 = z92.object({
7854
+ // Plan 120 TASK-4: `unique_handle` is the sole logical identifier; the
7855
+ // redundant config-level `id` is dropped (physical UUID PK stays in the row).
7856
+ unique_handle: z92.string().min(1).meta(Unrestricted52),
7857
+ name: z92.string().min(1).meta(Unrestricted52),
7858
+ sort_order: z92.number().int().min(0).default(0).meta(Unrestricted52),
7859
+ // Add-on visibility (to_do/91 Part B) — same rationale as plans: metadata,
7860
+ // not price, so it lives in the config independent of addon_variations.
7861
+ visibility: PlanVisibilitySchema2.default("public").meta(Unrestricted52)
7862
+ }).meta(
7863
+ { id: "RevTurbineConfigAddonsItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_AUTHORING_FACETS22 }
7864
+ );
7865
+ var RevTurbineConfigPlanVariationsItemSchema2 = z92.object({
7866
+ handle: z92.string().min(1).meta(Unrestricted52),
7867
+ plan_handle: z92.string().min(1).meta(Unrestricted52),
7868
+ billing_period: z92.enum(["monthly", "annual", "one_time", "custom"]).meta(Unrestricted52),
7869
+ segment_handle: z92.string().nullable().default(null).meta(Unrestricted52),
7870
+ price_amount: z92.number().min(0).meta(Unrestricted52),
7871
+ pricing_model: PricingModelSchema2.meta(Unrestricted52),
7872
+ visibility: PlanVisibilitySchema2.default("public").meta(Unrestricted52),
7873
+ stripe_price_id: z92.string().nullable().default(null).meta(Unrestricted52),
7874
+ price_source: PriceSourceSchema2.meta(Unrestricted52)
7875
+ }).meta(
7876
+ { id: "RevTurbineConfigPlanVariationsItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PENDING_PLAYBOOK_FACETS22 }
7877
+ );
7878
+ var RevTurbineConfigAddonVariationsItemSchema2 = z92.object({
7879
+ handle: z92.string().min(1).meta(Unrestricted52),
7880
+ addon_handle: z92.string().min(1).meta(Unrestricted52),
7881
+ billing_period: z92.enum(["monthly", "annual", "one_time", "custom"]).meta(Unrestricted52),
7882
+ segment_handle: z92.string().nullable().default(null).meta(Unrestricted52),
7883
+ price_amount: z92.number().min(0).meta(Unrestricted52),
7884
+ pricing_model: PricingModelSchema2.meta(Unrestricted52),
7885
+ visibility: PlanVisibilitySchema2.default("public").meta(Unrestricted52),
7886
+ stripe_price_id: z92.string().nullable().default(null).meta(Unrestricted52),
7887
+ price_source: PriceSourceSchema2.meta(Unrestricted52)
7888
+ }).meta(
7889
+ { id: "RevTurbineConfigAddonVariationsItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PENDING_PLAYBOOK_FACETS22 }
7890
+ );
7891
+ var RevTurbineConfigSeatTypesItemSchema2 = z92.object({
7892
+ handle: z92.string().min(1).meta(Unrestricted52),
7893
+ name: z92.string().min(1).meta(Unrestricted52),
7894
+ description: z92.string().nullable().default(null).meta(Unrestricted52),
7895
+ is_default: z92.boolean().default(false).meta(Unrestricted52),
7896
+ entitlement_handles: z92.array(z92.string()).default([]).meta(Unrestricted52)
7897
+ }).meta(
7898
+ { id: "RevTurbineConfigSeatTypesItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
7899
+ );
7900
+ var RevTurbineConfigEnforcementDefaultsItemSchema2 = z92.object({
7901
+ handle: z92.string().min(1).meta(Unrestricted52),
7902
+ entitlement_handle: z92.string().nullable().default(null).meta(Unrestricted52),
7903
+ soft_limit_percent: z92.number().int().min(0).nullable().default(null).meta(Unrestricted52),
7904
+ hard_limit_percent: z92.number().int().min(0).nullable().default(null).meta(Unrestricted52),
7905
+ soft_limit_action: z92.string().meta(Unrestricted52),
7906
+ hard_limit_action: z92.string().meta(Unrestricted52),
7907
+ grace_period_hours: z92.number().int().min(0).nullable().default(null).meta(Unrestricted52),
7908
+ notification_channels: z92.array(z92.string()).default([]).meta(Unrestricted52),
7909
+ is_active: z92.boolean().default(true).meta(Unrestricted52)
7910
+ }).meta(
7911
+ { id: "RevTurbineConfigEnforcementDefaultsItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
7912
+ );
7913
+ var RevTurbineConfigPlacementSettingsItemSchema2 = z92.object({
7914
+ handle: z92.string().min(1).meta(Unrestricted52),
7915
+ global_frequency_cap: PlacementSettingsCapStateSchema2.nullable().default(null).meta(Unrestricted52),
7916
+ global_frequency_cap_period: z92.enum(["hour", "day", "week", "month", "session"]).nullable().default(null).meta(Unrestricted52),
7917
+ suppress_for_paid: z92.boolean().default(false).meta(Unrestricted52),
7918
+ suppress_for_trial: z92.boolean().default(false).meta(Unrestricted52),
7919
+ default_dismiss_cooldown_hours: z92.number().int().min(0).nullable().default(null).meta(Unrestricted52),
7920
+ allow_stacking: z92.boolean().default(false).meta(Unrestricted52),
7921
+ priority_collision_strategy: z92.string().nullable().default(null).meta(Unrestricted52)
7922
+ }).meta(
7923
+ { id: "RevTurbineConfigPlacementSettingsItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
7924
+ );
7925
+ var RevTurbineConfigSegmentDimensionsItemSchema2 = z92.object({
7926
+ handle: z92.string().min(1).meta(Unrestricted52),
7927
+ name: z92.string().min(1).meta(Unrestricted52),
7928
+ category: z92.string().nullable().default(null).meta(Unrestricted52),
7929
+ visibility_toggle: z92.boolean().default(true).meta(Unrestricted52),
7930
+ source_type: z92.string().nullable().default(null).meta(Unrestricted52)
7931
+ }).meta(
7932
+ { id: "RevTurbineConfigSegmentDimensionsItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
7933
+ );
7934
+ var RevTurbineConfigMeterBindingsItemSchema2 = z92.object({
7935
+ handle: z92.string().min(1).meta(Unrestricted52),
7936
+ entitlement_handle: z92.string().min(1).meta(Unrestricted52),
7937
+ meter_handle: z92.string().min(1).meta(Unrestricted52),
7938
+ limit: z92.number().int().min(0).nullable().default(null).meta(Unrestricted52),
7939
+ reset_period: z92.string().nullable().default(null).meta(Unrestricted52)
7940
+ }).meta(
7941
+ { id: "RevTurbineConfigMeterBindingsItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
7942
+ );
7943
+ var RevTurbineConfigEntitlementsItemSchema2 = z92.object({
7944
+ // Plan 120 TASK-4: `unique_handle` is the sole logical identifier; the
7945
+ // redundant config-level `id` is dropped (physical UUID PK stays in the row).
7946
+ unique_handle: z92.string().min(1).meta(Unrestricted52),
7947
+ name: z92.string().min(1).meta(Unrestricted52),
7948
+ type: EntitlementTypeSchema2.meta(Unrestricted52),
7949
+ unit: z92.string().optional().meta(Unrestricted52),
7950
+ // Ordered tier ladder for a `capability_tier` entitlement — projection of
7951
+ // the authored `EntitlementSchema.tier_definitions` (plan 138 TASK-4).
7952
+ // ARRAY ORDER IS THE RANK: the `entitlement_gate.tier_threshold` placement
7953
+ // trigger fires when the user's current tier ranks below the threshold tier
7954
+ // on this ladder. `name`/`description` are UI-helper denormalizations (plan
7955
+ // 118); the runtime gate reads only the ordered `handle`s.
7956
+ tier_definitions: z92.array(z92.object({
7957
+ name: z92.string(),
7958
+ handle: z92.string(),
7959
+ description: z92.string().optional()
7960
+ })).optional().meta(Unrestricted52)
7961
+ }).meta(
7962
+ { id: "RevTurbineConfigEntitlementsItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
7963
+ );
7964
+ var RevTurbineConfigEntitlementRulesItemSchema2 = z92.object({
7965
+ id: z92.string().min(1).meta(Unrestricted52),
7966
+ entitlement_id: z92.string().min(1).meta(Unrestricted52),
7967
+ targets: z92.array(EntitlementRuleTargetSchema2).min(1).meta(Unrestricted52),
7968
+ // Plan #39 REQ-1: multi-segment scoping per spec §2.5. Empty array
7969
+ // means "match all users" (replaces the singular `segment_id` field
7970
+ // and its 'all'/null sentinels).
7971
+ segment_ids: z92.array(z92.string()).default([]).meta(Unrestricted52),
7972
+ // ── Derived denormalizations from the parent entitlement (plan 147, OQ-6).
7973
+ // Resolved via `entitlement_id` on export; ignored on import (the entitlement
7974
+ // is authoritative). `readOnly` → excluded from round-trip obligations: they
7975
+ // are computed, not authored, so requiring a sentinel to preserve them would
7976
+ // test derivation rather than authoring fidelity.
7977
+ kind: EntitlementTypeSchema2.optional().meta({ ...Unrestricted52, readOnly: true }),
7978
+ unit: z92.string().optional().meta({ ...Unrestricted52, readOnly: true }),
7979
+ tier_name: z92.string().optional().meta({ ...Unrestricted52, readOnly: true }),
7980
+ tier_description: z92.string().optional().meta({ ...Unrestricted52, readOnly: true }),
7981
+ // ── Flat per-rule fields (plan 147, OQ-6) — single-sourced from the persisted
7982
+ // `EntitlementRuleSchema` under its canonical names (REQ-1/REQ-2), replacing
7983
+ // the deleted nested `type_fields` union. Null-stripped on export. The
7984
+ // evaluated subset lowers into `TypeFieldsIR`; the persisted-not-evaluated
7985
+ // fields (`amount_cents`/`currency`/`rate_value`/`period_scope`/…) round-trip
7986
+ // via web import/export for content rendering (OQ-7), not the bundle.
7987
+ ...EntitlementRuleSchema2.pick({
7988
+ enabled: true,
7989
+ limit_value: true,
7990
+ enforcement: true,
7991
+ tier_value: true,
7992
+ period_scope: true,
7993
+ included_count: true,
7994
+ seat_type_id: true,
7995
+ initial_grant: true,
7996
+ allowance_value: true,
7997
+ rollover_enabled: true,
7998
+ max_balance: true,
7999
+ reset_period: true,
8000
+ max_seats: true,
8001
+ rate_value: true,
8002
+ amount_cents: true,
8003
+ currency: true
8004
+ }).shape,
8005
+ current_usage: z92.number().default(0).meta(Unrestricted52),
8006
+ /** How usage is partitioned across the identity hierarchy. */
8007
+ allocation: UsageAllocationSchema2.optional().meta(Unrestricted52)
8008
+ }).meta(
8009
+ { id: "RevTurbineConfigEntitlementRulesItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
8010
+ );
8011
+ var RevTurbineConfigSlotConfigsItemSchema2 = z92.object({
8012
+ slot_id: z92.string().min(1).meta(Unrestricted52),
8013
+ active: z92.boolean().meta(Unrestricted52),
8014
+ triggers: z92.array(z92.string()).meta(Unrestricted52)
8015
+ }).meta(
8016
+ { id: "RevTurbineConfigSlotConfigsItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
8017
+ );
8018
+ var RevTurbineConfigPlacementSlotsItemSchema2 = z92.object({
8019
+ id: z92.string().min(1).meta(Unrestricted52),
8020
+ label: z92.string().min(1).meta(Unrestricted52),
8021
+ description: z92.string().meta(Unrestricted52),
8022
+ surface_type: z92.string().meta(Unrestricted52),
8023
+ placement_handle: z92.string().min(1).meta(Unrestricted52),
8024
+ template: z92.string().optional().meta(Unrestricted52)
8025
+ }).meta(
8026
+ { id: "RevTurbineConfigPlacementSlotsItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
8027
+ );
8028
+ var RevTurbineConfigSurfaceTemplatesItemFieldsItemSchema2 = z92.object({
8029
+ name: z92.string().min(1).meta(Unrestricted52),
8030
+ type: z92.string().optional().meta(Unrestricted52),
8031
+ required: z92.boolean().optional().meta(Unrestricted52)
8032
+ }).meta(
8033
+ { id: "RevTurbineConfigSurfaceTemplatesItemFieldsItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42 }
8034
+ );
8035
+ var RevTurbineConfigSurfaceTemplatesItemSchema2 = z92.object({
8036
+ id: z92.string().min(1).meta(Unrestricted52),
8037
+ surface_type: z92.string().meta(Unrestricted52),
8038
+ fields: z92.array(RevTurbineConfigSurfaceTemplatesItemFieldsItemSchema2).optional().meta(Unrestricted52)
8039
+ }).meta(
8040
+ { id: "RevTurbineConfigSurfaceTemplatesItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
8041
+ );
8042
+ var RevTurbineConfigUiPathActionTypeSchema2 = z92.enum([
8043
+ "open_checkout_modal",
8044
+ "navigate_to_plans",
8045
+ "open_upgrade_modal",
8046
+ "open_placement",
8047
+ "book_demo",
8048
+ "open_feature_tour",
8049
+ "extend_trial",
8050
+ "switch_billing_period",
8051
+ "custom_url",
8052
+ "dismiss",
8053
+ // Additional client-side action types (aligned with CtaPathTypeSchema)
8054
+ "contact_sales",
8055
+ "complete_onboarding",
8056
+ "invite_teammate",
8057
+ "refer_friend",
8058
+ "verify_work_email",
8059
+ "update_payment_method",
8060
+ "enable_auto_renewal",
8061
+ "manage_subscription"
8062
+ ]).meta(
8063
+ { id: "RevTurbineConfigUiPathActionType", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42 }
8064
+ );
8065
+ var ContentUiPathSchema2 = z92.object({
8066
+ name: z92.string().min(1).meta(Unrestricted52),
8067
+ action_type: RevTurbineConfigUiPathActionTypeSchema2.meta(Unrestricted52),
8068
+ plan_handle: z92.string().optional().meta(Unrestricted52),
8069
+ promotion_id: z92.string().optional().meta(Unrestricted52),
8070
+ placement_handle: z92.string().optional().meta(Unrestricted52),
8071
+ url: z92.string().optional().meta(Unrestricted52),
8072
+ tour_id: z92.string().optional().meta(Unrestricted52),
8073
+ target_billing_period: z92.enum(["monthly", "annual"]).optional().meta(Unrestricted52),
8074
+ description: z92.string().optional().meta(Unrestricted52)
8075
+ }).meta(
8076
+ { id: "ContentUiPath", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
8077
+ );
8078
+ var ContentPromotionSchema2 = z92.object({
8079
+ id: z92.string().meta(Unrestricted52),
8080
+ name: z92.string().meta(Unrestricted52),
8081
+ discount: z92.string().meta(Unrestricted52),
8082
+ type: z92.string().meta(Unrestricted52),
8083
+ status: z92.string().meta(Unrestricted52)
8084
+ }).meta(
8085
+ { id: "ContentPromotion", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
8086
+ );
8087
+ var RevTurbineConfigPersonalizationTokensItemSchema2 = z92.object({
8088
+ token: z92.string().regex(/^[a-z][a-z0-9_]*$/).meta(Unrestricted52),
8089
+ label: z92.string().min(1).meta(Unrestricted52),
8090
+ description: z92.string().optional().meta(Unrestricted52),
8091
+ category: z92.enum(["user", "plan", "usage", "trial", "billing", "promotion", "custom"]).meta(Unrestricted52),
8092
+ data_source: z92.string().optional().meta(Unrestricted52),
8093
+ example_value: z92.string().optional().meta(Unrestricted52),
8094
+ value_map: z92.record(z92.string(), z92.string()).optional().meta(Unrestricted52),
8095
+ format: z92.enum(["string", "number", "currency", "percentage", "date"]).optional().meta(Unrestricted52)
8096
+ }).meta(
8097
+ { id: "RevTurbineConfigPersonalizationTokensItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
8098
+ );
8099
+ var MessageBlockContentSchema2 = z92.object({
8100
+ header: z92.string().optional().meta(Unrestricted52),
8101
+ body: z92.string().optional().meta(Unrestricted52),
8102
+ cta_label: z92.string().optional().meta(Unrestricted52),
8103
+ secondary_cta_label: z92.string().optional().meta(Unrestricted52)
8104
+ }).catchall(z92.unknown()).meta(
8105
+ { id: "MessageBlockContent", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42 }
8106
+ );
8107
+ var MessageBlockSchema2 = z92.object({
8108
+ block_id: z92.string().min(1).meta(Unrestricted52),
8109
+ tenant_id: z92.string().min(1).meta(Unrestricted52),
8110
+ name: z92.string().min(1).meta(Unrestricted52),
8111
+ surface_template_id: z92.string().optional().meta(Unrestricted52),
8112
+ default_content: MessageBlockContentSchema2.meta(Unrestricted52),
8113
+ segment_overrides: z92.array(z92.object({
8114
+ segment_value_id: z92.string(),
8115
+ content: MessageBlockContentSchema2
8116
+ })).optional().meta(Unrestricted52),
8117
+ child_blocks: z92.array(z92.object({
8118
+ slot: z92.string(),
8119
+ block_id: z92.string()
8120
+ })).optional().meta(Unrestricted52),
8121
+ tokens_used: z92.array(z92.string()).optional().meta(Unrestricted52),
8122
+ status: z92.enum(["draft", "active", "archived"]).meta(Unrestricted52),
8123
+ created_at: z92.string().datetime().meta({ ...Unrestricted52, readOnly: true }),
8124
+ updated_at: z92.string().datetime().meta({ ...Unrestricted52, readOnly: true })
8125
+ }).meta(
8126
+ { id: "MessageBlock", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
8127
+ );
8128
+ var RevTurbineConfigStudioCtaConfigSchema2 = z92.object({
8129
+ label: z92.string().meta(Unrestricted52),
8130
+ path: CtaActionTypeSchema2.meta(Unrestricted52),
8131
+ config: z92.record(z92.string(), z92.string()).optional().meta(Unrestricted52)
8132
+ }).meta(
8133
+ { id: "RevTurbineConfigStudioCtaConfig", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42 }
8134
+ );
8135
+ var RevTurbineConfigStudioPayloadSurfaceSchema2 = z92.object({
8136
+ template_id: z92.string().min(1).meta(Unrestricted52),
8137
+ fields: z92.record(z92.string(), z92.string()).meta(Unrestricted52),
8138
+ ctas: z92.array(RevTurbineConfigStudioCtaConfigSchema2).meta(Unrestricted52)
8139
+ }).meta(
8140
+ { id: "RevTurbineConfigStudioPayloadSurface", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42 }
8141
+ );
8142
+ var RevTurbineConfigStudioPayloadTargetSchema2 = z92.object({
8143
+ plan_ids: z92.array(z92.string()).meta(Unrestricted52),
8144
+ // Billing-cadence dimension of the Plan Filter (spec §3.1.1 Target).
8145
+ // Empty/absent = no cadence filter. Optional so pre-plan-76 exports parse.
8146
+ billing_cadences: z92.array(z92.string()).optional().meta(Unrestricted52),
8147
+ segment_chips: z92.array(z92.string()).meta(Unrestricted52)
8148
+ }).meta(
8149
+ { id: "RevTurbineConfigStudioPayloadTarget", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42 }
8150
+ );
8151
+ var RevTurbineConfigPeriodCapSchema2 = z92.object({
8152
+ count: z92.number().int().min(1).meta(Unrestricted52),
8153
+ period: z92.enum(["session", "day", "week", "month", "lifetime"]).meta(Unrestricted52)
8154
+ }).meta(
8155
+ { id: "RevTurbineConfigPeriodCap", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42 }
8156
+ );
8157
+ var RevTurbineConfigStudioPayloadCapsSchema2 = z92.object({
8158
+ max_per_period: RevTurbineConfigPeriodCapSchema2.optional().meta(Unrestricted52),
8159
+ cooldown_days: z92.number().int().min(0).optional().meta(Unrestricted52)
8160
+ }).meta(
8161
+ { id: "RevTurbineConfigStudioPayloadCaps", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42 }
8162
+ );
8163
+ var RevTurbineConfigStudioPayloadSchema2 = z92.object({
8164
+ id: z92.string().min(1).meta(Unrestricted52),
8165
+ target: RevTurbineConfigStudioPayloadTargetSchema2.meta(Unrestricted52),
8166
+ surfaces: z92.array(RevTurbineConfigStudioPayloadSurfaceSchema2).meta(Unrestricted52),
8167
+ caps: RevTurbineConfigStudioPayloadCapsSchema2.optional().meta(Unrestricted52),
8168
+ // Optional slot targeting (spec §3.1.1): empty/absent = any compatible slot.
8169
+ surface_slot_ids: z92.array(z92.string()).optional().meta(Unrestricted52),
8170
+ created_at: z92.string().optional().meta({ ...Unrestricted52, readOnly: true }),
8171
+ recommendation_strategy: z92.enum(["next_tier_up", "best_value", "custom"]).optional().default("next_tier_up").meta(Unrestricted52),
8172
+ recommendation_plan_override: z92.string().optional().meta(Unrestricted52)
8173
+ }).meta(
8174
+ { id: "RevTurbineConfigStudioPayload", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42 }
8175
+ );
8176
+ var RevTurbineConfigPlacementTriggerSchema2 = z92.discriminatedUnion("type", [
8177
+ z92.object({ type: z92.literal("surface_render"), slot_id: z92.string().min(1) }),
8178
+ z92.object({ type: z92.literal("entitlement_gate"), entitlement_handle: z92.string().min(1), tier_threshold: z92.string().optional() }),
8179
+ z92.object({ type: z92.literal("usage_threshold"), entitlement_handle: z92.string().min(1), threshold_percent: ThresholdPercentField2 }),
8180
+ z92.object({ type: z92.literal("credit_threshold"), entitlement_handle: z92.string().min(1), threshold_percent: ThresholdPercentField2 }),
8181
+ z92.object({ type: z92.literal("seat_threshold"), entitlement_handle: z92.string().min(1), threshold_percent: ThresholdPercentField2 }),
8182
+ z92.object({ type: z92.literal("trial_started"), trial_type: z92.enum(["free", "reverse"]).optional() }),
8183
+ z92.object({ type: z92.literal("trial_progress"), progress_percent: z92.number().min(1).max(100) }),
8184
+ z92.object({ type: z92.literal("trial_ending"), days_before_end: z92.number().int().min(0) }),
8185
+ z92.object({ type: z92.literal("trial_ended") }),
8186
+ z92.object({ type: z92.literal("trial_converted") }),
8187
+ z92.object({ type: z92.literal("qualifier"), qualifier: z92.string().min(1) })
8188
+ ]).meta(
8189
+ { id: "RevTurbineConfigPlacementTrigger", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42 }
8190
+ );
8191
+ var RevTurbineConfigPlacementCategorySchema2 = z92.enum(["fixed", "gated", "usage_credit_seat", "trials", "other_conversion", "retention"]).meta(
8192
+ { id: "RevTurbineConfigPlacementCategory", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42 }
8193
+ );
8194
+ var RevTurbineConfigPlacementItemSchema2 = z92.object({
8195
+ id: z92.string().min(1).meta(Unrestricted52),
8196
+ name: z92.string().min(1).meta(Unrestricted52),
8197
+ category: RevTurbineConfigPlacementCategorySchema2.meta(Unrestricted52),
8198
+ trigger: RevTurbineConfigPlacementTriggerSchema2.meta(Unrestricted52),
8199
+ payloads: z92.array(RevTurbineConfigStudioPayloadSchema2).meta(Unrestricted52),
8200
+ order: z92.number().int().min(0).meta(Unrestricted52)
8201
+ }).meta(
8202
+ { id: "RevTurbineConfigPlacementItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
8203
+ );
8204
+ var RevTurbineConfigPlacementPayloadItemSchema2 = z92.object({
8205
+ payload_id: z92.string().min(1).meta(Unrestricted52),
8206
+ placement_id: z92.string().min(1).meta(Unrestricted52),
8207
+ target: RevTurbineConfigStudioPayloadTargetSchema2.meta(Unrestricted52),
8208
+ caps: RevTurbineConfigStudioPayloadCapsSchema2.optional().meta(Unrestricted52),
8209
+ created_at: z92.string().meta({ ...Unrestricted52, readOnly: true }),
8210
+ updated_at: z92.string().datetime().optional().meta({ ...Unrestricted52, readOnly: true }),
8211
+ source_mode: z92.enum(["inline", "content_linked"]).meta(Unrestricted52),
8212
+ surfaces: z92.array(RevTurbineConfigStudioPayloadSurfaceSchema2).optional().meta(Unrestricted52),
8213
+ // Optional slot targeting (spec §3.1.1): empty/absent = any compatible slot.
8214
+ surface_slot_ids: z92.array(z92.string()).optional().meta(Unrestricted52),
8215
+ content_link: z92.object({
8216
+ message_block_id: z92.string().optional(),
8217
+ ui_path_id: z92.string().optional(),
8218
+ promotion_id: z92.string().optional(),
8219
+ content_payload_id: z92.string().optional()
8220
+ }).optional().meta(Unrestricted52)
8221
+ }).meta(
8222
+ { id: "RevTurbineConfigPlacementPayloadItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
8223
+ );
8224
+ var RevTurbineConfigExtensionRulesItemSchema2 = z92.object({
8225
+ kind: z92.string().min(1).meta(Unrestricted52),
8226
+ schema_version: z92.number().int().nonnegative().meta(Unrestricted52),
8227
+ config: z92.unknown().meta(Unrestricted52)
8228
+ }).meta(
8229
+ { id: "RevTurbineConfigExtensionRulesItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PLAYBOOK_SDK_FACETS42 }
8230
+ );
8231
+ var RevTurbineConfigFreeTrialRuleItemSchema2 = IdField2.merge(FreeTrialRuleCoreFieldsSchema2).meta(
8232
+ { id: "RevTurbineConfigFreeTrialRuleItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PENDING_PLAYBOOK_FACETS22 }
8233
+ );
8234
+ var RevTurbineConfigReverseTrialRuleItemSchema2 = IdField2.merge(ReverseTrialRuleCoreFieldsSchema2).meta(
8235
+ { id: "RevTurbineConfigReverseTrialRuleItem", "x-revturbine-schema-persistence": Transient52, "x-revturbine-schema-exposure": External42, ...PENDING_PLAYBOOK_FACETS22 }
8236
+ );
8237
+ var PlaybookBodySchema2 = z92.object({
8238
+ plans: z92.array(RevTurbineConfigPlansItemSchema2).meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8239
+ // Optional for back-compat: pre-plan-88 configs (and the live export until web
8240
+ // adopts the new @revt-eng/schema) omit it. Add-on definitions only; pricing
8241
+ // (addon_variations) stays in the Stripe layer, like plan_variations.
8242
+ addons: z92.array(RevTurbineConfigAddonsItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_AUTHORING_FACETS22 }),
8243
+ entitlements: z92.array(RevTurbineConfigEntitlementsItemSchema2).meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8244
+ entitlement_rules: z92.array(RevTurbineConfigEntitlementRulesItemSchema2).meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8245
+ segments: z92.array(RevTurbineConfigSegmentsItemSchema2).meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8246
+ content_ui_paths: z92.array(ContentUiPathSchema2).meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8247
+ slot_configs: z92.array(RevTurbineConfigSlotConfigsItemSchema2).optional().meta({
8248
+ ...Unrestricted52,
8249
+ ...PLAYBOOK_SDK_FACETS42,
8250
+ ...schemaDeprecation2({
8251
+ since: "0.1.117",
8252
+ replacement: "SDK-local activation/trigger state",
8253
+ removeAfter: "one compatibility window",
8254
+ reason: "Slot activation moved to SDK-local state (plan 118 TASK-6); no longer a Playbook authoring input."
8255
+ })
8256
+ }),
8257
+ content_overrides: z92.record(z92.string(), z92.record(z92.string(), z92.string())).optional().meta({
8258
+ ...Unrestricted52,
8259
+ ...PLAYBOOK_SDK_FACETS42,
8260
+ ...schemaDeprecation2({
8261
+ since: "0.1.117",
8262
+ replacement: "Message Block / Placement Payload content",
8263
+ removeAfter: "one compatibility window",
8264
+ reason: "Content overrides moved to Message Block / Payload content (plan 118 TASK-6); no longer a Playbook authoring input."
8265
+ })
8266
+ }),
8267
+ theme: z92.record(z92.string(), z92.unknown()).optional().meta({
8268
+ ...Unrestricted52,
8269
+ ...LEGACY_BRANDING_FACETS2,
8270
+ ...schemaDeprecation2({
8271
+ since: "0.1.111",
8272
+ replacement: "SDK branding argument",
8273
+ removeAfter: "one compatibility window",
8274
+ reason: "Branding is independently owned and is not Playbook strategy."
8275
+ })
8276
+ }),
8277
+ placement_slots: z92.array(RevTurbineConfigPlacementSlotsItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8278
+ message_blocks: z92.array(MessageBlockSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8279
+ placement_payloads: z92.array(RevTurbineConfigPlacementPayloadItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8280
+ placements: z92.array(RevTurbineConfigPlacementItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8281
+ content_promotions: z92.array(ContentPromotionSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8282
+ personalization_tokens: z92.array(RevTurbineConfigPersonalizationTokensItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8283
+ surface_templates: z92.array(RevTurbineConfigSurfaceTemplatesItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8284
+ /**
8285
+ * Free + reverse trial rule configurations (plan 43). Optional so
8286
+ * pre-trial-runtime configs continue to parse. /api/config/import
8287
+ * applies these to the tenant's free_trial_rules / reverse_trial_rules
8288
+ * tables; /api/config/export reads them out for round-trip.
8289
+ */
8290
+ free_trial_rules: z92.array(RevTurbineConfigFreeTrialRuleItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_AUTHORING_FACETS22 }),
8291
+ reverse_trial_rules: z92.array(RevTurbineConfigReverseTrialRuleItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_AUTHORING_FACETS22 }),
8292
+ // Plan / add-on variation prices carried by handle (plan 118 TASK-16). These
8293
+ // live on the legacy schema (not just the canonical Playbook body) so that a
8294
+ // legacy `version`-shaped config — the shape the demo-data configs and the
8295
+ // pre-sales/CLI upload flow still use — can carry variation prices through
8296
+ // normalization instead of having them stripped. Pending until web
8297
+ // import/export activates them (TASK-21).
8298
+ plan_variations: z92.array(RevTurbineConfigPlanVariationsItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_AUTHORING_FACETS22 }),
8299
+ addon_variations: z92.array(RevTurbineConfigAddonVariationsItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_AUTHORING_FACETS22 }),
8300
+ /**
8301
+ * Tagged-opaque rule entries (Phase 3 / strategy 2). Each entry is
8302
+ * dispatched to the corresponding `RuleAuthoringModule.kind` at
8303
+ * compile time; unknown kinds are skipped silently so authoring can
8304
+ * stage new kinds before the runtime catches up.
8305
+ */
8306
+ extension_rules: z92.array(RevTurbineConfigExtensionRulesItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8307
+ // Authored-config projections carried as active SDK inputs (plan 118
8308
+ // TASK-13/18). Declared here (not only on PlaybookBody) so the Bundle
8309
+ // compiler — which lowers the legacy `RevTurbineConfig` view — reads them
8310
+ // with proper types. Projected into the RuleBundle; see core/bundle.
8311
+ seat_types: z92.array(RevTurbineConfigSeatTypesItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8312
+ enforcement_defaults: z92.array(RevTurbineConfigEnforcementDefaultsItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8313
+ placement_settings: z92.array(RevTurbineConfigPlacementSettingsItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8314
+ segment_dimensions: z92.array(RevTurbineConfigSegmentDimensionsItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8315
+ meter_bindings: z92.array(RevTurbineConfigMeterBindingsItemSchema2).optional().meta({ ...Unrestricted52, ...PLAYBOOK_SDK_FACETS42 }),
8316
+ experiments: z92.array(z92.unknown()).max(0).optional().meta({
8317
+ ...Unrestricted52,
8318
+ ...PENDING_PLAYBOOK_FACETS22
8319
+ }),
8320
+ // Reserved like `experiments` above: claim the key now, ship the
8321
+ // semantics later. A Playbook will eventually pin the Signal Catalog
8322
+ // version its targeting was interpreted under, so a decision stays
8323
+ // reproducible after the event taxonomy moves on. Reserving costs one
8324
+ // schema change; adding a top-level Playbook key after the fact costs a
8325
+ // second breaking cascade through the IR, the CLI, demo data, and every
8326
+ // SDK port.
8327
+ //
8328
+ // Empty-object-only until a consumer exists — `.strict()` rejects a
8329
+ // populated `{ id, version }` rather than letting it round-trip as
8330
+ // config nothing reads. Catalog *definitions* stay server-side and must
8331
+ // never reach the browser Playbook; only the reference will live here.
8332
+ signal_catalog: z92.object({}).strict().optional().meta({
8333
+ ...Unrestricted52,
8334
+ ...PENDING_PLAYBOOK_FACETS22
8335
+ })
8336
+ }).meta(
8337
+ {
8338
+ id: "PlaybookBody",
8339
+ "x-revturbine-schema-persistence": Transient52,
8340
+ "x-revturbine-schema-exposure": External42,
8341
+ ...PLAYBOOK_SDK_FACETS42
8342
+ }
8343
+ );
8344
+ var PlaybookHeaderSchema2 = z92.object({
8345
+ artifact_type: z92.literal("playbook").meta({
8346
+ ...Unrestricted52,
8347
+ ...PLAYBOOK_VERSION_HEADER_FACETS2,
8348
+ readOnly: true
8349
+ }),
8350
+ format_version: z92.literal(PLAYBOOK_FORMAT_VERSION2).meta({
8351
+ ...Unrestricted52,
8352
+ ...PLAYBOOK_VERSION_HEADER_FACETS2,
8353
+ readOnly: true
8354
+ }),
8355
+ playbook_handle: z92.string().min(1).default("default").meta({
8356
+ ...Unrestricted52,
8357
+ ...PLAYBOOK_VERSION_HEADER_FACETS2
8358
+ }),
8359
+ playbook_version_id: z92.string().nullable().default(null).meta({
8360
+ ...Unrestricted52,
8361
+ ...PLAYBOOK_PROVENANCE_HEADER_FACETS2,
8362
+ readOnly: true
8363
+ }),
8364
+ // Origin target identity (plan 131 TASK-10). Optional so a hand-authored /
8365
+ // legacy `export-config.json` (which carries no target) parses unchanged —
8366
+ // the plan 147 TASK-1 header reconciliation: the one config schema must
8367
+ // absorb legacy files that predate target stamping. Stamped by the server
8368
+ // on export when present.
8369
+ tenant_id: z92.string().min(1).optional().meta({
8370
+ ...Unrestricted52,
8371
+ ...PLAYBOOK_TARGET_FACETS2,
8372
+ readOnly: true
8373
+ }),
8374
+ environment_id: z92.string().min(1).optional().meta({
8375
+ ...Unrestricted52,
8376
+ ...PLAYBOOK_TARGET_FACETS2,
8377
+ readOnly: true
8378
+ }),
8379
+ project_id: z92.string().min(1).optional().meta({
8380
+ ...Unrestricted52,
8381
+ ...PLAYBOOK_TARGET_FACETS2,
8382
+ readOnly: true
8383
+ }),
8384
+ exported_at: z92.string().datetime().optional().meta({
8385
+ ...Unrestricted52,
8386
+ ...PLAYBOOK_PROVENANCE_HEADER_FACETS2,
8387
+ readOnly: true
8388
+ }),
8389
+ schema_version: z92.string().min(1).optional().meta({
8390
+ ...Unrestricted52,
8391
+ ...PLAYBOOK_VERSION_HEADER_FACETS2,
8392
+ readOnly: true
8393
+ }),
8394
+ bundle_schema_version: z92.number().int().nonnegative().optional().meta({
8395
+ ...Unrestricted52,
8396
+ ...PLAYBOOK_VERSION_HEADER_FACETS2,
8397
+ readOnly: true
8398
+ })
8399
+ }).meta(
8400
+ {
8401
+ id: "PlaybookHeader",
8402
+ "x-revturbine-schema-persistence": Transient52,
8403
+ "x-revturbine-schema-exposure": External42,
8404
+ ...PLAYBOOK_PROVENANCE_HEADER_FACETS2
8405
+ }
8406
+ );
8407
+ var PlaybookObjectSchema2 = PlaybookHeaderSchema2.extend(PlaybookBodySchema2.shape).meta(
8408
+ {
8409
+ "x-revturbine-schema-persistence": Transient52,
8410
+ "x-revturbine-schema-exposure": External42,
8411
+ ...PLAYBOOK_SDK_FACETS42
8412
+ }
8413
+ );
8414
+ function isRecord2(value) {
8415
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8416
+ }
8417
+ function normalizeConfigHeaderInput2(input) {
8418
+ if (!isRecord2(input)) return input;
8419
+ const next = { ...input };
8420
+ if (!("artifact_type" in next)) {
8421
+ next.artifact_type = "playbook";
8422
+ }
8423
+ if ("version" in next) {
8424
+ if (!("format_version" in next)) next.format_version = next.version;
8425
+ delete next.version;
8426
+ }
8427
+ if ("change_set_id" in next) {
8428
+ if (!("playbook_version_id" in next)) next.playbook_version_id = next.change_set_id;
8429
+ delete next.change_set_id;
8430
+ }
8431
+ return next;
8432
+ }
8433
+ var PlaybookSchema2 = z92.preprocess(normalizeConfigHeaderInput2, PlaybookObjectSchema2).meta({
8434
+ id: "Playbook",
8435
+ "x-revturbine-schema-persistence": Transient52,
8436
+ "x-revturbine-schema-exposure": External42,
8437
+ ...PLAYBOOK_SDK_FACETS42
8438
+ });
8439
+ var configPaths2 = {
8440
+ "/api/seat-type-anchors": {
8441
+ get: operation2({
8442
+ operationId: "listSeatTypeAnchors",
8443
+ requestParams: { query: ListQueryParamsSchema2 },
8444
+ summary: "List seat type anchors (identity registry)",
8445
+ tags: ["config"],
8446
+ responses: {
8447
+ "200": { description: "Seat type anchor list", content: { "application/json": { schema: ListEnvelope2(SeatTypeAnchorSchema2) } } },
8448
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
8449
+ },
8450
+ "x-revturbine-operation": { exposure: "internal", resource: "seat-type-anchors", persistence: { table: "seatTypes", mode: "list" } }
8451
+ })
8452
+ },
8453
+ "/api/config/seat-types": {
8454
+ get: operation2({
8455
+ operationId: "listSeatTypes",
8456
+ requestParams: { query: ListQueryParamsSchema2 },
8457
+ summary: "List seat types",
8458
+ tags: ["config"],
8459
+ responses: { "200": { description: "Seat type list", content: { "application/json": { schema: ListEnvelope2(SeatTypeSchema2) } } } },
8460
+ "x-revturbine-operation": { exposure: "internal", resource: "seat-types", persistence: { table: "seatTypeVersions", mode: "list" } }
8461
+ }),
8462
+ post: operation2({
8463
+ operationId: "createSeatType",
8464
+ summary: "Create seat type",
8465
+ tags: ["config"],
8466
+ requestBody: { required: true, content: { "application/json": { schema: toCreateSchema2(SeatTypeSchema2) } } },
8467
+ responses: { "201": { description: "Created", content: { "application/json": { schema: SeatTypeSchema2 } } } },
8468
+ "x-revturbine-operation": { exposure: "internal", resource: "seat-types", persistence: { table: "seatTypeVersions", mode: "create" } }
8469
+ })
8470
+ },
8471
+ "/api/config/seat-types/{id}": {
8472
+ patch: operation2({
8473
+ operationId: "updateSeatType",
8474
+ requestParams: { path: z92.object({ id: z92.string() }) },
8475
+ summary: "Update seat type",
8476
+ tags: ["config"],
8477
+ requestBody: { required: true, content: { "application/json": { schema: SeatTypeSchema2.partial() } } },
8478
+ responses: { "200": { description: "Updated", content: { "application/json": { schema: SeatTypeSchema2 } } } },
8479
+ "x-revturbine-operation": { exposure: "internal", resource: "seat-types", persistence: { table: "seatTypeVersions", mode: "update" } }
8480
+ }),
8481
+ delete: operation2({
8482
+ operationId: "deleteSeatType",
8483
+ requestParams: { path: z92.object({ id: z92.string() }) },
8484
+ summary: "Delete seat type",
8485
+ tags: ["config"],
8486
+ responses: { "204": { description: "Deleted" } },
8487
+ "x-revturbine-operation": { exposure: "internal", resource: "seat-types", persistence: { table: "seatTypeVersions", mode: "delete" } }
8488
+ })
8489
+ },
8490
+ "/api/personalization-token-anchors": {
8491
+ get: operation2({
8492
+ operationId: "listPersonalizationTokenAnchors",
8493
+ requestParams: { query: ListQueryParamsSchema2 },
8494
+ summary: "List personalization token anchors (identity registry)",
8495
+ tags: ["config"],
8496
+ responses: {
8497
+ "200": { description: "Personalization token anchor list", content: { "application/json": { schema: ListEnvelope2(PersonalizationTokenAnchorSchema2) } } },
8498
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
8499
+ },
8500
+ "x-revturbine-operation": { exposure: "internal", resource: "personalization-token-anchors", persistence: { table: "personalizationTokens", mode: "list" } }
8501
+ })
8502
+ },
8503
+ "/api/config/personalization-tokens": {
8504
+ get: operation2({
8505
+ operationId: "listPersonalizationTokens",
8506
+ requestParams: { query: ListQueryParamsSchema2 },
8507
+ summary: "List personalization tokens",
8508
+ tags: ["config"],
8509
+ responses: { "200": { description: "Personalization token list", content: { "application/json": { schema: ListEnvelope2(PersonalizationTokenSchema2) } } } },
8510
+ "x-revturbine-operation": { exposure: "internal", resource: "personalization-tokens", persistence: { table: "personalizationTokenVersions", mode: "list" } }
8511
+ }),
8512
+ post: operation2({
8513
+ operationId: "createPersonalizationToken",
8514
+ summary: "Create personalization token",
8515
+ tags: ["config"],
8516
+ requestBody: { required: true, content: { "application/json": { schema: toCreateSchema2(PersonalizationTokenSchema2) } } },
8517
+ responses: { "201": { description: "Created", content: { "application/json": { schema: PersonalizationTokenSchema2 } } } },
8518
+ "x-revturbine-operation": { exposure: "internal", resource: "personalization-tokens", persistence: { table: "personalizationTokenVersions", mode: "create" } }
8519
+ })
8520
+ },
8521
+ "/api/config/personalization-tokens/{id}": {
8522
+ patch: operation2({
8523
+ operationId: "updatePersonalizationToken",
8524
+ requestParams: { path: z92.object({ id: z92.string() }) },
8525
+ summary: "Update personalization token",
8526
+ tags: ["config"],
8527
+ requestBody: { required: true, content: { "application/json": { schema: PersonalizationTokenSchema2.partial() } } },
8528
+ responses: { "200": { description: "Updated", content: { "application/json": { schema: PersonalizationTokenSchema2 } } } },
8529
+ "x-revturbine-operation": { exposure: "internal", resource: "personalization-tokens", persistence: { table: "personalizationTokenVersions", mode: "update" } }
8530
+ }),
8531
+ delete: operation2({
8532
+ operationId: "deletePersonalizationToken",
8533
+ requestParams: { path: z92.object({ id: z92.string() }) },
8534
+ summary: "Delete personalization token",
8535
+ tags: ["config"],
8536
+ responses: { "204": { description: "Deleted" } },
8537
+ "x-revturbine-operation": { exposure: "internal", resource: "personalization-tokens", persistence: { table: "personalizationTokenVersions", mode: "delete" } }
8538
+ })
8539
+ },
8540
+ "/api/config/stripe": {
8541
+ get: operation2({
8542
+ operationId: "getStripeIntegrationConfig",
8543
+ summary: "Get Stripe integration configuration",
8544
+ tags: ["config"],
8545
+ responses: { "200": { description: "Stripe config", content: { "application/json": { schema: StripeIntegrationConfigSchema2 } } } },
8546
+ "x-revturbine-operation": { exposure: "internal", resource: "stripe-config", persistence: { table: "stripeIntegrationConfig", mode: "get" } }
8547
+ }),
8548
+ put: operation2({
8549
+ operationId: "upsertStripeIntegrationConfig",
8550
+ summary: "Create or update Stripe integration config",
8551
+ tags: ["config"],
8552
+ requestBody: { required: true, content: { "application/json": { schema: toWritableSchema2(StripeIntegrationConfigSchema2) } } },
8553
+ responses: { "200": { description: "Saved", content: { "application/json": { schema: StripeIntegrationConfigSchema2 } } } },
8554
+ "x-revturbine-operation": { exposure: "internal", resource: "stripe-config", persistence: { table: "stripeIntegrationConfig", mode: "upsert" } }
8555
+ })
8556
+ },
8557
+ "/api/config/metering": {
8558
+ get: operation2({
8559
+ operationId: "listMeteringConfigs",
8560
+ requestParams: { query: ListQueryParamsSchema2 },
8561
+ summary: "List metering configurations",
8562
+ tags: ["config"],
8563
+ responses: { "200": { description: "Metering config list", content: { "application/json": { schema: ListEnvelope2(MeteringConfigSchema2) } } } },
8564
+ "x-revturbine-operation": { exposure: "internal", resource: "metering-config", persistence: { table: "meteringConfigs", mode: "list" } }
8565
+ }),
8566
+ post: operation2({
8567
+ operationId: "createMeteringConfig",
8568
+ summary: "Create metering configuration",
8569
+ tags: ["config"],
8570
+ requestBody: { required: true, content: { "application/json": { schema: toCreateSchema2(MeteringConfigSchema2) } } },
8571
+ responses: { "201": { description: "Created", content: { "application/json": { schema: MeteringConfigSchema2 } } } },
8572
+ "x-revturbine-operation": { exposure: "internal", resource: "metering-config", persistence: { table: "meteringConfigs", mode: "create" } }
8573
+ })
8574
+ },
8575
+ "/api/config/metering/{meteringId}": {
8576
+ patch: operation2({
8577
+ operationId: "updateMeteringConfig",
8578
+ requestParams: { path: z92.object({ meteringId: z92.string() }) },
8579
+ summary: "Update metering configuration",
8580
+ tags: ["config"],
8581
+ requestBody: { required: true, content: { "application/json": { schema: MeteringConfigSchema2.partial() } } },
8582
+ responses: { "200": { description: "Updated", content: { "application/json": { schema: MeteringConfigSchema2 } } } },
8583
+ "x-revturbine-operation": { exposure: "internal", resource: "metering-config", persistence: { table: "meteringConfigs", mode: "update" } }
8584
+ }),
8585
+ delete: operation2({
8586
+ operationId: "deleteMeteringConfig",
8587
+ requestParams: { path: z92.object({ meteringId: z92.string() }) },
8588
+ summary: "Delete metering configuration",
8589
+ tags: ["config"],
8590
+ responses: { "204": { description: "Deleted" } },
8591
+ "x-revturbine-operation": { exposure: "internal", resource: "metering-config", persistence: { table: "meteringConfigs", mode: "delete" } }
8592
+ })
8593
+ },
8594
+ "/api/usage-enforcement-setting-anchors": {
8595
+ get: operation2({
8596
+ operationId: "listUsageEnforcementSettingsAnchors",
8597
+ requestParams: { query: ListQueryParamsSchema2 },
8598
+ summary: "List usage enforcement setting anchors (identity registry)",
8599
+ tags: ["config"],
8600
+ responses: {
8601
+ "200": { description: "Usage enforcement setting anchor list", content: { "application/json": { schema: ListEnvelope2(UsageEnforcementSettingsAnchorSchema2) } } },
8602
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
8603
+ },
8604
+ "x-revturbine-operation": { exposure: "internal", resource: "usage-enforcement-setting-anchors", persistence: { table: "usageEnforcementSettings", mode: "list" } }
8605
+ })
8606
+ },
8607
+ "/api/config/usage-enforcement": {
8608
+ get: operation2({
8609
+ operationId: "listUsageEnforcementSettings",
8610
+ requestParams: { query: ListQueryParamsSchema2 },
8611
+ summary: "List usage enforcement settings",
8612
+ tags: ["config"],
8613
+ responses: { "200": { description: "Enforcement settings", content: { "application/json": { schema: ListEnvelope2(UsageEnforcementSettingsSchema2) } } } },
8614
+ "x-revturbine-operation": { exposure: "internal", resource: "usage-enforcement", persistence: { table: "usageEnforcementSettingVersions", mode: "list" } }
8615
+ }),
8616
+ post: operation2({
8617
+ operationId: "createUsageEnforcementSettings",
8618
+ summary: "Create usage enforcement settings",
8619
+ tags: ["config"],
8620
+ requestBody: { required: true, content: { "application/json": { schema: toCreateSchema2(UsageEnforcementSettingsSchema2) } } },
8621
+ responses: { "201": { description: "Created", content: { "application/json": { schema: UsageEnforcementSettingsSchema2 } } } },
8622
+ "x-revturbine-operation": { exposure: "internal", resource: "usage-enforcement", persistence: { table: "usageEnforcementSettingVersions", mode: "create" } }
8623
+ })
8624
+ },
8625
+ "/api/config/usage-enforcement/{settingsId}": {
8626
+ patch: operation2({
8627
+ operationId: "updateUsageEnforcementSettings",
8628
+ requestParams: { path: z92.object({ settingsId: z92.string() }) },
8629
+ summary: "Update usage enforcement settings",
8630
+ tags: ["config"],
8631
+ requestBody: { required: true, content: { "application/json": { schema: UsageEnforcementSettingsSchema2.partial() } } },
8632
+ responses: { "200": { description: "Updated", content: { "application/json": { schema: UsageEnforcementSettingsSchema2 } } } },
8633
+ "x-revturbine-operation": { exposure: "internal", resource: "usage-enforcement", persistence: { table: "usageEnforcementSettingVersions", mode: "update" } }
8634
+ }),
8635
+ delete: operation2({
8636
+ operationId: "deleteUsageEnforcementSettings",
8637
+ requestParams: { path: z92.object({ settingsId: z92.string() }) },
8638
+ summary: "Delete usage enforcement settings",
8639
+ tags: ["config"],
8640
+ responses: { "204": { description: "Deleted" } },
8641
+ "x-revturbine-operation": { exposure: "internal", resource: "usage-enforcement", persistence: { table: "usageEnforcementSettingVersions", mode: "delete" } }
8642
+ })
8643
+ },
8644
+ "/api/placement-setting-anchors": {
8645
+ get: operation2({
8646
+ operationId: "listPlacementSettingsAnchors",
8647
+ requestParams: { query: ListQueryParamsSchema2 },
8648
+ summary: "List placement setting anchors (identity registry)",
8649
+ tags: ["config"],
8650
+ responses: {
8651
+ "200": { description: "Placement setting anchor list", content: { "application/json": { schema: ListEnvelope2(PlacementSettingsAnchorSchema2) } } },
8652
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope2 } } }
8653
+ },
8654
+ "x-revturbine-operation": { exposure: "internal", resource: "placement-setting-anchors", persistence: { table: "placementSettings", mode: "list" } }
8655
+ })
8656
+ },
8657
+ "/api/config/placement-settings": {
8658
+ get: operation2({
8659
+ operationId: "getPlacementSettings",
8660
+ summary: "Get global placement settings",
8661
+ tags: ["config"],
8662
+ responses: { "200": { description: "Placement settings", content: { "application/json": { schema: PlacementSettingsSchema2 } } } },
8663
+ "x-revturbine-operation": { exposure: "internal", resource: "placement-settings", persistence: { table: "placementSettingVersions", mode: "get" } }
8664
+ }),
8665
+ put: operation2({
8666
+ operationId: "upsertPlacementSettings",
8667
+ summary: "Create or update global placement settings",
8668
+ tags: ["config"],
8669
+ requestBody: { required: true, content: { "application/json": { schema: toWritableSchema2(PlacementSettingsSchema2) } } },
8670
+ responses: { "200": { description: "Saved", content: { "application/json": { schema: PlacementSettingsSchema2 } } } },
8671
+ "x-revturbine-operation": { exposure: "internal", resource: "placement-settings", persistence: { table: "placementSettingVersions", mode: "upsert" } }
8672
+ })
8673
+ }
8674
+ };
8675
+ var DEPRECATED_FIELD_REPAIR_CODE = "VAL-DEP-01";
8676
+ function isRecord22(value) {
8677
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8678
+ }
8679
+ function readShape(schema) {
8680
+ const shape = schema.shape;
8681
+ if (shape && typeof shape === "object") {
8682
+ return shape;
8683
+ }
8684
+ return void 0;
8685
+ }
8686
+ function repairFinding(field, deprecation) {
8687
+ return {
8688
+ code: DEPRECATED_FIELD_REPAIR_CODE,
8689
+ severity: "warning",
8690
+ targetRef: { field, path: [field] },
8691
+ message: `Deprecated field '${field}' was removed from the config so it stays valid. ${deprecation.reason} Use ${deprecation.replacement} instead (the field is scheduled for removal after ${deprecation.removeAfter}).`,
8692
+ specRef: "config-validation.md deprecation repair (plan 147 REQ-4, AC-3)",
8693
+ detail: `Field deprecated since ${deprecation.since}. The config was repaired by absorbing (removing) this field rather than being rejected for carrying it; supply '${deprecation.replacement}' through its current owner instead.`
8694
+ };
8695
+ }
8696
+ function repairDeprecatedFields(config, schema = PlaybookObjectSchema2) {
8697
+ if (!isRecord22(config)) return { config, findings: [] };
8698
+ const shape = readShape(schema);
8699
+ if (!shape) return { config, findings: [] };
8700
+ const findings = [];
8701
+ let repaired;
8702
+ for (const [field, fieldSchema] of Object.entries(shape)) {
8703
+ const deprecation = getSchemaDeprecation(fieldSchema);
8704
+ if (!deprecation) continue;
8705
+ if (!Object.prototype.hasOwnProperty.call(config, field)) continue;
8706
+ repaired ??= { ...config };
8707
+ delete repaired[field];
8708
+ findings.push(repairFinding(field, deprecation));
8709
+ }
8710
+ return { config: repaired ?? config, findings };
8711
+ }
8712
+
8713
+ // src/schema/version.ts
8714
+ var SCHEMA_VERSION = "0.1.145";
8715
+
8716
+ // src/lib/credentials.ts
8717
+ import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
8718
+ import os from "os";
8719
+ import path from "path";
8720
+ function resolveConfigDir(cwd = process.cwd()) {
8721
+ const override = process.env.REVTURBINE_CONFIG_DIR;
8722
+ if (override) return { dir: path.resolve(override), source: "env" };
8723
+ const scoped = findWorktreeConfigDir(cwd);
8724
+ if (scoped) return { dir: scoped, source: "worktree" };
8725
+ return { dir: path.join(os.homedir(), ".revturbine"), source: "global" };
8726
+ }
8727
+ function findWorktreeConfigDir(start) {
8728
+ let dir = path.resolve(start);
8729
+ for (; ; ) {
8730
+ const candidate = path.join(dir, ".revturbine");
8731
+ if (existsSync(candidate) && statSync(candidate).isDirectory()) return candidate;
8732
+ const parent = path.dirname(dir);
8733
+ if (parent === dir) return null;
8734
+ dir = parent;
8735
+ }
8736
+ }
8737
+ function configDir() {
8738
+ return resolveConfigDir().dir;
8739
+ }
8740
+ function configFile() {
8741
+ return path.join(configDir(), "credentials.json");
8742
+ }
8743
+ function normalizeBaseUrl(input) {
8744
+ const s = input.trim();
8745
+ if (/^https?:\/\//i.test(s)) return s.replace(/\/+$/, "");
8746
+ const isLocal = /^(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(:\d+)?(\/|$)/i.test(s);
8747
+ return `${isLocal ? "http" : "https"}://${s}`.replace(/\/+$/, "");
8748
+ }
8749
+ function urlKey(baseUrl) {
8750
+ try {
8751
+ return new URL(normalizeBaseUrl(baseUrl)).origin;
8752
+ } catch {
8753
+ return baseUrl.replace(/\/+$/, "");
8754
+ }
8755
+ }
8756
+ function read() {
8757
+ const file = configFile();
8758
+ if (!existsSync(file)) return { version: 1, credentials: {} };
8759
+ try {
8760
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
8761
+ if (parsed && typeof parsed === "object" && parsed.credentials) {
8762
+ return { version: 1, credentials: parsed.credentials };
8763
+ }
8764
+ } catch {
8765
+ }
8766
+ return { version: 1, credentials: {} };
8767
+ }
8768
+ function write(data) {
8769
+ const dir = configDir();
8770
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
8771
+ writeFileSync(configFile(), `${JSON.stringify(data, null, 2)}
8772
+ `, { mode: 384 });
8773
+ try {
8774
+ chmodSync(configFile(), 384);
8775
+ } catch {
8776
+ }
8777
+ }
8778
+ function saveCredential(baseUrl, cred) {
8779
+ const data = read();
8780
+ data.credentials[urlKey(baseUrl)] = cred;
8781
+ write(data);
8782
+ }
8783
+ function getCredential(baseUrl) {
8784
+ return read().credentials[urlKey(baseUrl)] ?? null;
8785
+ }
8786
+ function removeCredential(baseUrl) {
8787
+ const data = read();
8788
+ const key = urlKey(baseUrl);
8789
+ if (!(key in data.credentials)) return false;
8790
+ delete data.credentials[key];
8791
+ write(data);
8792
+ return true;
8793
+ }
8794
+ function redactToken(token) {
8795
+ if (token.length <= 8) return "rtk_\u2026";
8796
+ return `${token.slice(0, 4)}\u2026${token.slice(-4)}`;
8797
+ }
8798
+
8799
+ // src/lib/device-auth.ts
8800
+ import { spawn } from "child_process";
8801
+ import os2 from "os";
8802
+ function trimUrl(baseUrl) {
8803
+ return baseUrl.replace(/\/+$/, "");
8804
+ }
8805
+ async function startDeviceAuth(baseUrl, label, fetchImpl) {
8806
+ const res = await fetchImpl(`${trimUrl(baseUrl)}/api/cli/device/code`, {
8807
+ method: "POST",
8808
+ headers: { "Content-Type": "application/json" },
8809
+ body: JSON.stringify(label ? { label } : {})
8810
+ });
8811
+ if (!res.ok) {
8812
+ throw new Error(`Could not start device login (${res.status}). Is ${baseUrl} reachable?`);
8813
+ }
8814
+ return await res.json();
8815
+ }
8816
+ async function pollForToken(opts) {
8817
+ const now = opts.now ?? (() => Date.now());
8818
+ const startedAt = now();
8819
+ let interval = opts.intervalSeconds;
8820
+ for (; ; ) {
8821
+ if (now() - startedAt > opts.expiresInSeconds * 1e3) {
8822
+ throw new Error("Device code expired before authorization. Run --login again.");
8823
+ }
8824
+ await opts.sleep(interval * 1e3);
8825
+ const res = await opts.fetchImpl(`${trimUrl(opts.baseUrl)}/api/cli/device/token`, {
8826
+ method: "POST",
8827
+ headers: { "Content-Type": "application/json" },
8828
+ body: JSON.stringify({ device_code: opts.deviceCode })
8829
+ });
8830
+ if (res.ok) {
8831
+ return await res.json();
8832
+ }
8833
+ const body = await res.json().catch(() => ({}));
8834
+ switch (body.error) {
8835
+ case "authorization_pending":
8836
+ opts.onPending?.();
8837
+ continue;
8838
+ case "slow_down":
8839
+ interval += 5;
8840
+ continue;
8841
+ case "access_denied":
8842
+ throw new Error("Authorization was denied in the browser.");
8843
+ case "expired_token":
8844
+ throw new Error("Device code expired. Run --login again.");
8845
+ default:
8846
+ throw new Error(`Authorization failed: ${body.error ?? `HTTP ${res.status}`}`);
8847
+ }
8848
+ }
8849
+ }
8850
+ function openBrowser(url) {
8851
+ try {
8852
+ const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
8853
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
8854
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
8855
+ child.on("error", () => {
8856
+ });
8857
+ child.unref();
8858
+ } catch {
6278
8859
  }
6279
8860
  }
6280
8861
  async function deviceLogin(baseUrl, log = console.log) {
@@ -6563,13 +9144,87 @@ async function resolveActiveDraft(baseUrl, headers, fetchImpl = fetch) {
6563
9144
  return { ok: res.ok, status: res.status, draft: res.ok ? json.active ?? null : null };
6564
9145
  }
6565
9146
 
9147
+ // src/lib/ingest-keys.ts
9148
+ async function readJson(res) {
9149
+ return await res.json().catch(() => ({}));
9150
+ }
9151
+ function asStringArray(value) {
9152
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
9153
+ }
9154
+ async function createIngestKey(baseUrl, headers, input, fetchImpl = fetch) {
9155
+ const res = await fetchImpl(`${baseUrl}/api/developer/ingest-keys`, {
9156
+ method: "POST",
9157
+ headers,
9158
+ body: JSON.stringify({
9159
+ origin_allowlist: input.originAllowlist,
9160
+ ip_allowlist: input.ipAllowlist ?? []
9161
+ })
9162
+ });
9163
+ const json = await readJson(res);
9164
+ if (!res.ok) return { ok: false, status: res.status, key: null, error: json };
9165
+ return {
9166
+ ok: true,
9167
+ status: res.status,
9168
+ key: {
9169
+ id: typeof json.id === "string" ? json.id : "",
9170
+ token: typeof json.token === "string" ? json.token : "",
9171
+ tokenPreview: typeof json.tokenPreview === "string" ? json.tokenPreview : "",
9172
+ originAllowlist: asStringArray(json.originAllowlist),
9173
+ ipAllowlist: asStringArray(json.ipAllowlist),
9174
+ createdAt: typeof json.createdAt === "string" ? json.createdAt : void 0
9175
+ },
9176
+ error: {}
9177
+ };
9178
+ }
9179
+ async function listIngestKeys(baseUrl, headers, fetchImpl = fetch) {
9180
+ const res = await fetchImpl(`${baseUrl}/api/developer/ingest-keys`, { headers });
9181
+ const json = await readJson(res);
9182
+ const rawKeys = Array.isArray(json.keys) ? json.keys : [];
9183
+ const keys = rawKeys.map((k) => {
9184
+ const row = k ?? {};
9185
+ return {
9186
+ id: typeof row.id === "string" ? row.id : "",
9187
+ tokenPreview: typeof row.tokenPreview === "string" ? row.tokenPreview : "",
9188
+ originAllowlist: asStringArray(row.originAllowlist),
9189
+ ipAllowlist: asStringArray(row.ipAllowlist),
9190
+ createdAt: typeof row.createdAt === "string" ? row.createdAt : void 0,
9191
+ lastUsedAt: typeof row.lastUsedAt === "string" ? row.lastUsedAt : null
9192
+ };
9193
+ });
9194
+ return { ok: res.ok, status: res.status, keys: res.ok ? keys : [] };
9195
+ }
9196
+ async function revokeIngestKey(baseUrl, headers, id, fetchImpl = fetch) {
9197
+ const res = await fetchImpl(`${baseUrl}/api/developer/ingest-keys/${encodeURIComponent(id)}`, {
9198
+ method: "DELETE",
9199
+ headers
9200
+ });
9201
+ return { ok: res.ok, status: res.status };
9202
+ }
9203
+ function formatIngestKeyLine(key) {
9204
+ const origins = key.originAllowlist.length ? key.originAllowlist.join(", ") : "(none)";
9205
+ const ips = key.ipAllowlist.length ? ` ips: ${key.ipAllowlist.join(", ")}` : "";
9206
+ const last = key.lastUsedAt ? ` last used ${key.lastUsedAt}` : "";
9207
+ return `${key.id} ${key.tokenPreview} origins: ${origins}${ips}${last}`;
9208
+ }
9209
+
6566
9210
  // src/lib/delegate.ts
6567
9211
  var DELEGATION_ENV = "REVTURBINE_DELEGATED";
6568
9212
  var NO_LOCAL_FLAG = "--no-local";
9213
+ var SETUP_COMMANDS = ["init", "create"];
9214
+ function commandFromArgv(argv) {
9215
+ for (const arg of argv.slice(2)) {
9216
+ if (!arg.startsWith("-")) return arg;
9217
+ }
9218
+ return null;
9219
+ }
6569
9220
  function planDelegation(params) {
6570
9221
  if (params.env[DELEGATION_ENV]) {
6571
9222
  return { delegate: false, reason: "already delegated" };
6572
9223
  }
9224
+ const command = commandFromArgv(params.argv);
9225
+ if (command && SETUP_COMMANDS.includes(command)) {
9226
+ return { delegate: false, reason: `${command} establishes the repo pin \u2014 never delegated` };
9227
+ }
6573
9228
  if (params.argv.includes(NO_LOCAL_FLAG)) {
6574
9229
  return { delegate: false, reason: `${NO_LOCAL_FLAG} requested` };
6575
9230
  }
@@ -6604,6 +9259,54 @@ function schemaForConfig(raw) {
6604
9259
  return header.shape === "canonical" ? { schema: PlaybookSchema, shape: "canonical" } : { schema: RevTurbineConfigSchema, shape: "legacy" };
6605
9260
  }
6606
9261
 
9262
+ // src/lib/offline-config-check.ts
9263
+ var repairDeprecatedFields2 = repairDeprecatedFields;
9264
+ var LEGACY_HEADER_ALIASES = /* @__PURE__ */ new Set(["version", "change_set_id"]);
9265
+ var UNKNOWN_FIELD_CODE = "cli/unknown-field";
9266
+ function isRecord3(value) {
9267
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9268
+ }
9269
+ function unknownTopLevelKeys(raw) {
9270
+ if (!isRecord3(raw)) return [];
9271
+ const { schema } = schemaForConfig(raw);
9272
+ const parsed = schema.safeParse(raw);
9273
+ if (!parsed.success || !isRecord3(parsed.data)) return [];
9274
+ const recognized = new Set(Object.keys(parsed.data));
9275
+ return Object.keys(raw).filter((key) => !recognized.has(key) && !LEGACY_HEADER_ALIASES.has(key));
9276
+ }
9277
+ function offlineAdvisories(raw) {
9278
+ const advisories = [];
9279
+ for (const finding2 of repairDeprecatedFields2(raw).findings) {
9280
+ advisories.push({
9281
+ code: finding2.code,
9282
+ severity: "warning",
9283
+ message: finding2.message,
9284
+ targetRef: { ...finding2.targetRef ?? {} }
9285
+ });
9286
+ }
9287
+ for (const key of unknownTopLevelKeys(raw)) {
9288
+ advisories.push({
9289
+ code: UNKNOWN_FIELD_CODE,
9290
+ severity: "warning",
9291
+ message: `Unknown top-level field '${key}' is not part of the RevTurbine config schema (bundled offline snapshot). The CLI uploads it unchanged so the server can validate it \u2014 remove it if it was a typo.`,
9292
+ targetRef: { field: key, path: [key] }
9293
+ });
9294
+ }
9295
+ return advisories;
9296
+ }
9297
+ function checkOfflineConfig(raw) {
9298
+ const { schema, shape } = schemaForConfig(raw);
9299
+ const advisories = offlineAdvisories(raw);
9300
+ if (shape === "canonical") {
9301
+ return { ok: true, body: raw, shape, advisories, problems: [] };
9302
+ }
9303
+ const parsed = schema.safeParse(raw);
9304
+ if (parsed.success) {
9305
+ return { ok: true, body: raw, shape, advisories, problems: [] };
9306
+ }
9307
+ return { ok: false, body: raw, shape, advisories, problems: parsed.error.issues };
9308
+ }
9309
+
6607
9310
  // src/lib/init.ts
6608
9311
  var LOCKFILES = [
6609
9312
  ["pnpm-lock.yaml", "pnpm"],
@@ -6639,6 +9342,13 @@ function detectStack(signals) {
6639
9342
  }
6640
9343
  var SDK_PACKAGE = "@revturbine/sdk";
6641
9344
  var CLI_PACKAGE = "@revturbine/cli";
9345
+ function projectNameFromDir(dirName) {
9346
+ const slug = dirName.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[-_.]+|[-_.]+$/g, "");
9347
+ return slug || "my-app";
9348
+ }
9349
+ function newProjectManifest(dirName) {
9350
+ return { name: projectNameFromDir(dirName), version: "0.1.0", private: true, type: "module" };
9351
+ }
6642
9352
  function planInstall(params) {
6643
9353
  const deps = params.dependencies ?? {};
6644
9354
  const devDeps = params.devDependencies ?? {};
@@ -6689,7 +9399,7 @@ var STARTER_PLAYBOOK = {
6689
9399
  entitlement_id: "ent_advanced_export",
6690
9400
  targets: [{ kind: "plan", id: "plan_free" }],
6691
9401
  segment_ids: [],
6692
- type_fields: { kind: "feature", enabled: false },
9402
+ enabled: false,
6693
9403
  current_usage: 0
6694
9404
  },
6695
9405
  {
@@ -6697,7 +9407,7 @@ var STARTER_PLAYBOOK = {
6697
9407
  entitlement_id: "ent_advanced_export",
6698
9408
  targets: [{ kind: "plan", id: "plan_pro" }],
6699
9409
  segment_ids: [],
6700
- type_fields: { kind: "feature", enabled: true },
9410
+ enabled: true,
6701
9411
  current_usage: 0
6702
9412
  }
6703
9413
  ],
@@ -6753,7 +9463,7 @@ function finalOutputLines(params) {
6753
9463
  lines.push(` ${params.harness.invocation}`);
6754
9464
  if (params.harness.label) lines.push(` (detected ${params.harness.label})`);
6755
9465
  lines.push("", "Your setup path:");
6756
- lines.push(" create playbook \u2192 billing \u2192 app wiring \u2192 verify \u2192 launch");
9466
+ lines.push(" create playbook \u2192 app wiring \u2192 billing \u2192 verify \u2192 launch");
6757
9467
  lines.push("", "Docs: https://revturbine.com/docs", "");
6758
9468
  return lines;
6759
9469
  }
@@ -6889,7 +9599,6 @@ function serverSchemaIsNewer(config, bundledVersion) {
6889
9599
  // src/cli.ts
6890
9600
  var DOCS_URL = "https://github.com/revt-eng/revturbine-cli#readme";
6891
9601
  var DEFAULT_URL = "https://revturbine.com/app";
6892
- var schema = RevTurbineConfigSchema;
6893
9602
  function loadConfig(file) {
6894
9603
  const resolved = path2.resolve(file);
6895
9604
  if (!existsSync2(resolved)) fail(EXIT.USAGE, `Config not found: ${resolved}`);
@@ -6911,20 +9620,19 @@ function verifyConfig(file) {
6911
9620
  }
6912
9621
  throw err;
6913
9622
  }
6914
- if (header.shape === "canonical") {
6915
- diag(`\u2713 ${file}: ${describePlaybookHeader(header)} \u2014 body validated server-side on import`);
6916
- return parsed;
6917
- }
6918
- const result = schema.safeParse(parsed);
6919
- if (result.success) {
6920
- diag(`\u2713 ${file}: schema validation passed \u2014 ${describePlaybookHeader(header)}`);
6921
- return result.data;
6922
- }
6923
- diag(`\u2717 ${file}: schema validation FAILED:`);
6924
- for (const issue of result.error.issues) {
6925
- diagRaw(` ${issue.path.join(".") || "<root>"}: ${issue.message}`);
9623
+ const check = checkOfflineConfig(parsed);
9624
+ if (!check.ok) {
9625
+ diag(`\u2717 ${file}: schema validation FAILED:`);
9626
+ for (const issue of check.problems) {
9627
+ diagRaw(` ${issue.path.join(".") || "<root>"}: ${issue.message}`);
9628
+ }
9629
+ return null;
6926
9630
  }
6927
- return null;
9631
+ diag(
9632
+ check.shape === "canonical" ? `\u2713 ${file}: ${describePlaybookHeader(header)} \u2014 body validated server-side on import` : `\u2713 ${file}: schema validation passed \u2014 ${describePlaybookHeader(header)}`
9633
+ );
9634
+ if (check.advisories.length > 0) diagRaw(formatFindings(check.advisories));
9635
+ return check.body;
6928
9636
  }
6929
9637
  function connect(rawUrl, explicitTenantId) {
6930
9638
  const url = normalizeBaseUrl(rawUrl);
@@ -7060,6 +9768,22 @@ async function confirmOrExit(promptText, yes) {
7060
9768
  process.exit(0);
7061
9769
  }
7062
9770
  }
9771
+ async function confirmNewProject(dir, yes) {
9772
+ if (yes) return;
9773
+ if (!process.stdin.isTTY) {
9774
+ fail(
9775
+ EXIT.USAGE,
9776
+ `No package.json in ${dir}. Re-run with --yes to start a new project here, or --dir <path> to target an existing one.`
9777
+ );
9778
+ }
9779
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
9780
+ const answer = (await rl.question(`No package.json in ${dir}. Start a new project here? [Y/n] `)).trim().toLowerCase();
9781
+ rl.close();
9782
+ if (answer === "n" || answer === "no") {
9783
+ diag("Aborted \u2014 no project created.");
9784
+ process.exit(0);
9785
+ }
9786
+ }
7063
9787
  function table(rows) {
7064
9788
  if (rows.length === 0) return " (none)";
7065
9789
  const widths = rows[0].map((_, col) => Math.max(...rows.map((r) => (r[col] ?? "").length)));
@@ -7185,20 +9909,30 @@ function runInstall(manager, args, cwd) {
7185
9909
  child.on("close", (code) => resolve(code ?? 1));
7186
9910
  });
7187
9911
  }
7188
- program.command("init").alias("create").description("Scaffold RevTurbine into this app: detect the stack, install the SDK, pin the CLI, drop a starter Playbook, and install the Agent Skills.").option("-d, --dir <path>", "Target directory (defaults to the current directory)").option("--dry-run", "Report what would be installed without running the package manager").option("--no-skills", "Do not install the RevTurbine Agent Skills").option("--json", "Emit the scaffold plan as JSON").action(async (opts) => {
9912
+ program.command("init").alias("create").description("Scaffold RevTurbine into this app: detect the stack, install the SDK, pin the CLI, drop a starter Playbook, and install the Agent Skills. Offers to start a new project when the directory has no package.json.").option("-d, --dir <path>", "Target directory (defaults to the current directory)").option("-y, --yes", "Skip prompts \u2014 create a new project non-interactively when the directory has none").option("--dry-run", "Report what would be installed without running the package manager").option("--no-skills", "Do not install the RevTurbine Agent Skills").option("--json", "Emit the scaffold plan as JSON").action(async (opts) => {
7189
9913
  const dir = path2.resolve(opts.dir ?? process.cwd());
7190
9914
  const manifestPath = path2.join(dir, "package.json");
7191
- if (!existsSync2(manifestPath)) {
7192
- fail(EXIT.USAGE, `No package.json in ${dir} \u2014 run init inside a JavaScript project (or pass --dir).`);
7193
- }
7194
9915
  let manifest;
7195
- try {
7196
- manifest = JSON.parse(readFileSync2(manifestPath, "utf8"));
7197
- } catch (err) {
7198
- fail(EXIT.VALIDATION, `invalid JSON in ${manifestPath}: ${err.message}`);
9916
+ let createdProject = false;
9917
+ if (existsSync2(manifestPath)) {
9918
+ try {
9919
+ manifest = JSON.parse(readFileSync2(manifestPath, "utf8"));
9920
+ } catch (err) {
9921
+ fail(EXIT.VALIDATION, `invalid JSON in ${manifestPath}: ${err.message}`);
9922
+ }
9923
+ } else {
9924
+ manifest = newProjectManifest(path2.basename(dir));
9925
+ createdProject = true;
9926
+ if (!opts.dryRun) {
9927
+ await confirmNewProject(dir, opts.yes === true);
9928
+ mkdirSync2(dir, { recursive: true });
9929
+ writeFileSync2(manifestPath, `${JSON.stringify(manifest, null, 2)}
9930
+ `, "utf8");
9931
+ diag(`\u2713 Created a new project (package.json \u2014 ${manifest.name})`);
9932
+ }
7199
9933
  }
7200
9934
  const manager = detectPackageManager({
7201
- files: readdirSync(dir),
9935
+ files: existsSync2(dir) ? readdirSync(dir) : [],
7202
9936
  packageManagerField: manifest.packageManager,
7203
9937
  userAgent: process.env["npm_config_user_agent"]
7204
9938
  });
@@ -7219,6 +9953,7 @@ program.command("init").alias("create").description("Scaffold RevTurbine into th
7219
9953
  emit(
7220
9954
  {
7221
9955
  dir,
9956
+ project: createdProject ? "created" : "existing",
7222
9957
  manager: manager.name,
7223
9958
  stack,
7224
9959
  install: plan.install,
@@ -7230,6 +9965,7 @@ program.command("init").alias("create").description("Scaffold RevTurbine into th
7230
9965
  );
7231
9966
  }
7232
9967
  if (opts.dryRun) {
9968
+ if (createdProject) diag(`would create: package.json (new project \u2014 ${manifest.name})`);
7233
9969
  for (const step of plan.install) {
7234
9970
  diag(`would run: ${manager.name} ${installArgs(manager.name, step).join(" ")}`);
7235
9971
  }
@@ -7480,10 +10216,14 @@ program.command("validate").description("Validate a Config File offline (schema)
7480
10216
  const findings = evaluate(parsed.success ? parsed.data : raw, {
7481
10217
  structuralErrors: parsed.success ? void 0 : parsed.error
7482
10218
  });
10219
+ const allFindings = [
10220
+ ...findings,
10221
+ ...offlineAdvisories(raw)
10222
+ ];
7483
10223
  diag(`Validation for ${file} (offline, ${shape} shape, schema ${SCHEMA_VERSION}):`);
7484
- process.stdout.write(`${formatFindings(findings)}
10224
+ process.stdout.write(`${formatFindings(allFindings)}
7485
10225
  `);
7486
- if (hasBlockingFindings(findings)) blockedFiles += 1;
10226
+ if (hasBlockingFindings(allFindings)) blockedFiles += 1;
7487
10227
  }
7488
10228
  if (files.length > 1) diag(`${files.length - blockedFiles}/${files.length} passed.`);
7489
10229
  if (blockedFiles > 0) {
@@ -7683,6 +10423,45 @@ program.command("evaluate").description("Run placement/entitlement decisions for
7683
10423
  emit(json, true);
7684
10424
  }
7685
10425
  );
10426
+ var ingestKeys = program.command("ingest-keys").description("Manage public ingest keys (embeddable SDK telemetry credentials) for the tenant.");
10427
+ ingestKeys.command("create").description("Mint a public ingest key. The full token is printed ONCE and cannot be retrieved again.").requiredOption("--origin <url...>", "Allowed browser origin(s), e.g. https://app.example.com (repeatable; at least one required)").option("--ip <cidr...>", "Optional IP / CIDR allowlist (empty = no IP restriction)").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").option("--json", "Machine-readable output (token on stdout for scripted capture)").action(async (opts) => {
10428
+ const conn = connect(opts.url, opts.tenantId);
10429
+ const result = await createIngestKey(conn.url, conn.headers, { originAllowlist: opts.origin, ipAllowlist: opts.ip });
10430
+ if (!result.ok || !result.key) httpFail(conn, "ingest-keys create", result.status, result.error);
10431
+ const key = result.key;
10432
+ if (opts.json) {
10433
+ emit(key, true);
10434
+ return;
10435
+ }
10436
+ diag("\u2713 Minted a public ingest key. STORE THE TOKEN NOW \u2014 it is shown once and cannot be retrieved again.");
10437
+ emit(
10438
+ key,
10439
+ false,
10440
+ [
10441
+ ` id: ${key.id}`,
10442
+ ` token: ${key.token}`,
10443
+ ` origins: ${key.originAllowlist.join(", ")}`,
10444
+ key.ipAllowlist.length ? ` ips: ${key.ipAllowlist.join(", ")}` : void 0
10445
+ ].filter((line) => Boolean(line)).join("\n")
10446
+ );
10447
+ });
10448
+ ingestKeys.command("list").description("List the active public ingest keys for the tenant (previews only \u2014 never the full token).").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").option("--json", "Machine-readable output").action(async (opts) => {
10449
+ const conn = connect(opts.url, opts.tenantId);
10450
+ const result = await listIngestKeys(conn.url, conn.headers);
10451
+ if (!result.ok) httpFail(conn, "ingest-keys list", result.status);
10452
+ emit(
10453
+ result.keys,
10454
+ Boolean(opts.json),
10455
+ result.keys.length ? result.keys.map(formatIngestKeyLine).join("\n") : "(no active ingest keys)"
10456
+ );
10457
+ });
10458
+ ingestKeys.command("revoke").description("Revoke a public ingest key by id. The key stops working immediately.").argument("<id>", "The ingest key id (see `revturbine ingest-keys list`)").option("-u, --url <url>", "RevTurbine instance URL", DEFAULT_URL).option("-t, --tenant-id <id>", "x-tenant-id (defaults to the stored token tenant)").option("--yes", "Skip the confirmation prompt").action(async (id, opts) => {
10459
+ const conn = connect(opts.url, opts.tenantId);
10460
+ await confirmOrExit(`Revoke ingest key ${id} on ${conn.url}? The key stops working immediately.`, !!opts.yes);
10461
+ const result = await revokeIngestKey(conn.url, conn.headers, id);
10462
+ if (!result.ok) httpFail(conn, "ingest-keys revoke", result.status);
10463
+ diag(`\u2713 Revoked ingest key ${id}.`);
10464
+ });
7686
10465
  var COMMAND_EXAMPLES = {
7687
10466
  download: [
7688
10467
  "",