@revturbine/cli 0.7.1 → 0.9.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 (3) hide show
  1. package/README.md +16 -4
  2. package/dist/cli.js +1250 -233
  3. package/package.json +22 -11
package/dist/cli.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
4
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
5
5
  import path2 from "path";
6
6
  import { createRequire } from "module";
7
+ import { fileURLToPath } from "url";
7
8
  import { createInterface } from "readline/promises";
8
- import { spawn as spawn2 } from "child_process";
9
+ import { spawn as spawn2, spawnSync } from "child_process";
9
10
  import { Command, CommanderError, Option } from "commander";
10
11
  import { z as z29 } from "zod";
11
12
 
@@ -196,6 +197,7 @@ var NameField = z2.string().min(1).max(200);
196
197
  var HandleField = z2.string().min(1).max(100);
197
198
  var DescriptionField = z2.string().max(500).optional();
198
199
  var MetadataField = z2.record(z2.string(), z2.unknown()).default({});
200
+ var ThresholdPercentField = z2.number().int().min(10).max(100).multipleOf(10);
199
201
  var NullableDatetimeField = z2.string().datetime().nullable().default(null);
200
202
  var AnchorFields = z2.object({
201
203
  environment_id: z2.string().min(1).default("production").meta({ ...Unrestricted, readOnly: true })
@@ -265,6 +267,52 @@ var CtaActionTypeSchema = z2.enum([
265
267
  "snooze",
266
268
  "custom"
267
269
  ]).meta({ id: "CtaActionType", "x-revturbine-schema-persistence": Transient, "x-revturbine-schema-exposure": External });
270
+ var SchemaContext = {
271
+ Playbook: "playbook",
272
+ Branding: "branding",
273
+ Billing: "billing",
274
+ Metering: "metering",
275
+ CustomerOperations: "customer_operations",
276
+ EventIngestion: "event_ingestion"
277
+ };
278
+ var SchemaSource = {
279
+ Customer: "customer",
280
+ Stripe: "stripe",
281
+ CodeConstant: "code-constant",
282
+ Runtime: "runtime"
283
+ };
284
+ var SCHEMA_CONTEXT_META_KEY = "x-revturbine-context";
285
+ var SCHEMA_IN_CONFIG_META_KEY = "x-revturbine-in-config";
286
+ var SCHEMA_SDK_INPUT_META_KEY = "x-revturbine-sdk-input";
287
+ var SCHEMA_SOURCE_META_KEY = "x-revturbine-source";
288
+ var SCHEMA_DEPRECATION_META_KEY = "x-revturbine-deprecation";
289
+ var DEFAULT_SOURCE_BY_CONTEXT = {
290
+ [SchemaContext.Playbook]: SchemaSource.Customer,
291
+ [SchemaContext.Branding]: SchemaSource.Customer,
292
+ [SchemaContext.Billing]: SchemaSource.Stripe,
293
+ [SchemaContext.Metering]: SchemaSource.Customer,
294
+ [SchemaContext.CustomerOperations]: SchemaSource.Runtime,
295
+ [SchemaContext.EventIngestion]: SchemaSource.Runtime
296
+ };
297
+ function schemaFacets(context, options) {
298
+ return {
299
+ [SCHEMA_CONTEXT_META_KEY]: context,
300
+ [SCHEMA_IN_CONFIG_META_KEY]: options.inConfig ?? context === SchemaContext.Playbook,
301
+ [SCHEMA_SDK_INPUT_META_KEY]: options.sdkInput,
302
+ [SCHEMA_SOURCE_META_KEY]: options.source ?? DEFAULT_SOURCE_BY_CONTEXT[context]
303
+ };
304
+ }
305
+ function schemaDeprecation(declaration) {
306
+ return {
307
+ deprecated: true,
308
+ [SCHEMA_DEPRECATION_META_KEY]: {
309
+ since: declaration.since,
310
+ replacement: declaration.replacement,
311
+ remove_after: declaration.removeAfter,
312
+ reason: declaration.reason
313
+ }
314
+ };
315
+ }
268
316
  var IdentityKind = {
269
317
  /** Author-given, human-meaningful handle (plans, entitlements, segments, …). */
270
318
  Named: "named",
@@ -297,6 +345,9 @@ var ListQueryParamsSchema = z4.object({
297
345
  var { Unrestricted: Unrestricted2, Financial } = DataClassification;
298
346
  var { Persisted: Persisted2, Transient: Transient2 } = SchemaPersistence;
299
347
  var { External: External2 } = SchemaExposure;
348
+ var PLAYBOOK_SDK_FACETS = schemaFacets(SchemaContext.Playbook, { sdkInput: true });
349
+ var PLAYBOOK_AUTHORING_FACETS = schemaFacets(SchemaContext.Playbook, { sdkInput: false });
350
+ var BILLING_FACETS = schemaFacets(SchemaContext.Billing, { sdkInput: false });
300
351
  var PlanVisibilitySchema = z5.enum(["public", "unlisted", "legacy"]).meta(
301
352
  {
302
353
  id: "PlanVisibility",
@@ -330,6 +381,7 @@ var PlanSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(Ancho
330
381
  id: "Plan",
331
382
  "x-revturbine-schema-persistence": Persisted2,
332
383
  "x-revturbine-schema-exposure": External2,
384
+ ...PLAYBOOK_SDK_FACETS,
333
385
  ...namedIdentity()
334
386
  }
335
387
  );
@@ -343,6 +395,9 @@ var PlanVariationSchema = IdField.merge(TimestampFields).merge(TenantIdField).me
343
395
  price_amount: z5.number().min(0).meta(Financial),
344
396
  pricing_model: PricingModelSchema.meta(Unrestricted2),
345
397
  visibility: PlanVisibilitySchema.default("public").meta(Unrestricted2),
398
+ // Soft reference → StripePrice.stripe_price_id (the backend Stripe-price mirror).
399
+ // No DB foreign key: stripe_price_id lives on the append-only version tables and
400
+ // a hard FK would block plan-122 price-deletion sync (plan 118 FK decision, devkit #472).
346
401
  stripe_price_id: z5.string().optional().meta(Unrestricted2),
347
402
  price_source: PriceSourceSchema.default("static").meta(Unrestricted2)
348
403
  }).meta(
@@ -350,6 +405,7 @@ var PlanVariationSchema = IdField.merge(TimestampFields).merge(TenantIdField).me
350
405
  id: "PlanVariation",
351
406
  "x-revturbine-schema-persistence": Persisted2,
352
407
  "x-revturbine-schema-exposure": External2,
408
+ ...BILLING_FACETS,
353
409
  ...mintedIdentity()
354
410
  }
355
411
  );
@@ -365,6 +421,7 @@ var AddOnSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(Anch
365
421
  id: "AddOn",
366
422
  "x-revturbine-schema-persistence": Persisted2,
367
423
  "x-revturbine-schema-exposure": External2,
424
+ ...PLAYBOOK_AUTHORING_FACETS,
368
425
  ...namedIdentity()
369
426
  }
370
427
  );
@@ -380,6 +437,9 @@ var AddOnVariationSchema = IdField.merge(TimestampFields).merge(TenantIdField).m
380
437
  price_amount: z5.number().min(0).meta(Financial),
381
438
  pricing_model: PricingModelSchema.meta(Unrestricted2),
382
439
  visibility: PlanVisibilitySchema.default("public").meta(Unrestricted2),
440
+ // Soft reference → StripePrice.stripe_price_id (the backend Stripe-price mirror).
441
+ // No DB foreign key: stripe_price_id lives on the append-only version tables and
442
+ // a hard FK would block plan-122 price-deletion sync (plan 118 FK decision, devkit #472).
383
443
  stripe_price_id: z5.string().optional().meta(Unrestricted2),
384
444
  price_source: PriceSourceSchema.default("static").meta(Unrestricted2)
385
445
  }).meta(
@@ -387,33 +447,41 @@ var AddOnVariationSchema = IdField.merge(TimestampFields).merge(TenantIdField).m
387
447
  id: "AddOnVariation",
388
448
  "x-revturbine-schema-persistence": Persisted2,
389
449
  "x-revturbine-schema-exposure": External2,
450
+ ...BILLING_FACETS,
390
451
  ...mintedIdentity()
391
452
  }
392
453
  );
393
454
  var AddOnVariationAnchorSchema = makeAnchor("AddOnVariationAnchor");
394
- var StripePriceMockBillingPeriodSchema = z5.enum(["monthly", "annual", "one_time", "custom"]).meta({
395
- id: "StripePriceMockBillingPeriod",
455
+ var StripePriceBillingPeriodSchema = z5.enum(["monthly", "annual", "one_time", "custom"]).meta({
456
+ id: "StripePriceBillingPeriod",
396
457
  "x-revturbine-schema-persistence": Transient2,
397
458
  "x-revturbine-schema-exposure": External2
398
459
  });
399
- var StripePriceMockSchema = IdField.merge(TimestampFields).merge(TenantIdField).extend({
460
+ var StripePriceSchema = IdField.merge(TimestampFields).merge(TenantIdField).extend({
400
461
  stripe_price_id: z5.string().min(1).meta(Unrestricted2),
401
462
  stripe_product_id: z5.string().min(1).meta(Unrestricted2),
402
- billing_period: StripePriceMockBillingPeriodSchema.meta(Unrestricted2),
463
+ billing_period: StripePriceBillingPeriodSchema.meta(Unrestricted2),
403
464
  unit_amount_cents: z5.number().int().min(0).nullable().default(null).meta(Financial),
404
465
  currency: CurrencySchema.meta(Financial),
405
466
  pricing_model: PricingModelSchema.meta(Unrestricted2),
406
- nickname: z5.string().nullable().default(null).meta(Unrestricted2)
467
+ nickname: z5.string().nullable().default(null).meta(Unrestricted2),
468
+ // Placeholder/seed price (demo or pre-integration tenants) vs a real
469
+ // Stripe-synced mirror row. Migration backfills existing seed rows to true.
470
+ is_mock: z5.boolean().default(false).meta(Unrestricted2),
471
+ // Timestamp of the last successful sync from Stripe; null for rows that were
472
+ // never sourced from a real Stripe Price (seeds/mocks).
473
+ last_updated_from_stripe: NullableDatetimeField.meta(Unrestricted2)
407
474
  }).meta({
408
- id: "StripePriceMock",
475
+ id: "StripePrice",
409
476
  "x-revturbine-schema-persistence": Persisted2,
410
- "x-revturbine-schema-exposure": External2
477
+ "x-revturbine-schema-exposure": External2,
478
+ ...BILLING_FACETS
411
479
  });
412
480
  var PlanWriteSchema = toWritableSchema(PlanSchema);
413
481
  var PlanVariationWriteSchema = toWritableSchema(PlanVariationSchema);
414
482
  var AddOnWriteSchema = toWritableSchema(AddOnSchema);
415
483
  var AddOnVariationWriteSchema = toWritableSchema(AddOnVariationSchema);
416
- var StripePriceMockWriteSchema = toWritableSchema(StripePriceMockSchema);
484
+ var StripePriceWriteSchema = toWritableSchema(StripePriceSchema);
417
485
  var planPaths = {
418
486
  "/api/plan-anchors": {
419
487
  get: operation({
@@ -674,55 +742,60 @@ var planPaths = {
674
742
  "x-revturbine-operation": { exposure: "external", resource: "addon-variations", persistence: { table: "addonVariationVersions", mode: "delete" } }
675
743
  })
676
744
  },
677
- "/api/stripe-prices-mock": {
745
+ "/api/stripe-prices": {
678
746
  get: operation({
679
- operationId: "listStripePriceMocks",
747
+ operationId: "listStripePrices",
680
748
  requestParams: { query: ListQueryParamsSchema },
681
- summary: "List Stripe price mocks",
749
+ summary: "List Stripe prices",
682
750
  tags: ["plans"],
683
- responses: { "200": { description: "Stripe price mock list", content: { "application/json": { schema: ListEnvelope(StripePriceMockSchema) } } } },
684
- "x-revturbine-operation": { exposure: "external", resource: "stripe-prices-mock", persistence: { table: "stripePricesMock", mode: "list" } }
751
+ responses: { "200": { description: "Stripe price list", content: { "application/json": { schema: ListEnvelope(StripePriceSchema) } } } },
752
+ "x-revturbine-operation": { exposure: "external", resource: "stripe-prices", persistence: { table: "stripePrices", mode: "list" } }
685
753
  }),
686
754
  post: operation({
687
- operationId: "createStripePriceMock",
688
- summary: "Create Stripe price mock",
755
+ operationId: "createStripePrice",
756
+ summary: "Create Stripe price",
689
757
  tags: ["plans"],
690
- requestBody: { required: true, content: { "application/json": { schema: toCreateSchema(StripePriceMockSchema) } } },
691
- responses: { "201": { description: "Created Stripe price mock", content: { "application/json": { schema: StripePriceMockSchema } } } },
692
- "x-revturbine-operation": { exposure: "external", resource: "stripe-prices-mock", persistence: { table: "stripePricesMock", mode: "create" } }
758
+ requestBody: { required: true, content: { "application/json": { schema: toCreateSchema(StripePriceSchema) } } },
759
+ responses: { "201": { description: "Created Stripe price", content: { "application/json": { schema: StripePriceSchema } } } },
760
+ "x-revturbine-operation": { exposure: "external", resource: "stripe-prices", persistence: { table: "stripePrices", mode: "create" } }
693
761
  })
694
762
  },
695
- "/api/stripe-prices-mock/{id}": {
763
+ "/api/stripe-prices/{id}": {
696
764
  get: operation({
697
- operationId: "getStripePriceMock",
765
+ operationId: "getStripePrice",
698
766
  requestParams: { path: z5.object({ id: z5.string() }) },
699
- summary: "Get Stripe price mock by ID",
767
+ summary: "Get Stripe price by ID",
700
768
  tags: ["plans"],
701
- responses: { "200": { description: "Stripe price mock detail", content: { "application/json": { schema: StripePriceMockSchema } } } },
702
- "x-revturbine-operation": { exposure: "external", resource: "stripe-prices-mock", persistence: { table: "stripePricesMock", mode: "get" } }
769
+ responses: { "200": { description: "Stripe price detail", content: { "application/json": { schema: StripePriceSchema } } } },
770
+ "x-revturbine-operation": { exposure: "external", resource: "stripe-prices", persistence: { table: "stripePrices", mode: "get" } }
703
771
  }),
704
772
  patch: operation({
705
- operationId: "updateStripePriceMock",
773
+ operationId: "updateStripePrice",
706
774
  requestParams: { path: z5.object({ id: z5.string() }) },
707
- summary: "Update Stripe price mock",
775
+ summary: "Update Stripe price",
708
776
  tags: ["plans"],
709
- requestBody: { required: true, content: { "application/json": { schema: StripePriceMockWriteSchema } } },
710
- responses: { "200": { description: "Updated Stripe price mock", content: { "application/json": { schema: StripePriceMockSchema } } } },
711
- "x-revturbine-operation": { exposure: "external", resource: "stripe-prices-mock", persistence: { table: "stripePricesMock", mode: "update" } }
777
+ requestBody: { required: true, content: { "application/json": { schema: StripePriceWriteSchema } } },
778
+ responses: { "200": { description: "Updated Stripe price", content: { "application/json": { schema: StripePriceSchema } } } },
779
+ "x-revturbine-operation": { exposure: "external", resource: "stripe-prices", persistence: { table: "stripePrices", mode: "update" } }
712
780
  }),
713
781
  delete: operation({
714
- operationId: "deleteStripePriceMock",
782
+ operationId: "deleteStripePrice",
715
783
  requestParams: { path: z5.object({ id: z5.string() }) },
716
- summary: "Delete Stripe price mock",
784
+ summary: "Delete Stripe price",
717
785
  tags: ["plans"],
718
- responses: { "204": { description: "Stripe price mock deleted" } },
719
- "x-revturbine-operation": { exposure: "external", resource: "stripe-prices-mock", persistence: { table: "stripePricesMock", mode: "delete" } }
786
+ responses: { "204": { description: "Stripe price deleted" } },
787
+ "x-revturbine-operation": { exposure: "external", resource: "stripe-prices", persistence: { table: "stripePrices", mode: "delete" } }
720
788
  })
721
789
  }
722
790
  };
723
791
  var { Unrestricted: Unrestricted3 } = DataClassification;
724
792
  var { Persisted: Persisted3, Transient: Transient3 } = SchemaPersistence;
725
793
  var { Internal: Internal2, External: External3 } = SchemaExposure;
794
+ var PLAYBOOK_SDK_FACETS2 = schemaFacets(SchemaContext.Playbook, { sdkInput: true });
795
+ var PENDING_PLAYBOOK_FACETS = schemaFacets(SchemaContext.Playbook, {
796
+ inConfig: false,
797
+ sdkInput: false
798
+ });
726
799
  var UsagePeriodScopeSchema = z6.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(
727
800
  { id: "UsagePeriodScope", "x-revturbine-schema-persistence": Transient3, "x-revturbine-schema-exposure": External3 }
728
801
  );
@@ -792,6 +865,94 @@ var EntitlementRuleTargetSchema = z6.object({
792
865
  var EntitlementRulePeriodUnitSchema = z6.enum(["month", "day", "week", "quarter", "year", "billing_period", "on_purchase", "hour", "six_hours"]).meta(
793
866
  { id: "EntitlementRulePeriodUnit", "x-revturbine-schema-persistence": Transient3, "x-revturbine-schema-exposure": External3 }
794
867
  );
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
+ );
795
956
  var EntitlementSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
796
957
  anchor_id: z6.string().min(1).meta({ ...Unrestricted3, readOnly: true }),
797
958
  name: NameField.meta(Unrestricted3),
@@ -813,25 +974,11 @@ var EntitlementSchema = IdField.merge(TimestampFields).merge(TenantIdField).merg
813
974
  id: "Entitlement",
814
975
  "x-revturbine-schema-persistence": Persisted3,
815
976
  "x-revturbine-schema-exposure": External3,
977
+ ...PLAYBOOK_SDK_FACETS2,
816
978
  ...namedIdentity()
817
979
  }
818
980
  );
819
981
  var EntitlementAnchorSchema = makeAnchor("EntitlementAnchor");
820
- var PlanEntitlementSchema = TenantIdField.merge(AnchorFields).merge(VersionFields).extend({
821
- handle: HandleField.meta({ ...Unrestricted3, readOnly: true }),
822
- id: z6.number().int().meta({ ...Unrestricted3, readOnly: true }),
823
- plan_id: z6.string().min(1).meta(Unrestricted3),
824
- entitlement_id: z6.string().min(1).meta(Unrestricted3),
825
- value: z6.union([z6.boolean(), z6.number(), z6.string()]).meta(Unrestricted3),
826
- override_label: z6.string().max(200).optional().meta(Unrestricted3)
827
- }).meta(
828
- {
829
- id: "PlanEntitlement",
830
- "x-revturbine-schema-persistence": Persisted3,
831
- "x-revturbine-schema-exposure": Internal2,
832
- ...mintedIdentity()
833
- }
834
- );
835
982
  var EntitlementRuleSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
836
983
  anchor_id: z6.string().min(1).meta({ ...Unrestricted3, readOnly: true }),
837
984
  handle: HandleField.meta({ ...Unrestricted3, readOnly: true }),
@@ -854,6 +1001,12 @@ var EntitlementRuleSchema = IdField.merge(TimestampFields).merge(TenantIdField).
854
1001
  // Type-specific fields (populated based on entitlement type)
855
1002
  limit_value: z6.union([z6.number(), z6.literal("unlimited")]).optional().meta(Unrestricted3),
856
1003
  enforcement: EnforcementModeSchema.optional().meta(Unrestricted3),
1004
+ // Usage-warning emitter (plan 138 REQ-13): the percent at which this rule
1005
+ // emits a usage-warning crossing event. 10% increments so a placement's
1006
+ // `threshold_percent` (same field type) can align with — "should match one
1007
+ // of those global values" (placement-studio-ui.md §3.4). Optional; a rule
1008
+ // with no warning threshold emits none.
1009
+ warning_threshold_percent: ThresholdPercentField.optional().meta(Unrestricted3),
857
1010
  overage_price_ref: z6.string().optional().meta(Unrestricted3),
858
1011
  grace_period_hours: z6.number().int().min(0).optional().meta(Unrestricted3),
859
1012
  tier_value: z6.string().optional().meta(Unrestricted3),
@@ -873,6 +1026,7 @@ var EntitlementRuleSchema = IdField.merge(TimestampFields).merge(TenantIdField).
873
1026
  id: "EntitlementRule",
874
1027
  "x-revturbine-schema-persistence": Persisted3,
875
1028
  "x-revturbine-schema-exposure": Internal2,
1029
+ ...PLAYBOOK_SDK_FACETS2,
876
1030
  // Bucket 2: a rule IS its scope (entitlement + segments + targets); its
877
1031
  // limit/enforcement is the mutable payload. Behaviour-only edits coalesce
878
1032
  // as the same rule; a scope edit is a new rule identity (plan 121 REQ-3).
@@ -930,12 +1084,12 @@ var EntitlementRuleVariantSchema = IdField.merge(TimestampFields).merge(TenantId
930
1084
  id: "EntitlementRuleVariant",
931
1085
  "x-revturbine-schema-persistence": Persisted3,
932
1086
  "x-revturbine-schema-exposure": Internal2,
1087
+ ...PENDING_PLAYBOOK_FACETS,
933
1088
  ...mintedIdentity()
934
1089
  }
935
1090
  );
936
1091
  var EntitlementWriteSchema = toWritableSchema(EntitlementSchema);
937
1092
  var EntitlementRuleWriteSchema = toWritableSchema(EntitlementRuleSchema);
938
- var PlanEntitlementWriteSchema = toWritableSchema(PlanEntitlementSchema);
939
1093
  var entitlementPaths = {
940
1094
  "/api/entitlement-anchors": {
941
1095
  get: operation({
@@ -1074,56 +1228,26 @@ var entitlementPaths = {
1074
1228
  responses: { "201": { description: "Created", content: { "application/json": { schema: EntitlementRuleVariantSchema } } } },
1075
1229
  "x-revturbine-operation": { exposure: "external", resource: "entitlement-rule-variants", persistence: { table: "entitlementRuleVariants", mode: "create" } }
1076
1230
  })
1077
- },
1078
- "/api/plan-entitlements": {
1079
- get: operation({
1080
- operationId: "listPlanEntitlements",
1081
- requestParams: { query: ListQueryParamsSchema },
1082
- summary: "List plan \u2194 entitlement mappings",
1083
- tags: ["entitlements"],
1084
- responses: { "200": { description: "Plan entitlement list", content: { "application/json": { schema: ListEnvelope(PlanEntitlementSchema) } } } },
1085
- "x-revturbine-operation": { exposure: "internal", resource: "plan-entitlements", persistence: { table: "planEntitlements", mode: "list" } }
1086
- }),
1087
- post: operation({
1088
- operationId: "createPlanEntitlement",
1089
- summary: "Create plan \u2194 entitlement mapping",
1090
- tags: ["entitlements"],
1091
- requestBody: { required: true, content: { "application/json": { schema: toCreateSchema(PlanEntitlementSchema) } } },
1092
- responses: { "201": { description: "Created", content: { "application/json": { schema: PlanEntitlementSchema } } } },
1093
- "x-revturbine-operation": { exposure: "internal", resource: "plan-entitlements", persistence: { table: "planEntitlements", mode: "create" } }
1094
- })
1095
- },
1096
- "/api/plan-entitlements/{id}": {
1097
- get: operation({
1098
- operationId: "getPlanEntitlement",
1099
- requestParams: { path: z6.object({ id: z6.string() }) },
1100
- summary: "Get plan \u2194 entitlement mapping",
1101
- tags: ["entitlements"],
1102
- responses: { "200": { description: "Plan entitlement detail", content: { "application/json": { schema: PlanEntitlementSchema } } } },
1103
- "x-revturbine-operation": { exposure: "internal", resource: "plan-entitlements", persistence: { table: "planEntitlements", mode: "get" } }
1104
- }),
1105
- put: operation({
1106
- operationId: "updatePlanEntitlement",
1107
- requestParams: { path: z6.object({ id: z6.string() }) },
1108
- summary: "Update plan \u2194 entitlement mapping",
1109
- tags: ["entitlements"],
1110
- requestBody: { required: true, content: { "application/json": { schema: PlanEntitlementWriteSchema } } },
1111
- responses: { "200": { description: "Updated", content: { "application/json": { schema: PlanEntitlementSchema } } } },
1112
- "x-revturbine-operation": { exposure: "internal", resource: "plan-entitlements", persistence: { table: "planEntitlements", mode: "update" } }
1113
- }),
1114
- delete: operation({
1115
- operationId: "deletePlanEntitlement",
1116
- requestParams: { path: z6.object({ id: z6.string() }) },
1117
- summary: "Delete plan \u2194 entitlement mapping",
1118
- tags: ["entitlements"],
1119
- responses: { "204": { description: "Deleted" }, default: { description: "Error", content: { "application/json": { schema: ErrorEnvelope } } } },
1120
- "x-revturbine-operation": { exposure: "internal", resource: "plan-entitlements", persistence: { table: "planEntitlements", mode: "delete" } }
1121
- })
1122
1231
  }
1123
1232
  };
1124
1233
  var { Unrestricted: Unrestricted4 } = DataClassification;
1125
1234
  var { Persisted: Persisted4, Transient: Transient4 } = SchemaPersistence;
1126
1235
  var { External: External4, Internal: Internal3 } = SchemaExposure;
1236
+ var PLAYBOOK_SDK_FACETS3 = schemaFacets(SchemaContext.Playbook, { sdkInput: true });
1237
+ var PENDING_PLAYBOOK_FACETS2 = schemaFacets(SchemaContext.Playbook, {
1238
+ inConfig: false,
1239
+ sdkInput: false
1240
+ });
1241
+ var EMBEDDED_PLAYBOOK_SDK_FACETS = schemaFacets(SchemaContext.Playbook, {
1242
+ inConfig: false,
1243
+ sdkInput: true
1244
+ });
1245
+ var DISCOVERED_PLAYBOOK_FACETS = schemaFacets(SchemaContext.Playbook, {
1246
+ inConfig: false,
1247
+ sdkInput: false,
1248
+ source: SchemaSource.Runtime
1249
+ });
1250
+ var PlacementQualifierSchema = z7.enum(["none_always_on", "overage_vs_upgrade", "time_bound", "payment_failed", "payment_at_risk"]);
1127
1251
  var PlacementCategorySchema = z7.enum([
1128
1252
  "fixed",
1129
1253
  "gated_feature",
@@ -1152,7 +1276,8 @@ var SurfaceTypeCapRuleSchema = z7.object({
1152
1276
  id: "SurfaceTypeCapRule",
1153
1277
  "x-revturbine-schema-persistence": Persisted4,
1154
1278
  "x-revturbine-schema-exposure": External4,
1155
- "x-revturbine-data-classification": "operational"
1279
+ "x-revturbine-data-classification": "operational",
1280
+ ...EMBEDDED_PLAYBOOK_SDK_FACETS
1156
1281
  }
1157
1282
  );
1158
1283
  var CtaPathTypeSchema = z7.enum([
@@ -1187,12 +1312,12 @@ var PlacementSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(
1187
1312
  surface_slot_id: z7.string().optional().meta(Unrestricted4),
1188
1313
  entitlement_id: z7.string().optional().meta(Unrestricted4),
1189
1314
  tier_threshold: z7.string().optional().meta(Unrestricted4),
1190
- threshold_percent: z7.number().min(0).max(100).optional().meta(Unrestricted4),
1315
+ threshold_percent: ThresholdPercentField.optional().meta(Unrestricted4),
1191
1316
  trial_type: z7.enum(["free", "reverse"]).optional().meta(Unrestricted4),
1192
1317
  trigger_type: z7.string().optional().meta(Unrestricted4),
1193
1318
  trial_progress_percent: z7.number().min(0).max(100).optional().meta(Unrestricted4),
1194
1319
  days_before_end: z7.number().int().min(0).optional().meta(Unrestricted4),
1195
- qualifier: z7.enum(["none_always_on", "overage_vs_upgrade", "time_bound", "payment_failed", "payment_at_risk"]).optional().meta(Unrestricted4),
1320
+ qualifier: PlacementQualifierSchema.optional().meta(Unrestricted4),
1196
1321
  activation_window_start: z7.string().datetime().optional().meta(Unrestricted4),
1197
1322
  activation_window_end: z7.string().datetime().optional().meta(Unrestricted4),
1198
1323
  metadata: MetadataField.meta(Unrestricted4)
@@ -1201,10 +1326,31 @@ var PlacementSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(
1201
1326
  id: "Placement",
1202
1327
  "x-revturbine-schema-persistence": Persisted4,
1203
1328
  "x-revturbine-schema-exposure": External4,
1329
+ ...PLAYBOOK_SDK_FACETS3,
1204
1330
  ...namedIdentity()
1205
1331
  }
1206
1332
  );
1207
1333
  var PlacementAnchorSchema = makeAnchor("PlacementAnchor");
1334
+ var PlacementWarningCodeSchema = z7.enum(["threshold_not_emitted"]).meta(
1335
+ { id: "PlacementWarningCode", "x-revturbine-schema-persistence": Transient4, "x-revturbine-schema-exposure": External4 }
1336
+ );
1337
+ var PlacementWarningSchema = z7.object({
1338
+ code: PlacementWarningCodeSchema.meta(Unrestricted4),
1339
+ message: z7.string().min(1).meta(Unrestricted4),
1340
+ /** Optional field path the warning relates to (e.g. `['threshold_percent']`). */
1341
+ path: z7.array(z7.union([z7.string(), z7.number().int()])).optional().meta(Unrestricted4)
1342
+ }).meta(
1343
+ { id: "PlacementWarning", "x-revturbine-schema-persistence": Transient4, "x-revturbine-schema-exposure": External4 }
1344
+ );
1345
+ var PlacementWriteResponseSchema = PlacementSchema.extend({
1346
+ warnings: z7.array(PlacementWarningSchema).optional().meta(Unrestricted4)
1347
+ }).meta(
1348
+ {
1349
+ id: "PlacementWriteResponse",
1350
+ "x-revturbine-schema-persistence": Transient4,
1351
+ "x-revturbine-schema-exposure": External4
1352
+ }
1353
+ );
1208
1354
  var CtaObjectSchema = z7.object({
1209
1355
  label: z7.string().min(1).max(200).meta(Unrestricted4),
1210
1356
  cta_path_type: CtaPathTypeSchema.meta(Unrestricted4),
@@ -1239,6 +1385,7 @@ var PlacementPayloadSchema = IdField.merge(TimestampFields).merge(TenantIdField)
1239
1385
  id: "PlacementPayload",
1240
1386
  "x-revturbine-schema-persistence": Persisted4,
1241
1387
  "x-revturbine-schema-exposure": External4,
1388
+ ...PLAYBOOK_SDK_FACETS3,
1242
1389
  // KENT-REVIEW (plan 121): defaulted to Bucket 2 on (placement_id, drag_order)
1243
1390
  // — a payload's ordinal within its placement. This is the strongest Bucket-3
1244
1391
  // candidate (reordering a key edit would fork history); if lineage must survive
@@ -1262,6 +1409,7 @@ var PlacementVariantSchema = IdField.merge(TimestampFields).merge(TenantIdField)
1262
1409
  id: "PlacementVariant",
1263
1410
  "x-revturbine-schema-persistence": Persisted4,
1264
1411
  "x-revturbine-schema-exposure": Internal3,
1412
+ ...PENDING_PLAYBOOK_FACETS2,
1265
1413
  ...mintedIdentity()
1266
1414
  }
1267
1415
  );
@@ -1279,6 +1427,7 @@ var SurfaceSlotSchema = IdField.merge(TimestampFields).merge(TenantIdField).merg
1279
1427
  id: "SurfaceSlot",
1280
1428
  "x-revturbine-schema-persistence": Persisted4,
1281
1429
  "x-revturbine-schema-exposure": External4,
1430
+ ...DISCOVERED_PLAYBOOK_FACETS,
1282
1431
  ...namedIdentity("surface_slot_handle")
1283
1432
  }
1284
1433
  );
@@ -1312,7 +1461,7 @@ var placementPaths = {
1312
1461
  summary: "Create placement",
1313
1462
  tags: ["placements"],
1314
1463
  requestBody: { required: true, content: { "application/json": { schema: toCreateSchema(PlacementSchema) } } },
1315
- responses: { "201": { description: "Created", content: { "application/json": { schema: PlacementSchema } } } },
1464
+ responses: { "201": { description: "Created", content: { "application/json": { schema: PlacementWriteResponseSchema } } } },
1316
1465
  "x-revturbine-operation": { exposure: "external", resource: "placements", persistence: { table: "placementVersions", mode: "create" } }
1317
1466
  })
1318
1467
  },
@@ -1331,7 +1480,7 @@ var placementPaths = {
1331
1480
  summary: "Update placement",
1332
1481
  tags: ["placements"],
1333
1482
  requestBody: { required: true, content: { "application/json": { schema: PlacementWriteSchema } } },
1334
- responses: { "200": { description: "Updated", content: { "application/json": { schema: PlacementSchema } } } },
1483
+ responses: { "200": { description: "Updated", content: { "application/json": { schema: PlacementWriteResponseSchema } } } },
1335
1484
  "x-revturbine-operation": { exposure: "external", resource: "placements", persistence: { table: "placementVersions", mode: "update" } }
1336
1485
  }),
1337
1486
  delete: operation({
@@ -1510,6 +1659,27 @@ var UserContextSchema = IdField.merge(TenantIdField).merge(TimestampFields).exte
1510
1659
  /** Aggregate usage entries across all instances, keyed by handle. */
1511
1660
  usage: z8.record(z8.string(), UserUsageEntrySchema).default({}).meta(Unrestricted5),
1512
1661
  trial: UserTrialStatusSchema.optional().meta(Unrestricted5),
1662
+ /**
1663
+ * Billing-recovery signals (account-level). `payment_failed` reflects a
1664
+ * hard payment failure (e.g. Stripe `invoice.payment_failed`);
1665
+ * `payment_at_risk` an expiring / missing-backup payment method. They
1666
+ * drive the Retention `qualifier` placement triggers
1667
+ * (placement-studio-ui.md §3.7) — the SDK surfaces them onto the
1668
+ * PlanProvider state the placement resolver reads. Omitted for users in
1669
+ * good standing; a qualifier gate reads `=== true`, so an absent signal
1670
+ * never fires the recovery placement.
1671
+ */
1672
+ payment_failed: z8.boolean().optional().meta(Unrestricted5),
1673
+ payment_at_risk: z8.boolean().optional().meta(Unrestricted5),
1674
+ /**
1675
+ * The user's current tier per `capability_tier` entitlement, keyed by
1676
+ * entitlement handle → tier handle (plan 138 TASK-4). Drives the
1677
+ * `entitlement_gate.tier_threshold` placement trigger: the SDK surfaces
1678
+ * this onto the EntitlementProvider state, and the resolver ranks the
1679
+ * current tier against the entitlement's ordered ladder. Omitted for a
1680
+ * user holding no tier (ranks below every threshold).
1681
+ */
1682
+ tiers: z8.record(z8.string(), z8.string()).optional().meta(Unrestricted5),
1513
1683
  /** Account-level entitlements, keyed by handle. */
1514
1684
  entitlements: z8.record(z8.string(), z8.union([z8.boolean(), EntitlementGrantSchema])).default({}).meta(Unrestricted5),
1515
1685
  /** Per-instance breakdowns when the account has multiple product instances. */
@@ -1594,6 +1764,9 @@ var userContextPaths = {
1594
1764
  var { Unrestricted: Unrestricted6, Pii: Pii2 } = DataClassification;
1595
1765
  var { Persisted: Persisted6, Transient: Transient6 } = SchemaPersistence;
1596
1766
  var { Internal: Internal4 } = SchemaExposure;
1767
+ var CUSTOMER_OPERATIONS_FACETS = schemaFacets(SchemaContext.CustomerOperations, {
1768
+ sdkInput: false
1769
+ });
1597
1770
  var IdentitySchema = z9.object({
1598
1771
  external_id: z9.string().min(1).meta(Pii2),
1599
1772
  traits: z9.record(z9.string(), z9.unknown()).default({}).meta(Pii2),
@@ -1654,6 +1827,7 @@ var CustomerOverrideSchema = IdField.merge(TimestampFields).merge(TenantIdField)
1654
1827
  id: "CustomerOverride",
1655
1828
  "x-revturbine-schema-persistence": Persisted6,
1656
1829
  "x-revturbine-schema-exposure": Internal4,
1830
+ ...CUSTOMER_OPERATIONS_FACETS,
1657
1831
  // KENT-REVIEW (plan 121): defaulted to Bucket 2 on (customer_id, override_type,
1658
1832
  // target_id) — one override per customer per target per type. Note the optional
1659
1833
  // `customer_id_list` (multi-customer overrides) complicates the tuple; confirm
@@ -1752,6 +1926,11 @@ var customerPaths = {
1752
1926
  var { Unrestricted: Unrestricted7 } = DataClassification;
1753
1927
  var { Persisted: Persisted7, Transient: Transient7 } = SchemaPersistence;
1754
1928
  var { Internal: Internal5 } = SchemaExposure;
1929
+ var PLAYBOOK_SDK_FACETS4 = schemaFacets(SchemaContext.Playbook, { sdkInput: true });
1930
+ var PENDING_PLAYBOOK_SDK_FACETS = schemaFacets(SchemaContext.Playbook, {
1931
+ inConfig: false,
1932
+ sdkInput: true
1933
+ });
1755
1934
  var DimensionCategorySchema = z10.enum(["default", "custom"]).meta(
1756
1935
  { id: "DimensionCategory", "x-revturbine-schema-persistence": Transient7, "x-revturbine-schema-exposure": Internal5 }
1757
1936
  );
@@ -1775,6 +1954,7 @@ var SegmentDimensionSchema = IdField.merge(TimestampFields).merge(TenantIdField)
1775
1954
  id: "SegmentDimension",
1776
1955
  "x-revturbine-schema-persistence": Persisted7,
1777
1956
  "x-revturbine-schema-exposure": Internal5,
1957
+ ...PENDING_PLAYBOOK_SDK_FACETS,
1778
1958
  ...namedIdentity()
1779
1959
  }
1780
1960
  );
@@ -1792,6 +1972,7 @@ var SegmentValueSchema = IdField.merge(TimestampFields).merge(TenantIdField).mer
1792
1972
  id: "SegmentValue",
1793
1973
  "x-revturbine-schema-persistence": Persisted7,
1794
1974
  "x-revturbine-schema-exposure": Internal5,
1975
+ ...PENDING_PLAYBOOK_SDK_FACETS,
1795
1976
  ...namedIdentity()
1796
1977
  }
1797
1978
  );
@@ -1810,6 +1991,7 @@ var SegmentSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(An
1810
1991
  id: "Segment",
1811
1992
  "x-revturbine-schema-persistence": Persisted7,
1812
1993
  "x-revturbine-schema-exposure": Internal5,
1994
+ ...PLAYBOOK_SDK_FACETS4,
1813
1995
  ...namedIdentity()
1814
1996
  }
1815
1997
  );
@@ -1923,6 +2105,20 @@ var segmentPaths = {
1923
2105
  var { Unrestricted: Unrestricted8 } = DataClassification;
1924
2106
  var { Persisted: Persisted8, Transient: Transient8 } = SchemaPersistence;
1925
2107
  var { External: External6 } = SchemaExposure;
2108
+ var PLAYBOOK_SDK_FACETS5 = schemaFacets(SchemaContext.Playbook, { sdkInput: true });
2109
+ var PENDING_PLAYBOOK_FACETS3 = schemaFacets(SchemaContext.Playbook, {
2110
+ inConfig: false,
2111
+ sdkInput: false
2112
+ });
2113
+ var EMBEDDED_PLAYBOOK_SDK_FACETS2 = schemaFacets(SchemaContext.Playbook, {
2114
+ inConfig: false,
2115
+ sdkInput: true
2116
+ });
2117
+ var PLAYBOOK_VOCABULARY_FACETS = schemaFacets(SchemaContext.Playbook, {
2118
+ inConfig: false,
2119
+ sdkInput: true,
2120
+ source: SchemaSource.CodeConstant
2121
+ });
1926
2122
  var MessageSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
1927
2123
  anchor_id: z11.string().min(1).meta({ ...Unrestricted8, readOnly: true }),
1928
2124
  handle: HandleField.meta({ ...Unrestricted8, readOnly: true }),
@@ -1933,7 +2129,7 @@ var MessageSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(An
1933
2129
  template_variables: z11.array(z11.string()).default([]).meta(Unrestricted8),
1934
2130
  metadata: MetadataField.meta(Unrestricted8)
1935
2131
  }).meta(
1936
- { id: "Message", "x-revturbine-schema-persistence": Persisted8, "x-revturbine-schema-exposure": External6, ...mintedIdentity() }
2132
+ { id: "Message", "x-revturbine-schema-persistence": Persisted8, "x-revturbine-schema-exposure": External6, ...PENDING_PLAYBOOK_FACETS3, ...mintedIdentity() }
1937
2133
  );
1938
2134
  var CtaPathSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
1939
2135
  anchor_id: z11.string().min(1).meta({ ...Unrestricted8, readOnly: true }),
@@ -1945,7 +2141,7 @@ var CtaPathSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(An
1945
2141
  config_fields: z11.record(z11.string(), z11.unknown()).default({}).meta(Unrestricted8),
1946
2142
  metadata: MetadataField.meta(Unrestricted8)
1947
2143
  }).meta(
1948
- { id: "CtaPath", "x-revturbine-schema-persistence": Persisted8, "x-revturbine-schema-exposure": External6, ...namedIdentity() }
2144
+ { id: "CtaPath", "x-revturbine-schema-persistence": Persisted8, "x-revturbine-schema-exposure": External6, ...PLAYBOOK_SDK_FACETS5, ...namedIdentity() }
1949
2145
  );
1950
2146
  var CtaPathAnchorSchema = makeAnchor("CtaPathAnchor");
1951
2147
  var TemplateFieldTypeSchema = z11.enum([
@@ -1964,7 +2160,7 @@ var TemplateFieldTypeSchema = z11.enum([
1964
2160
  "dropdown",
1965
2161
  "json"
1966
2162
  ]).meta(
1967
- { id: "TemplateFieldType", "x-revturbine-schema-persistence": Persisted8, "x-revturbine-schema-exposure": External6 }
2163
+ { id: "TemplateFieldType", "x-revturbine-schema-persistence": Persisted8, "x-revturbine-schema-exposure": External6, ...PLAYBOOK_VOCABULARY_FACETS }
1968
2164
  );
1969
2165
  var FieldDefinitionSchema = z11.object({
1970
2166
  name: z11.string().min(1),
@@ -1979,7 +2175,7 @@ var FieldDefinitionSchema = z11.object({
1979
2175
  order: z11.number().int().min(0).optional(),
1980
2176
  help_text: z11.string().max(500).optional()
1981
2177
  }).meta(
1982
- { id: "FieldDefinition", "x-revturbine-schema-persistence": Persisted8, "x-revturbine-schema-exposure": External6 }
2178
+ { id: "FieldDefinition", "x-revturbine-schema-persistence": Persisted8, "x-revturbine-schema-exposure": External6, ...EMBEDDED_PLAYBOOK_SDK_FACETS2 }
1983
2179
  );
1984
2180
  var SurfaceTemplateSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
1985
2181
  anchor_id: z11.string().min(1).meta({ ...Unrestricted8, readOnly: true }),
@@ -1989,7 +2185,7 @@ var SurfaceTemplateSchema = IdField.merge(TimestampFields).merge(TenantIdField).
1989
2185
  field_definitions: z11.array(FieldDefinitionSchema).default([]).meta(Unrestricted8),
1990
2186
  description: DescriptionField.meta(Unrestricted8)
1991
2187
  }).meta(
1992
- { id: "SurfaceTemplate", "x-revturbine-schema-persistence": Persisted8, "x-revturbine-schema-exposure": External6, ...namedIdentity() }
2188
+ { id: "SurfaceTemplate", "x-revturbine-schema-persistence": Persisted8, "x-revturbine-schema-exposure": External6, ...PLAYBOOK_SDK_FACETS5, ...namedIdentity() }
1993
2189
  );
1994
2190
  var SurfaceTemplateAnchorSchema = makeAnchor("SurfaceTemplateAnchor");
1995
2191
  var MessageBlockRecordSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
@@ -2005,7 +2201,7 @@ var MessageBlockRecordSchema = IdField.merge(TimestampFields).merge(TenantIdFiel
2005
2201
  notes: z11.string().max(1e3).optional().meta(Unrestricted8),
2006
2202
  used_in_count: z11.number().int().min(0).default(0).meta({ ...Unrestricted8, readOnly: true })
2007
2203
  }).meta(
2008
- { id: "MessageBlockRecord", "x-revturbine-schema-persistence": Persisted8, "x-revturbine-schema-exposure": External6, ...namedIdentity() }
2204
+ { id: "MessageBlockRecord", "x-revturbine-schema-persistence": Persisted8, "x-revturbine-schema-exposure": External6, ...PLAYBOOK_SDK_FACETS5, ...namedIdentity() }
2009
2205
  );
2010
2206
  var MessageBlockRecordAnchorSchema = makeAnchor("MessageBlockRecordAnchor");
2011
2207
  var ContentPayloadSegmentEntrySchema = z11.object({
@@ -2129,6 +2325,7 @@ var contentPaths = {
2129
2325
  var { Unrestricted: Unrestricted9, Pii: Pii3 } = DataClassification;
2130
2326
  var { Persisted: Persisted9, Transient: Transient9 } = SchemaPersistence;
2131
2327
  var { Internal: Internal6, External: External7 } = SchemaExposure;
2328
+ var BRANDING_FACETS = schemaFacets(SchemaContext.Branding, { sdkInput: false });
2132
2329
  var ThemeSchema = z12.object({
2133
2330
  id: z12.string().min(1).meta({ ...Unrestricted9, readOnly: true }),
2134
2331
  name: z12.string().min(1).max(120).meta(Unrestricted9),
@@ -2138,7 +2335,8 @@ var ThemeSchema = z12.object({
2138
2335
  {
2139
2336
  id: "Theme",
2140
2337
  "x-revturbine-schema-persistence": Persisted9,
2141
- "x-revturbine-schema-exposure": External7
2338
+ "x-revturbine-schema-exposure": External7,
2339
+ ...BRANDING_FACETS
2142
2340
  }
2143
2341
  );
2144
2342
  var UiPreferenceSchema = IdField.merge(TimestampFields).extend({
@@ -2642,6 +2840,13 @@ var EventIngestResponseSchema = z14.object({
2642
2840
  }
2643
2841
  );
2644
2842
  var MAX_TRACK_EVENTS_PER_BATCH = 500;
2843
+ var EventOriginSchema = z14.enum(["explicit", "automatic", "derived", "raw"]).meta(
2844
+ {
2845
+ id: "EventOrigin",
2846
+ "x-revturbine-schema-persistence": Transient11,
2847
+ "x-revturbine-schema-exposure": External9
2848
+ }
2849
+ );
2645
2850
  var TrackEventSchema = z14.object({
2646
2851
  environment_id: z14.string().min(1).meta(Unrestricted11),
2647
2852
  user_id: z14.string().min(1).meta(Pii4),
@@ -2655,7 +2860,28 @@ var TrackEventSchema = z14.object({
2655
2860
  request_id: z14.string().optional().meta(Unrestricted11),
2656
2861
  experiment_id: z14.string().nullable().optional().meta(Unrestricted11),
2657
2862
  variant_key: z14.string().nullable().optional().meta(Unrestricted11),
2658
- tenant_id: z14.string().optional().meta(Unrestricted11)
2863
+ tenant_id: z14.string().optional().meta(Unrestricted11),
2864
+ // ── Lifted provenance ────────────────────────────────────────────────
2865
+ //
2866
+ // `properties` is a serialized JSON string, so anything left inside it is
2867
+ // readable via JSONExtract but unindexed and scan-costly — fine for a
2868
+ // forensic lookup, not for a dashboard that groups 90 days of events. The
2869
+ // four fields below are the ones analytics actually slices by, so they
2870
+ // get named columns.
2871
+ //
2872
+ // The asymmetry that decides this: an unused column costs bytes, while a
2873
+ // missing one costs a forward-only recreate of a populated datasource
2874
+ // (the Tinybird TS SDK cannot emit FORWARD_QUERY). Lift deliberately, and
2875
+ // route genuinely new high-volume event classes to their own datasource
2876
+ // rather than widening this one.
2877
+ /** Sortable unique id minted at capture. Carried now so historical rows have it if dedup ever moves off `request_id` — a destructive sorting-key change not worth paying for while storage-layer dedup already collapses re-delivery. */
2878
+ event_id: z14.string().nullable().optional().meta(Unrestricted11),
2879
+ /** See {@link EventOriginSchema}. Low cardinality; every scoring query filters on it. */
2880
+ origin: EventOriginSchema.nullable().optional().meta(Unrestricted11),
2881
+ /** Immutable Playbook version that produced the experience. The field that makes a past decision reproducible. */
2882
+ playbook_version: z14.string().nullable().optional().meta(Unrestricted11),
2883
+ /** Correlates every event caused by one decision — a join key, not a group-by. */
2884
+ decision_id: z14.string().nullable().optional().meta(Unrestricted11)
2659
2885
  }).meta(
2660
2886
  {
2661
2887
  id: "TrackEvent",
@@ -2804,6 +3030,11 @@ var eventPaths = {
2804
3030
  var { Unrestricted: Unrestricted12 } = DataClassification;
2805
3031
  var { Persisted: Persisted12, Transient: Transient12 } = SchemaPersistence;
2806
3032
  var { Internal: Internal9 } = SchemaExposure;
3033
+ var PLAYBOOK_SDK_FACETS6 = schemaFacets(SchemaContext.Playbook, { sdkInput: true });
3034
+ var PENDING_PLAYBOOK_SDK_FACETS2 = schemaFacets(SchemaContext.Playbook, {
3035
+ inConfig: false,
3036
+ sdkInput: true
3037
+ });
2807
3038
  var TrialStatusSchema = z15.enum(["not_started", "active", "expired", "converted", "cancelled"]).meta(
2808
3039
  { id: "TrialStatus", "x-revturbine-schema-persistence": Transient12, "x-revturbine-schema-exposure": Internal9 }
2809
3040
  );
@@ -2856,7 +3087,7 @@ var FreeTrialRuleCoreFieldsSchema = z15.object({
2856
3087
  var FreeTrialRuleSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
2857
3088
  anchor_id: z15.string().min(1).meta({ ...Unrestricted12, readOnly: true })
2858
3089
  }).merge(FreeTrialRuleCoreFieldsSchema).meta(
2859
- { id: "FreeTrialRule", "x-revturbine-schema-persistence": Persisted12, "x-revturbine-schema-exposure": Internal9, ...namedIdentity() }
3090
+ { id: "FreeTrialRule", "x-revturbine-schema-persistence": Persisted12, "x-revturbine-schema-exposure": Internal9, ...PLAYBOOK_SDK_FACETS6, ...namedIdentity() }
2860
3091
  );
2861
3092
  var FreeTrialRuleAnchorSchema = makeAnchor("FreeTrialRuleAnchor");
2862
3093
  var ReverseTrialStartPolicySchema = z15.enum(["signup", "first_premium_access", "manual"]).meta(
@@ -2881,7 +3112,7 @@ var ReverseTrialRuleCoreFieldsSchema = z15.object({
2881
3112
  var ReverseTrialRuleSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
2882
3113
  anchor_id: z15.string().min(1).meta({ ...Unrestricted12, readOnly: true })
2883
3114
  }).merge(ReverseTrialRuleCoreFieldsSchema).meta(
2884
- { id: "ReverseTrialRule", "x-revturbine-schema-persistence": Persisted12, "x-revturbine-schema-exposure": Internal9, ...namedIdentity() }
3115
+ { id: "ReverseTrialRule", "x-revturbine-schema-persistence": Persisted12, "x-revturbine-schema-exposure": Internal9, ...PLAYBOOK_SDK_FACETS6, ...namedIdentity() }
2885
3116
  );
2886
3117
  var ReverseTrialRuleAnchorSchema = makeAnchor("ReverseTrialRuleAnchor");
2887
3118
  var TrialLimitPolicySchema = z15.enum(["1_per_lifetime", "1_per_plan", "1_per_year", "unlimited"]).meta(
@@ -2894,13 +3125,13 @@ var FreeTrialSettingsSchema = IdField.merge(TimestampFields).merge(TenantIdField
2894
3125
  trial_limit_policy: TrialLimitPolicySchema.default("1_per_lifetime").meta(Unrestricted12),
2895
3126
  eligibility_scope: TrialEligibilityScopeSchema.default("per_customer").meta(Unrestricted12)
2896
3127
  }).meta(
2897
- { id: "FreeTrialSettings", "x-revturbine-schema-persistence": Persisted12, "x-revturbine-schema-exposure": Internal9 }
3128
+ { id: "FreeTrialSettings", "x-revturbine-schema-persistence": Persisted12, "x-revturbine-schema-exposure": Internal9, ...PENDING_PLAYBOOK_SDK_FACETS2 }
2898
3129
  );
2899
3130
  var ReverseTrialSettingsSchema = IdField.merge(TimestampFields).merge(TenantIdField).extend({
2900
3131
  trial_limit_policy: TrialLimitPolicySchema.default("1_per_lifetime").meta(Unrestricted12),
2901
3132
  eligibility_scope: TrialEligibilityScopeSchema.default("per_customer").meta(Unrestricted12)
2902
3133
  }).meta(
2903
- { id: "ReverseTrialSettings", "x-revturbine-schema-persistence": Persisted12, "x-revturbine-schema-exposure": Internal9 }
3134
+ { id: "ReverseTrialSettings", "x-revturbine-schema-persistence": Persisted12, "x-revturbine-schema-exposure": Internal9, ...PENDING_PLAYBOOK_SDK_FACETS2 }
2904
3135
  );
2905
3136
  var TrialInstanceSchema = IdField.merge(TimestampFields).merge(TenantIdField).extend({
2906
3137
  customer_id: z15.string().min(1).meta(Unrestricted12),
@@ -3178,6 +3409,10 @@ var trialPaths = {
3178
3409
  var { Unrestricted: Unrestricted13 } = DataClassification;
3179
3410
  var { Persisted: Persisted13, Transient: Transient13 } = SchemaPersistence;
3180
3411
  var { Internal: Internal10 } = SchemaExposure;
3412
+ var PENDING_PLAYBOOK_SDK_FACETS3 = schemaFacets(SchemaContext.Playbook, {
3413
+ inConfig: false,
3414
+ sdkInput: true
3415
+ });
3181
3416
  var ExperimentStatusSchema = z16.enum(["draft", "ramping", "winning", "neutral", "needs_attention", "paused", "complete"]).meta(
3182
3417
  { id: "ExperimentStatus", "x-revturbine-schema-persistence": Transient13, "x-revturbine-schema-exposure": Internal10 }
3183
3418
  );
@@ -3191,7 +3426,7 @@ var ExperimentVariantSchema = z16.object({
3191
3426
  is_control: z16.boolean().default(false),
3192
3427
  config: z16.record(z16.string(), z16.unknown()).default({})
3193
3428
  }).meta(
3194
- { id: "ExperimentVariant", "x-revturbine-schema-persistence": Persisted13, "x-revturbine-schema-exposure": Internal10 }
3429
+ { id: "ExperimentVariant", "x-revturbine-schema-persistence": Persisted13, "x-revturbine-schema-exposure": Internal10, ...PENDING_PLAYBOOK_SDK_FACETS3 }
3195
3430
  );
3196
3431
  var ExperimentSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
3197
3432
  anchor_id: z16.string().min(1).meta({ ...Unrestricted13, readOnly: true }),
@@ -3215,7 +3450,7 @@ var ExperimentSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge
3215
3450
  winning_variant_id: z16.string().nullable().default(null).meta(Unrestricted13),
3216
3451
  metadata: MetadataField.meta(Unrestricted13)
3217
3452
  }).meta(
3218
- { id: "Experiment", "x-revturbine-schema-persistence": Persisted13, "x-revturbine-schema-exposure": Internal10, ...namedIdentity() }
3453
+ { id: "Experiment", "x-revturbine-schema-persistence": Persisted13, "x-revturbine-schema-exposure": Internal10, ...PENDING_PLAYBOOK_SDK_FACETS3, ...namedIdentity() }
3219
3454
  );
3220
3455
  var ExperimentAnchorSchema = makeAnchor("ExperimentAnchor");
3221
3456
  var SuggestionSeveritySchema = SeveritySchema;
@@ -3347,6 +3582,7 @@ var experimentPaths = {
3347
3582
  var { Unrestricted: Unrestricted14, Financial: Financial3 } = DataClassification;
3348
3583
  var { Persisted: Persisted14, Transient: Transient14 } = SchemaPersistence;
3349
3584
  var { Internal: Internal11 } = SchemaExposure;
3585
+ var PLAYBOOK_SDK_FACETS7 = schemaFacets(SchemaContext.Playbook, { sdkInput: true });
3350
3586
  var PromotionStatusSchema = z17.enum(["draft", "scheduled", "live", "expired", "archived"]).meta(
3351
3587
  { id: "PromotionStatus", "x-revturbine-schema-persistence": Transient14, "x-revturbine-schema-exposure": Internal11 }
3352
3588
  );
@@ -3376,7 +3612,7 @@ var PromotionSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(
3376
3612
  auto_sync_stripe: z17.boolean().default(false).meta(Unrestricted14),
3377
3613
  metadata: MetadataField.meta(Unrestricted14)
3378
3614
  }).meta(
3379
- { id: "Promotion", "x-revturbine-schema-persistence": Persisted14, "x-revturbine-schema-exposure": Internal11, ...namedIdentity() }
3615
+ { id: "Promotion", "x-revturbine-schema-persistence": Persisted14, "x-revturbine-schema-exposure": Internal11, ...PLAYBOOK_SDK_FACETS7, ...namedIdentity() }
3380
3616
  );
3381
3617
  var PromotionAnchorSchema = makeAnchor("PromotionAnchor");
3382
3618
  var promotionPaths = {
@@ -3542,17 +3778,39 @@ var runtimePaths = {
3542
3778
  var { Unrestricted: Unrestricted16 } = DataClassification;
3543
3779
  var { Persisted: Persisted16, Transient: Transient16 } = SchemaPersistence;
3544
3780
  var { Internal: Internal13, External: External11 } = SchemaExposure;
3545
- var ApplicationSurfaceSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
3546
- name: NameField.meta(Unrestricted16),
3547
- handle: HandleField.meta(Unrestricted16),
3548
- surface_type: StudioSurfaceTypeSchema.meta(Unrestricted16),
3549
- description: DescriptionField.meta(Unrestricted16),
3550
- is_active: z19.boolean().default(true).meta(Unrestricted16),
3551
- ui_path: z19.string().optional().meta(Unrestricted16),
3552
- metadata: MetadataField.meta(Unrestricted16)
3553
- }).meta(
3554
- { id: "ApplicationSurface", "x-revturbine-schema-persistence": Persisted16, "x-revturbine-schema-exposure": Internal13, ...namedIdentity() }
3555
- );
3781
+ var PLAYBOOK_SDK_FACETS8 = schemaFacets(SchemaContext.Playbook, { sdkInput: true });
3782
+ var PLAYBOOK_AUTHORING_FACETS2 = schemaFacets(SchemaContext.Playbook, { sdkInput: false });
3783
+ var PENDING_PLAYBOOK_FACETS4 = schemaFacets(SchemaContext.Playbook, {
3784
+ inConfig: false,
3785
+ sdkInput: false
3786
+ });
3787
+ var PENDING_PLAYBOOK_SDK_FACETS4 = schemaFacets(SchemaContext.Playbook, {
3788
+ inConfig: false,
3789
+ sdkInput: true
3790
+ });
3791
+ var PLAYBOOK_VERSION_HEADER_FACETS = schemaFacets(SchemaContext.Playbook, {
3792
+ inConfig: false,
3793
+ sdkInput: true,
3794
+ source: SchemaSource.CodeConstant
3795
+ });
3796
+ var PLAYBOOK_PROVENANCE_HEADER_FACETS = schemaFacets(SchemaContext.Playbook, {
3797
+ inConfig: false,
3798
+ sdkInput: true,
3799
+ source: SchemaSource.Runtime
3800
+ });
3801
+ var PLAYBOOK_TARGET_FACETS = schemaFacets(SchemaContext.Playbook, {
3802
+ inConfig: false,
3803
+ sdkInput: false,
3804
+ source: SchemaSource.Runtime
3805
+ });
3806
+ var BILLING_FACETS2 = schemaFacets(SchemaContext.Billing, { sdkInput: false });
3807
+ var METERING_FACETS = schemaFacets(SchemaContext.Metering, { sdkInput: false });
3808
+ var LEGACY_BRANDING_FACETS = schemaFacets(SchemaContext.Branding, {
3809
+ inConfig: true,
3810
+ sdkInput: true
3811
+ });
3812
+ var BRANDING_FACETS2 = schemaFacets(SchemaContext.Branding, { sdkInput: false });
3813
+ var PLAYBOOK_FORMAT_VERSION = "1.0.0";
3556
3814
  var SeatTypeSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
3557
3815
  anchor_id: z19.string().min(1).meta({ ...Unrestricted16, readOnly: true }),
3558
3816
  name: NameField.meta(Unrestricted16),
@@ -3562,9 +3820,24 @@ var SeatTypeSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(A
3562
3820
  entitlement_ids: z19.array(z19.string()).default([]).meta(Unrestricted16),
3563
3821
  metadata: MetadataField.meta(Unrestricted16)
3564
3822
  }).meta(
3565
- { id: "SeatType", "x-revturbine-schema-persistence": Persisted16, "x-revturbine-schema-exposure": Internal13, ...namedIdentity() }
3823
+ { id: "SeatType", "x-revturbine-schema-persistence": Persisted16, "x-revturbine-schema-exposure": Internal13, ...PENDING_PLAYBOOK_FACETS4, ...namedIdentity() }
3566
3824
  );
3567
3825
  var SeatTypeAnchorSchema = makeAnchor("SeatTypeAnchor");
3826
+ var PersonalizationTokenSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
3827
+ anchor_id: z19.string().min(1).meta({ ...Unrestricted16, readOnly: true }),
3828
+ handle: HandleField.meta(Unrestricted16),
3829
+ label: z19.string().min(1).meta(Unrestricted16),
3830
+ description: z19.string().nullable().default(null).meta(Unrestricted16),
3831
+ category: z19.enum(["user", "plan", "usage", "trial", "billing", "promotion", "custom"]).meta(Unrestricted16),
3832
+ data_source: z19.string().nullable().default(null).meta(Unrestricted16),
3833
+ example_value: z19.string().nullable().default(null).meta(Unrestricted16),
3834
+ value_map: z19.record(z19.string(), z19.string()).default({}).meta(Unrestricted16),
3835
+ format: z19.enum(["string", "number", "currency", "percentage", "date"]).nullable().default(null).meta(Unrestricted16),
3836
+ metadata: MetadataField.meta(Unrestricted16)
3837
+ }).meta(
3838
+ { id: "PersonalizationToken", "x-revturbine-schema-persistence": Persisted16, "x-revturbine-schema-exposure": Internal13, ...PENDING_PLAYBOOK_FACETS4, ...namedIdentity() }
3839
+ );
3840
+ var PersonalizationTokenAnchorSchema = makeAnchor("PersonalizationTokenAnchor");
3568
3841
  var OnboardingStateSchema = z19.enum(["not_started", "started", "details_submitted", "charges_enabled", "activated", "deauthorized"]).meta({ id: "OnboardingState", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": Internal13 });
3569
3842
  var StripeIntegrationConfigSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
3570
3843
  handle: HandleField.meta({ ...Unrestricted16, readOnly: true }),
@@ -3591,7 +3864,20 @@ var StripeIntegrationConfigSchema = IdField.merge(TimestampFields).merge(TenantI
3591
3864
  last_sync_at: z19.string().optional().meta({ ...Unrestricted16, readOnly: true }),
3592
3865
  metadata: MetadataField.meta(Unrestricted16)
3593
3866
  }).meta(
3594
- { id: "StripeIntegrationConfig", "x-revturbine-schema-persistence": Persisted16, "x-revturbine-schema-exposure": Internal13, ...mintedIdentity() }
3867
+ { id: "StripeIntegrationConfig", "x-revturbine-schema-persistence": Persisted16, "x-revturbine-schema-exposure": Internal13, ...BILLING_FACETS2, ...mintedIdentity() }
3868
+ );
3869
+ var BrandingConfigSchema = z19.object({
3870
+ theme: z19.record(z19.string(), z19.unknown()).optional().meta(Unrestricted16),
3871
+ workspace_name: z19.string().optional().meta(Unrestricted16),
3872
+ logo_url: z19.string().optional().meta(Unrestricted16),
3873
+ support_email: z19.string().optional().meta(Unrestricted16)
3874
+ }).meta(
3875
+ {
3876
+ id: "BrandingConfig",
3877
+ "x-revturbine-schema-persistence": Transient16,
3878
+ "x-revturbine-schema-exposure": External11,
3879
+ ...BRANDING_FACETS2
3880
+ }
3595
3881
  );
3596
3882
  var MeteringConfigSchema = IdField.merge(TimestampFields).merge(TenantIdField).merge(AnchorFields).merge(VersionFields).extend({
3597
3883
  handle: HandleField.meta({ ...Unrestricted16, readOnly: true }),
@@ -3603,7 +3889,7 @@ var MeteringConfigSchema = IdField.merge(TimestampFields).merge(TenantIdField).m
3603
3889
  is_active: z19.boolean().default(true).meta(Unrestricted16),
3604
3890
  metadata: MetadataField.meta(Unrestricted16)
3605
3891
  }).meta(
3606
- { id: "MeteringConfig", "x-revturbine-schema-persistence": Persisted16, "x-revturbine-schema-exposure": Internal13, ...mintedIdentity() }
3892
+ { id: "MeteringConfig", "x-revturbine-schema-persistence": Persisted16, "x-revturbine-schema-exposure": Internal13, ...METERING_FACETS, ...mintedIdentity() }
3607
3893
  );
3608
3894
  var EnforcementActionSchema = z19.enum(["block", "warn", "downgrade", "throttle", "notify_admin", "custom"]).meta(
3609
3895
  { id: "EnforcementAction", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": Internal13 }
@@ -3620,7 +3906,7 @@ var UsageEnforcementSettingsSchema = IdField.merge(TimestampFields).merge(Tenant
3620
3906
  notification_channels: z19.array(z19.enum(["email", "in_app", "webhook"])).default(["in_app"]).meta(Unrestricted16),
3621
3907
  is_active: z19.boolean().default(true).meta(Unrestricted16)
3622
3908
  }).meta(
3623
- { id: "UsageEnforcementSettings", "x-revturbine-schema-persistence": Persisted16, "x-revturbine-schema-exposure": Internal13, ...mintedIdentity() }
3909
+ { id: "UsageEnforcementSettings", "x-revturbine-schema-persistence": Persisted16, "x-revturbine-schema-exposure": Internal13, ...PENDING_PLAYBOOK_SDK_FACETS4, ...mintedIdentity() }
3624
3910
  );
3625
3911
  var UsageEnforcementSettingsAnchorSchema = makeAnchor("UsageEnforcementSettingsAnchor");
3626
3912
  var PlacementSettingsCapRuleGroupItemSchema = z19.object({
@@ -3664,7 +3950,7 @@ var PlacementSettingsSchema = IdField.merge(TimestampFields).merge(TenantIdField
3664
3950
  allow_stacking: z19.boolean().default(false).meta(Unrestricted16),
3665
3951
  priority_collision_strategy: z19.enum(["highest_priority", "most_recent", "random"]).default("highest_priority").meta(Unrestricted16)
3666
3952
  }).meta(
3667
- { id: "PlacementSettings", "x-revturbine-schema-persistence": Persisted16, "x-revturbine-schema-exposure": Internal13, ...mintedIdentity() }
3953
+ { id: "PlacementSettings", "x-revturbine-schema-persistence": Persisted16, "x-revturbine-schema-exposure": Internal13, ...PENDING_PLAYBOOK_SDK_FACETS4, ...mintedIdentity() }
3668
3954
  );
3669
3955
  var PlacementSettingsAnchorSchema = makeAnchor("PlacementSettingsAnchor");
3670
3956
  var RevTurbineConfigSegmentsItemPredicatesItemSchema = z19.object({
@@ -3689,7 +3975,7 @@ var RevTurbineConfigSegmentsItemSchema = z19.object({
3689
3975
  // back to flat-OR (legacy single-segment behaviour).
3690
3976
  dimension_id: z19.string().optional().meta(Unrestricted16)
3691
3977
  }).meta(
3692
- { id: "RevTurbineConfigSegmentsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
3978
+ { id: "RevTurbineConfigSegmentsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3693
3979
  );
3694
3980
  var RevTurbineConfigPlansItemSchema = z19.object({
3695
3981
  // Plan 120 TASK-4: `unique_handle` is the sole logical identifier; the
@@ -3704,7 +3990,7 @@ var RevTurbineConfigPlansItemSchema = z19.object({
3704
3990
  // own visibility for per-price overrides; this is the plan's default.
3705
3991
  visibility: PlanVisibilitySchema.default("public").meta(Unrestricted16)
3706
3992
  }).meta(
3707
- { id: "RevTurbineConfigPlansItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
3993
+ { id: "RevTurbineConfigPlansItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3708
3994
  );
3709
3995
  var RevTurbineConfigAddonsItemSchema = z19.object({
3710
3996
  // Plan 120 TASK-4: `unique_handle` is the sole logical identifier; the
@@ -3716,7 +4002,85 @@ var RevTurbineConfigAddonsItemSchema = z19.object({
3716
4002
  // not price, so it lives in the config independent of addon_variations.
3717
4003
  visibility: PlanVisibilitySchema.default("public").meta(Unrestricted16)
3718
4004
  }).meta(
3719
- { id: "RevTurbineConfigAddonsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4005
+ { id: "RevTurbineConfigAddonsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_AUTHORING_FACETS2 }
4006
+ );
4007
+ var RevTurbineConfigPlanVariationsItemSchema = z19.object({
4008
+ handle: z19.string().min(1).meta(Unrestricted16),
4009
+ plan_handle: z19.string().min(1).meta(Unrestricted16),
4010
+ billing_period: z19.enum(["monthly", "annual", "one_time", "custom"]).meta(Unrestricted16),
4011
+ segment_handle: z19.string().nullable().default(null).meta(Unrestricted16),
4012
+ price_amount: z19.number().min(0).meta(Unrestricted16),
4013
+ pricing_model: PricingModelSchema.meta(Unrestricted16),
4014
+ visibility: PlanVisibilitySchema.default("public").meta(Unrestricted16),
4015
+ stripe_price_id: z19.string().nullable().default(null).meta(Unrestricted16),
4016
+ price_source: PriceSourceSchema.meta(Unrestricted16)
4017
+ }).meta(
4018
+ { id: "RevTurbineConfigPlanVariationsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PENDING_PLAYBOOK_FACETS4 }
4019
+ );
4020
+ var RevTurbineConfigAddonVariationsItemSchema = z19.object({
4021
+ handle: z19.string().min(1).meta(Unrestricted16),
4022
+ addon_handle: z19.string().min(1).meta(Unrestricted16),
4023
+ billing_period: z19.enum(["monthly", "annual", "one_time", "custom"]).meta(Unrestricted16),
4024
+ segment_handle: z19.string().nullable().default(null).meta(Unrestricted16),
4025
+ price_amount: z19.number().min(0).meta(Unrestricted16),
4026
+ pricing_model: PricingModelSchema.meta(Unrestricted16),
4027
+ visibility: PlanVisibilitySchema.default("public").meta(Unrestricted16),
4028
+ stripe_price_id: z19.string().nullable().default(null).meta(Unrestricted16),
4029
+ price_source: PriceSourceSchema.meta(Unrestricted16)
4030
+ }).meta(
4031
+ { id: "RevTurbineConfigAddonVariationsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PENDING_PLAYBOOK_FACETS4 }
4032
+ );
4033
+ var RevTurbineConfigSeatTypesItemSchema = z19.object({
4034
+ handle: z19.string().min(1).meta(Unrestricted16),
4035
+ name: z19.string().min(1).meta(Unrestricted16),
4036
+ description: z19.string().nullable().default(null).meta(Unrestricted16),
4037
+ is_default: z19.boolean().default(false).meta(Unrestricted16),
4038
+ entitlement_handles: z19.array(z19.string()).default([]).meta(Unrestricted16)
4039
+ }).meta(
4040
+ { id: "RevTurbineConfigSeatTypesItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
4041
+ );
4042
+ var RevTurbineConfigEnforcementDefaultsItemSchema = z19.object({
4043
+ handle: z19.string().min(1).meta(Unrestricted16),
4044
+ entitlement_handle: z19.string().nullable().default(null).meta(Unrestricted16),
4045
+ soft_limit_percent: z19.number().int().min(0).nullable().default(null).meta(Unrestricted16),
4046
+ hard_limit_percent: z19.number().int().min(0).nullable().default(null).meta(Unrestricted16),
4047
+ soft_limit_action: z19.string().meta(Unrestricted16),
4048
+ hard_limit_action: z19.string().meta(Unrestricted16),
4049
+ grace_period_hours: z19.number().int().min(0).nullable().default(null).meta(Unrestricted16),
4050
+ notification_channels: z19.array(z19.string()).default([]).meta(Unrestricted16),
4051
+ is_active: z19.boolean().default(true).meta(Unrestricted16)
4052
+ }).meta(
4053
+ { id: "RevTurbineConfigEnforcementDefaultsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
4054
+ );
4055
+ var RevTurbineConfigPlacementSettingsItemSchema = z19.object({
4056
+ handle: z19.string().min(1).meta(Unrestricted16),
4057
+ global_frequency_cap: PlacementSettingsCapStateSchema.nullable().default(null).meta(Unrestricted16),
4058
+ global_frequency_cap_period: z19.enum(["hour", "day", "week", "month", "session"]).nullable().default(null).meta(Unrestricted16),
4059
+ suppress_for_paid: z19.boolean().default(false).meta(Unrestricted16),
4060
+ suppress_for_trial: z19.boolean().default(false).meta(Unrestricted16),
4061
+ default_dismiss_cooldown_hours: z19.number().int().min(0).nullable().default(null).meta(Unrestricted16),
4062
+ allow_stacking: z19.boolean().default(false).meta(Unrestricted16),
4063
+ priority_collision_strategy: z19.string().nullable().default(null).meta(Unrestricted16)
4064
+ }).meta(
4065
+ { id: "RevTurbineConfigPlacementSettingsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
4066
+ );
4067
+ var RevTurbineConfigSegmentDimensionsItemSchema = z19.object({
4068
+ handle: z19.string().min(1).meta(Unrestricted16),
4069
+ name: z19.string().min(1).meta(Unrestricted16),
4070
+ category: z19.string().nullable().default(null).meta(Unrestricted16),
4071
+ visibility_toggle: z19.boolean().default(true).meta(Unrestricted16),
4072
+ source_type: z19.string().nullable().default(null).meta(Unrestricted16)
4073
+ }).meta(
4074
+ { id: "RevTurbineConfigSegmentDimensionsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
4075
+ );
4076
+ var RevTurbineConfigMeterBindingsItemSchema = z19.object({
4077
+ handle: z19.string().min(1).meta(Unrestricted16),
4078
+ entitlement_handle: z19.string().min(1).meta(Unrestricted16),
4079
+ meter_handle: z19.string().min(1).meta(Unrestricted16),
4080
+ limit: z19.number().int().min(0).nullable().default(null).meta(Unrestricted16),
4081
+ reset_period: z19.string().nullable().default(null).meta(Unrestricted16)
4082
+ }).meta(
4083
+ { id: "RevTurbineConfigMeterBindingsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3720
4084
  );
3721
4085
  var RevTurbineConfigEntitlementsItemSchema = z19.object({
3722
4086
  // Plan 120 TASK-4: `unique_handle` is the sole logical identifier; the
@@ -3724,9 +4088,20 @@ var RevTurbineConfigEntitlementsItemSchema = z19.object({
3724
4088
  unique_handle: z19.string().min(1).meta(Unrestricted16),
3725
4089
  name: z19.string().min(1).meta(Unrestricted16),
3726
4090
  type: EntitlementTypeSchema.meta(Unrestricted16),
3727
- unit: z19.string().optional().meta(Unrestricted16)
4091
+ unit: z19.string().optional().meta(Unrestricted16),
4092
+ // Ordered tier ladder for a `capability_tier` entitlement — projection of
4093
+ // the authored `EntitlementSchema.tier_definitions` (plan 138 TASK-4).
4094
+ // ARRAY ORDER IS THE RANK: the `entitlement_gate.tier_threshold` placement
4095
+ // trigger fires when the user's current tier ranks below the threshold tier
4096
+ // on this ladder. `name`/`description` are UI-helper denormalizations (plan
4097
+ // 118); the runtime gate reads only the ordered `handle`s.
4098
+ tier_definitions: z19.array(z19.object({
4099
+ name: z19.string(),
4100
+ handle: z19.string(),
4101
+ description: z19.string().optional()
4102
+ })).optional().meta(Unrestricted16)
3728
4103
  }).meta(
3729
- { id: "RevTurbineConfigEntitlementsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4104
+ { id: "RevTurbineConfigEntitlementsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3730
4105
  );
3731
4106
  var RevTurbineConfigEntitlementRulesItemSchema = z19.object({
3732
4107
  id: z19.string().min(1).meta(Unrestricted16),
@@ -3736,19 +4111,22 @@ var RevTurbineConfigEntitlementRulesItemSchema = z19.object({
3736
4111
  // means "match all users" (replaces the singular `segment_id` field
3737
4112
  // and its 'all'/null sentinels).
3738
4113
  segment_ids: z19.array(z19.string()).default([]).meta(Unrestricted16),
3739
- type_fields: z19.record(z19.string(), z19.unknown()).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),
3740
4118
  current_usage: z19.number().default(0).meta(Unrestricted16),
3741
4119
  /** How usage is partitioned across the identity hierarchy. */
3742
4120
  allocation: UsageAllocationSchema.optional().meta(Unrestricted16)
3743
4121
  }).meta(
3744
- { id: "RevTurbineConfigEntitlementRulesItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4122
+ { id: "RevTurbineConfigEntitlementRulesItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3745
4123
  );
3746
4124
  var RevTurbineConfigSlotConfigsItemSchema = z19.object({
3747
4125
  slot_id: z19.string().min(1).meta(Unrestricted16),
3748
4126
  active: z19.boolean().meta(Unrestricted16),
3749
4127
  triggers: z19.array(z19.string()).meta(Unrestricted16)
3750
4128
  }).meta(
3751
- { id: "RevTurbineConfigSlotConfigsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4129
+ { id: "RevTurbineConfigSlotConfigsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3752
4130
  );
3753
4131
  var RevTurbineConfigPlacementSlotsItemSchema = z19.object({
3754
4132
  id: z19.string().min(1).meta(Unrestricted16),
@@ -3758,7 +4136,7 @@ var RevTurbineConfigPlacementSlotsItemSchema = z19.object({
3758
4136
  placement_handle: z19.string().min(1).meta(Unrestricted16),
3759
4137
  template: z19.string().optional().meta(Unrestricted16)
3760
4138
  }).meta(
3761
- { id: "RevTurbineConfigPlacementSlotsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4139
+ { id: "RevTurbineConfigPlacementSlotsItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3762
4140
  );
3763
4141
  var RevTurbineConfigSurfaceTemplatesItemFieldsItemSchema = z19.object({
3764
4142
  name: z19.string().min(1).meta(Unrestricted16),
@@ -3772,7 +4150,7 @@ var RevTurbineConfigSurfaceTemplatesItemSchema = z19.object({
3772
4150
  surface_type: z19.string().meta(Unrestricted16),
3773
4151
  fields: z19.array(RevTurbineConfigSurfaceTemplatesItemFieldsItemSchema).optional().meta(Unrestricted16)
3774
4152
  }).meta(
3775
- { id: "RevTurbineConfigSurfaceTemplatesItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4153
+ { id: "RevTurbineConfigSurfaceTemplatesItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3776
4154
  );
3777
4155
  var RevTurbineConfigUiPathActionTypeSchema = z19.enum([
3778
4156
  "open_checkout_modal",
@@ -3808,7 +4186,7 @@ var ContentUiPathSchema = z19.object({
3808
4186
  target_billing_period: z19.enum(["monthly", "annual"]).optional().meta(Unrestricted16),
3809
4187
  description: z19.string().optional().meta(Unrestricted16)
3810
4188
  }).meta(
3811
- { id: "ContentUiPath", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4189
+ { id: "ContentUiPath", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3812
4190
  );
3813
4191
  var ContentPromotionSchema = z19.object({
3814
4192
  id: z19.string().meta(Unrestricted16),
@@ -3817,9 +4195,9 @@ var ContentPromotionSchema = z19.object({
3817
4195
  type: z19.string().meta(Unrestricted16),
3818
4196
  status: z19.string().meta(Unrestricted16)
3819
4197
  }).meta(
3820
- { id: "ContentPromotion", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4198
+ { id: "ContentPromotion", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3821
4199
  );
3822
- var PersonalizationTokenSchema = z19.object({
4200
+ var RevTurbineConfigPersonalizationTokensItemSchema = z19.object({
3823
4201
  token: z19.string().regex(/^[a-z][a-z0-9_]*$/).meta(Unrestricted16),
3824
4202
  label: z19.string().min(1).meta(Unrestricted16),
3825
4203
  description: z19.string().optional().meta(Unrestricted16),
@@ -3829,7 +4207,7 @@ var PersonalizationTokenSchema = z19.object({
3829
4207
  value_map: z19.record(z19.string(), z19.string()).optional().meta(Unrestricted16),
3830
4208
  format: z19.enum(["string", "number", "currency", "percentage", "date"]).optional().meta(Unrestricted16)
3831
4209
  }).meta(
3832
- { id: "PersonalizationToken", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4210
+ { id: "RevTurbineConfigPersonalizationTokensItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3833
4211
  );
3834
4212
  var MessageBlockContentSchema = z19.object({
3835
4213
  header: z19.string().optional().meta(Unrestricted16),
@@ -3858,7 +4236,7 @@ var MessageBlockSchema = z19.object({
3858
4236
  created_at: z19.string().datetime().meta({ ...Unrestricted16, readOnly: true }),
3859
4237
  updated_at: z19.string().datetime().meta({ ...Unrestricted16, readOnly: true })
3860
4238
  }).meta(
3861
- { id: "MessageBlock", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4239
+ { id: "MessageBlock", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3862
4240
  );
3863
4241
  var RevTurbineConfigStudioCtaConfigSchema = z19.object({
3864
4242
  label: z19.string().meta(Unrestricted16),
@@ -3911,9 +4289,9 @@ var RevTurbineConfigStudioPayloadSchema = z19.object({
3911
4289
  var RevTurbineConfigPlacementTriggerSchema = z19.discriminatedUnion("type", [
3912
4290
  z19.object({ type: z19.literal("surface_render"), slot_id: z19.string().min(1) }),
3913
4291
  z19.object({ type: z19.literal("entitlement_gate"), entitlement_handle: z19.string().min(1), tier_threshold: z19.string().optional() }),
3914
- z19.object({ type: z19.literal("usage_threshold"), entitlement_handle: z19.string().min(1), threshold_percent: z19.number().min(1).max(100) }),
3915
- z19.object({ type: z19.literal("credit_threshold"), entitlement_handle: z19.string().min(1), threshold_percent: z19.number().min(1).max(100) }),
3916
- z19.object({ type: z19.literal("seat_threshold"), entitlement_handle: z19.string().min(1), threshold_percent: z19.number().min(1).max(100) }),
4292
+ z19.object({ type: z19.literal("usage_threshold"), entitlement_handle: z19.string().min(1), threshold_percent: ThresholdPercentField }),
4293
+ z19.object({ type: z19.literal("credit_threshold"), entitlement_handle: z19.string().min(1), threshold_percent: ThresholdPercentField }),
4294
+ z19.object({ type: z19.literal("seat_threshold"), entitlement_handle: z19.string().min(1), threshold_percent: ThresholdPercentField }),
3917
4295
  z19.object({ type: z19.literal("trial_started"), trial_type: z19.enum(["free", "reverse"]).optional() }),
3918
4296
  z19.object({ type: z19.literal("trial_progress"), progress_percent: z19.number().min(1).max(100) }),
3919
4297
  z19.object({ type: z19.literal("trial_ending"), days_before_end: z19.number().int().min(0) }),
@@ -3934,7 +4312,7 @@ var RevTurbineConfigPlacementItemSchema = z19.object({
3934
4312
  payloads: z19.array(RevTurbineConfigStudioPayloadSchema).meta(Unrestricted16),
3935
4313
  order: z19.number().int().min(0).meta(Unrestricted16)
3936
4314
  }).meta(
3937
- { id: "RevTurbineConfigPlacementItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4315
+ { id: "RevTurbineConfigPlacementItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3938
4316
  );
3939
4317
  var RevTurbineConfigPlacementPayloadItemSchema = z19.object({
3940
4318
  payload_id: z19.string().min(1).meta(Unrestricted16),
@@ -3954,121 +4332,244 @@ var RevTurbineConfigPlacementPayloadItemSchema = z19.object({
3954
4332
  content_payload_id: z19.string().optional()
3955
4333
  }).optional().meta(Unrestricted16)
3956
4334
  }).meta(
3957
- { id: "RevTurbineConfigPlacementPayloadItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4335
+ { id: "RevTurbineConfigPlacementPayloadItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3958
4336
  );
3959
4337
  var RevTurbineConfigExtensionRulesItemSchema = z19.object({
3960
4338
  kind: z19.string().min(1).meta(Unrestricted16),
3961
4339
  schema_version: z19.number().int().nonnegative().meta(Unrestricted16),
3962
4340
  config: z19.unknown().meta(Unrestricted16)
3963
4341
  }).meta(
3964
- { id: "RevTurbineConfigExtensionRulesItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4342
+ { id: "RevTurbineConfigExtensionRulesItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PLAYBOOK_SDK_FACETS8 }
3965
4343
  );
3966
4344
  var RevTurbineConfigFreeTrialRuleItemSchema = IdField.merge(FreeTrialRuleCoreFieldsSchema).meta(
3967
- { id: "RevTurbineConfigFreeTrialRuleItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4345
+ { id: "RevTurbineConfigFreeTrialRuleItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PENDING_PLAYBOOK_FACETS4 }
3968
4346
  );
3969
4347
  var RevTurbineConfigReverseTrialRuleItemSchema = IdField.merge(ReverseTrialRuleCoreFieldsSchema).meta(
3970
- { id: "RevTurbineConfigReverseTrialRuleItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4348
+ { id: "RevTurbineConfigReverseTrialRuleItem", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11, ...PENDING_PLAYBOOK_FACETS4 }
3971
4349
  );
3972
- var RevTurbineConfigSchema = z19.object({
3973
- version: z19.string().meta(Unrestricted16),
3974
- exported_at: z19.string().datetime().optional().meta({ ...Unrestricted16, readOnly: true }),
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 }),
3975
4353
  // The @revt-eng/schema package version this config was generated with.
3976
- schema_version: z19.string().optional().meta({ ...Unrestricted16, readOnly: true }),
4354
+ schema_version: z19.string().optional().meta({ ...Unrestricted16, ...PLAYBOOK_VERSION_HEADER_FACETS, readOnly: true }),
3977
4355
  // The compiled-bundle wire-format SCHEMA_VERSION (core/bundle/ir.ts).
3978
- bundle_schema_version: z19.number().int().optional().meta({ ...Unrestricted16, readOnly: true }),
4356
+ bundle_schema_version: z19.number().int().optional().meta({ ...Unrestricted16, ...PLAYBOOK_VERSION_HEADER_FACETS, readOnly: true }),
3979
4357
  // The change set this export represents: the active change set by default,
3980
4358
  // or a specific change set when one is requested. Null for an unscoped export.
3981
- change_set_id: z19.string().nullable().default(null).meta({ ...Unrestricted16, readOnly: true }),
4359
+ change_set_id: z19.string().nullable().default(null).meta({ ...Unrestricted16, ...PLAYBOOK_PROVENANCE_HEADER_FACETS, readOnly: true }),
3982
4360
  // Origin target identity (plan 131 TASK-10, cli.md "Target-aware, portable"):
3983
4361
  // stamped by the server on export so a downloaded Config File records where
3984
4362
  // it came from; upload tooling targets these by default and flags a tenant
3985
4363
  // mismatch against the session before sending. Optional — hand-authored and
3986
4364
  // pre-existing configs carry no target.
3987
- tenant_id: z19.string().optional().meta({ ...Unrestricted16, readOnly: true }),
3988
- environment_id: z19.string().optional().meta({ ...Unrestricted16, readOnly: true }),
3989
- plans: z19.array(RevTurbineConfigPlansItemSchema).meta(Unrestricted16),
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 }),
4367
+ plans: z19.array(RevTurbineConfigPlansItemSchema).meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
3990
4368
  // Optional for back-compat: pre-plan-88 configs (and the live export until web
3991
4369
  // adopts the new @revt-eng/schema) omit it. Add-on definitions only; pricing
3992
4370
  // (addon_variations) stays in the Stripe layer, like plan_variations.
3993
- addons: z19.array(RevTurbineConfigAddonsItemSchema).optional().meta(Unrestricted16),
3994
- entitlements: z19.array(RevTurbineConfigEntitlementsItemSchema).meta(Unrestricted16),
3995
- entitlement_rules: z19.array(RevTurbineConfigEntitlementRulesItemSchema).meta(Unrestricted16),
3996
- segments: z19.array(RevTurbineConfigSegmentsItemSchema).meta(Unrestricted16),
3997
- content_ui_paths: z19.array(ContentUiPathSchema).meta(Unrestricted16),
3998
- slot_configs: z19.array(RevTurbineConfigSlotConfigsItemSchema).optional().meta(Unrestricted16),
3999
- content_overrides: z19.record(z19.string(), z19.record(z19.string(), z19.string())).optional().meta(Unrestricted16),
4000
- theme: z19.record(z19.string(), z19.unknown()).optional().meta(Unrestricted16),
4001
- placement_slots: z19.array(RevTurbineConfigPlacementSlotsItemSchema).optional().meta(Unrestricted16),
4002
- message_blocks: z19.array(MessageBlockSchema).optional().meta(Unrestricted16),
4003
- placement_payloads: z19.array(RevTurbineConfigPlacementPayloadItemSchema).optional().meta(Unrestricted16),
4004
- placements: z19.array(RevTurbineConfigPlacementItemSchema).optional().meta(Unrestricted16),
4005
- content_promotions: z19.array(ContentPromotionSchema).optional().meta(Unrestricted16),
4006
- personalization_tokens: z19.array(PersonalizationTokenSchema).optional().meta(Unrestricted16),
4007
- surface_templates: z19.array(RevTurbineConfigSurfaceTemplatesItemSchema).optional().meta(Unrestricted16),
4371
+ addons: z19.array(RevTurbineConfigAddonsItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_AUTHORING_FACETS2 }),
4372
+ entitlements: z19.array(RevTurbineConfigEntitlementsItemSchema).meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4373
+ entitlement_rules: z19.array(RevTurbineConfigEntitlementRulesItemSchema).meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4374
+ segments: z19.array(RevTurbineConfigSegmentsItemSchema).meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4375
+ content_ui_paths: z19.array(ContentUiPathSchema).meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4376
+ slot_configs: z19.array(RevTurbineConfigSlotConfigsItemSchema).optional().meta({
4377
+ ...Unrestricted16,
4378
+ ...PLAYBOOK_SDK_FACETS8,
4379
+ ...schemaDeprecation({
4380
+ since: "0.1.117",
4381
+ replacement: "SDK-local activation/trigger state",
4382
+ removeAfter: "one compatibility window",
4383
+ reason: "Slot activation moved to SDK-local state (plan 118 TASK-6); no longer a Playbook authoring input."
4384
+ })
4385
+ }),
4386
+ content_overrides: z19.record(z19.string(), z19.record(z19.string(), z19.string())).optional().meta({
4387
+ ...Unrestricted16,
4388
+ ...PLAYBOOK_SDK_FACETS8,
4389
+ ...schemaDeprecation({
4390
+ since: "0.1.117",
4391
+ replacement: "Message Block / Placement Payload content",
4392
+ removeAfter: "one compatibility window",
4393
+ reason: "Content overrides moved to Message Block / Payload content (plan 118 TASK-6); no longer a Playbook authoring input."
4394
+ })
4395
+ }),
4396
+ theme: z19.record(z19.string(), z19.unknown()).optional().meta({
4397
+ ...Unrestricted16,
4398
+ ...LEGACY_BRANDING_FACETS,
4399
+ ...schemaDeprecation({
4400
+ since: "0.1.111",
4401
+ replacement: "SDK branding argument",
4402
+ removeAfter: "one compatibility window",
4403
+ reason: "Branding is independently owned and is not Playbook strategy."
4404
+ })
4405
+ }),
4406
+ placement_slots: z19.array(RevTurbineConfigPlacementSlotsItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4407
+ message_blocks: z19.array(MessageBlockSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4408
+ placement_payloads: z19.array(RevTurbineConfigPlacementPayloadItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4409
+ placements: z19.array(RevTurbineConfigPlacementItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4410
+ content_promotions: z19.array(ContentPromotionSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4411
+ personalization_tokens: z19.array(RevTurbineConfigPersonalizationTokensItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4412
+ surface_templates: z19.array(RevTurbineConfigSurfaceTemplatesItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4008
4413
  /**
4009
4414
  * Free + reverse trial rule configurations (plan 43). Optional so
4010
4415
  * pre-trial-runtime configs continue to parse. /api/config/import
4011
4416
  * applies these to the tenant's free_trial_rules / reverse_trial_rules
4012
4417
  * tables; /api/config/export reads them out for round-trip.
4013
4418
  */
4014
- free_trial_rules: z19.array(RevTurbineConfigFreeTrialRuleItemSchema).optional().meta(Unrestricted16),
4015
- reverse_trial_rules: z19.array(RevTurbineConfigReverseTrialRuleItemSchema).optional().meta(Unrestricted16),
4419
+ free_trial_rules: z19.array(RevTurbineConfigFreeTrialRuleItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_AUTHORING_FACETS2 }),
4420
+ reverse_trial_rules: z19.array(RevTurbineConfigReverseTrialRuleItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_AUTHORING_FACETS2 }),
4421
+ // Plan / add-on variation prices carried by handle (plan 118 TASK-16). These
4422
+ // live on the legacy schema (not just the canonical Playbook body) so that a
4423
+ // legacy `version`-shaped config — the shape the demo-data configs and the
4424
+ // pre-sales/CLI upload flow still use — can carry variation prices through
4425
+ // normalization instead of having them stripped. Pending until web
4426
+ // import/export activates them (TASK-21).
4427
+ plan_variations: z19.array(RevTurbineConfigPlanVariationsItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_AUTHORING_FACETS2 }),
4428
+ addon_variations: z19.array(RevTurbineConfigAddonVariationsItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_AUTHORING_FACETS2 }),
4016
4429
  /**
4017
4430
  * Tagged-opaque rule entries (Phase 3 / strategy 2). Each entry is
4018
4431
  * dispatched to the corresponding `RuleAuthoringModule.kind` at
4019
4432
  * compile time; unknown kinds are skipped silently so authoring can
4020
4433
  * stage new kinds before the runtime catches up.
4021
4434
  */
4022
- extension_rules: z19.array(RevTurbineConfigExtensionRulesItemSchema).optional().meta(Unrestricted16)
4435
+ extension_rules: z19.array(RevTurbineConfigExtensionRulesItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4436
+ // Authored-config projections carried as active SDK inputs (plan 118
4437
+ // TASK-13/18). Declared here (not only on PlaybookBody) so the Bundle
4438
+ // compiler — which lowers the legacy `RevTurbineConfig` view — reads them
4439
+ // with proper types. Projected into the RuleBundle; see core/bundle.
4440
+ seat_types: z19.array(RevTurbineConfigSeatTypesItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4441
+ enforcement_defaults: z19.array(RevTurbineConfigEnforcementDefaultsItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4442
+ placement_settings: z19.array(RevTurbineConfigPlacementSettingsItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4443
+ segment_dimensions: z19.array(RevTurbineConfigSegmentDimensionsItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 }),
4444
+ meter_bindings: z19.array(RevTurbineConfigMeterBindingsItemSchema).optional().meta({ ...Unrestricted16, ...PLAYBOOK_SDK_FACETS8 })
4023
4445
  }).meta(
4024
- { id: "RevTurbineConfig", "x-revturbine-schema-persistence": Transient16, "x-revturbine-schema-exposure": External11 }
4446
+ {
4447
+ id: "RevTurbineConfig",
4448
+ "x-revturbine-schema-persistence": Transient16,
4449
+ "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
+ })
4457
+ }
4458
+ );
4459
+ var PlaybookHeaderSchema = z19.object({
4460
+ artifact_type: z19.literal("playbook").meta({
4461
+ ...Unrestricted16,
4462
+ ...PLAYBOOK_VERSION_HEADER_FACETS,
4463
+ readOnly: true
4464
+ }),
4465
+ format_version: z19.literal(PLAYBOOK_FORMAT_VERSION).meta({
4466
+ ...Unrestricted16,
4467
+ ...PLAYBOOK_VERSION_HEADER_FACETS,
4468
+ readOnly: true
4469
+ }),
4470
+ playbook_handle: z19.string().min(1).default("default").meta({
4471
+ ...Unrestricted16,
4472
+ ...PLAYBOOK_VERSION_HEADER_FACETS
4473
+ }),
4474
+ playbook_version_id: z19.string().nullable().default(null).meta({
4475
+ ...Unrestricted16,
4476
+ ...PLAYBOOK_PROVENANCE_HEADER_FACETS,
4477
+ readOnly: true
4478
+ }),
4479
+ tenant_id: z19.string().min(1).meta({
4480
+ ...Unrestricted16,
4481
+ ...PLAYBOOK_TARGET_FACETS,
4482
+ readOnly: true
4483
+ }),
4484
+ environment_id: z19.string().min(1).meta({
4485
+ ...Unrestricted16,
4486
+ ...PLAYBOOK_TARGET_FACETS,
4487
+ readOnly: true
4488
+ }),
4489
+ project_id: z19.string().min(1).optional().meta({
4490
+ ...Unrestricted16,
4491
+ ...PLAYBOOK_TARGET_FACETS,
4492
+ readOnly: true
4493
+ }),
4494
+ exported_at: z19.string().datetime().optional().meta({
4495
+ ...Unrestricted16,
4496
+ ...PLAYBOOK_PROVENANCE_HEADER_FACETS,
4497
+ readOnly: true
4498
+ }),
4499
+ schema_version: z19.string().min(1).optional().meta({
4500
+ ...Unrestricted16,
4501
+ ...PLAYBOOK_VERSION_HEADER_FACETS,
4502
+ readOnly: true
4503
+ }),
4504
+ bundle_schema_version: z19.number().int().nonnegative().optional().meta({
4505
+ ...Unrestricted16,
4506
+ ...PLAYBOOK_VERSION_HEADER_FACETS,
4507
+ readOnly: true
4508
+ })
4509
+ }).meta(
4510
+ {
4511
+ id: "PlaybookHeader",
4512
+ "x-revturbine-schema-persistence": Transient16,
4513
+ "x-revturbine-schema-exposure": External11,
4514
+ ...PLAYBOOK_PROVENANCE_HEADER_FACETS
4515
+ }
4516
+ );
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(
4556
+ {
4557
+ id: "PlaybookBody",
4558
+ "x-revturbine-schema-persistence": Transient16,
4559
+ "x-revturbine-schema-exposure": External11,
4560
+ ...PLAYBOOK_SDK_FACETS8
4561
+ }
4025
4562
  );
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
4569
+ }
4570
+ );
4571
+ var RevTurbineConfigSchema = LegacyRevTurbineConfigSchema;
4026
4572
  var configPaths = {
4027
- "/api/config/application-surfaces": {
4028
- get: operation({
4029
- operationId: "listApplicationSurfaces",
4030
- requestParams: { query: ListQueryParamsSchema },
4031
- summary: "List application surfaces",
4032
- tags: ["config"],
4033
- responses: { "200": { description: "Surface list", content: { "application/json": { schema: ListEnvelope(ApplicationSurfaceSchema) } } } },
4034
- "x-revturbine-operation": { exposure: "internal", resource: "application-surfaces", persistence: { table: "applicationSurfaces", mode: "list" } }
4035
- }),
4036
- post: operation({
4037
- operationId: "createApplicationSurface",
4038
- summary: "Create application surface",
4039
- tags: ["config"],
4040
- requestBody: { required: true, content: { "application/json": { schema: toCreateSchema(ApplicationSurfaceSchema) } } },
4041
- responses: { "201": { description: "Created", content: { "application/json": { schema: ApplicationSurfaceSchema } } } },
4042
- "x-revturbine-operation": { exposure: "internal", resource: "application-surfaces", persistence: { table: "applicationSurfaces", mode: "create" } }
4043
- })
4044
- },
4045
- "/api/config/application-surfaces/{id}": {
4046
- get: operation({
4047
- operationId: "getApplicationSurface",
4048
- requestParams: { path: z19.object({ id: z19.string() }) },
4049
- summary: "Get application surface by ID",
4050
- tags: ["config"],
4051
- responses: { "200": { description: "Application surface detail", content: { "application/json": { schema: ApplicationSurfaceSchema } } } },
4052
- "x-revturbine-operation": { exposure: "internal", resource: "application-surfaces", persistence: { table: "applicationSurfaces", mode: "get" } }
4053
- }),
4054
- patch: operation({
4055
- operationId: "updateApplicationSurface",
4056
- requestParams: { path: z19.object({ id: z19.string() }) },
4057
- summary: "Update application surface",
4058
- tags: ["config"],
4059
- requestBody: { required: true, content: { "application/json": { schema: ApplicationSurfaceSchema.partial() } } },
4060
- responses: { "200": { description: "Updated", content: { "application/json": { schema: ApplicationSurfaceSchema } } } },
4061
- "x-revturbine-operation": { exposure: "internal", resource: "application-surfaces", persistence: { table: "applicationSurfaces", mode: "update" } }
4062
- }),
4063
- delete: operation({
4064
- operationId: "deleteApplicationSurface",
4065
- requestParams: { path: z19.object({ id: z19.string() }) },
4066
- summary: "Delete application surface",
4067
- tags: ["config"],
4068
- responses: { "204": { description: "Deleted" } },
4069
- "x-revturbine-operation": { exposure: "internal", resource: "application-surfaces", persistence: { table: "applicationSurfaces", mode: "delete" } }
4070
- })
4071
- },
4072
4573
  "/api/seat-type-anchors": {
4073
4574
  get: operation({
4074
4575
  operationId: "listSeatTypeAnchors",
@@ -4119,6 +4620,56 @@ var configPaths = {
4119
4620
  "x-revturbine-operation": { exposure: "internal", resource: "seat-types", persistence: { table: "seatTypeVersions", mode: "delete" } }
4120
4621
  })
4121
4622
  },
4623
+ "/api/personalization-token-anchors": {
4624
+ get: operation({
4625
+ operationId: "listPersonalizationTokenAnchors",
4626
+ requestParams: { query: ListQueryParamsSchema },
4627
+ summary: "List personalization token anchors (identity registry)",
4628
+ tags: ["config"],
4629
+ responses: {
4630
+ "200": { description: "Personalization token anchor list", content: { "application/json": { schema: ListEnvelope(PersonalizationTokenAnchorSchema) } } },
4631
+ default: { description: "Error response", content: { "application/json": { schema: ErrorEnvelope } } }
4632
+ },
4633
+ "x-revturbine-operation": { exposure: "internal", resource: "personalization-token-anchors", persistence: { table: "personalizationTokens", mode: "list" } }
4634
+ })
4635
+ },
4636
+ "/api/config/personalization-tokens": {
4637
+ get: operation({
4638
+ operationId: "listPersonalizationTokens",
4639
+ requestParams: { query: ListQueryParamsSchema },
4640
+ summary: "List personalization tokens",
4641
+ tags: ["config"],
4642
+ responses: { "200": { description: "Personalization token list", content: { "application/json": { schema: ListEnvelope(PersonalizationTokenSchema) } } } },
4643
+ "x-revturbine-operation": { exposure: "internal", resource: "personalization-tokens", persistence: { table: "personalizationTokenVersions", mode: "list" } }
4644
+ }),
4645
+ post: operation({
4646
+ operationId: "createPersonalizationToken",
4647
+ summary: "Create personalization token",
4648
+ tags: ["config"],
4649
+ requestBody: { required: true, content: { "application/json": { schema: toCreateSchema(PersonalizationTokenSchema) } } },
4650
+ responses: { "201": { description: "Created", content: { "application/json": { schema: PersonalizationTokenSchema } } } },
4651
+ "x-revturbine-operation": { exposure: "internal", resource: "personalization-tokens", persistence: { table: "personalizationTokenVersions", mode: "create" } }
4652
+ })
4653
+ },
4654
+ "/api/config/personalization-tokens/{id}": {
4655
+ patch: operation({
4656
+ operationId: "updatePersonalizationToken",
4657
+ requestParams: { path: z19.object({ id: z19.string() }) },
4658
+ summary: "Update personalization token",
4659
+ tags: ["config"],
4660
+ requestBody: { required: true, content: { "application/json": { schema: PersonalizationTokenSchema.partial() } } },
4661
+ responses: { "200": { description: "Updated", content: { "application/json": { schema: PersonalizationTokenSchema } } } },
4662
+ "x-revturbine-operation": { exposure: "internal", resource: "personalization-tokens", persistence: { table: "personalizationTokenVersions", mode: "update" } }
4663
+ }),
4664
+ delete: operation({
4665
+ operationId: "deletePersonalizationToken",
4666
+ requestParams: { path: z19.object({ id: z19.string() }) },
4667
+ summary: "Delete personalization token",
4668
+ tags: ["config"],
4669
+ responses: { "204": { description: "Deleted" } },
4670
+ "x-revturbine-operation": { exposure: "internal", resource: "personalization-tokens", persistence: { table: "personalizationTokenVersions", mode: "delete" } }
4671
+ })
4672
+ },
4122
4673
  "/api/config/stripe": {
4123
4674
  get: operation({
4124
4675
  operationId: "getStripeIntegrationConfig",
@@ -4528,6 +5079,16 @@ var EntitlementCheckResultSchema = z23.object({
4528
5079
  allowed: z23.boolean().meta(Unrestricted20),
4529
5080
  reason: z23.string().optional().meta(Unrestricted20),
4530
5081
  current_tier: z23.string().optional().meta(Unrestricted20),
5082
+ /**
5083
+ * Effective numeric limit from the matched entitlement rule (or usage
5084
+ * snapshot) — plan 133. Present only on limit-bearing outcomes
5085
+ * (usage_limit / credits); absence means limit-agnostic, not unlimited.
5086
+ */
5087
+ limit: z23.number().optional().meta(Unrestricted20),
5088
+ /** Consumed amount the evaluation applied against `limit`. */
5089
+ used: z23.number().optional().meta(Unrestricted20),
5090
+ /** `max(0, limit - used)`. */
5091
+ remaining: z23.number().optional().meta(Unrestricted20),
4531
5092
  /** Upsell placement to render when entitlement is denied. */
4532
5093
  placement: PlacementDecisionOutputSchema.optional().meta(Unrestricted20)
4533
5094
  }).meta({ id: "EntitlementCheckResult", "x-revturbine-schema-persistence": Transient19, "x-revturbine-schema-exposure": External12 });
@@ -5569,7 +6130,7 @@ function evaluate(graph, opts = {}) {
5569
6130
  }
5570
6131
 
5571
6132
  // src/schema/version.ts
5572
- var SCHEMA_VERSION = "0.1.108";
6133
+ var SCHEMA_VERSION = "0.1.138";
5573
6134
 
5574
6135
  // src/lib/credentials.ts
5575
6136
  import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
@@ -5926,6 +6487,75 @@ async function fetchValidation(baseUrl, playbookVersionId, headers, fetchImpl =
5926
6487
  };
5927
6488
  }
5928
6489
 
6490
+ // src/lib/playbook-header.ts
6491
+ var SUPPORTED_FORMAT_VERSIONS = /* @__PURE__ */ new Set(["1.0.0"]);
6492
+ var UnsupportedFormatError = class extends Error {
6493
+ constructor(message) {
6494
+ super(message);
6495
+ this.name = "UnsupportedFormatError";
6496
+ }
6497
+ };
6498
+ function str(value) {
6499
+ return typeof value === "string" ? value : void 0;
6500
+ }
6501
+ function readPlaybookHeader(config) {
6502
+ const c = config && typeof config === "object" ? config : {};
6503
+ const artifactType = str(c.artifact_type);
6504
+ const tenantId = str(c.tenant_id);
6505
+ const environmentId = str(c.environment_id);
6506
+ if (artifactType === "playbook") {
6507
+ return {
6508
+ shape: "canonical",
6509
+ artifactType,
6510
+ formatVersion: str(c.format_version),
6511
+ playbookHandle: str(c.playbook_handle),
6512
+ playbookVersionId: c.playbook_version_id ?? null,
6513
+ tenantId,
6514
+ environmentId
6515
+ };
6516
+ }
6517
+ if (artifactType === void 0 && typeof c.version === "string") {
6518
+ return {
6519
+ shape: "legacy",
6520
+ formatVersion: c.version,
6521
+ playbookVersionId: c.change_set_id ?? null,
6522
+ tenantId,
6523
+ environmentId
6524
+ };
6525
+ }
6526
+ return { shape: "unknown", artifactType, tenantId, environmentId };
6527
+ }
6528
+ function assertSupportedFormat(config) {
6529
+ const header = readPlaybookHeader(config);
6530
+ if (header.shape === "canonical") {
6531
+ const version = header.formatVersion;
6532
+ if (!version || !SUPPORTED_FORMAT_VERSIONS.has(version)) {
6533
+ throw new UnsupportedFormatError(
6534
+ `Unsupported Playbook format_version ${version ? `"${version}"` : "(missing)"}. This CLI supports: ${[...SUPPORTED_FORMAT_VERSIONS].join(", ")}. Update the CLI pinned in this repo (\`npm install -D @revturbine/cli@latest\`, or your package manager's equivalent).`
6535
+ );
6536
+ }
6537
+ return header;
6538
+ }
6539
+ if (header.shape === "unknown") {
6540
+ throw new UnsupportedFormatError(
6541
+ header.artifactType ? `Unrecognized artifact_type "${header.artifactType}" \u2014 expected "playbook" or a legacy Config File.` : 'File is neither a canonical Playbook (artifact_type: "playbook") nor a legacy Config File (missing "version").'
6542
+ );
6543
+ }
6544
+ return header;
6545
+ }
6546
+ function describePlaybookHeader(header) {
6547
+ if (header.shape === "legacy") {
6548
+ return `legacy Config File (version ${header.formatVersion ?? "?"})`;
6549
+ }
6550
+ if (header.shape === "canonical") {
6551
+ const parts = [`Playbook format_version ${header.formatVersion ?? "?"}`];
6552
+ if (header.playbookHandle) parts.push(`handle ${header.playbookHandle}`);
6553
+ if (header.playbookVersionId) parts.push(`playbook_version_id ${header.playbookVersionId}`);
6554
+ return parts.join(", ");
6555
+ }
6556
+ return "unrecognized artifact";
6557
+ }
6558
+
5929
6559
  // src/lib/drafts.ts
5930
6560
  async function resolveActiveDraft(baseUrl, headers, fetchImpl = fetch) {
5931
6561
  const res = await fetchImpl(`${baseUrl}/api/optimization/drafts`, { headers });
@@ -5933,6 +6563,201 @@ async function resolveActiveDraft(baseUrl, headers, fetchImpl = fetch) {
5933
6563
  return { ok: res.ok, status: res.status, draft: res.ok ? json.active ?? null : null };
5934
6564
  }
5935
6565
 
6566
+ // src/lib/delegate.ts
6567
+ var DELEGATION_ENV = "REVTURBINE_DELEGATED";
6568
+ var NO_LOCAL_FLAG = "--no-local";
6569
+ function planDelegation(params) {
6570
+ if (params.env[DELEGATION_ENV]) {
6571
+ return { delegate: false, reason: "already delegated" };
6572
+ }
6573
+ if (params.argv.includes(NO_LOCAL_FLAG)) {
6574
+ return { delegate: false, reason: `${NO_LOCAL_FLAG} requested` };
6575
+ }
6576
+ if (!params.local) {
6577
+ return { delegate: false, reason: "no repo-pinned CLI found" };
6578
+ }
6579
+ if (samePath(params.local.entry, params.ownEntry)) {
6580
+ return { delegate: false, reason: "already running the repo-pinned CLI" };
6581
+ }
6582
+ return {
6583
+ delegate: true,
6584
+ target: params.local.entry,
6585
+ // Only surface skew when it exists: a diagnostic on every invocation is
6586
+ // noise, and noise gets filtered out along with the signal.
6587
+ skew: params.local.version === params.ownVersion ? void 0 : { local: params.local.version, global: params.ownVersion }
6588
+ };
6589
+ }
6590
+ function samePath(a, b) {
6591
+ const norm = (p) => {
6592
+ const unified = p.replace(/\\/g, "/").replace(/\/+$/, "");
6593
+ return process.platform === "win32" ? unified.toLowerCase() : unified;
6594
+ };
6595
+ return norm(a) === norm(b);
6596
+ }
6597
+ function skewNotice(skew) {
6598
+ return `using the repo-pinned CLI ${skew.local} (this global is ${skew.global}) \u2014 the repo's pin wins because the schema snapshot is pinned with it. Run with ${NO_LOCAL_FLAG} to use the global instead.`;
6599
+ }
6600
+
6601
+ // src/lib/offline-schema.ts
6602
+ function schemaForConfig(raw) {
6603
+ const header = readPlaybookHeader(raw);
6604
+ return header.shape === "canonical" ? { schema: PlaybookSchema, shape: "canonical" } : { schema: RevTurbineConfigSchema, shape: "legacy" };
6605
+ }
6606
+
6607
+ // src/lib/init.ts
6608
+ var LOCKFILES = [
6609
+ ["pnpm-lock.yaml", "pnpm"],
6610
+ ["yarn.lock", "yarn"],
6611
+ ["package-lock.json", "npm"]
6612
+ ];
6613
+ function detectPackageManager(signals) {
6614
+ const field = signals.packageManagerField?.trim();
6615
+ if (field) {
6616
+ const name = field.split("@")[0]?.trim();
6617
+ if (name === "pnpm" || name === "npm" || name === "yarn") {
6618
+ return { name, reason: `packageManager field (${field})` };
6619
+ }
6620
+ }
6621
+ const files = new Set(signals.files ?? []);
6622
+ for (const [lockfile, name] of LOCKFILES) {
6623
+ if (files.has(lockfile)) return { name, reason: lockfile };
6624
+ }
6625
+ const agent = signals.userAgent ?? "";
6626
+ for (const name of ["pnpm", "yarn", "npm"]) {
6627
+ if (agent.startsWith(`${name}/`)) return { name, reason: `npm_config_user_agent (${name})` };
6628
+ }
6629
+ return { name: "npm", reason: "default" };
6630
+ }
6631
+ function detectStack(signals) {
6632
+ const deps = { ...signals.dependencies, ...signals.devDependencies };
6633
+ if (deps["next"]) return "next";
6634
+ if (deps["@remix-run/react"] || deps["@react-router/dev"]) return "remix";
6635
+ if (deps["astro"]) return "astro";
6636
+ if (deps["vite"]) return "vite";
6637
+ if (deps["react"]) return "react";
6638
+ return "unknown";
6639
+ }
6640
+ var SDK_PACKAGE = "@revturbine/sdk";
6641
+ var CLI_PACKAGE = "@revturbine/cli";
6642
+ function planInstall(params) {
6643
+ const deps = params.dependencies ?? {};
6644
+ const devDeps = params.devDependencies ?? {};
6645
+ const install = [];
6646
+ const skipped = [];
6647
+ const existingSdk = deps[SDK_PACKAGE] ?? devDeps[SDK_PACKAGE];
6648
+ if (existingSdk) {
6649
+ skipped.push(`${SDK_PACKAGE} already declared (${existingSdk})`);
6650
+ } else {
6651
+ install.push({ spec: SDK_PACKAGE, dev: false, exact: false });
6652
+ }
6653
+ const existingCli = devDeps[CLI_PACKAGE] ?? deps[CLI_PACKAGE];
6654
+ if (existingCli) {
6655
+ skipped.push(
6656
+ existingCli === params.cliVersion ? `${CLI_PACKAGE} already pinned (${existingCli})` : `${CLI_PACKAGE} already declared (${existingCli}) \u2014 left as-is; this CLI is ${params.cliVersion}`
6657
+ );
6658
+ } else {
6659
+ install.push({ spec: `${CLI_PACKAGE}@${params.cliVersion}`, dev: true, exact: true });
6660
+ }
6661
+ return { install, skipped };
6662
+ }
6663
+ function installArgs(manager, plan) {
6664
+ const exact = plan.exact ? [manager === "yarn" ? "--exact" : "--save-exact"] : [];
6665
+ const dev = plan.dev ? ["-D"] : [];
6666
+ const verb = manager === "npm" ? "install" : "add";
6667
+ return [verb, ...dev, ...exact, plan.spec];
6668
+ }
6669
+
6670
+ // src/lib/starter-playbook.ts
6671
+ var STARTER_PLAYBOOK_FILENAME = "revturbine.playbook.json";
6672
+ var STARTER_PLAYBOOK = {
6673
+ artifact_type: "playbook",
6674
+ format_version: "1.0.0",
6675
+ playbook_handle: "default",
6676
+ playbook_version_id: null,
6677
+ tenant_id: "local",
6678
+ environment_id: "local",
6679
+ plans: [
6680
+ { id: "plan_free", unique_handle: "free", name: "Free", tier_position: 0, sort_order: 0 },
6681
+ { id: "plan_pro", unique_handle: "pro", name: "Pro", tier_position: 1, sort_order: 1 }
6682
+ ],
6683
+ entitlements: [
6684
+ { id: "ent_advanced_export", unique_handle: "advanced_export", name: "Advanced Export", type: "feature" }
6685
+ ],
6686
+ entitlement_rules: [
6687
+ {
6688
+ id: "er_advanced_export_free",
6689
+ entitlement_id: "ent_advanced_export",
6690
+ targets: [{ kind: "plan", id: "plan_free" }],
6691
+ segment_ids: [],
6692
+ type_fields: { kind: "feature", enabled: false },
6693
+ current_usage: 0
6694
+ },
6695
+ {
6696
+ id: "er_advanced_export_pro",
6697
+ entitlement_id: "ent_advanced_export",
6698
+ targets: [{ kind: "plan", id: "plan_pro" }],
6699
+ segment_ids: [],
6700
+ type_fields: { kind: "feature", enabled: true },
6701
+ current_usage: 0
6702
+ }
6703
+ ],
6704
+ segments: [],
6705
+ content_ui_paths: [],
6706
+ surface_templates: [],
6707
+ placements: []
6708
+ };
6709
+ function validatePlaybook(config) {
6710
+ const parsed = PlaybookSchema.safeParse(config);
6711
+ if (!parsed.success) {
6712
+ return { ok: false, problems: parsed.error.issues ?? [parsed.error] };
6713
+ }
6714
+ const findings = evaluate(parsed.data, {});
6715
+ const blocking = findings.filter((f) => f.severity === "error_draft" || f.severity === "error_launch");
6716
+ return { ok: blocking.length === 0, problems: blocking };
6717
+ }
6718
+
6719
+ // src/lib/init-skills.ts
6720
+ var SKILLS_SOURCE = "revt-eng/revturbine-skills";
6721
+ var START_HERE_SKILL = "revturbine-start-here";
6722
+ function detectHarness(env) {
6723
+ if (env["CLAUDECODE"] || env["CLAUDE_CODE_SESSION_ID"] || env["CLAUDE_CODE_ENTRYPOINT"]) {
6724
+ return { label: "Claude Code", invocation: `Run the skill: /${START_HERE_SKILL}` };
6725
+ }
6726
+ if (env["CURSOR_TRACE_ID"] || env["CURSOR"]) {
6727
+ return { label: "Cursor", invocation: `Mention the skill: @${START_HERE_SKILL}` };
6728
+ }
6729
+ return {
6730
+ label: null,
6731
+ invocation: `Ask your coding agent to run the "${START_HERE_SKILL}" skill.`
6732
+ };
6733
+ }
6734
+ function skillsAddArgs(source = SKILLS_SOURCE) {
6735
+ return ["--yes", "skills", "add", source, "-y", "--copy"];
6736
+ }
6737
+ function finalOutputLines(params) {
6738
+ const lines = ["", "RevTurbine is set up.", ""];
6739
+ if (params.installedSdkAndCli) {
6740
+ lines.push(` \u2713 Installed the SDK and pinned the CLI (${params.cliVersion}) to this repo`);
6741
+ }
6742
+ if (params.playbookAdded) {
6743
+ lines.push(" \u2713 Added a starter playbook (local mode \u2014 no account needed)");
6744
+ }
6745
+ if (params.skills === "installed") {
6746
+ lines.push(" \u2713 Installed the Agent Skills");
6747
+ } else if (params.skills === "skipped") {
6748
+ lines.push(" \u2022 Skipped the Agent Skills (--no-skills)");
6749
+ } else {
6750
+ lines.push(" \u26A0 Agent Skills not installed \u2014 see above to add them by hand");
6751
+ }
6752
+ lines.push("", "Next step:");
6753
+ lines.push(` ${params.harness.invocation}`);
6754
+ if (params.harness.label) lines.push(` (detected ${params.harness.label})`);
6755
+ lines.push("", "Your setup path:");
6756
+ lines.push(" create playbook \u2192 billing \u2192 app wiring \u2192 verify \u2192 launch");
6757
+ lines.push("", "Docs: https://revturbine.com/docs", "");
6758
+ return lines;
6759
+ }
6760
+
5936
6761
  // src/lib/output.ts
5937
6762
  var EXIT = {
5938
6763
  OK: 0,
@@ -6076,9 +6901,23 @@ function loadConfig(file) {
6076
6901
  }
6077
6902
  function verifyConfig(file) {
6078
6903
  const parsed = loadConfig(file);
6904
+ const header = readPlaybookHeader(parsed);
6905
+ try {
6906
+ assertSupportedFormat(parsed);
6907
+ } catch (err) {
6908
+ if (err instanceof UnsupportedFormatError) {
6909
+ diag(`\u2717 ${file}: ${err.message}`);
6910
+ return null;
6911
+ }
6912
+ throw err;
6913
+ }
6914
+ if (header.shape === "canonical") {
6915
+ diag(`\u2713 ${file}: ${describePlaybookHeader(header)} \u2014 body validated server-side on import`);
6916
+ return parsed;
6917
+ }
6079
6918
  const result = schema.safeParse(parsed);
6080
6919
  if (result.success) {
6081
- diag(`\u2713 ${file}: schema validation passed`);
6920
+ diag(`\u2713 ${file}: schema validation passed \u2014 ${describePlaybookHeader(header)}`);
6082
6921
  return result.data;
6083
6922
  }
6084
6923
  diag(`\u2717 ${file}: schema validation FAILED:`);
@@ -6156,7 +6995,7 @@ function warnIfSchemaBehind(config) {
6156
6995
  const server = serverSchemaIsNewer(config, SCHEMA_VERSION);
6157
6996
  if (server) {
6158
6997
  diag(
6159
- `WARNING: the server's schema (${server}) is newer than this CLI's bundled snapshot (${SCHEMA_VERSION}) \u2014 offline validation may be missing rules. Update: npm i -g @revturbine/cli`
6998
+ `WARNING: the server's schema (${server}) is newer than this CLI's bundled snapshot (${SCHEMA_VERSION}) \u2014 offline validation may be missing rules. Update the CLI pinned in this repo: npm install -D @revturbine/cli@latest (or your package manager's equivalent).`
6160
6999
  );
6161
7000
  }
6162
7001
  }
@@ -6283,6 +7122,7 @@ function renderShow(kind, config) {
6283
7122
  var pkgVersion = createRequire(import.meta.url)("../package.json").version ?? "0.0.0";
6284
7123
  var HELP_AFTER = `
6285
7124
  Command groups:
7125
+ Set up init
6286
7126
  Auth & meta login, logout, signup, whoami, schema, docs
6287
7127
  Download download
6288
7128
  Check validate, diff, show
@@ -6296,6 +7136,9 @@ Version selectors (no defaults \u2014 a command that reads a config requires one
6296
7136
  --release <id> a specific playbook version / Release
6297
7137
 
6298
7138
  Common workflows:
7139
+ # Add RevTurbine to an app (same routine as \`npm create revturbine@latest\`)
7140
+ revturbine init
7141
+
6299
7142
  # Author, validate, and ship against the default instance (revturbine.com/app)
6300
7143
  revturbine login
6301
7144
  revturbine download --live --save ./config.json
@@ -6334,7 +7177,127 @@ program.hook("postAction", async (_thisCommand, actionCommand) => {
6334
7177
  command: actionCommand.name()
6335
7178
  });
6336
7179
  });
6337
- program.name("revturbine").description("Validate RevTurbine Config Files and ship them through the playbook-version lifecycle (draft \u2192 Release).").version(`${pkgVersion} (schema ${SCHEMA_VERSION})`, "-V, --version", "Print the revturbine and bundled schema versions").showHelpAfterError().addHelpText("after", HELP_AFTER);
7180
+ program.name("revturbine").description("Validate RevTurbine Config Files and ship them through the playbook-version lifecycle (draft \u2192 Release).").version(`${pkgVersion} (schema ${SCHEMA_VERSION})`, "-V, --version", "Print the revturbine and bundled schema versions").option(NO_LOCAL_FLAG, "Ignore the repo-pinned CLI and run this installation").showHelpAfterError().addHelpText("after", HELP_AFTER);
7181
+ function runInstall(manager, args, cwd) {
7182
+ return new Promise((resolve, reject) => {
7183
+ const child = spawn2(manager, args, { cwd, stdio: "inherit", shell: true });
7184
+ child.on("error", reject);
7185
+ child.on("close", (code) => resolve(code ?? 1));
7186
+ });
7187
+ }
7188
+ program.command("init").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) => {
7189
+ const dir = path2.resolve(opts.dir ?? process.cwd());
7190
+ 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
+ let manifest;
7195
+ try {
7196
+ manifest = JSON.parse(readFileSync2(manifestPath, "utf8"));
7197
+ } catch (err) {
7198
+ fail(EXIT.VALIDATION, `invalid JSON in ${manifestPath}: ${err.message}`);
7199
+ }
7200
+ const manager = detectPackageManager({
7201
+ files: readdirSync(dir),
7202
+ packageManagerField: manifest.packageManager,
7203
+ userAgent: process.env["npm_config_user_agent"]
7204
+ });
7205
+ const stack = detectStack(manifest);
7206
+ const plan = planInstall({
7207
+ dependencies: manifest.dependencies,
7208
+ devDependencies: manifest.devDependencies,
7209
+ cliVersion: pkgVersion
7210
+ });
7211
+ diag(`\u2713 Detected ${manager.name} (${manager.reason})`);
7212
+ if (stack !== "unknown") diag(`\u2713 Detected ${stack}`);
7213
+ for (const note of plan.skipped) diag(`\u2022 ${note}`);
7214
+ const playbookPath = path2.join(dir, STARTER_PLAYBOOK_FILENAME);
7215
+ const playbookExists = existsSync2(playbookPath);
7216
+ if (playbookExists) diag(`\u2022 ${STARTER_PLAYBOOK_FILENAME} already present \u2014 left as-is`);
7217
+ const installSkills = opts.skills !== false;
7218
+ if (opts.json) {
7219
+ emit(
7220
+ {
7221
+ dir,
7222
+ manager: manager.name,
7223
+ stack,
7224
+ install: plan.install,
7225
+ skipped: plan.skipped,
7226
+ playbook: playbookExists ? "present" : STARTER_PLAYBOOK_FILENAME,
7227
+ skills: installSkills ? SKILLS_SOURCE : "skipped"
7228
+ },
7229
+ true
7230
+ );
7231
+ }
7232
+ if (opts.dryRun) {
7233
+ for (const step of plan.install) {
7234
+ diag(`would run: ${manager.name} ${installArgs(manager.name, step).join(" ")}`);
7235
+ }
7236
+ if (!playbookExists) diag(`would write: ${STARTER_PLAYBOOK_FILENAME}`);
7237
+ if (installSkills) diag(`would run: npx ${skillsAddArgs().join(" ")}`);
7238
+ if (plan.install.length === 0 && playbookExists) diag("\u2713 Already set up \u2014 nothing to do.");
7239
+ return;
7240
+ }
7241
+ for (const step of plan.install) {
7242
+ const args = installArgs(manager.name, step);
7243
+ diag(`${manager.name} ${args.join(" ")}`);
7244
+ let code;
7245
+ try {
7246
+ code = await runInstall(manager.name, args, dir);
7247
+ } catch (err) {
7248
+ fail(EXIT.UNEXPECTED, `could not run ${manager.name}: ${err.message}`);
7249
+ }
7250
+ if (code !== 0) fail(EXIT.UNEXPECTED, `${manager.name} ${args.join(" ")} failed (exit ${code}).`);
7251
+ }
7252
+ if (plan.install.length > 0) {
7253
+ diag(`\u2713 Installed the RevTurbine SDK and CLI (CLI pinned to ${pkgVersion})`);
7254
+ }
7255
+ const playbookAdded = !playbookExists;
7256
+ if (playbookAdded) {
7257
+ const check = validatePlaybook(STARTER_PLAYBOOK);
7258
+ if (!check.ok) {
7259
+ fail(
7260
+ EXIT.VALIDATION,
7261
+ `the bundled starter Playbook failed validation against schema ${SCHEMA_VERSION} \u2014 this is a CLI bug, please report it.`
7262
+ );
7263
+ }
7264
+ writeFileSync2(playbookPath, `${JSON.stringify(STARTER_PLAYBOOK, null, 2)}
7265
+ `, "utf8");
7266
+ diag(`\u2713 Added a starter playbook (${STARTER_PLAYBOOK_FILENAME} \u2014 local mode, no account needed)`);
7267
+ }
7268
+ let skillsOutcome = installSkills ? "installed" : "skipped";
7269
+ if (installSkills) {
7270
+ diag("Installing the RevTurbine Agent Skills (npx skills)\u2026");
7271
+ const args = skillsAddArgs();
7272
+ let code;
7273
+ try {
7274
+ code = await runInstall("npx", args, dir);
7275
+ } catch {
7276
+ code = 1;
7277
+ }
7278
+ if (code === 0) {
7279
+ diag("\u2713 Installed the RevTurbine Agent Skills (some write app code \u2014 review before running them)");
7280
+ } else {
7281
+ skillsOutcome = "failed";
7282
+ diag("\u26A0 Could not install the Agent Skills automatically. Add them by hand:");
7283
+ diag(` npx ${args.join(" ")}`);
7284
+ }
7285
+ } else {
7286
+ diag("\u2022 Skipping the Agent Skills (--no-skills)");
7287
+ }
7288
+ if (!opts.json) {
7289
+ const lines = finalOutputLines({
7290
+ managerLabel: manager.name,
7291
+ installedSdkAndCli: plan.install.length > 0,
7292
+ cliVersion: pkgVersion,
7293
+ playbookAdded,
7294
+ skills: skillsOutcome,
7295
+ harness: detectHarness(process.env)
7296
+ });
7297
+ process.stdout.write(`${lines.join("\n")}
7298
+ `);
7299
+ }
7300
+ });
6338
7301
  program.command("login").description("Authorize this machine via the browser (device flow) and store a token for the instance (default: the production instance).").argument("[url]", `RevTurbine instance URL (default: ${DEFAULT_URL})`).action(async (url) => {
6339
7302
  try {
6340
7303
  const base = normalizeBaseUrl(url ?? DEFAULT_URL);
@@ -6490,6 +7453,7 @@ program.command("download").description("Fetch a config version from the server.
6490
7453
  return;
6491
7454
  }
6492
7455
  const config = await downloadConfig(conn, playbookVersionId);
7456
+ diag(`Downloaded: ${describePlaybookHeader(readPlaybookHeader(config))}`);
6493
7457
  const out = `${JSON.stringify(config, null, 2)}
6494
7458
  `;
6495
7459
  if (opts.save) {
@@ -6511,11 +7475,12 @@ program.command("validate").description("Validate a Config File offline (schema)
6511
7475
  let blockedFiles = 0;
6512
7476
  for (const file of files) {
6513
7477
  const raw = loadConfig(file);
6514
- const parsed = schema.safeParse(raw);
7478
+ const { schema: fileSchema, shape } = schemaForConfig(raw);
7479
+ const parsed = fileSchema.safeParse(raw);
6515
7480
  const findings = evaluate(parsed.success ? parsed.data : raw, {
6516
7481
  structuralErrors: parsed.success ? void 0 : parsed.error
6517
7482
  });
6518
- diag(`Validation for ${file} (offline, schema ${SCHEMA_VERSION}):`);
7483
+ diag(`Validation for ${file} (offline, ${shape} shape, schema ${SCHEMA_VERSION}):`);
6519
7484
  process.stdout.write(`${formatFindings(findings)}
6520
7485
  `);
6521
7486
  if (hasBlockingFindings(findings)) blockedFiles += 1;
@@ -6757,6 +7722,58 @@ var COMMAND_EXAMPLES = {
6757
7722
  for (const [name, text] of Object.entries(COMMAND_EXAMPLES)) {
6758
7723
  program.commands.find((c) => c.name() === name)?.addHelpText("after", text);
6759
7724
  }
7725
+ function findRepoPinnedCli(from) {
7726
+ const start = path2.resolve(from);
7727
+ const stopAt = gitRootOf(start) ?? start;
7728
+ let dir = start;
7729
+ for (; ; ) {
7730
+ const pkgDir = path2.join(dir, "node_modules", "@revturbine", "cli");
7731
+ const pkgJson = path2.join(pkgDir, "package.json");
7732
+ if (existsSync2(pkgJson)) {
7733
+ try {
7734
+ const pkg = JSON.parse(readFileSync2(pkgJson, "utf8"));
7735
+ const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.revturbine;
7736
+ if (rel) {
7737
+ const entry = path2.resolve(pkgDir, rel);
7738
+ if (existsSync2(entry)) return { entry, version: pkg.version ?? "0.0.0" };
7739
+ }
7740
+ } catch {
7741
+ }
7742
+ return null;
7743
+ }
7744
+ if (dir === stopAt) return null;
7745
+ const parent = path2.dirname(dir);
7746
+ if (parent === dir) return null;
7747
+ dir = parent;
7748
+ }
7749
+ }
7750
+ function gitRootOf(from) {
7751
+ let dir = path2.resolve(from);
7752
+ for (; ; ) {
7753
+ if (existsSync2(path2.join(dir, ".git"))) return dir;
7754
+ const parent = path2.dirname(dir);
7755
+ if (parent === dir) return null;
7756
+ dir = parent;
7757
+ }
7758
+ }
7759
+ {
7760
+ const ownEntry = fileURLToPath(import.meta.url);
7761
+ const decision = planDelegation({
7762
+ ownEntry,
7763
+ ownVersion: pkgVersion,
7764
+ local: findRepoPinnedCli(process.cwd()),
7765
+ env: process.env,
7766
+ argv: process.argv
7767
+ });
7768
+ if (decision.delegate) {
7769
+ if (decision.skew) diag(skewNotice(decision.skew));
7770
+ const child = spawnSync(process.execPath, [decision.target, ...process.argv.slice(2)], {
7771
+ stdio: "inherit",
7772
+ env: { ...process.env, [DELEGATION_ENV]: "1" }
7773
+ });
7774
+ process.exit(child.status ?? EXIT.UNEXPECTED);
7775
+ }
7776
+ }
6760
7777
  program.exitOverride();
6761
7778
  try {
6762
7779
  await program.parseAsync(process.argv);