@farthershore/product 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -154,15 +154,9 @@ var planLimitsSchema = z.array(planLimitRuleSchema).max(20);
154
154
 
155
155
  // ../contracts/dist/plans/spec/subscriber-change-policy.js
156
156
  import { z as z2 } from "zod";
157
- var subscriberChangeActionSchema = z2.enum([
158
- "preserve_current_period",
159
- "switch_immediately",
160
- "switch_immediately_prorate",
161
- "new_subscribers_only"
162
- ]);
157
+ var subscriberChangeActionSchema = z2.enum(["immediate", "period_end"]);
163
158
  var subscriberChangePolicySchema = z2.object({
164
- default: subscriberChangeActionSchema.default("preserve_current_period"),
165
- proration: z2.enum(["none", "prorate", "credit"]).default("none"),
159
+ default: subscriberChangeActionSchema.default("period_end"),
166
160
  when: z2.object({
167
161
  price_increase: subscriberChangeActionSchema.optional(),
168
162
  price_decrease: subscriberChangeActionSchema.optional(),
@@ -174,24 +168,20 @@ var subscriberChangePolicySchema = z2.object({
174
168
  credit_reduced: subscriberChangeActionSchema.optional(),
175
169
  rating_changed: subscriberChangeActionSchema.optional()
176
170
  }).strict().default({
177
- price_increase: "preserve_current_period",
178
- price_decrease: "switch_immediately",
179
- feature_added: "switch_immediately",
180
- feature_removed: "preserve_current_period",
181
- limit_increased: "switch_immediately",
182
- limit_reduced: "preserve_current_period",
183
- credit_increased: "switch_immediately",
184
- credit_reduced: "preserve_current_period",
185
- rating_changed: "preserve_current_period"
171
+ price_increase: "period_end",
172
+ price_decrease: "immediate",
173
+ feature_added: "immediate",
174
+ feature_removed: "period_end",
175
+ limit_increased: "immediate",
176
+ limit_reduced: "period_end",
177
+ credit_increased: "immediate",
178
+ credit_reduced: "period_end",
179
+ rating_changed: "period_end"
186
180
  }),
187
181
  allowImmediatePriceIncrease: z2.boolean().default(false),
188
182
  allowImmediateEntitlementReduction: z2.boolean().default(false)
189
183
  }).strict().superRefine((policy, ctx) => {
190
- const immediate = /* @__PURE__ */ new Set([
191
- "switch_immediately",
192
- "switch_immediately_prorate"
193
- ]);
194
- if (immediate.has(policy.when.price_increase ?? policy.default) && !policy.allowImmediatePriceIncrease) {
184
+ if ((policy.when.price_increase ?? policy.default) === "immediate" && !policy.allowImmediatePriceIncrease) {
195
185
  ctx.addIssue({
196
186
  code: "custom",
197
187
  path: ["when", "price_increase"],
@@ -203,7 +193,7 @@ var subscriberChangePolicySchema = z2.object({
203
193
  "limit_reduced",
204
194
  "credit_reduced"
205
195
  ]) {
206
- if (immediate.has(policy.when[key] ?? policy.default) && !policy.allowImmediateEntitlementReduction) {
196
+ if ((policy.when[key] ?? policy.default) === "immediate" && !policy.allowImmediateEntitlementReduction) {
207
197
  ctx.addIssue({
208
198
  code: "custom",
209
199
  path: ["when", key],
@@ -214,30 +204,22 @@ var subscriberChangePolicySchema = z2.object({
214
204
  });
215
205
 
216
206
  // ../contracts/dist/plans/spec/plan-pricing.js
217
- import { z as z4 } from "zod";
207
+ import { z as z5 } from "zod";
218
208
 
219
209
  // ../contracts/dist/plans/grants.js
220
210
  import { z as z3 } from "zod";
221
- var recurringGrantSchema = z3.object({
222
- kind: z3.literal("recurring"),
223
- amount_cents: z3.number().int().nonnegative()
224
- });
225
- var oneTimeGrantSchema = z3.object({
226
- kind: z3.literal("one_time"),
227
- amount_cents: z3.number().int().nonnegative()
228
- });
229
- var promotionalGrantSchema = z3.object({
230
- kind: z3.literal("promotional"),
211
+ var creditGrantSchema = z3.object({
212
+ kind: z3.literal("credit"),
231
213
  amount_cents: z3.number().int().nonnegative(),
232
- label: z3.string().min(1).max(120),
233
- expires_after_days: z3.number().int().positive().optional()
234
- });
235
- var trialGrantSchema = z3.object({
236
- kind: z3.literal("trial")
237
- });
238
- var rolloverGrantSchema = z3.object({
239
- kind: z3.literal("rollover"),
240
- percent: z3.number().int().min(0).max(100)
214
+ /** When true, the credit is re-granted at every period rollover. Default
215
+ * false = a one-shot grant at subscription start. */
216
+ recurs: z3.boolean().optional(),
217
+ /** Optional expiry — Stripe expires the grant this many days after issue.
218
+ * Mainly used by promotional credits to prevent indefinite carry-forward. */
219
+ expires_after_days: z3.number().int().positive().optional(),
220
+ /** Optional campaign label. A credit carrying a label is a promotional /
221
+ * ops-issued grant rather than the canonical recurring/one-time credit. */
222
+ label: z3.string().min(1).max(120).optional()
241
223
  });
242
224
  var topUpGrantSchema = z3.object({
243
225
  kind: z3.literal("top_up"),
@@ -250,67 +232,75 @@ var topUpGrantSchema = z3.object({
250
232
  * Typically >= price_cents (the difference is the bulk discount). */
251
233
  credit_cents: z3.number().int().positive()
252
234
  });
253
- var autoRechargeGrantSchema = z3.object({
254
- kind: z3.literal("auto_recharge"),
255
- /** When current balance < this many cents, trigger a refill. */
256
- threshold_cents: z3.number().int().nonnegative(),
257
- /** Charge + grant this much on each refill, in cents. */
258
- refill_cents: z3.number().int().positive()
259
- });
260
235
  var grantSchema = z3.discriminatedUnion("kind", [
261
- recurringGrantSchema,
262
- oneTimeGrantSchema,
263
- promotionalGrantSchema,
264
- trialGrantSchema,
265
- rolloverGrantSchema,
266
- topUpGrantSchema,
267
- autoRechargeGrantSchema
236
+ creditGrantSchema,
237
+ topUpGrantSchema
268
238
  ]);
239
+ function isRecurringCredit(g) {
240
+ return g.kind === "credit" && g.recurs === true && g.label === void 0;
241
+ }
242
+ function isOneTimeCredit(g) {
243
+ return g.kind === "credit" && g.recurs !== true && g.label === void 0;
244
+ }
269
245
  function refineSingleCanonicalGrant(grants, ctx, path = ["grants"]) {
270
246
  if (!grants)
271
247
  return;
272
248
  let recurringCount = 0;
273
249
  let oneTimeCount = 0;
274
250
  for (const g of grants) {
275
- if (g.kind === "recurring")
251
+ if (isRecurringCredit(g))
276
252
  recurringCount += 1;
277
- else if (g.kind === "one_time")
253
+ else if (isOneTimeCredit(g))
278
254
  oneTimeCount += 1;
279
255
  }
280
256
  if (recurringCount > 1) {
281
257
  ctx.addIssue({
282
258
  code: "custom",
283
- message: "At most one `recurring` grant is allowed \u2014 recurring credit is a single canonical entry.",
259
+ message: "At most one recurring `credit` grant (recurs:true, no label) is allowed \u2014 recurring credit is a single canonical entry.",
284
260
  path
285
261
  });
286
262
  }
287
263
  if (oneTimeCount > 1) {
288
264
  ctx.addIssue({
289
265
  code: "custom",
290
- message: "At most one `one_time` grant is allowed \u2014 one-time credit is a single canonical entry.",
266
+ message: "At most one one-time `credit` grant (recurs:false, no label) is allowed \u2014 one-time credit is a single canonical entry.",
291
267
  path
292
268
  });
293
269
  }
294
270
  }
295
271
 
272
+ // ../contracts/dist/plans/credit-policy.js
273
+ import { z as z4 } from "zod";
274
+ var rolloverPolicySchema = z4.object({
275
+ percent: z4.number().int().min(0).max(100)
276
+ });
277
+ var autoRechargePolicySchema = z4.object({
278
+ threshold_cents: z4.number().int().nonnegative(),
279
+ refill_cents: z4.number().int().positive()
280
+ });
281
+ var creditPolicySchema = z4.object({
282
+ rollover: rolloverPolicySchema.optional(),
283
+ auto_recharge: autoRechargePolicySchema.optional()
284
+ });
285
+
296
286
  // ../contracts/dist/plans/spec/plan-pricing.js
297
- var meterKindSchema = z4.enum(["linear", "active_count"]);
298
- var meterTierSchema = z4.object({
299
- up_to: z4.number().int().positive().nullable(),
300
- price_per_unit_micros: z4.number().int().nonnegative()
287
+ var meterKindSchema = z5.enum(["linear", "active_count"]);
288
+ var meterTierSchema = z5.object({
289
+ up_to: z5.number().int().positive().nullable(),
290
+ price_per_unit_micros: z5.number().int().nonnegative()
301
291
  });
302
- var tieredPricingSchema = z4.object({
303
- strategy: z4.enum(["graduated", "volume"]),
304
- tiers: z4.array(meterTierSchema).min(1).max(20)
292
+ var tieredPricingSchema = z5.object({
293
+ strategy: z5.enum(["graduated", "volume"]),
294
+ tiers: z5.array(meterTierSchema).min(1).max(20)
305
295
  });
306
- var meterSchema = z4.object({
307
- dimension: z4.string().min(1).max(64).regex(/^[a-z0-9_]+$/, "Meter dimension must be lowercase alphanumeric with underscores"),
296
+ var meterSchema = z5.object({
297
+ dimension: z5.string().min(1).max(64).regex(/^[a-z0-9_]+$/, "Meter dimension must be lowercase alphanumeric with underscores"),
308
298
  /** Aggregation kind. `linear` (default) sums event quantities across
309
299
  * the period. `active_count` samples the latest quantity in the
310
300
  * period (use for seats / active-user / occupancy snapshots). */
311
301
  kind: meterKindSchema.default("linear"),
312
302
  /** Flat per-unit rate. Mutually exclusive with `tiered`. */
313
- price_per_unit_micros: z4.number().int().nonnegative().default(0),
303
+ price_per_unit_micros: z5.number().int().nonnegative().default(0),
314
304
  /** Tiered schedule. Mutually exclusive with a non-zero
315
305
  * `price_per_unit_micros`. */
316
306
  tiered: tieredPricingSchema.optional(),
@@ -324,7 +314,7 @@ var meterSchema = z4.object({
324
314
  * Mutually exclusive with `tiered` — a tiered schedule already
325
315
  * expresses the full breakpoint ladder (model the free pool as a
326
316
  * zero-priced first tier instead). Only meaningful on a flat meter. */
327
- included_units: z4.number().int().positive().optional()
317
+ included_units: z5.number().int().positive().optional()
328
318
  }).superRefine((m, ctx) => {
329
319
  if (m.tiered && m.price_per_unit_micros > 0) {
330
320
  ctx.addIssue({
@@ -375,99 +365,106 @@ var meterSchema = z4.object({
375
365
  }
376
366
  }
377
367
  });
378
- var billingIntervalSchema = z4.enum(["month", "year"]);
379
- var planPricingSchema = z4.object({
368
+ var billingIntervalSchema = z5.enum(["month", "year"]);
369
+ var planPricingSchema = z5.object({
380
370
  /** Per-dimension price list. Empty array = no metered billing. */
381
- meters: z4.array(meterSchema).default([]),
371
+ meters: z5.array(meterSchema).default([]),
382
372
  /** Flat subscription fee per period, in cents. 0 = no recurring fee. */
383
- recurring_fee_cents: z4.number().int().nonnegative().default(0),
373
+ recurring_fee_cents: z5.number().int().nonnegative().default(0),
384
374
  /** Billing cadence for the recurring fee + metered usage. Defaults to
385
375
  * `month`; set `year` for annual billing (drives both the Stripe price
386
376
  * `recurring.interval` and the spend-cap / quota billing-period window
387
377
  * length). */
388
378
  billing_interval: billingIntervalSchema.default("month"),
389
379
  /** Unified credit-grant array — the SINGLE credit surface. Recurring +
390
- * one-time credit are canonical entries here (the legacy scalar knobs
391
- * were removed). */
392
- grants: z4.array(grantSchema).max(40).default([]),
380
+ * one-time credit are canonical `credit` entries here (the legacy scalar
381
+ * knobs were removed). */
382
+ grants: z5.array(grantSchema).max(40).default([]),
383
+ /** Non-grant credit policies (rollover + auto-recharge). Optional; absent =
384
+ * no rollover / no auto-recharge. */
385
+ creditPolicy: creditPolicySchema.optional(),
393
386
  /** Free-trial length in days. Trial enforcement runs through Stripe
394
387
  * webhooks → `lifecycle_block` constraint at runtime. */
395
- trial_days: z4.number().int().nonnegative().default(0),
388
+ trial_days: z5.number().int().nonnegative().default(0),
396
389
  /** Optional hard cap on monthly spend, in cents. Orthogonal to the
397
390
  * billing math — the gateway blocks once this cap is reached even if
398
391
  * credit balance remains. Stripe doesn't enforce this; the runtime
399
392
  * does, via a `quota`-shaped constraint. */
400
- max_monthly_spend_cents: z4.number().int().nonnegative().optional()
393
+ max_monthly_spend_cents: z5.number().int().nonnegative().optional()
401
394
  });
402
395
 
403
396
  // ../contracts/dist/plans/spec/plan-variant.js
404
- import { z as z5 } from "zod";
405
- var proRationOnRollbackSchema = z5.enum(["NONE", "PRORATE", "CREDIT"]).default("NONE");
397
+ import { z as z6 } from "zod";
398
+ var proRationOnRollbackSchema = z6.enum(["NONE", "PRORATE", "CREDIT"]).default("NONE");
406
399
  var billingKnobsShape = {
407
- meters: z5.array(meterSchema).default([]),
408
- recurring_fee_cents: z5.number().int().nonnegative().default(0),
400
+ meters: z6.array(meterSchema).default([]),
401
+ recurring_fee_cents: z6.number().int().nonnegative().default(0),
409
402
  /** Billing cadence (`month` default / `year`). Drives the Stripe price
410
403
  * `recurring.interval` and the entitlement billing-period window. */
411
404
  billing_interval: billingIntervalSchema.default("month"),
412
- trial_days: z5.number().int().nonnegative().default(0),
405
+ trial_days: z6.number().int().nonnegative().default(0),
413
406
  /** Hard maximum on metered spend per period. Gateway blocks once
414
407
  * cumulative usage cost reaches this cap. Orthogonal to the
415
408
  * recurring fee — the fee is always charged. */
416
- max_monthly_spend_cents: z5.number().int().nonnegative().optional(),
409
+ max_monthly_spend_cents: z6.number().int().nonnegative().optional(),
417
410
  /** Minimum metered spend per period. When set, the invoice floors
418
411
  * the metered total at this value (Railway-style minimum-spend).
419
412
  * Orthogonal to `recurring_fee_cents` — the fee is added on top.
420
413
  * Set to 0 / omit to disable. */
421
- min_monthly_spend_cents: z5.number().int().nonnegative().optional(),
414
+ min_monthly_spend_cents: z6.number().int().nonnegative().optional(),
422
415
  /**
423
- * Unified grants array — the SINGLE credit surface (v0.56+). Each entry
424
- * expresses one credit grant primitive: `recurring`, `one_time`,
425
- * `promotional`, `trial`, `rollover`, `top_up`, `auto_recharge`.
416
+ * Unified grants array — the SINGLE credit surface. Each entry expresses
417
+ * one credit grant primitive: a parametrized `credit` (recurring /
418
+ * one-time / promotional, distinguished by `recurs` / `label`) or a
419
+ * purchasable `top_up` pack.
426
420
  *
427
- * Recurring + one-time credit are declared here as canonical
428
- * `{kind:"recurring"}` / `{kind:"one_time"}` entries (at most one of
429
- * each — enforced by `refineSingleCanonicalGrant`). The legacy
430
- * `recurring_credit_grant_cents` / `one_time_credit_grant_cents` scalar
431
- * knobs were removed; call `getEffectiveGrants(plan)` to read the
432
- * consolidated list.
421
+ * Recurring + one-time credit are declared here as canonical `credit`
422
+ * entries (at most one recurring + one one-time, enforced by
423
+ * `refineSingleCanonicalGrant`); call `getEffectiveGrants(plan)` to read
424
+ * the consolidated list. Trial is driven by `trial_days`; rollover +
425
+ * auto-recharge live in `creditPolicy`.
433
426
  */
434
- grants: z5.array(grantSchema).max(40).default([])
427
+ grants: z6.array(grantSchema).max(40).default([]),
428
+ /** Non-grant credit policies (rollover + auto-recharge). */
429
+ creditPolicy: creditPolicySchema.optional()
435
430
  };
436
431
  var billingKnobsOptionalShape = {
437
- meters: z5.array(meterSchema).optional(),
438
- recurring_fee_cents: z5.number().int().nonnegative().optional(),
432
+ meters: z6.array(meterSchema).optional(),
433
+ recurring_fee_cents: z6.number().int().nonnegative().optional(),
439
434
  /** Variant override for the billing cadence. Absent → inherit the
440
435
  * parent plan's `billing_interval`. */
441
436
  billing_interval: billingIntervalSchema.optional(),
442
- trial_days: z5.number().int().nonnegative().optional(),
443
- max_monthly_spend_cents: z5.number().int().nonnegative().optional(),
444
- min_monthly_spend_cents: z5.number().int().nonnegative().optional(),
437
+ trial_days: z6.number().int().nonnegative().optional(),
438
+ max_monthly_spend_cents: z6.number().int().nonnegative().optional(),
439
+ min_monthly_spend_cents: z6.number().int().nonnegative().optional(),
445
440
  /** Variant override for the grants array — the single credit surface.
446
441
  * When present, fully replaces the parent plan's grants (no shallow
447
442
  * merge — grants are collectively meaningful). */
448
- grants: z5.array(grantSchema).max(40).optional()
443
+ grants: z6.array(grantSchema).max(40).optional(),
444
+ /** Variant override for the credit-policy block. */
445
+ creditPolicy: creditPolicySchema.optional()
449
446
  };
450
- var planVariantObjectSchema = z5.object({
447
+ var planVariantObjectSchema = z6.object({
451
448
  /**
452
449
  * Stable variant id — used as the second half of the CompiledPlan
453
450
  * lineage key, so changing it counts as create-new + archive-old.
454
451
  * Lower-case kebab-case to match URL-share-link readability.
455
452
  */
456
- id: z5.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Variant id must be lowercase alphanumeric with hyphens/underscores"),
453
+ id: z6.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Variant id must be lowercase alphanumeric with hyphens/underscores"),
457
454
  /** Human-readable label for dashboards / observability. */
458
- label: z5.string().max(200).optional(),
455
+ label: z6.string().max(200).optional(),
459
456
  /**
460
457
  * Rollout percentage (0-100). 0 = paused (variant exists but no new
461
458
  * assignments); 100 = full takeover (effectively a forced graduation
462
459
  * for new subscribers, but legacy subs stay on parent until period end).
463
460
  */
464
- rolloutPercent: z5.number().int().min(0).max(100),
461
+ rolloutPercent: z6.number().int().min(0).max(100),
465
462
  /**
466
463
  * Seed for the deterministic hash function. Rotating the seed
467
464
  * invalidates existing variant assignments — useful for re-running an
468
465
  * experiment with a fresh cohort.
469
466
  */
470
- assignmentSeed: z5.string().min(1).max(100).default("default"),
467
+ assignmentSeed: z6.string().min(1).max(100).default("default"),
471
468
  /**
472
469
  * What happens to billing when this variant gets rolled back AND the
473
470
  * subscriber has already been billed for the experimental price:
@@ -481,22 +478,22 @@ var planVariantObjectSchema = z5.object({
481
478
  // plan. The pre-0.53 `pricing: planPricingSchema.optional()` field is
482
479
  // gone; variants now override the knobs directly.
483
480
  ...billingKnobsOptionalShape,
484
- limits: z5.array(planLimitRuleSchema).max(20).optional(),
485
- featureGates: z5.record(z5.string(), z5.boolean()).optional(),
481
+ limits: z6.array(planLimitRuleSchema).max(20).optional(),
482
+ featureGates: z6.record(z6.string(), z6.boolean()).optional(),
486
483
  /** See `planSpecSchema.capability_limits`. Variant overrides are
487
484
  * merged shallowly onto the parent plan's map — missing keys
488
485
  * inherit from the parent. */
489
- capability_limits: z5.record(z5.string().min(1).max(120), z5.union([z5.number().int().nonnegative(), z5.boolean()])).optional(),
490
- overageBehavior: z5.enum(["block", "allow_and_bill"]).optional()
486
+ capability_limits: z6.record(z6.string().min(1).max(120), z6.union([z6.number().int().nonnegative(), z6.boolean()])).optional(),
487
+ overageBehavior: z6.enum(["block", "allow_and_bill"]).optional()
491
488
  });
492
489
  var planVariantSchema = planVariantObjectSchema.superRefine((variant, ctx) => {
493
490
  refineSingleCanonicalGrant(variant.grants, ctx);
494
491
  });
495
- var planSpecObjectSchema = z5.object({
496
- key: z5.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Plan key must be lowercase alphanumeric with hyphens/underscores"),
497
- name: z5.string().min(1).max(100),
498
- description: z5.string().max(500).optional(),
499
- details: z5.array(z5.string().max(200)).max(10).optional(),
492
+ var planSpecObjectSchema = z6.object({
493
+ key: z6.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Plan key must be lowercase alphanumeric with hyphens/underscores"),
494
+ name: z6.string().min(1).max(100),
495
+ description: z6.string().max(500).optional(),
496
+ details: z6.array(z6.string().max(200)).max(10).optional(),
500
497
  // ---------------------------------------------------------------------
501
498
  // 5-knob billing shape (v0.53.0)
502
499
  // ---------------------------------------------------------------------
@@ -510,7 +507,7 @@ var planSpecObjectSchema = z5.object({
510
507
  // runs through Stripe webhooks → `lifecycle_block` constraint at
511
508
  // runtime; there's no `trial_expiry` constraint kind anymore.
512
509
  ...billingKnobsShape,
513
- free: z5.boolean().default(false),
510
+ free: z6.boolean().default(false),
514
511
  // `plan.features` removed in feat/feature-plans-link — features
515
512
  // declare their plans via `feature.plans[]` (canonical, feature-first
516
513
  // direction). Builders writing the manifest now go to the feature
@@ -518,8 +515,8 @@ var planSpecObjectSchema = z5.object({
518
515
  // compiler walks `spec.features` and includes a feature's routes for
519
516
  // a plan when the plan's key appears in `feature.plans[]`. See
520
517
  // shared-types/src/plans/spec/product.ts (featureCatalogEntrySchema).
521
- limits: z5.array(planLimitRuleSchema).max(20).default([]),
522
- featureGates: z5.record(z5.string(), z5.boolean()).optional(),
518
+ limits: z6.array(planLimitRuleSchema).max(20).default([]),
519
+ featureGates: z6.record(z6.string(), z6.boolean()).optional(),
523
520
  /**
524
521
  * Control-plane capability limits (Phase 1a, v0.56+).
525
522
  *
@@ -549,9 +546,9 @@ var planSpecObjectSchema = z5.object({
549
546
  * environments: 1
550
547
  * enterprise_sso: true
551
548
  */
552
- capability_limits: z5.record(z5.string().min(1).max(120), z5.union([z5.number().int().nonnegative(), z5.boolean()])).default({}),
553
- overageBehavior: z5.enum(["block", "allow_and_bill"]).default("block"),
554
- selfServeEnabled: z5.boolean().default(true),
549
+ capability_limits: z6.record(z6.string().min(1).max(120), z6.union([z6.number().int().nonnegative(), z6.boolean()])).default({}),
550
+ overageBehavior: z6.enum(["block", "allow_and_bill"]).default("block"),
551
+ selfServeEnabled: z6.boolean().default(true),
555
552
  /**
556
553
  * Phase A0 — multi-stable plan support.
557
554
  *
@@ -563,7 +560,7 @@ var planSpecObjectSchema = z5.object({
563
560
  * from YAML while subs are pinned to it is a compile error (would
564
561
  * orphan the cohort).
565
562
  */
566
- legacy: z5.boolean().optional().default(false),
563
+ legacy: z6.boolean().optional().default(false),
567
564
  /**
568
565
  * Phase A0 — A/B testing variants of this plan. Each variant compiles
569
566
  * into a sibling `CompiledPlan` with status `EXPERIMENTAL` and
@@ -573,11 +570,11 @@ var planSpecObjectSchema = z5.object({
573
570
  * - Cannot coexist with `legacy: true` on the same plan
574
571
  * - Variant `id` must be unique within the variants array
575
572
  */
576
- variants: z5.array(planVariantSchema).max(4).optional(),
577
- archive: z5.object({
578
- at: z5.string().datetime().optional(),
579
- transitionTo: z5.string().optional(),
580
- strategy: z5.enum(["auto", "explicit", "block"]).default("auto")
573
+ variants: z6.array(planVariantSchema).max(4).optional(),
574
+ archive: z6.object({
575
+ at: z6.string().datetime().optional(),
576
+ transitionTo: z6.string().optional(),
577
+ strategy: z6.enum(["auto", "explicit", "block"]).default("auto")
581
578
  }).optional()
582
579
  });
583
580
  var planSpecSchema = planSpecObjectSchema.superRefine((plan, ctx) => {
@@ -585,10 +582,10 @@ var planSpecSchema = planSpecObjectSchema.superRefine((plan, ctx) => {
585
582
  });
586
583
 
587
584
  // ../contracts/dist/plans/spec/webhooks.js
588
- import { z as z7 } from "zod";
585
+ import { z as z8 } from "zod";
589
586
 
590
587
  // ../contracts/dist/webhooks/events.js
591
- import { z as z6 } from "zod";
588
+ import { z as z7 } from "zod";
592
589
  var WEBHOOK_EVENT_NAMES = [
593
590
  "subscription.created",
594
591
  "subscription.updated",
@@ -599,26 +596,26 @@ var WEBHOOK_EVENT_NAMES = [
599
596
  "entitlement.changed",
600
597
  "usage.threshold_reached"
601
598
  ];
602
- var webhookEventNameSchema = z6.enum(WEBHOOK_EVENT_NAMES);
599
+ var webhookEventNameSchema = z7.enum(WEBHOOK_EVENT_NAMES);
603
600
 
604
601
  // ../contracts/dist/plans/spec/webhooks.js
605
602
  var WEBHOOK_SECRET_PLACEHOLDER_PATTERN = /^\$\{[A-Z][A-Z0-9_]{0,127}\}$/;
606
- var webhookSecretSchema = z7.string().min(3).max(200).refine((value) => WEBHOOK_SECRET_PLACEHOLDER_PATTERN.test(value), {
603
+ var webhookSecretSchema = z8.string().min(3).max(200).refine((value) => WEBHOOK_SECRET_PLACEHOLDER_PATTERN.test(value), {
607
604
  message: "secret must use ${VAR} interpolation syntax (e.g. ${WEBHOOK_SECRET}); raw secrets in YAML are rejected. Define the env var in the per-product secret store and reference it here."
608
605
  });
609
- var webhookRetryPolicySchema = z7.object({
610
- maxAttempts: z7.number().int().min(1).max(20).default(5),
611
- backoff: z7.enum(["exponential", "fixed"]).default("exponential")
606
+ var webhookRetryPolicySchema = z8.object({
607
+ maxAttempts: z8.number().int().min(1).max(20).default(5),
608
+ backoff: z8.enum(["exponential", "fixed"]).default("exponential")
612
609
  });
613
- var webhookEndpointSchema = z7.object({
610
+ var webhookEndpointSchema = z8.object({
614
611
  /**
615
612
  * Stable endpoint id — used as the third key of the
616
613
  * `(productId, environmentId, id)` uniqueness tuple. Idempotent upsert
617
614
  * keys on this id; renaming an id is delete + recreate.
618
615
  */
619
- id: z7.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Webhook endpoint id must be lowercase alphanumeric with hyphens/underscores"),
616
+ id: z8.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Webhook endpoint id must be lowercase alphanumeric with hyphens/underscores"),
620
617
  /** Public HTTPS URL the dispatcher POSTs to. */
621
- url: z7.string().url("webhooks.endpoints[].url must be a valid URL"),
618
+ url: z8.string().url("webhooks.endpoints[].url must be a valid URL"),
622
619
  /**
623
620
  * Signing secret. MUST be a `${VAR}` placeholder; raw secrets in YAML
624
621
  * are rejected by invariant 8. The seal pass resolves this against the
@@ -631,22 +628,22 @@ var webhookEndpointSchema = z7.object({
631
628
  * Each value is validated against `webhookEventNameSchema` —
632
629
  * unknown events fail invariant 9.
633
630
  */
634
- events: z7.array(webhookEventNameSchema).min(1, "webhooks.endpoints[].events must subscribe to \u2265 1 event"),
635
- enabled: z7.boolean().default(true),
631
+ events: z8.array(webhookEventNameSchema).min(1, "webhooks.endpoints[].events must subscribe to \u2265 1 event"),
632
+ enabled: z8.boolean().default(true),
636
633
  retryPolicy: webhookRetryPolicySchema.default({
637
634
  maxAttempts: 5,
638
635
  backoff: "exponential"
639
636
  })
640
637
  });
641
- var webhooksBlockSchema = z7.object({
642
- endpoints: z7.array(webhookEndpointSchema).max(50).default([])
638
+ var webhooksBlockSchema = z8.object({
639
+ endpoints: z8.array(webhookEndpointSchema).max(50).default([])
643
640
  });
644
641
 
645
642
  // ../contracts/dist/plans/spec/environments.js
646
- import { z as z8 } from "zod";
647
- var planOverrideSchema = z8.object({
648
- meters: z8.array(meterSchema).optional(),
649
- recurring_fee_cents: z8.number().int().nonnegative().optional(),
643
+ import { z as z9 } from "zod";
644
+ var planOverrideSchema = z9.object({
645
+ meters: z9.array(meterSchema).optional(),
646
+ recurring_fee_cents: z9.number().int().nonnegative().optional(),
650
647
  /** Per-env override for the billing cadence. Absent → inherit the
651
648
  * base plan's `billing_interval`. */
652
649
  billing_interval: billingIntervalSchema.optional(),
@@ -654,35 +651,38 @@ var planOverrideSchema = z8.object({
654
651
  * surface. When present it fully replaces the parent plan's grants
655
652
  * for this environment (the legacy recurring/one-time scalar knobs
656
653
  * were removed). */
657
- grants: z8.array(grantSchema).max(40).optional(),
658
- trial_days: z8.number().int().nonnegative().optional(),
659
- max_monthly_spend_cents: z8.number().int().nonnegative().optional(),
660
- limits: z8.array(planLimitRuleSchema).max(20).optional(),
661
- featureGates: z8.record(z8.string(), z8.boolean()).optional(),
662
- overageBehavior: z8.enum(["block", "allow_and_bill"]).optional(),
663
- selfServeEnabled: z8.boolean().optional(),
664
- legacy: z8.boolean().optional()
654
+ grants: z9.array(grantSchema).max(40).optional(),
655
+ /** Per-env override for the credit-policy block (rollover +
656
+ * auto-recharge). */
657
+ creditPolicy: creditPolicySchema.optional(),
658
+ trial_days: z9.number().int().nonnegative().optional(),
659
+ max_monthly_spend_cents: z9.number().int().nonnegative().optional(),
660
+ limits: z9.array(planLimitRuleSchema).max(20).optional(),
661
+ featureGates: z9.record(z9.string(), z9.boolean()).optional(),
662
+ overageBehavior: z9.enum(["block", "allow_and_bill"]).optional(),
663
+ selfServeEnabled: z9.boolean().optional(),
664
+ legacy: z9.boolean().optional()
665
665
  }).strict();
666
- var webhookEndpointOverrideSchema = z8.object({
667
- url: z8.string().url().optional(),
666
+ var webhookEndpointOverrideSchema = z9.object({
667
+ url: z9.string().url().optional(),
668
668
  secret: webhookSecretSchema.optional(),
669
- events: z8.array(webhookEventNameSchema).optional(),
670
- enabled: z8.boolean().optional(),
669
+ events: z9.array(webhookEventNameSchema).optional(),
670
+ enabled: z9.boolean().optional(),
671
671
  retryPolicy: webhookRetryPolicySchema.partial().optional()
672
672
  }).strict();
673
- var environmentOverrideBlockSchema = z8.object({
674
- plans: z8.record(z8.string(), planOverrideSchema).optional(),
675
- webhooks: z8.object({
676
- endpoints: z8.record(z8.string(), webhookEndpointOverrideSchema).optional()
673
+ var environmentOverrideBlockSchema = z9.object({
674
+ plans: z9.record(z9.string(), planOverrideSchema).optional(),
675
+ webhooks: z9.object({
676
+ endpoints: z9.record(z9.string(), webhookEndpointOverrideSchema).optional()
677
677
  }).strict().optional()
678
678
  }).strict();
679
- var environmentsBlockSchema = z8.record(z8.string().min(1).max(64), environmentOverrideBlockSchema);
679
+ var environmentsBlockSchema = z9.record(z9.string().min(1).max(64), environmentOverrideBlockSchema);
680
680
 
681
681
  // ../contracts/dist/plans/spec/product.js
682
- import { z as z18 } from "zod";
682
+ import { z as z19 } from "zod";
683
683
 
684
684
  // ../contracts/dist/plans/spec/frontend-layer.js
685
- import { z as z9 } from "zod";
685
+ import { z as z10 } from "zod";
686
686
  var FRONTEND_MANIFEST_SCHEMA_VERSION = 1;
687
687
  var KNOWN_FRONTEND_COMPONENT_IDS = [
688
688
  "plans_table",
@@ -712,31 +712,31 @@ var RESERVED_TEMPLATE_PATHS = [
712
712
  "/persona",
713
713
  "/persona-sign-in"
714
714
  ];
715
- var frontendPathSchema = z9.string().min(1).max(200).regex(/^\/[a-zA-Z0-9_./-]*$/, "path must start with / and contain only [a-zA-Z0-9_./-]");
716
- var frontendCapabilityRefSchema = z9.string().min(1).max(120).regex(/^[a-z0-9_-]+$/);
717
- var frontendNavItemSchema = z9.object({
718
- label: z9.string().min(1).max(80),
715
+ var frontendPathSchema = z10.string().min(1).max(200).regex(/^\/[a-zA-Z0-9_./-]*$/, "path must start with / and contain only [a-zA-Z0-9_./-]");
716
+ var frontendCapabilityRefSchema = z10.string().min(1).max(120).regex(/^[a-z0-9_-]+$/);
717
+ var frontendNavItemSchema = z10.object({
718
+ label: z10.string().min(1).max(80),
719
719
  path: frontendPathSchema,
720
720
  capability: frontendCapabilityRefSchema.optional()
721
721
  });
722
- var frontendComponentIdSchema = z9.enum(KNOWN_FRONTEND_COMPONENT_IDS);
723
- var frontendComponentSchema = z9.object({
722
+ var frontendComponentIdSchema = z10.enum(KNOWN_FRONTEND_COMPONENT_IDS);
723
+ var frontendComponentSchema = z10.object({
724
724
  component: frontendComponentIdSchema,
725
- props: z9.record(z9.string().min(1).max(80), z9.unknown()).optional(),
725
+ props: z10.record(z10.string().min(1).max(80), z10.unknown()).optional(),
726
726
  capability: frontendCapabilityRefSchema.optional(),
727
- gateMode: z9.enum(["hide", "disable", "upsell"]).default("hide")
727
+ gateMode: z10.enum(["hide", "disable", "upsell"]).default("hide")
728
728
  });
729
- var frontendPageSchema = z9.object({
729
+ var frontendPageSchema = z10.object({
730
730
  path: frontendPathSchema,
731
- title: z9.string().min(1).max(120),
732
- requiresAuth: z9.boolean(),
731
+ title: z10.string().min(1).max(120),
732
+ requiresAuth: z10.boolean(),
733
733
  capability: frontendCapabilityRefSchema.optional(),
734
- components: z9.array(frontendComponentSchema).max(12).default([])
734
+ components: z10.array(frontendComponentSchema).max(12).default([])
735
735
  });
736
- var frontendManifestSchema = z9.object({
737
- version: z9.literal(FRONTEND_MANIFEST_SCHEMA_VERSION),
738
- nav: z9.array(frontendNavItemSchema).max(12).default([]),
739
- pages: z9.array(frontendPageSchema).max(24).default([])
736
+ var frontendManifestSchema = z10.object({
737
+ version: z10.literal(FRONTEND_MANIFEST_SCHEMA_VERSION),
738
+ nav: z10.array(frontendNavItemSchema).max(12).default([]),
739
+ pages: z10.array(frontendPageSchema).max(24).default([])
740
740
  }).superRefine((manifest, ctx) => {
741
741
  const seenPages = /* @__PURE__ */ new Set();
742
742
  manifest.pages.forEach((page, index) => {
@@ -768,32 +768,35 @@ function isReservedTemplatePath(path) {
768
768
  }
769
769
 
770
770
  // ../contracts/dist/plans/spec/migrations-layer.js
771
- import { z as z10 } from "zod";
772
- var planKeySchema = z10.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Plan key must be lowercase alphanumeric with hyphens/underscores");
773
- var versionRefSchema = z10.string().min(1).max(100);
774
- var planVersionRefSchema = z10.object({
771
+ import { z as z11 } from "zod";
772
+ var planKeySchema = z11.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Plan key must be lowercase alphanumeric with hyphens/underscores");
773
+ var versionRefSchema = z11.union([
774
+ z11.literal("head"),
775
+ z11.string().regex(/^[1-9][0-9]*$/, 'version must be "head" or a positive integer')
776
+ ]);
777
+ var planVersionRefSchema = z11.object({
775
778
  plan: planKeySchema,
776
779
  version: versionRefSchema.optional()
777
780
  });
778
- var migrationTargetSchema = z10.object({
781
+ var migrationTargetSchema = z11.object({
779
782
  plan: planKeySchema,
780
- version: z10.literal("head").default("head")
783
+ version: z11.literal("head").default("head")
781
784
  });
782
- var pinnedPlanVersionSchema = z10.object({
785
+ var pinnedPlanVersionSchema = z11.object({
783
786
  plan: planKeySchema,
784
787
  version: versionRefSchema
785
788
  });
786
- var migrationEffectiveSchema = z10.enum([
789
+ var migrationEffectiveSchema = z11.enum([
787
790
  "grandfather",
788
791
  "next_renewal",
789
792
  "by_date",
790
793
  "immediate",
791
794
  "opt_in"
792
795
  ]);
793
- var migrationProrationSchema = z10.enum(["none", "prorate", "credit"]);
794
- var migrationExistingCustomersSchema = z10.object({
796
+ var migrationProrationSchema = z11.enum(["none", "prorate", "credit"]);
797
+ var migrationExistingCustomersSchema = z11.object({
795
798
  effective: migrationEffectiveSchema,
796
- date: z10.string().datetime().optional(),
799
+ date: z11.string().datetime().optional(),
797
800
  proration: migrationProrationSchema.optional()
798
801
  }).superRefine((policy, ctx) => {
799
802
  if (policy.effective === "by_date" && policy.date === void 0) {
@@ -804,21 +807,21 @@ var migrationExistingCustomersSchema = z10.object({
804
807
  });
805
808
  }
806
809
  });
807
- var migrationPinSchema = z10.object({
808
- subscriber: z10.string().min(1).max(200),
810
+ var migrationPinSchema = z11.object({
811
+ subscriber: z11.string().min(1).max(200),
809
812
  pinTo: pinnedPlanVersionSchema,
810
- until: z10.string().datetime().optional(),
811
- notes: z10.string().max(1e3).optional()
813
+ until: z11.string().datetime().optional(),
814
+ notes: z11.string().max(1e3).optional()
812
815
  });
813
- var migrationDeclSchema = z10.object({
814
- id: z10.string().min(1).max(120).regex(/^[a-z0-9][a-z0-9_.-]*$/, "Migration id must be lowercase alphanumeric with dots, hyphens, or underscores"),
816
+ var migrationDeclSchema = z11.object({
817
+ id: z11.string().min(1).max(120).regex(/^[a-z0-9][a-z0-9_.-]*$/, "Migration id must be lowercase alphanumeric with dots, hyphens, or underscores"),
815
818
  from: planVersionRefSchema,
816
819
  to: migrationTargetSchema,
817
- newCustomers: z10.literal("immediate").default("immediate"),
820
+ newCustomers: z11.literal("immediate").default("immediate"),
818
821
  existingCustomers: migrationExistingCustomersSchema,
819
- pins: z10.array(migrationPinSchema).max(50).default([])
822
+ pins: z11.array(migrationPinSchema).max(50).default([])
820
823
  });
821
- var migrationDeclsSchema = z10.array(migrationDeclSchema).superRefine((migrations, ctx) => {
824
+ var migrationDeclsSchema = z11.array(migrationDeclSchema).superRefine((migrations, ctx) => {
822
825
  const seen = /* @__PURE__ */ new Set();
823
826
  migrations.forEach((migration, index) => {
824
827
  if (seen.has(migration.id)) {
@@ -833,18 +836,18 @@ var migrationDeclsSchema = z10.array(migrationDeclSchema).superRefine((migration
833
836
  });
834
837
 
835
838
  // ../contracts/dist/plans/spec/counted-resources.js
836
- import { z as z11 } from "zod";
837
- var countedResourceScopeSchema = z11.enum(["subscription", "subject"]);
838
- var countedResourceCountSourceSchema = z11.enum([
839
+ import { z as z12 } from "zod";
840
+ var countedResourceScopeSchema = z12.enum(["subscription", "subject"]);
841
+ var countedResourceCountSourceSchema = z12.enum([
839
842
  "reported",
840
843
  "action_inferred"
841
844
  ]);
842
- var countedResourceNameSchema = z11.string().min(1).max(100).regex(/^[a-z0-9_.:-]+$/);
843
- var countedResourceSchema = z11.object({
845
+ var countedResourceNameSchema = z12.string().min(1).max(100).regex(/^[a-z0-9_.:-]+$/);
846
+ var countedResourceSchema = z12.object({
844
847
  name: countedResourceNameSchema,
845
- display: z11.string().min(1).max(120).optional(),
848
+ display: z12.string().min(1).max(120).optional(),
846
849
  scope: countedResourceScopeSchema.default("subscription"),
847
- subjectType: z11.string().min(1).max(64).regex(/^[a-zA-Z0-9_.:-]+$/).optional(),
850
+ subjectType: z12.string().min(1).max(64).regex(/^[a-zA-Z0-9_.:-]+$/).optional(),
848
851
  countSource: countedResourceCountSourceSchema.default("reported")
849
852
  }).superRefine((resource, ctx) => {
850
853
  if (resource.scope === "subject" && !resource.subjectType) {
@@ -862,23 +865,23 @@ var countedResourceSchema = z11.object({
862
865
  });
863
866
  }
864
867
  });
865
- var countedResourcesSchema = z11.array(countedResourceSchema).max(100).default([]);
868
+ var countedResourcesSchema = z12.array(countedResourceSchema).max(100).default([]);
866
869
 
867
870
  // ../contracts/dist/plans/addons.js
868
- import { z as z12 } from "zod";
869
- var addOnSchema = z12.object({
871
+ import { z as z13 } from "zod";
872
+ var addOnSchema = z13.object({
870
873
  /** Stable identifier — appears in `SubscriptionAddOn.addOnKey` and in
871
874
  * Stripe metadata. Lowercase alphanumeric with hyphens/underscores
872
875
  * to match the plan-key grammar. */
873
- key: z12.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "AddOn key must be lowercase alphanumeric with hyphens/underscores"),
876
+ key: z13.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "AddOn key must be lowercase alphanumeric with hyphens/underscores"),
874
877
  /** Display name (Pricing page chip, settings list, etc.). */
875
- name: z12.string().min(1).max(100),
878
+ name: z13.string().min(1).max(100),
876
879
  /** Short description for tooltips / chooser. */
877
- description: z12.string().max(500).optional(),
880
+ description: z13.string().max(500).optional(),
878
881
  /** Recurring fee added to the subscription invoice while the AddOn is
879
882
  * active. 0 = no recurring fee (e.g. for boolean feature toggles or
880
883
  * one-time-grant-only packs). */
881
- recurring_fee_cents: z12.number().int().nonnegative().default(0),
884
+ recurring_fee_cents: z13.number().int().nonnegative().default(0),
882
885
  /** Billing cadence for this add-on's recurring fee (`month` default /
883
886
  * `year`). Drives the add-on's Stripe price `recurring.interval`. */
884
887
  billing_interval: billingIntervalSchema.default("month"),
@@ -886,83 +889,83 @@ var addOnSchema = z12.object({
886
889
  * into the effective entitlement's meter list (by dimension).
887
890
  * Conflict resolution at compile time: AddOn meter wins over base
888
891
  * plan meter for the same dimension (PR 2a-2 implements this). */
889
- meters: z12.array(meterSchema).default([]),
892
+ meters: z13.array(meterSchema).default([]),
890
893
  /** Grants issued when the AddOn is activated and/or on each period
891
894
  * rollover (kind-dependent). Same vocabulary as plan grants. */
892
- grants: z12.array(grantSchema).max(40).default([]),
895
+ grants: z13.array(grantSchema).max(40).default([]),
893
896
  /** Capability bumps. Numeric limits combine with the base via MAX
894
897
  * (so an "extra seats" addon raises the ceiling without lowering
895
898
  * it). Boolean flags combine with OR (any true wins). */
896
- capability_limits: z12.record(z12.string().min(1).max(120), z12.union([z12.number().int().nonnegative(), z12.boolean()])).default({}),
899
+ capability_limits: z13.record(z13.string().min(1).max(120), z13.union([z13.number().int().nonnegative(), z13.boolean()])).default({}),
897
900
  /** Additive feature gates. Keys true here are appended to the
898
901
  * effective entitlement's gate set. */
899
- featureGates: z12.record(z12.string(), z12.boolean()).optional(),
902
+ featureGates: z13.record(z13.string(), z13.boolean()).optional(),
900
903
  /** Whether the AddOn is visible in the subscriber-facing catalog.
901
904
  * When false, only admin-issued (not self-serve). Defaults true. */
902
- selfServeEnabled: z12.boolean().default(true)
905
+ selfServeEnabled: z13.boolean().default(true)
903
906
  });
904
- var addOnsBlockSchema = z12.record(z12.string().min(1).max(64), addOnSchema).default({});
905
- var subscriptionAddOnStatusSchema = z12.enum([
907
+ var addOnsBlockSchema = z13.record(z13.string().min(1).max(64), addOnSchema).default({});
908
+ var subscriptionAddOnStatusSchema = z13.enum([
906
909
  "active",
907
910
  "canceled",
908
911
  "paused",
909
912
  "pending_activation"
910
913
  ]);
911
- var subscriptionAddOnSchema = z12.object({
914
+ var subscriptionAddOnSchema = z13.object({
912
915
  /** References `product.add_ons.<addOnKey>`. */
913
- addOnKey: z12.string().min(1).max(64),
916
+ addOnKey: z13.string().min(1).max(64),
914
917
  /** Status drives entitlement composition: only `active` AddOns
915
918
  * contribute to the effective entitlement. */
916
919
  status: subscriptionAddOnStatusSchema.default("active"),
917
920
  /** When the AddOn became active (ISO-8601). Drives one-time grant
918
921
  * issuance and recurring-fee start. */
919
- activatedAt: z12.string().datetime().optional(),
922
+ activatedAt: z13.string().datetime().optional(),
920
923
  /** When the AddOn was canceled (ISO-8601). Null while active. */
921
- canceledAt: z12.string().datetime().nullable().optional(),
924
+ canceledAt: z13.string().datetime().nullable().optional(),
922
925
  /** Stripe subscription-item id for the recurring-fee line, when
923
926
  * the AddOn declares a non-zero `recurring_fee_cents`. */
924
- stripeSubscriptionItemId: z12.string().optional()
927
+ stripeSubscriptionItemId: z13.string().optional()
925
928
  });
926
929
 
927
930
  // ../contracts/dist/plans/spec/backend-layer.js
928
- import { z as z13 } from "zod";
931
+ import { z as z14 } from "zod";
929
932
  var BACKEND_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
930
- var backendIdSchema = z13.string().min(1).max(64).regex(BACKEND_ID_PATTERN, "backend id must be a lowercase slug ([a-z0-9][a-z0-9_-]*)");
931
- var backendTransportModeSchema = z13.enum([
933
+ var backendIdSchema = z14.string().min(1).max(64).regex(BACKEND_ID_PATTERN, "backend id must be a lowercase slug ([a-z0-9][a-z0-9_-]*)");
934
+ var backendTransportModeSchema = z14.enum([
932
935
  "public_origin",
933
936
  "mtls",
934
937
  "cloudflare_tunnel"
935
938
  ]);
936
- var backendTransportRunnerSchema = z13.enum([
939
+ var backendTransportRunnerSchema = z14.enum([
937
940
  "managed_cloudflared",
938
941
  "sidecar"
939
942
  ]);
940
- var backendTransportSchema = z13.object({
943
+ var backendTransportSchema = z14.object({
941
944
  mode: backendTransportModeSchema.default("public_origin"),
942
945
  runner: backendTransportRunnerSchema.optional()
943
946
  }).strict();
944
- var backendVerificationSchema = z13.object({
945
- required: z13.boolean().default(false)
947
+ var backendVerificationSchema = z14.object({
948
+ required: z14.boolean().default(false)
946
949
  }).strict();
947
- var backendDefinitionSchema = z13.object({
950
+ var backendDefinitionSchema = z14.object({
948
951
  /** Human-friendly label. Defaults to the id when omitted. */
949
- name: z13.string().min(1).max(120).optional(),
952
+ name: z14.string().min(1).max(120).optional(),
950
953
  /** Stable slug for the backend (origin-hostname / token scoping). Defaults
951
954
  * to the id when omitted. */
952
955
  slug: backendIdSchema.optional(),
953
956
  transport: backendTransportSchema.default({ mode: "public_origin" }),
954
957
  verification: backendVerificationSchema.default({ required: false }),
955
958
  /** Meter allow-list. Omitted = all product meters allowed. */
956
- meters: z13.array(z13.string().min(1).max(64)).max(100).optional(),
959
+ meters: z14.array(z14.string().min(1).max(64)).max(100).optional(),
957
960
  /** Marks the default backend when a product declares more than one. At
958
961
  * most one backend may set this (compiler enforces / AMBIGUOUS_DEFAULT). */
959
- default: z13.boolean().optional(),
962
+ default: z14.boolean().optional(),
960
963
  /** Reachable origin for `public_origin` / `mtls`. */
961
- originUrl: z13.string().url().optional(),
964
+ originUrl: z14.string().url().optional(),
962
965
  /** Access-protected `*.fs-origin` host for `cloudflare_tunnel`. */
963
- originHostname: z13.string().min(1).max(255).optional()
966
+ originHostname: z14.string().min(1).max(255).optional()
964
967
  }).strict();
965
- var productBackendBlockSchema = z13.record(backendIdSchema, backendDefinitionSchema);
968
+ var productBackendBlockSchema = z14.record(backendIdSchema, backendDefinitionSchema);
966
969
  var routeBackendBindingSchema = backendIdSchema;
967
970
  var BACKEND_DIAGNOSTIC_CODES = {
968
971
  unknownBackendInRoute: "UNKNOWN_BACKEND_IN_ROUTE",
@@ -984,16 +987,16 @@ function resolveDefaultBackendId(backends) {
984
987
  }
985
988
 
986
989
  // ../contracts/dist/plans/spec/routes-layer.js
987
- import { z as z17 } from "zod";
990
+ import { z as z18 } from "zod";
988
991
 
989
992
  // ../contracts/dist/plans/spec/policies-layer.js
990
- import { z as z15 } from "zod";
993
+ import { z as z16 } from "zod";
991
994
 
992
995
  // ../contracts/dist/plans/spec/policy-types.js
993
- import { z as z14 } from "zod";
994
- var rateLimitWindowSchema = z14.string().min(2).max(20).regex(/^\d+(ms|s|m|h)$/, "rate_limit window must look like `60s`, `5m`, `1h`");
995
- var rateLimitConfigSchema = z14.object({
996
- strategy: z14.enum(["token_bucket", "sliding_window", "fixed_window"]).default("token_bucket"),
996
+ import { z as z15 } from "zod";
997
+ var rateLimitWindowSchema = z15.string().min(2).max(20).regex(/^\d+(ms|s|m|h)$/, "rate_limit window must look like `60s`, `5m`, `1h`");
998
+ var rateLimitConfigSchema = z15.object({
999
+ strategy: z15.enum(["token_bucket", "sliding_window", "fixed_window"]).default("token_bucket"),
997
1000
  /**
998
1001
  * Which request dimensions identify the bucket. v0.3.0 supports a
999
1002
  * fixed set; extending requires a coordinated gateway/policy-engine
@@ -1001,10 +1004,10 @@ var rateLimitConfigSchema = z14.object({
1001
1004
  * `ip` is for unauthenticated probes; `credential` is finer-grained
1002
1005
  * than subscription (per-key throttling).
1003
1006
  */
1004
- dimensions: z14.array(z14.enum(["subscription", "credential", "ip", "route"])).min(1).max(4).default(["subscription"]),
1005
- limits: z14.array(z14.object({
1007
+ dimensions: z15.array(z15.enum(["subscription", "credential", "ip", "route"])).min(1).max(4).default(["subscription"]),
1008
+ limits: z15.array(z15.object({
1006
1009
  window: rateLimitWindowSchema,
1007
- max: z14.number().int().positive().max(1e7)
1010
+ max: z15.number().int().positive().max(1e7)
1008
1011
  })).min(1).max(10),
1009
1012
  /**
1010
1013
  * Bounded fail-open behaviour for DO outages. See architecture RFC
@@ -1014,11 +1017,11 @@ var rateLimitConfigSchema = z14.object({
1014
1017
  * `recovery_threshold` consecutive successes restore normal
1015
1018
  * evaluation.
1016
1019
  */
1017
- fail_open: z14.object({
1018
- max_consecutive_failures: z14.number().int().positive().default(100),
1019
- max_window_seconds: z14.number().int().positive().default(60),
1020
- recovery_threshold: z14.number().int().positive().default(50),
1021
- degraded_mode: z14.enum([
1020
+ fail_open: z15.object({
1021
+ max_consecutive_failures: z15.number().int().positive().default(100),
1022
+ max_window_seconds: z15.number().int().positive().default(60),
1023
+ recovery_threshold: z15.number().int().positive().default(50),
1024
+ degraded_mode: z15.enum([
1022
1025
  "safe_mode_block",
1023
1026
  "safe_mode_throttle",
1024
1027
  "runtime_killswitch_trigger"
@@ -1030,19 +1033,19 @@ var rateLimitConfigSchema = z14.object({
1030
1033
  degraded_mode: "safe_mode_throttle"
1031
1034
  })
1032
1035
  });
1033
- var authConfigSchema = z14.object({
1034
- header_name: z14.string().min(1).max(100).default("x-api-key"),
1036
+ var authConfigSchema = z15.object({
1037
+ header_name: z15.string().min(1).max(100).default("x-api-key"),
1035
1038
  /**
1036
1039
  * How the gateway constructs the upstream Authorization header:
1037
1040
  * - `none` → no upstream auth header added
1038
1041
  * - `static_bearer` → forward a configured static token
1039
1042
  * - `subscriber_jwt` → mint a per-subscriber JWT (out of scope v0.3.0)
1040
1043
  */
1041
- upstream_token_source: z14.discriminatedUnion("type", [
1042
- z14.object({ type: z14.literal("none") }),
1043
- z14.object({
1044
- type: z14.literal("static_bearer"),
1045
- token_secret_ref: z14.string().min(1).max(200).describe("Reference into the secret store (e.g. CF Secret name); not the raw token")
1044
+ upstream_token_source: z15.discriminatedUnion("type", [
1045
+ z15.object({ type: z15.literal("none") }),
1046
+ z15.object({
1047
+ type: z15.literal("static_bearer"),
1048
+ token_secret_ref: z15.string().min(1).max(200).describe("Reference into the secret store (e.g. CF Secret name); not the raw token")
1046
1049
  })
1047
1050
  ]).default({ type: "none" }),
1048
1051
  /**
@@ -1050,32 +1053,32 @@ var authConfigSchema = z14.object({
1050
1053
  * additional gating beyond the entitlement check. v0.3.0 ships with
1051
1054
  * `strict` as the default.
1052
1055
  */
1053
- scope_mode: z14.enum(["strict", "advisory", "off"]).default("strict")
1056
+ scope_mode: z15.enum(["strict", "advisory", "off"]).default("strict")
1054
1057
  });
1055
- var concurrencyConfigSchema = z14.object({
1056
- max_in_flight: z14.number().int().positive().max(1e4),
1058
+ var concurrencyConfigSchema = z15.object({
1059
+ max_in_flight: z15.number().int().positive().max(1e4),
1057
1060
  /**
1058
1061
  * Which dimensions key the lease bucket. Matches the existing
1059
1062
  * ConcurrencyLease DO `idFromName` pattern (subscription | capability
1060
1063
  * tuple).
1061
1064
  */
1062
- dimensions: z14.array(z14.enum(["subscription", "credential", "capability"])).min(1).max(3).default(["subscription"]),
1065
+ dimensions: z15.array(z15.enum(["subscription", "credential", "capability"])).min(1).max(3).default(["subscription"]),
1063
1066
  /**
1064
1067
  * Optional capability scope. When set, the lease bucket is keyed
1065
1068
  * partly by this capability name — separate buckets per capability.
1066
1069
  */
1067
- capability: z14.string().min(1).max(120).optional(),
1070
+ capability: z15.string().min(1).max(120).optional(),
1068
1071
  /**
1069
1072
  * Lease TTL — releases automatically after this many seconds even if
1070
1073
  * the request never returns (defensive default 30s, mirrors existing
1071
1074
  * ConcurrencyLease behaviour).
1072
1075
  */
1073
- lease_ttl_seconds: z14.number().int().positive().max(600).default(30),
1074
- fail_open: z14.object({
1075
- max_consecutive_failures: z14.number().int().positive().default(50),
1076
- max_window_seconds: z14.number().int().positive().default(60),
1077
- recovery_threshold: z14.number().int().positive().default(20),
1078
- degraded_mode: z14.enum([
1076
+ lease_ttl_seconds: z15.number().int().positive().max(600).default(30),
1077
+ fail_open: z15.object({
1078
+ max_consecutive_failures: z15.number().int().positive().default(50),
1079
+ max_window_seconds: z15.number().int().positive().default(60),
1080
+ recovery_threshold: z15.number().int().positive().default(20),
1081
+ degraded_mode: z15.enum([
1079
1082
  "safe_mode_block",
1080
1083
  "safe_mode_throttle",
1081
1084
  "runtime_killswitch_trigger"
@@ -1087,22 +1090,22 @@ var concurrencyConfigSchema = z14.object({
1087
1090
  degraded_mode: "safe_mode_throttle"
1088
1091
  })
1089
1092
  });
1090
- var retryConfigSchema = z14.object({
1091
- max_attempts: z14.number().int().min(1).max(5).default(2),
1093
+ var retryConfigSchema = z15.object({
1094
+ max_attempts: z15.number().int().min(1).max(5).default(2),
1092
1095
  /**
1093
1096
  * HTTP status codes that trigger a retry. 5xx is the default; opt
1094
1097
  * into 429 retries only when the upstream understands `Retry-After`.
1095
1098
  */
1096
- retry_on_status: z14.array(z14.number().int().min(400).max(599)).min(1).max(20).default([502, 503, 504]),
1099
+ retry_on_status: z15.array(z15.number().int().min(400).max(599)).min(1).max(20).default([502, 503, 504]),
1097
1100
  /**
1098
1101
  * Backoff curve. Total wall-clock attempt time is bounded so the
1099
1102
  * gateway worker cannot block past `total_budget_ms`.
1100
1103
  */
1101
- backoff: z14.object({
1102
- initial_ms: z14.number().int().positive().max(5e3).default(100),
1103
- multiplier: z14.number().positive().max(10).default(2),
1104
- jitter: z14.enum(["none", "full", "equal"]).default("equal"),
1105
- total_budget_ms: z14.number().int().positive().max(3e4).default(5e3)
1104
+ backoff: z15.object({
1105
+ initial_ms: z15.number().int().positive().max(5e3).default(100),
1106
+ multiplier: z15.number().positive().max(10).default(2),
1107
+ jitter: z15.enum(["none", "full", "equal"]).default("equal"),
1108
+ total_budget_ms: z15.number().int().positive().max(3e4).default(5e3)
1106
1109
  }).default({
1107
1110
  initial_ms: 100,
1108
1111
  multiplier: 2,
@@ -1110,46 +1113,46 @@ var retryConfigSchema = z14.object({
1110
1113
  total_budget_ms: 5e3
1111
1114
  })
1112
1115
  });
1113
- var transformConfigSchema = z14.object({
1116
+ var transformConfigSchema = z15.object({
1114
1117
  /**
1115
1118
  * When the transform applies. `request` runs before upstream forward;
1116
1119
  * `response` runs after. Most transforms are one or the other; both
1117
1120
  * is rare.
1118
1121
  */
1119
- applies_to: z14.enum(["request", "response", "both"]).default("request"),
1122
+ applies_to: z15.enum(["request", "response", "both"]).default("request"),
1120
1123
  /**
1121
1124
  * List of key rewrites. Source path uses dot notation (`a.b.c`);
1122
1125
  * `target` may include the same syntax to move keys around. Drops
1123
1126
  * are expressed as `target: null`.
1124
1127
  */
1125
- rewrites: z14.array(z14.object({
1126
- source: z14.string().min(1).max(200),
1127
- target: z14.string().min(1).max(200).nullable()
1128
+ rewrites: z15.array(z15.object({
1129
+ source: z15.string().min(1).max(200),
1130
+ target: z15.string().min(1).max(200).nullable()
1128
1131
  })).min(1).max(20)
1129
1132
  });
1130
- var policyBodySchema = z14.discriminatedUnion("type", [
1131
- z14.object({ type: z14.literal("rate_limit"), config: rateLimitConfigSchema }),
1132
- z14.object({ type: z14.literal("auth"), config: authConfigSchema }),
1133
- z14.object({ type: z14.literal("concurrency"), config: concurrencyConfigSchema }),
1134
- z14.object({ type: z14.literal("retry"), config: retryConfigSchema }),
1135
- z14.object({ type: z14.literal("transform"), config: transformConfigSchema })
1133
+ var policyBodySchema = z15.discriminatedUnion("type", [
1134
+ z15.object({ type: z15.literal("rate_limit"), config: rateLimitConfigSchema }),
1135
+ z15.object({ type: z15.literal("auth"), config: authConfigSchema }),
1136
+ z15.object({ type: z15.literal("concurrency"), config: concurrencyConfigSchema }),
1137
+ z15.object({ type: z15.literal("retry"), config: retryConfigSchema }),
1138
+ z15.object({ type: z15.literal("transform"), config: transformConfigSchema })
1136
1139
  ]);
1137
1140
 
1138
1141
  // ../contracts/dist/plans/spec/policies-layer.js
1139
- var cacheProfileSchema = z15.enum(["long", "short", "blocking"]).default("long");
1140
- var policyCompatibilitySchema = z15.object({
1141
- route_types: z15.array(z15.enum(["http"])).max(5).optional(),
1142
- meters: z15.array(z15.string().min(1).max(64)).max(20).optional(),
1143
- auth_modes: z15.array(z15.enum(["api_key", "oauth2", "anonymous"])).max(5).optional()
1142
+ var cacheProfileSchema = z16.enum(["long", "short", "blocking"]).default("long");
1143
+ var policyCompatibilitySchema = z16.object({
1144
+ route_types: z16.array(z16.enum(["http"])).max(5).optional(),
1145
+ meters: z16.array(z16.string().min(1).max(64)).max(20).optional(),
1146
+ auth_modes: z16.array(z16.enum(["api_key", "oauth2", "anonymous"])).max(5).optional()
1144
1147
  });
1145
- var policyLayerSchema = z15.intersection(z15.object({
1148
+ var policyLayerSchema = z16.intersection(z16.object({
1146
1149
  /**
1147
1150
  * Policy name. Referenced by routes via `policies: [<name>]`. Must
1148
1151
  * be unique across the product; the compiler enforces this in the
1149
1152
  * cross-layer validation pass.
1150
1153
  */
1151
- name: z15.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Policy name must be lowercase alphanumeric with hyphens/underscores"),
1152
- description: z15.string().max(500).optional(),
1154
+ name: z16.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Policy name must be lowercase alphanumeric with hyphens/underscores"),
1155
+ description: z16.string().max(500).optional(),
1153
1156
  compatible_with: policyCompatibilitySchema.default({}),
1154
1157
  /**
1155
1158
  * Mutation class — runtime vs contractual. Policies are operational
@@ -1157,38 +1160,38 @@ var policyLayerSchema = z15.intersection(z15.object({
1157
1160
  * `contractual` signals that changes to it require human approval
1158
1161
  * (invariant #16).
1159
1162
  */
1160
- mutation_class: z15.enum(["runtime", "contractual"]).default("runtime"),
1163
+ mutation_class: z16.enum(["runtime", "contractual"]).default("runtime"),
1161
1164
  cacheProfile: cacheProfileSchema
1162
1165
  }), policyBodySchema);
1163
1166
 
1164
1167
  // ../contracts/dist/framework/actions/index.js
1165
- import { z as z16 } from "zod";
1166
- var actionKindSchema = z16.enum(["query", "mutation"]);
1167
- var actionAuditPolicySchema = z16.enum(["none", "metadata", "full"]);
1168
- var actionSubjectBindingSchema = z16.object({
1169
- type: z16.string().min(1).max(64).regex(/^[a-zA-Z0-9_.:-]+$/),
1170
- from: z16.enum(["header", "path_param"]),
1171
- name: z16.string().min(1).max(120)
1172
- });
1173
- var actionResourceEffectSchema = z16.object({
1174
- resource: z16.string().min(1).max(100).regex(/^[a-z0-9_.:-]+$/),
1175
- effect: z16.enum(["create", "delete"])
1176
- });
1177
- var actionSpecSchema = z16.object({
1178
- id: z16.string().min(1).max(160).regex(/^[a-z0-9_.:-]+$/),
1179
- title: z16.string().min(1).max(160).optional(),
1168
+ import { z as z17 } from "zod";
1169
+ var actionKindSchema = z17.enum(["query", "mutation"]);
1170
+ var actionAuditPolicySchema = z17.enum(["none", "metadata", "full"]);
1171
+ var actionSubjectBindingSchema = z17.object({
1172
+ type: z17.string().min(1).max(64).regex(/^[a-zA-Z0-9_.:-]+$/),
1173
+ from: z17.enum(["header", "path_param"]),
1174
+ name: z17.string().min(1).max(120)
1175
+ });
1176
+ var actionResourceEffectSchema = z17.object({
1177
+ resource: z17.string().min(1).max(100).regex(/^[a-z0-9_.:-]+$/),
1178
+ effect: z17.enum(["create", "delete"])
1179
+ });
1180
+ var actionSpecSchema = z17.object({
1181
+ id: z17.string().min(1).max(160).regex(/^[a-z0-9_.:-]+$/),
1182
+ title: z17.string().min(1).max(160).optional(),
1180
1183
  kind: actionKindSchema,
1181
- actorType: z16.string().min(1).max(64).regex(/^[a-zA-Z0-9_.:-]+$/).optional(),
1184
+ actorType: z17.string().min(1).max(64).regex(/^[a-zA-Z0-9_.:-]+$/).optional(),
1182
1185
  subject: actionSubjectBindingSchema.optional(),
1183
- inputSchemaRef: z16.string().min(1).max(240).optional(),
1186
+ inputSchemaRef: z17.string().min(1).max(240).optional(),
1184
1187
  audit: actionAuditPolicySchema.default("metadata"),
1185
1188
  resource: actionResourceEffectSchema.optional()
1186
1189
  });
1187
1190
 
1188
1191
  // ../contracts/dist/plans/spec/routes-layer.js
1189
- var routeMatchSchema = z17.object({
1190
- method: z17.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "*"]).default("*"),
1191
- path: z17.string().min(1).max(500).regex(/^\/[a-zA-Z0-9_/:.{}*-]*$/, "path must start with / and contain only [a-zA-Z0-9_/:.{}*-]")
1192
+ var routeMatchSchema = z18.object({
1193
+ method: z18.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "*"]).default("*"),
1194
+ path: z18.string().min(1).max(500).regex(/^\/[a-zA-Z0-9_/:.{}*-]*$/, "path must start with / and contain only [a-zA-Z0-9_/:.{}*-]")
1192
1195
  });
1193
1196
  function statusPolicyPartIsValid(part) {
1194
1197
  const trimmed = part.trim();
@@ -1206,60 +1209,60 @@ function statusPolicyPartIsValid(part) {
1206
1209
  function isRouteStatusCodePolicyString(value) {
1207
1210
  return value.split(",").every(statusPolicyPartIsValid);
1208
1211
  }
1209
- var routeStatusCodePolicySchema = z17.union([
1210
- z17.string().min(1).max(100).refine(isRouteStatusCodePolicyString, {
1212
+ var routeStatusCodePolicySchema = z18.union([
1213
+ z18.string().min(1).max(100).refine(isRouteStatusCodePolicyString, {
1211
1214
  message: "onStatusCodes must be comma-separated HTTP status codes or numeric ranges, e.g. 200-299,304"
1212
1215
  }),
1213
- z17.array(z17.number().int().min(100).max(599)).min(1).max(100)
1216
+ z18.array(z18.number().int().min(100).max(599)).min(1).max(100)
1214
1217
  ]);
1215
- var routeDefinitionSchema = z17.object({
1218
+ var routeDefinitionSchema = z18.object({
1216
1219
  match: routeMatchSchema,
1217
- metering: z17.object({
1218
- defaults: z17.record(z17.string().min(1).max(64), z17.number().finite().nonnegative()).optional(),
1219
- reports: z17.array(z17.string().min(1).max(64)).max(20).optional(),
1220
- estimates: z17.record(z17.string().min(1).max(64), z17.number().finite().nonnegative()).optional(),
1220
+ metering: z18.object({
1221
+ defaults: z18.record(z18.string().min(1).max(64), z18.number().finite().nonnegative()).optional(),
1222
+ reports: z18.array(z18.string().min(1).max(64)).max(20).optional(),
1223
+ estimates: z18.record(z18.string().min(1).max(64), z18.number().finite().nonnegative()).optional(),
1221
1224
  onStatusCodes: routeStatusCodePolicySchema.optional()
1222
1225
  }).optional(),
1223
- unmetered: z17.boolean().optional(),
1224
- inheritDefaultMeters: z17.boolean().optional(),
1226
+ unmetered: z18.boolean().optional(),
1227
+ inheritDefaultMeters: z18.boolean().optional(),
1225
1228
  /** Optional explicit action id. When absent, the compiler derives an
1226
1229
  * implicit action from feature + method + path. */
1227
- action: z17.string().min(1).max(160).regex(/^[a-z0-9_.:-]+$/).optional(),
1230
+ action: z18.string().min(1).max(160).regex(/^[a-z0-9_.:-]+$/).optional(),
1228
1231
  /** BYO-Backend V1 — optional route→backend binding. Omitted = the sole /
1229
1232
  * default backend (single-backend products stay zero-config). The schema
1230
1233
  * is `.strict()`, so this key MUST be declared here or `parse` throws on
1231
1234
  * it (anti-`.strict()`). */
1232
1235
  backend: routeBackendBindingSchema.optional()
1233
1236
  }).strict();
1234
- var routeUpstreamSchema = z17.object({
1235
- override_origin: z17.string().url("override_origin must be a valid URL").nullable().default(null)
1237
+ var routeUpstreamSchema = z18.object({
1238
+ override_origin: z18.string().url("override_origin must be a valid URL").nullable().default(null)
1236
1239
  });
1237
- var routeRuntimeSchema = z17.object({
1238
- rollout_key: z17.string().min(1).max(120).regex(/^[a-z0-9_-]+$/, "rollout_key must be lowercase alphanumeric with hyphens/underscores").optional(),
1240
+ var routeRuntimeSchema = z18.object({
1241
+ rollout_key: z18.string().min(1).max(120).regex(/^[a-z0-9_-]+$/, "rollout_key must be lowercase alphanumeric with hyphens/underscores").optional(),
1239
1242
  /**
1240
1243
  * Optional runtime flags this feature depends on. The runtime
1241
1244
  * evaluator AND's the feature's enablement across all referenced
1242
1245
  * flags. If any flag is disabled, the route returns the configured
1243
1246
  * fallback (404 by default — see /runtime failure matrix).
1244
1247
  */
1245
- required_flags: z17.array(z17.string().min(1).max(120).regex(/^[a-z0-9_-]+$/)).max(10).optional()
1248
+ required_flags: z18.array(z18.string().min(1).max(120).regex(/^[a-z0-9_-]+$/)).max(10).optional()
1246
1249
  });
1247
- var routeLayerSchema = z17.object({
1250
+ var routeLayerSchema = z18.object({
1248
1251
  /**
1249
1252
  * Feature key — the entitlement unit. Surfaced in dashboards,
1250
1253
  * subscriptions, and the gateway's matched-route trace.
1251
1254
  */
1252
- feature: z17.string().min(1).max(100).regex(/^[a-z0-9_.:-]+$/, "feature key must be lowercase alphanumeric with [_.:-]"),
1253
- description: z17.string().max(500).optional(),
1255
+ feature: z18.string().min(1).max(100).regex(/^[a-z0-9_.:-]+$/, "feature key must be lowercase alphanumeric with [_.:-]"),
1256
+ description: z18.string().max(500).optional(),
1254
1257
  /**
1255
1258
  * Route additions are contractual by default — they expose new API
1256
1259
  * surface to subscribers. Internal/non-customer-visible routes can
1257
1260
  * mark themselves `runtime` to allow autonomous agent flips
1258
1261
  * (invariant #16; see RFC approval matrix).
1259
1262
  */
1260
- mutation_class: z17.enum(["runtime", "contractual"]).default("contractual"),
1263
+ mutation_class: z18.enum(["runtime", "contractual"]).default("contractual"),
1261
1264
  cacheProfile: cacheProfileSchema,
1262
- routes: z17.array(routeDefinitionSchema).min(1).max(50),
1265
+ routes: z18.array(routeDefinitionSchema).min(1).max(50),
1263
1266
  upstream: routeUpstreamSchema.default({ override_origin: null }),
1264
1267
  /**
1265
1268
  * Ordered list of policy names to apply. Executed sequentially by
@@ -1267,17 +1270,17 @@ var routeLayerSchema = z17.object({
1267
1270
  * MUST declare compatible `compatible_with` envelopes for this
1268
1271
  * feature's route/meter shape — the compiler enforces.
1269
1272
  */
1270
- policies: z17.array(z17.string().min(1).max(64).regex(/^[a-z0-9_-]+$/)).max(20).default([]),
1273
+ policies: z18.array(z18.string().min(1).max(64).regex(/^[a-z0-9_-]+$/)).max(20).default([]),
1271
1274
  runtime: routeRuntimeSchema.default({}),
1272
1275
  /**
1273
1276
  * Plans that grant this feature directly. Shared feature bundles are
1274
1277
  * expressed in capability layers via `includes_features`; route layers do
1275
1278
  * not declare capability membership.
1276
1279
  */
1277
- plans: z17.array(z17.string().min(1).max(64)).max(20).default([]),
1280
+ plans: z18.array(z18.string().min(1).max(64)).max(20).default([]),
1278
1281
  /** Explicit actions declared by this feature. Routes reference them by
1279
1282
  * `route.action`; routes without a binding receive implicit actions. */
1280
- actions: z17.array(actionSpecSchema).max(100).optional(),
1283
+ actions: z18.array(actionSpecSchema).max(100).optional(),
1281
1284
  /** BYO-Backend V1 — feature-level default backend binding. Routes in this
1282
1285
  * layer with no explicit `route.backend` inherit this; routes may still
1283
1286
  * override per-route. Omitted = the product's sole / default backend. */
@@ -1597,18 +1600,18 @@ function planPriceKey(plan, monthly) {
1597
1600
  }
1598
1601
 
1599
1602
  // ../contracts/dist/plans/spec/product.js
1600
- var productIdentitySchema = z18.object({
1601
- subdomain: z18.string().min(1).max(63).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/, "Subdomain must be lowercase alphanumeric with optional hyphens")
1603
+ var productIdentitySchema = z19.object({
1604
+ subdomain: z19.string().min(1).max(63).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/, "Subdomain must be lowercase alphanumeric with optional hyphens")
1602
1605
  });
1603
- var meterEnforcementTypeSchema = z18.enum([
1606
+ var meterEnforcementTypeSchema = z19.enum([
1604
1607
  "exact_pre_request",
1605
1608
  "estimated_then_settled",
1606
1609
  "postpaid",
1607
1610
  "strict_concurrency"
1608
1611
  ]);
1609
- var meterDefinitionSchema = z18.object({
1610
- key: z18.string().min(1).max(64).regex(/^[a-z0-9_]+$/, "Meter key must be lowercase alphanumeric with underscores"),
1611
- display: z18.string().min(1).max(100),
1612
+ var meterDefinitionSchema = z19.object({
1613
+ key: z19.string().min(1).max(64).regex(/^[a-z0-9_]+$/, "Meter key must be lowercase alphanumeric with underscores"),
1614
+ display: z19.string().min(1).max(100),
1612
1615
  // v0.42.0 — `type: "built-in" | "custom"` removed. The runtime never
1613
1616
  // read it (gateway estimators key on meter NAME, Stripe meter
1614
1617
  // creation keys on meter KEY); it was schema documentation. Wizard
@@ -1616,11 +1619,11 @@ var meterDefinitionSchema = z18.object({
1616
1619
  // ai_usage → `dollars`); Custom template lets builders define keys
1617
1620
  // freely. Old specs with `type: ...` parse cleanly because Zod
1618
1621
  // strips unknown fields by default.
1619
- unit: z18.string().max(20).optional(),
1622
+ unit: z19.string().max(20).optional(),
1620
1623
  /** Reusable pre-request estimate for routes that dynamically report this meter. */
1621
- estimate: z18.number().finite().nonnegative().optional(),
1624
+ estimate: z19.number().finite().nonnegative().optional(),
1622
1625
  /** Fixed per-request default applied by Product SDK helpers. */
1623
- routeDefault: z18.number().finite().nonnegative().optional(),
1626
+ routeDefault: z19.number().finite().nonnegative().optional(),
1624
1627
  /**
1625
1628
  * Runtime enforcement semantics for this meter. This is compiled into
1626
1629
  * signed gateway artifacts so the edge chooses reservation, settlement,
@@ -1645,7 +1648,7 @@ var meterDefinitionSchema = z18.object({
1645
1648
  * current `window`. Defaults to `COUNT` (one event = one unit) so
1646
1649
  * existing meters that didn't declare aggregation continue to work.
1647
1650
  */
1648
- aggregation: z18.enum(["SUM", "COUNT", "MAX", "UNIQUE_COUNT", "LATEST"]).default("COUNT"),
1651
+ aggregation: z19.enum(["SUM", "COUNT", "MAX", "UNIQUE_COUNT", "LATEST"]).default("COUNT"),
1649
1652
  /**
1650
1653
  * Aggregation window. `billing_period` (the default) makes the
1651
1654
  * meter accumulate across the subscription's billing period.
@@ -1653,88 +1656,65 @@ var meterDefinitionSchema = z18.object({
1653
1656
  * rate-limit-shaped meters where the period boundary is a fixed
1654
1657
  * wall-clock interval, not the subscription anniversary.
1655
1658
  */
1656
- window: z18.enum(["minute", "hour", "day", "month", "billing_period"]).default("billing_period"),
1659
+ window: z19.enum(["minute", "hour", "day", "month", "billing_period"]).default("billing_period"),
1657
1660
  /**
1658
1661
  * Property on the event payload to read for `SUM` and `MAX`
1659
1662
  * aggregations. Optional at the schema level; core's `validate.ts`
1660
1663
  * (Phase 1b) enforces "required when aggregation is SUM or MAX".
1661
1664
  */
1662
- valueProperty: z18.string().optional(),
1665
+ valueProperty: z19.string().optional(),
1663
1666
  /**
1664
1667
  * Property on the event payload to dedupe on for `UNIQUE_COUNT`
1665
1668
  * aggregation. Optional at the schema level; core's validate-pass
1666
1669
  * enforces "required when aggregation is UNIQUE_COUNT".
1667
1670
  */
1668
- uniqueProperty: z18.string().optional(),
1671
+ uniqueProperty: z19.string().optional(),
1669
1672
  /**
1670
1673
  * Optional grouping dimensions. When set, the aggregation is per
1671
1674
  * unique combination of these properties' values, not a single
1672
1675
  * scalar. Used for per-region or per-model breakdowns.
1673
1676
  */
1674
- groupBy: z18.array(z18.string()).optional(),
1677
+ groupBy: z19.array(z19.string()).optional(),
1675
1678
  /**
1676
1679
  * Lago-side event code for ingress correlation. Matches Lago's
1677
1680
  * BillableMetric `code` so events sent to Lago land on the right
1678
1681
  * meter without a per-meter translation table.
1679
1682
  */
1680
- eventCode: z18.string().optional()
1681
- });
1682
- var usageMeasureSchema = z18.string().min(1).max(64).regex(/^[a-z0-9_]+$/, "Usage measure must be lowercase alphanumeric with underscores");
1683
- var usageRatingPricePolicySchema = z18.enum([
1684
- "pass_through",
1685
- "markup",
1686
- "fixed_margin",
1687
- "customer_rate"
1688
- ]);
1689
- var fixedRatingSchema = z18.object({
1690
- source: z18.literal("fixed"),
1691
- rates: z18.record(z18.string().min(1), z18.record(usageMeasureSchema, z18.number().int().nonnegative()))
1683
+ eventCode: z19.string().optional()
1692
1684
  });
1693
- var providerCatalogRatingSchema = z18.object({
1694
- source: z18.literal("provider_catalog"),
1695
- catalog: z18.string().min(1).max(100),
1696
- pricePolicy: usageRatingPricePolicySchema.default("pass_through"),
1697
- markupPercent: z18.number().nonnegative().max(1e4).optional(),
1698
- marginMicros: z18.number().int().nonnegative().optional()
1699
- });
1700
- var upstreamReportedRatingSchema = z18.object({
1701
- source: z18.literal("upstream_reported"),
1702
- amountField: z18.string().min(1).max(500),
1703
- currencyField: z18.string().min(1).max(500).optional()
1704
- });
1705
- var externalRateApiRatingSchema = z18.object({
1706
- source: z18.literal("external_rate_api"),
1707
- resolver: z18.string().min(1).max(100),
1708
- configRef: z18.string().min(1).max(200).optional()
1685
+ var usageMeasureSchema = z19.string().min(1).max(64).regex(/^[a-z0-9_]+$/, "Usage measure must be lowercase alphanumeric with underscores");
1686
+ var usageRatingPricePolicySchema = z19.enum(["pass_through", "markup"]);
1687
+ var fixedRatingSchema = z19.object({
1688
+ source: z19.literal("fixed"),
1689
+ rates: z19.record(z19.string().min(1), z19.record(usageMeasureSchema, z19.number().int().nonnegative()))
1709
1690
  });
1710
- var customRatingSchema = z18.object({
1711
- source: z18.literal("custom"),
1712
- resolver: z18.string().min(1).max(100),
1713
- configRef: z18.string().min(1).max(200).optional()
1691
+ var providerCatalogRatingSchema = z19.object({
1692
+ source: z19.literal("provider_catalog"),
1693
+ catalog: z19.string().min(1).max(100),
1694
+ pricePolicy: usageRatingPricePolicySchema.default("pass_through"),
1695
+ markupPercent: z19.number().nonnegative().max(1e4).optional(),
1696
+ marginMicros: z19.number().int().nonnegative().optional()
1714
1697
  });
1715
- var usageRatingSchema = z18.discriminatedUnion("source", [
1698
+ var usageRatingSchema = z19.discriminatedUnion("source", [
1716
1699
  fixedRatingSchema,
1717
- providerCatalogRatingSchema,
1718
- upstreamReportedRatingSchema,
1719
- externalRateApiRatingSchema,
1720
- customRatingSchema
1700
+ providerCatalogRatingSchema
1721
1701
  ]);
1722
- var usageMeterSchema = z18.object({
1723
- selector: z18.string().min(1).max(100).optional(),
1724
- measures: z18.array(usageMeasureSchema).min(1).max(20),
1702
+ var usageMeterSchema = z19.object({
1703
+ selector: z19.string().min(1).max(100).optional(),
1704
+ measures: z19.array(usageMeasureSchema).min(1).max(20),
1725
1705
  rating: usageRatingSchema.optional()
1726
1706
  });
1727
- var usageBlockSchema = z18.object({
1728
- meters: z18.record(z18.string().min(1).max(64).regex(/^[a-z0-9_]+$/), usageMeterSchema)
1707
+ var usageBlockSchema = z19.object({
1708
+ meters: z19.record(z19.string().min(1).max(64).regex(/^[a-z0-9_]+$/), usageMeterSchema)
1729
1709
  });
1730
- var routeMeteringSchema = z18.object({
1731
- defaults: z18.record(z18.string().min(1).max(64), z18.number().finite().nonnegative()).optional(),
1732
- reports: z18.array(z18.string().min(1).max(64)).max(20).optional(),
1733
- estimates: z18.record(z18.string().min(1).max(64), z18.number().finite().nonnegative()).optional(),
1710
+ var routeMeteringSchema = z19.object({
1711
+ defaults: z19.record(z19.string().min(1).max(64), z19.number().finite().nonnegative()).optional(),
1712
+ reports: z19.array(z19.string().min(1).max(64)).max(20).optional(),
1713
+ estimates: z19.record(z19.string().min(1).max(64), z19.number().finite().nonnegative()).optional(),
1734
1714
  onStatusCodes: routeStatusCodePolicySchema.optional()
1735
1715
  });
1736
- var featureRouteSchema = z18.object({
1737
- method: z18.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "*"]).default("*"),
1716
+ var featureRouteSchema = z19.object({
1717
+ method: z19.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "*"]).default("*"),
1738
1718
  // Path is the route under the product's baseUrl. OpenAPI parameter
1739
1719
  // syntax is supported and translated by the compiler:
1740
1720
  // /users/:id → /users/*
@@ -1742,48 +1722,48 @@ var featureRouteSchema = z18.object({
1742
1722
  // Path-globs `*` (one segment) and `**` (any subpath) are passed
1743
1723
  // through. The compiler rejects ambiguous compound segments like
1744
1724
  // `/foo/:a-:b` — parameter names must occupy whole segments.
1745
- path: z18.string().min(1).max(500).regex(/^\/[a-zA-Z0-9_/:.{}*-]*$/, "path must start with / and contain only [a-zA-Z0-9_/:.{}*-]"),
1725
+ path: z19.string().min(1).max(500).regex(/^\/[a-zA-Z0-9_/:.{}*-]*$/, "path must start with / and contain only [a-zA-Z0-9_/:.{}*-]"),
1746
1726
  // Explicit no-usage route. Dynamic/static route metering is declared
1747
1727
  // exclusively under `metering`.
1748
- unmetered: z18.boolean().optional(),
1728
+ unmetered: z19.boolean().optional(),
1749
1729
  metering: routeMeteringSchema.optional(),
1750
- inheritDefaultMeters: z18.boolean().optional(),
1730
+ inheritDefaultMeters: z19.boolean().optional(),
1751
1731
  // BYO-Backend V1 — route→backend binding. The compiler materializes
1752
1732
  // per-feature route layers into this strict route shape, so the key MUST be
1753
1733
  // declared here or `parse` throws on it (anti-`.strict()`).
1754
1734
  backend: routeBackendBindingSchema.optional()
1755
1735
  }).strict();
1756
- var featureCatalogEntrySchema = z18.object({
1736
+ var featureCatalogEntrySchema = z19.object({
1757
1737
  // Optional human-friendly summary; surfaced in dashboards / settings UI.
1758
- description: z18.string().max(500).optional(),
1759
- routes: z18.array(featureRouteSchema).min(1).max(50),
1738
+ description: z19.string().max(500).optional(),
1739
+ routes: z19.array(featureRouteSchema).min(1).max(50),
1760
1740
  // Plans that grant this feature. Feature-first canonical mapping —
1761
1741
  // builders declare "which plans get this feature" on the feature
1762
1742
  // itself rather than enumerating features per plan. Required and
1763
1743
  // non-empty: a feature with no plans grants nothing and is a likely
1764
1744
  // typo. Cross-reference validation (every key resolves to an
1765
1745
  // existing plan) lives in `validateFeatureReferences` below.
1766
- plans: z18.array(z18.string().min(1)).min(1).max(20)
1746
+ plans: z19.array(z19.string().min(1)).min(1).max(20)
1767
1747
  });
1768
- var featureCatalogSchema = z18.record(z18.string().min(1).max(100).regex(/^[a-z0-9_.:-]+$/), featureCatalogEntrySchema);
1769
- var productCleanupPolicyModeSchema = z18.enum([
1748
+ var featureCatalogSchema = z19.record(z19.string().min(1).max(100).regex(/^[a-z0-9_.:-]+$/), featureCatalogEntrySchema);
1749
+ var productCleanupPolicyModeSchema = z19.enum([
1770
1750
  "report",
1771
1751
  "pull_request"
1772
1752
  ]);
1773
- var productChangeApprovalRiskSchema = z18.enum([
1753
+ var productChangeApprovalRiskSchema = z19.enum([
1774
1754
  "safe",
1775
1755
  "non_blocking",
1776
1756
  "economic_risk",
1777
1757
  "blocking"
1778
1758
  ]);
1779
- var productOperatorPoliciesSchema = z18.object({
1759
+ var productOperatorPoliciesSchema = z19.object({
1780
1760
  /**
1781
1761
  * Route cleanup operator. Disabled by default; report-mode is the safe
1782
1762
  * default so a product can surface zero-traffic runtime-route candidates
1783
1763
  * before it opts into draft PR mutation.
1784
1764
  */
1785
- cleanup: z18.object({
1786
- enabled: z18.boolean().default(false),
1765
+ cleanup: z19.object({
1766
+ enabled: z19.boolean().default(false),
1787
1767
  mode: productCleanupPolicyModeSchema.default("report")
1788
1768
  }).default({ enabled: false, mode: "report" }),
1789
1769
  /**
@@ -1791,9 +1771,9 @@ var productOperatorPoliciesSchema = z18.object({
1791
1771
  * the policy a first-class product-as-code field even while enforcement is
1792
1772
  * still report/label-only.
1793
1773
  */
1794
- change_approval: z18.object({
1795
- auto_merge_max_risk: z18.enum(["none", "safe", "non_blocking"]).default("none"),
1796
- require_human_for: z18.array(productChangeApprovalRiskSchema).default(["economic_risk", "blocking"])
1774
+ change_approval: z19.object({
1775
+ auto_merge_max_risk: z19.enum(["none", "safe", "non_blocking"]).default("none"),
1776
+ require_human_for: z19.array(productChangeApprovalRiskSchema).default(["economic_risk", "blocking"])
1797
1777
  }).default({
1798
1778
  auto_merge_max_risk: "none",
1799
1779
  require_human_for: ["economic_risk", "blocking"]
@@ -1805,15 +1785,15 @@ var productOperatorPoliciesSchema = z18.object({
1805
1785
  require_human_for: ["economic_risk", "blocking"]
1806
1786
  }
1807
1787
  });
1808
- var customerIdentityRequirementSchema = z18.enum([
1788
+ var customerIdentityRequirementSchema = z19.enum([
1809
1789
  "org_only",
1810
1790
  "org_and_user"
1811
1791
  ]);
1812
- var customerPortalAuthStrategySchema = z18.enum([
1792
+ var customerPortalAuthStrategySchema = z19.enum([
1813
1793
  "clerk",
1814
1794
  "test-personas"
1815
1795
  ]);
1816
- var productCustomerContextSchema = z18.object({
1796
+ var productCustomerContextSchema = z19.object({
1817
1797
  /**
1818
1798
  * Edge credential identity policy. This is intentionally Product-scoped:
1819
1799
  * B7 keeps Product as the product boundary and avoids customer-side
@@ -1825,19 +1805,19 @@ var productCustomerContextSchema = z18.object({
1825
1805
  * runtime signing secret in product/product.config.ts. Core generates/preserves the
1826
1806
  * secret when this is true and clears it when explicitly false.
1827
1807
  */
1828
- context_tokens: z18.object({
1829
- enabled: z18.boolean().default(true)
1808
+ context_tokens: z19.object({
1809
+ enabled: z19.boolean().default(true)
1830
1810
  }).optional(),
1831
1811
  /**
1832
1812
  * Portal auth strategy for environment-scoped product applies. Production
1833
1813
  * portal auth is provisioner-owned; preview/test environments can opt into
1834
1814
  * test personas through Product-as-Code.
1835
1815
  */
1836
- portal_auth: z18.object({
1816
+ portal_auth: z19.object({
1837
1817
  strategy: customerPortalAuthStrategySchema
1838
1818
  }).optional()
1839
1819
  });
1840
- var productSurfaceTypeSchema = z18.enum([
1820
+ var productSurfaceTypeSchema = z19.enum([
1841
1821
  "frontend",
1842
1822
  "api",
1843
1823
  "docs",
@@ -1847,84 +1827,84 @@ var productSurfaceTypeSchema = z18.enum([
1847
1827
  "worker",
1848
1828
  "agent"
1849
1829
  ]);
1850
- var productSurfaceSchema = z18.object({
1851
- key: z18.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Surface key must be lowercase alphanumeric with hyphens/underscores"),
1830
+ var productSurfaceSchema = z19.object({
1831
+ key: z19.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Surface key must be lowercase alphanumeric with hyphens/underscores"),
1852
1832
  type: productSurfaceTypeSchema,
1853
- display: z18.string().min(1).max(100).optional(),
1854
- description: z18.string().max(500).optional()
1833
+ display: z19.string().min(1).max(100).optional(),
1834
+ description: z19.string().max(500).optional()
1855
1835
  });
1856
- var productEntitlementSchema = z18.object({
1857
- key: z18.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Entitlement key must be lowercase alphanumeric with hyphens/underscores"),
1858
- description: z18.string().max(500).optional(),
1859
- capabilities: z18.array(z18.string().min(1).max(100)).max(100).optional(),
1860
- featureGates: z18.record(z18.string().min(1), z18.boolean()).optional(),
1861
- limits: z18.array(planLimitRuleSchema).max(100).optional(),
1862
- meters: z18.array(z18.string().min(1).max(64)).max(100).optional()
1863
- });
1864
- var productSurfacesSchema = z18.array(productSurfaceSchema).max(20).default([]);
1865
- var productEntitlementsSchema = z18.array(productEntitlementSchema).max(100).default([]);
1866
- var productWorkflowKindSchema = z18.enum([
1836
+ var productEntitlementSchema = z19.object({
1837
+ key: z19.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Entitlement key must be lowercase alphanumeric with hyphens/underscores"),
1838
+ description: z19.string().max(500).optional(),
1839
+ capabilities: z19.array(z19.string().min(1).max(100)).max(100).optional(),
1840
+ featureGates: z19.record(z19.string().min(1), z19.boolean()).optional(),
1841
+ limits: z19.array(planLimitRuleSchema).max(100).optional(),
1842
+ meters: z19.array(z19.string().min(1).max(64)).max(100).optional()
1843
+ });
1844
+ var productSurfacesSchema = z19.array(productSurfaceSchema).max(20).default([]);
1845
+ var productEntitlementsSchema = z19.array(productEntitlementSchema).max(100).default([]);
1846
+ var productWorkflowKindSchema = z19.enum([
1867
1847
  "async_job",
1868
1848
  "agent_task",
1869
1849
  "scheduled",
1870
1850
  "lifecycle",
1871
1851
  "background"
1872
1852
  ]);
1873
- var productWorkflowTriggerSchema = z18.discriminatedUnion("type", [
1874
- z18.object({ type: z18.literal("manual") }),
1875
- z18.object({
1876
- type: z18.literal("schedule"),
1877
- cron: z18.string().min(1).max(120)
1853
+ var productWorkflowTriggerSchema = z19.discriminatedUnion("type", [
1854
+ z19.object({ type: z19.literal("manual") }),
1855
+ z19.object({
1856
+ type: z19.literal("schedule"),
1857
+ cron: z19.string().min(1).max(120)
1878
1858
  }),
1879
- z18.object({
1880
- type: z18.literal("event"),
1881
- event: z18.string().min(1).max(120)
1859
+ z19.object({
1860
+ type: z19.literal("event"),
1861
+ event: z19.string().min(1).max(120)
1882
1862
  }),
1883
- z18.object({
1884
- type: z18.literal("api"),
1885
- path: z18.string().min(1).max(240).regex(/^\//, "path must start with /")
1863
+ z19.object({
1864
+ type: z19.literal("api"),
1865
+ path: z19.string().min(1).max(240).regex(/^\//, "path must start with /")
1886
1866
  }),
1887
- z18.object({
1888
- type: z18.literal("lifecycle"),
1889
- event: z18.string().min(1).max(120)
1867
+ z19.object({
1868
+ type: z19.literal("lifecycle"),
1869
+ event: z19.string().min(1).max(120)
1890
1870
  })
1891
1871
  ]);
1892
- var productWorkflowSchema = z18.object({
1893
- key: z18.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Workflow key must be lowercase alphanumeric with hyphens/underscores"),
1894
- title: z18.string().min(1).max(120).optional(),
1895
- description: z18.string().max(1e3).optional(),
1872
+ var productWorkflowSchema = z19.object({
1873
+ key: z19.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Workflow key must be lowercase alphanumeric with hyphens/underscores"),
1874
+ title: z19.string().min(1).max(120).optional(),
1875
+ description: z19.string().max(1e3).optional(),
1896
1876
  kind: productWorkflowKindSchema,
1897
1877
  trigger: productWorkflowTriggerSchema,
1898
- capabilities: z18.array(z18.string().min(1).max(100)).max(100).optional(),
1899
- meters: z18.array(z18.string().min(1).max(64)).max(100).optional(),
1900
- estimates: z18.record(z18.string().min(1).max(64), z18.number().finite()).optional(),
1901
- metadata: z18.record(z18.string().min(1), z18.unknown()).optional()
1902
- });
1903
- var productWorkflowsSchema = z18.array(productWorkflowSchema).max(100).default([]);
1904
- var productSpecSchema = z18.object({
1905
- product: z18.object({
1906
- name: z18.string().min(1).max(100),
1907
- displayName: z18.string().max(200).optional(),
1908
- description: z18.string().max(2e3).optional(),
1909
- baseUrl: z18.string().url("baseUrl must be a valid URL"),
1910
- sandboxBaseUrl: z18.string().url("sandboxBaseUrl must be a valid URL").optional(),
1911
- visibility: z18.enum(["public", "private"]).default("public"),
1878
+ capabilities: z19.array(z19.string().min(1).max(100)).max(100).optional(),
1879
+ meters: z19.array(z19.string().min(1).max(64)).max(100).optional(),
1880
+ estimates: z19.record(z19.string().min(1).max(64), z19.number().finite()).optional(),
1881
+ metadata: z19.record(z19.string().min(1), z19.unknown()).optional()
1882
+ });
1883
+ var productWorkflowsSchema = z19.array(productWorkflowSchema).max(100).default([]);
1884
+ var productSpecSchema = z19.object({
1885
+ product: z19.object({
1886
+ name: z19.string().min(1).max(100),
1887
+ displayName: z19.string().max(200).optional(),
1888
+ description: z19.string().max(2e3).optional(),
1889
+ baseUrl: z19.string().url("baseUrl must be a valid URL"),
1890
+ sandboxBaseUrl: z19.string().url("sandboxBaseUrl must be a valid URL").optional(),
1891
+ visibility: z19.enum(["public", "private"]).default("public"),
1912
1892
  // Branding
1913
- logoUrl: z18.string().url().optional(),
1914
- primaryColor: z18.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
1893
+ logoUrl: z19.string().url().optional(),
1894
+ primaryColor: z19.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
1915
1895
  // Environment
1916
- envBranchPrefix: z18.string().max(50).nullable().optional()
1896
+ envBranchPrefix: z19.string().max(50).nullable().optional()
1917
1897
  }),
1918
- gateway: z18.object({
1919
- authHeader: z18.string().min(1).max(100).default("x-api-key"),
1920
- upstreamAuth: z18.object({
1921
- type: z18.enum(["none", "static_bearer"]),
1922
- token: z18.string().optional()
1898
+ gateway: z19.object({
1899
+ authHeader: z19.string().min(1).max(100).default("x-api-key"),
1900
+ upstreamAuth: z19.object({
1901
+ type: z19.enum(["none", "static_bearer"]),
1902
+ token: z19.string().optional()
1923
1903
  }).default({ type: "none" })
1924
1904
  }),
1925
- metering: z18.object({
1926
- meters: z18.array(meterDefinitionSchema).max(10).default([]),
1927
- billOn4xx: z18.boolean().default(false)
1905
+ metering: z19.object({
1906
+ meters: z19.array(meterDefinitionSchema).max(10).default([]),
1907
+ billOn4xx: z19.boolean().default(false)
1928
1908
  }).default({ meters: [], billOn4xx: false }),
1929
1909
  /**
1930
1910
  * BYO-Backend V1 — first-class backend declarations, keyed by backend id.
@@ -1939,7 +1919,7 @@ var productSpecSchema = z18.object({
1939
1919
  */
1940
1920
  backend: productBackendBlockSchema.optional(),
1941
1921
  usage: usageBlockSchema.optional(),
1942
- usagePricing: z18.never({
1922
+ usagePricing: z19.never({
1943
1923
  error: "usagePricing is not supported. Define usage.meters.<key>.rating instead."
1944
1924
  }).optional(),
1945
1925
  features: featureCatalogSchema.optional(),
@@ -1960,53 +1940,51 @@ var productSpecSchema = z18.object({
1960
1940
  // billing shape via the unified 5-knob spec (`meters`,
1961
1941
  // `recurring_fee_cents`, `recurring_credit_grant_cents`,
1962
1942
  // `one_time_credit_grant_cents`, `trial_days`,
1963
- // `max_monthly_spend_cents`). The product-level `billing` block
1964
- // retains the transition-policy fields (`gracePeriodDays`,
1965
- // `subscriberChangePolicy`); the strategy enum is gone.
1966
- billing: z18.object({
1967
- gracePeriodDays: z18.number().int().nonnegative().default(3),
1943
+ // `max_monthly_spend_cents`). The product-level `billing` block retains
1944
+ // only the live transition-policy fields. v(refactor) — the inert
1945
+ // `gracePeriodDays` knob was deleted (the compiler hardcoded it and
1946
+ // Stripe Smart Retries owns dunning); the block now carries the
1947
+ // subscriber-change timing policy + the limit-upgrade projection flag.
1948
+ billing: z19.object({
1968
1949
  // When true (default), a plan limit INCREASE re-projects onto active
1969
1950
  // subscribers immediately; a DECREASE always defers to period end.
1970
1951
  // Read by advanceActiveSubscribersToLatestCompiledPlans.
1971
- applyLimitUpgradesInstantly: z18.boolean().optional(),
1952
+ applyLimitUpgradesInstantly: z19.boolean().optional(),
1972
1953
  subscriberChangePolicy: subscriberChangePolicySchema.default({
1973
- default: "preserve_current_period",
1974
- proration: "none",
1954
+ default: "period_end",
1975
1955
  when: {
1976
- price_increase: "preserve_current_period",
1977
- price_decrease: "switch_immediately",
1978
- feature_added: "switch_immediately",
1979
- feature_removed: "preserve_current_period",
1980
- limit_increased: "switch_immediately",
1981
- limit_reduced: "preserve_current_period",
1982
- credit_increased: "switch_immediately",
1983
- credit_reduced: "preserve_current_period",
1984
- rating_changed: "preserve_current_period"
1956
+ price_increase: "period_end",
1957
+ price_decrease: "immediate",
1958
+ feature_added: "immediate",
1959
+ feature_removed: "period_end",
1960
+ limit_increased: "immediate",
1961
+ limit_reduced: "period_end",
1962
+ credit_increased: "immediate",
1963
+ credit_reduced: "period_end",
1964
+ rating_changed: "period_end"
1985
1965
  },
1986
1966
  allowImmediatePriceIncrease: false,
1987
1967
  allowImmediateEntitlementReduction: false
1988
1968
  })
1989
1969
  }).default({
1990
- gracePeriodDays: 3,
1991
1970
  subscriberChangePolicy: {
1992
- default: "preserve_current_period",
1993
- proration: "none",
1971
+ default: "period_end",
1994
1972
  when: {
1995
- price_increase: "preserve_current_period",
1996
- price_decrease: "switch_immediately",
1997
- feature_added: "switch_immediately",
1998
- feature_removed: "preserve_current_period",
1999
- limit_increased: "switch_immediately",
2000
- limit_reduced: "preserve_current_period",
2001
- credit_increased: "switch_immediately",
2002
- credit_reduced: "preserve_current_period",
2003
- rating_changed: "preserve_current_period"
1973
+ price_increase: "period_end",
1974
+ price_decrease: "immediate",
1975
+ feature_added: "immediate",
1976
+ feature_removed: "period_end",
1977
+ limit_increased: "immediate",
1978
+ limit_reduced: "period_end",
1979
+ credit_increased: "immediate",
1980
+ credit_reduced: "period_end",
1981
+ rating_changed: "period_end"
2004
1982
  },
2005
1983
  allowImmediatePriceIncrease: false,
2006
1984
  allowImmediateEntitlementReduction: false
2007
1985
  }
2008
1986
  }),
2009
- plans: z18.array(planSpecSchema).max(4).default([]),
1987
+ plans: z19.array(planSpecSchema).max(4).default([]),
2010
1988
  /**
2011
1989
  * Add-on catalog (v0.56+). Composable economic + entitlement
2012
1990
  * overlays that subscribers can pile on top of their base plan.
@@ -2049,16 +2027,16 @@ var productSpecSchema = z18.object({
2049
2027
  * require_deprecation_window_days: 90
2050
2028
  * require_successor_route: true
2051
2029
  */
2052
- lifecycle: z18.object({
2053
- breaking_changes: z18.object({
2030
+ lifecycle: z19.object({
2031
+ breaking_changes: z19.object({
2054
2032
  /** Minimum days a route must have been marked for removal
2055
2033
  * (in main-branch YAML) before the publish gate will let
2056
2034
  * it actually be removed. Set to 0 to disable. */
2057
- require_deprecation_window_days: z18.number().int().nonnegative().default(0),
2035
+ require_deprecation_window_days: z19.number().int().nonnegative().default(0),
2058
2036
  /** When true, a route removal must declare a successor
2059
2037
  * route via the lifecycle metadata (mechanics in core
2060
2038
  * 3b-2) before the publish gate accepts it. */
2061
- require_successor_route: z18.boolean().default(false)
2039
+ require_successor_route: z19.boolean().default(false)
2062
2040
  }).default({
2063
2041
  require_deprecation_window_days: 0,
2064
2042
  require_successor_route: false
@@ -2104,8 +2082,8 @@ var productSpecSchema = z18.object({
2104
2082
  * (preserves today's behaviour). Compiler validation pins the
2105
2083
  * value to a real `plans[].key`.
2106
2084
  */
2107
- ephemeral: z18.object({
2108
- defaultPlan: z18.string().min(1).optional()
2085
+ ephemeral: z19.object({
2086
+ defaultPlan: z19.string().min(1).optional()
2109
2087
  }).optional()
2110
2088
  }).superRefine((spec, ctx) => {
2111
2089
  rejectUsagePricing(spec, ctx);
@@ -2117,24 +2095,24 @@ var productSpecSchema = z18.object({
2117
2095
  validateLimitMeterReachability(spec, ctx);
2118
2096
  validateBackendReferences(spec, ctx);
2119
2097
  });
2120
- var productPhaseSchema = z18.object({
2098
+ var productPhaseSchema = z19.object({
2121
2099
  product: productSpecSchema.shape.product
2122
2100
  });
2123
- var gatewayPhaseSchema = z18.object({
2101
+ var gatewayPhaseSchema = z19.object({
2124
2102
  gateway: productSpecSchema.shape.gateway
2125
2103
  });
2126
- var meteringPhaseSchema = z18.object({
2104
+ var meteringPhaseSchema = z19.object({
2127
2105
  metering: productSpecSchema.shape.metering
2128
2106
  });
2129
- var plansPhaseSchema = z18.object({
2107
+ var plansPhaseSchema = z19.object({
2130
2108
  plans: productSpecSchema.shape.plans
2131
2109
  });
2132
2110
 
2133
2111
  // ../contracts/dist/plans/spec/capabilities-layer.js
2134
- import { z as z19 } from "zod";
2135
- var capabilityLayerSchema = z19.object({
2136
- capability: z19.string().min(1).max(120).regex(/^[a-z0-9_-]+$/, "capability name must be lowercase alphanumeric with hyphens/underscores"),
2137
- description: z19.string().max(500).optional(),
2112
+ import { z as z20 } from "zod";
2113
+ var capabilityLayerSchema = z20.object({
2114
+ capability: z20.string().min(1).max(120).regex(/^[a-z0-9_-]+$/, "capability name must be lowercase alphanumeric with hyphens/underscores"),
2115
+ description: z20.string().max(500).optional(),
2138
2116
  /**
2139
2117
  * Capability composition is contractual by default — including a new
2140
2118
  * feature changes the customer's effective entitlement. Mark
@@ -2142,28 +2120,28 @@ var capabilityLayerSchema = z19.object({
2142
2120
  * (e.g. an internal "monitoring" capability that gates dashboard
2143
2121
  * pages without affecting billable behaviour).
2144
2122
  */
2145
- mutation_class: z19.enum(["runtime", "contractual"]).default("contractual"),
2146
- includes_features: z19.array(z19.string().min(1).max(100).regex(/^[a-z0-9_.:-]+$/)).max(20).default([]),
2147
- includes_policies: z19.array(z19.string().min(1).max(64).regex(/^[a-z0-9_-]+$/)).max(20).default([]),
2148
- includes_capabilities: z19.array(z19.string().min(1).max(120).regex(/^[a-z0-9_-]+$/)).max(20).default([])
2123
+ mutation_class: z20.enum(["runtime", "contractual"]).default("contractual"),
2124
+ includes_features: z20.array(z20.string().min(1).max(100).regex(/^[a-z0-9_.:-]+$/)).max(20).default([]),
2125
+ includes_policies: z20.array(z20.string().min(1).max(64).regex(/^[a-z0-9_-]+$/)).max(20).default([]),
2126
+ includes_capabilities: z20.array(z20.string().min(1).max(120).regex(/^[a-z0-9_-]+$/)).max(20).default([])
2149
2127
  });
2150
2128
 
2151
2129
  // ../contracts/dist/plans/spec/manifest-ir.js
2152
2130
  import { createHash } from "node:crypto";
2153
- import { z as z20 } from "zod";
2131
+ import { z as z21 } from "zod";
2154
2132
  var MANIFEST_IR_VERSION = 1;
2155
- var manifestIrSchema = z20.object({
2156
- irVersion: z20.literal(MANIFEST_IR_VERSION),
2133
+ var manifestIrSchema = z21.object({
2134
+ irVersion: z21.literal(MANIFEST_IR_VERSION),
2157
2135
  /** Version of @farthershore/product that emitted this envelope. */
2158
- sdkVersion: z20.string().min(1).max(64),
2136
+ sdkVersion: z21.string().min(1).max(64),
2159
2137
  /** Legacy unified ProductSpec — the live `CompileProductOptions.sourceSpec`. */
2160
2138
  product: productSpecSchema,
2161
2139
  /** One entry per feature, sorted by `feature`. */
2162
- routes: z20.array(routeLayerSchema).max(200).default([]),
2140
+ routes: z21.array(routeLayerSchema).max(200).default([]),
2163
2141
  /** Sorted by `name`. */
2164
- policies: z20.array(policyLayerSchema).max(200).default([]),
2142
+ policies: z21.array(policyLayerSchema).max(200).default([]),
2165
2143
  /** Sorted by `capability`. */
2166
- capabilities: z20.array(capabilityLayerSchema).max(200).default([])
2144
+ capabilities: z21.array(capabilityLayerSchema).max(200).default([])
2167
2145
  }).strict();
2168
2146
  function canonicalManifestJson(value) {
2169
2147
  return stableJson(JSON.parse(JSON.stringify(value)));
@@ -2203,7 +2181,7 @@ var STARTER = {
2203
2181
  ],
2204
2182
  recurring_fee_cents: 2e3,
2205
2183
  billing_interval: "month",
2206
- grants: [{ kind: "recurring", amount_cents: 2e3 }],
2184
+ grants: [{ kind: "credit", amount_cents: 2e3, recurs: true }],
2207
2185
  trial_days: 0
2208
2186
  }
2209
2187
  };
@@ -2217,7 +2195,7 @@ var PRO = {
2217
2195
  ],
2218
2196
  recurring_fee_cents: 1e4,
2219
2197
  billing_interval: "month",
2220
- grants: [{ kind: "recurring", amount_cents: 2e4 }],
2198
+ grants: [{ kind: "credit", amount_cents: 2e4, recurs: true }],
2221
2199
  trial_days: 14
2222
2200
  }
2223
2201
  };
@@ -2231,7 +2209,7 @@ var PREPAID = {
2231
2209
  ],
2232
2210
  recurring_fee_cents: 0,
2233
2211
  billing_interval: "month",
2234
- grants: [{ kind: "one_time", amount_cents: 5e3 }],
2212
+ grants: [{ kind: "credit", amount_cents: 5e3, recurs: false }],
2235
2213
  trial_days: 0
2236
2214
  }
2237
2215
  };
@@ -2258,28 +2236,31 @@ var PLAN_PRESETS = Object.freeze({
2258
2236
  });
2259
2237
 
2260
2238
  // ../contracts/dist/plans/subscription-pricing-override.js
2261
- import { z as z21 } from "zod";
2262
- var subscriptionPricingOverrideSchema = z21.object({
2239
+ import { z as z22 } from "zod";
2240
+ var subscriptionPricingOverrideSchema = z22.object({
2263
2241
  /** Override the plan's recurring fee for this subscriber. */
2264
- recurring_fee_cents: z21.number().int().nonnegative().optional(),
2242
+ recurring_fee_cents: z22.number().int().nonnegative().optional(),
2265
2243
  /**
2266
2244
  * Override the plan's credit grants. `grants[]` is the single credit
2267
2245
  * surface — when present, it fully replaces the plan's grants for this
2268
2246
  * subscriber (recurring + one-time credit are canonical entries here;
2269
2247
  * the legacy recurring/one-time scalar knobs were removed).
2270
2248
  */
2271
- grants: z21.array(grantSchema).max(40).optional(),
2249
+ grants: z22.array(grantSchema).max(40).optional(),
2250
+ /** Override the plan's credit-policy block (rollover + auto-recharge)
2251
+ * for this subscriber. */
2252
+ creditPolicy: creditPolicySchema.optional(),
2272
2253
  /** Override the minimum-spend floor. */
2273
- min_monthly_spend_cents: z21.number().int().nonnegative().optional(),
2254
+ min_monthly_spend_cents: z22.number().int().nonnegative().optional(),
2274
2255
  /** Override the maximum-spend ceiling. */
2275
- max_monthly_spend_cents: z21.number().int().nonnegative().optional(),
2256
+ max_monthly_spend_cents: z22.number().int().nonnegative().optional(),
2276
2257
  /** Replace the entire meter list for this subscriber. When set, the
2277
2258
  * full plan meter array is replaced (not merged) — call it explicit
2278
2259
  * rather than implicit so a deal can also REMOVE billable meters,
2279
2260
  * not just adjust rates. */
2280
- meters: z21.array(meterSchema).optional(),
2261
+ meters: z22.array(meterSchema).optional(),
2281
2262
  /** Free-text notes about the deal. Surfaced in admin UI for audit. */
2282
- notes: z21.string().max(2e3).optional()
2263
+ notes: z22.string().max(2e3).optional()
2283
2264
  });
2284
2265
 
2285
2266
  // src/validate.ts
@@ -2305,7 +2286,7 @@ function hashIr(ir) {
2305
2286
  }
2306
2287
 
2307
2288
  // src/version.ts
2308
- var SDK_VERSION = true ? "0.6.0" : "0.0.0-dev";
2289
+ var SDK_VERSION = true ? "0.6.1" : "0.0.0-dev";
2309
2290
 
2310
2291
  // src/refs.ts
2311
2292
  function isCapabilityGrant(value) {
@@ -3061,6 +3042,7 @@ function buildPlanSpec(key, options) {
3061
3042
  } : {},
3062
3043
  ...options.meters ? { meters: options.meters } : {},
3063
3044
  ...creditGrants.length ? { grants: creditGrants } : {},
3045
+ ...options.creditPolicy ? { creditPolicy: options.creditPolicy } : {},
3064
3046
  ...options.trialDays !== void 0 ? { trial_days: options.trialDays } : {},
3065
3047
  ...options.maxMonthlySpendCents !== void 0 ? { max_monthly_spend_cents: options.maxMonthlySpendCents } : {},
3066
3048
  ...options.minMonthlySpendCents !== void 0 ? { min_monthly_spend_cents: options.minMonthlySpendCents } : {},