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