@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/README.md +1 -1
- package/dist/bin.js +540 -558
- package/dist/codegen.js +203 -165
- package/dist/index.js +540 -558
- package/dist/types/codegen/index.d.ts +9 -1
- package/dist/types/declarations.d.ts +3 -1
- package/dist/types/ir-types.d.ts +13 -1
- package/dist/types/product.d.ts +12 -4
- package/dist/types/route-metering.d.ts +3 -10
- package/package.json +1 -1
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("
|
|
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: "
|
|
170
|
-
price_decrease: "
|
|
171
|
-
feature_added: "
|
|
172
|
-
feature_removed: "
|
|
173
|
-
limit_increased: "
|
|
174
|
-
limit_reduced: "
|
|
175
|
-
credit_increased: "
|
|
176
|
-
credit_reduced: "
|
|
177
|
-
rating_changed: "
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
|
214
|
-
kind: z3.literal("
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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
|
|
243
|
+
if (isRecurringCredit(g))
|
|
268
244
|
recurringCount += 1;
|
|
269
|
-
else if (g
|
|
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
|
|
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 `
|
|
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 =
|
|
290
|
-
var meterTierSchema =
|
|
291
|
-
up_to:
|
|
292
|
-
price_per_unit_micros:
|
|
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 =
|
|
295
|
-
strategy:
|
|
296
|
-
tiers:
|
|
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 =
|
|
299
|
-
dimension:
|
|
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:
|
|
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:
|
|
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 =
|
|
371
|
-
var planPricingSchema =
|
|
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:
|
|
363
|
+
meters: z5.array(meterSchema).default([]),
|
|
374
364
|
/** Flat subscription fee per period, in cents. 0 = no recurring fee. */
|
|
375
|
-
recurring_fee_cents:
|
|
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
|
|
383
|
-
* were removed). */
|
|
384
|
-
grants:
|
|
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:
|
|
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:
|
|
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
|
|
397
|
-
var proRationOnRollbackSchema =
|
|
389
|
+
import { z as z6 } from "zod";
|
|
390
|
+
var proRationOnRollbackSchema = z6.enum(["NONE", "PRORATE", "CREDIT"]).default("NONE");
|
|
398
391
|
var billingKnobsShape = {
|
|
399
|
-
meters:
|
|
400
|
-
recurring_fee_cents:
|
|
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:
|
|
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:
|
|
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:
|
|
406
|
+
min_monthly_spend_cents: z6.number().int().nonnegative().optional(),
|
|
414
407
|
/**
|
|
415
|
-
* Unified grants array — the SINGLE credit surface
|
|
416
|
-
*
|
|
417
|
-
*
|
|
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
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
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:
|
|
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:
|
|
430
|
-
recurring_fee_cents:
|
|
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:
|
|
435
|
-
max_monthly_spend_cents:
|
|
436
|
-
min_monthly_spend_cents:
|
|
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:
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
477
|
-
featureGates:
|
|
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:
|
|
482
|
-
overageBehavior:
|
|
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 =
|
|
488
|
-
key:
|
|
489
|
-
name:
|
|
490
|
-
description:
|
|
491
|
-
details:
|
|
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:
|
|
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:
|
|
514
|
-
featureGates:
|
|
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:
|
|
545
|
-
overageBehavior:
|
|
546
|
-
selfServeEnabled:
|
|
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:
|
|
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:
|
|
569
|
-
archive:
|
|
570
|
-
at:
|
|
571
|
-
transitionTo:
|
|
572
|
-
strategy:
|
|
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
|
|
577
|
+
import { z as z8 } from "zod";
|
|
581
578
|
|
|
582
579
|
// ../contracts/dist/webhooks/events.js
|
|
583
|
-
import { z as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
602
|
-
maxAttempts:
|
|
603
|
-
backoff:
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
627
|
-
enabled:
|
|
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 =
|
|
634
|
-
endpoints:
|
|
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
|
|
639
|
-
var planOverrideSchema =
|
|
640
|
-
meters:
|
|
641
|
-
recurring_fee_cents:
|
|
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:
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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 =
|
|
659
|
-
url:
|
|
658
|
+
var webhookEndpointOverrideSchema = z9.object({
|
|
659
|
+
url: z9.string().url().optional(),
|
|
660
660
|
secret: webhookSecretSchema.optional(),
|
|
661
|
-
events:
|
|
662
|
-
enabled:
|
|
661
|
+
events: z9.array(webhookEventNameSchema).optional(),
|
|
662
|
+
enabled: z9.boolean().optional(),
|
|
663
663
|
retryPolicy: webhookRetryPolicySchema.partial().optional()
|
|
664
664
|
}).strict();
|
|
665
|
-
var environmentOverrideBlockSchema =
|
|
666
|
-
plans:
|
|
667
|
-
webhooks:
|
|
668
|
-
endpoints:
|
|
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 =
|
|
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
|
|
674
|
+
import { z as z19 } from "zod";
|
|
675
675
|
|
|
676
676
|
// ../contracts/dist/plans/spec/frontend-layer.js
|
|
677
|
-
import { z as
|
|
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 =
|
|
708
|
-
var frontendCapabilityRefSchema =
|
|
709
|
-
var frontendNavItemSchema =
|
|
710
|
-
label:
|
|
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 =
|
|
715
|
-
var frontendComponentSchema =
|
|
714
|
+
var frontendComponentIdSchema = z10.enum(KNOWN_FRONTEND_COMPONENT_IDS);
|
|
715
|
+
var frontendComponentSchema = z10.object({
|
|
716
716
|
component: frontendComponentIdSchema,
|
|
717
|
-
props:
|
|
717
|
+
props: z10.record(z10.string().min(1).max(80), z10.unknown()).optional(),
|
|
718
718
|
capability: frontendCapabilityRefSchema.optional(),
|
|
719
|
-
gateMode:
|
|
719
|
+
gateMode: z10.enum(["hide", "disable", "upsell"]).default("hide")
|
|
720
720
|
});
|
|
721
|
-
var frontendPageSchema =
|
|
721
|
+
var frontendPageSchema = z10.object({
|
|
722
722
|
path: frontendPathSchema,
|
|
723
|
-
title:
|
|
724
|
-
requiresAuth:
|
|
723
|
+
title: z10.string().min(1).max(120),
|
|
724
|
+
requiresAuth: z10.boolean(),
|
|
725
725
|
capability: frontendCapabilityRefSchema.optional(),
|
|
726
|
-
components:
|
|
726
|
+
components: z10.array(frontendComponentSchema).max(12).default([])
|
|
727
727
|
});
|
|
728
|
-
var frontendManifestSchema =
|
|
729
|
-
version:
|
|
730
|
-
nav:
|
|
731
|
-
pages:
|
|
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
|
|
764
|
-
var planKeySchema =
|
|
765
|
-
var versionRefSchema =
|
|
766
|
-
|
|
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 =
|
|
773
|
+
var migrationTargetSchema = z11.object({
|
|
771
774
|
plan: planKeySchema,
|
|
772
|
-
version:
|
|
775
|
+
version: z11.literal("head").default("head")
|
|
773
776
|
});
|
|
774
|
-
var pinnedPlanVersionSchema =
|
|
777
|
+
var pinnedPlanVersionSchema = z11.object({
|
|
775
778
|
plan: planKeySchema,
|
|
776
779
|
version: versionRefSchema
|
|
777
780
|
});
|
|
778
|
-
var migrationEffectiveSchema =
|
|
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 =
|
|
786
|
-
var migrationExistingCustomersSchema =
|
|
788
|
+
var migrationProrationSchema = z11.enum(["none", "prorate", "credit"]);
|
|
789
|
+
var migrationExistingCustomersSchema = z11.object({
|
|
787
790
|
effective: migrationEffectiveSchema,
|
|
788
|
-
date:
|
|
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 =
|
|
800
|
-
subscriber:
|
|
802
|
+
var migrationPinSchema = z11.object({
|
|
803
|
+
subscriber: z11.string().min(1).max(200),
|
|
801
804
|
pinTo: pinnedPlanVersionSchema,
|
|
802
|
-
until:
|
|
803
|
-
notes:
|
|
805
|
+
until: z11.string().datetime().optional(),
|
|
806
|
+
notes: z11.string().max(1e3).optional()
|
|
804
807
|
});
|
|
805
|
-
var migrationDeclSchema =
|
|
806
|
-
id:
|
|
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:
|
|
812
|
+
newCustomers: z11.literal("immediate").default("immediate"),
|
|
810
813
|
existingCustomers: migrationExistingCustomersSchema,
|
|
811
|
-
pins:
|
|
814
|
+
pins: z11.array(migrationPinSchema).max(50).default([])
|
|
812
815
|
});
|
|
813
|
-
var migrationDeclsSchema =
|
|
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
|
|
829
|
-
var countedResourceScopeSchema =
|
|
830
|
-
var countedResourceCountSourceSchema =
|
|
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 =
|
|
835
|
-
var countedResourceSchema =
|
|
837
|
+
var countedResourceNameSchema = z12.string().min(1).max(100).regex(/^[a-z0-9_.:-]+$/);
|
|
838
|
+
var countedResourceSchema = z12.object({
|
|
836
839
|
name: countedResourceNameSchema,
|
|
837
|
-
display:
|
|
840
|
+
display: z12.string().min(1).max(120).optional(),
|
|
838
841
|
scope: countedResourceScopeSchema.default("subscription"),
|
|
839
|
-
subjectType:
|
|
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 =
|
|
860
|
+
var countedResourcesSchema = z12.array(countedResourceSchema).max(100).default([]);
|
|
858
861
|
|
|
859
862
|
// ../contracts/dist/plans/addons.js
|
|
860
|
-
import { z as
|
|
861
|
-
var addOnSchema =
|
|
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:
|
|
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:
|
|
870
|
+
name: z13.string().min(1).max(100),
|
|
868
871
|
/** Short description for tooltips / chooser. */
|
|
869
|
-
description:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
897
|
+
selfServeEnabled: z13.boolean().default(true)
|
|
895
898
|
});
|
|
896
|
-
var addOnsBlockSchema =
|
|
897
|
-
var subscriptionAddOnStatusSchema =
|
|
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 =
|
|
906
|
+
var subscriptionAddOnSchema = z13.object({
|
|
904
907
|
/** References `product.add_ons.<addOnKey>`. */
|
|
905
|
-
addOnKey:
|
|
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:
|
|
914
|
+
activatedAt: z13.string().datetime().optional(),
|
|
912
915
|
/** When the AddOn was canceled (ISO-8601). Null while active. */
|
|
913
|
-
canceledAt:
|
|
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:
|
|
919
|
+
stripeSubscriptionItemId: z13.string().optional()
|
|
917
920
|
});
|
|
918
921
|
|
|
919
922
|
// ../contracts/dist/plans/spec/backend-layer.js
|
|
920
|
-
import { z as
|
|
923
|
+
import { z as z14 } from "zod";
|
|
921
924
|
var BACKEND_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
|
|
922
|
-
var backendIdSchema =
|
|
923
|
-
var backendTransportModeSchema =
|
|
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 =
|
|
931
|
+
var backendTransportRunnerSchema = z14.enum([
|
|
929
932
|
"managed_cloudflared",
|
|
930
933
|
"sidecar"
|
|
931
934
|
]);
|
|
932
|
-
var backendTransportSchema =
|
|
935
|
+
var backendTransportSchema = z14.object({
|
|
933
936
|
mode: backendTransportModeSchema.default("public_origin"),
|
|
934
937
|
runner: backendTransportRunnerSchema.optional()
|
|
935
938
|
}).strict();
|
|
936
|
-
var backendVerificationSchema =
|
|
937
|
-
required:
|
|
939
|
+
var backendVerificationSchema = z14.object({
|
|
940
|
+
required: z14.boolean().default(false)
|
|
938
941
|
}).strict();
|
|
939
|
-
var backendDefinitionSchema =
|
|
942
|
+
var backendDefinitionSchema = z14.object({
|
|
940
943
|
/** Human-friendly label. Defaults to the id when omitted. */
|
|
941
|
-
name:
|
|
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:
|
|
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:
|
|
954
|
+
default: z14.boolean().optional(),
|
|
952
955
|
/** Reachable origin for `public_origin` / `mtls`. */
|
|
953
|
-
originUrl:
|
|
956
|
+
originUrl: z14.string().url().optional(),
|
|
954
957
|
/** Access-protected `*.fs-origin` host for `cloudflare_tunnel`. */
|
|
955
|
-
originHostname:
|
|
958
|
+
originHostname: z14.string().min(1).max(255).optional()
|
|
956
959
|
}).strict();
|
|
957
|
-
var productBackendBlockSchema =
|
|
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
|
|
982
|
+
import { z as z18 } from "zod";
|
|
980
983
|
|
|
981
984
|
// ../contracts/dist/plans/spec/policies-layer.js
|
|
982
|
-
import { z as
|
|
985
|
+
import { z as z16 } from "zod";
|
|
983
986
|
|
|
984
987
|
// ../contracts/dist/plans/spec/policy-types.js
|
|
985
|
-
import { z as
|
|
986
|
-
var rateLimitWindowSchema =
|
|
987
|
-
var rateLimitConfigSchema =
|
|
988
|
-
strategy:
|
|
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:
|
|
997
|
-
limits:
|
|
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:
|
|
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:
|
|
1010
|
-
max_consecutive_failures:
|
|
1011
|
-
max_window_seconds:
|
|
1012
|
-
recovery_threshold:
|
|
1013
|
-
degraded_mode:
|
|
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 =
|
|
1026
|
-
header_name:
|
|
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:
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
type:
|
|
1037
|
-
token_secret_ref:
|
|
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:
|
|
1048
|
+
scope_mode: z15.enum(["strict", "advisory", "off"]).default("strict")
|
|
1046
1049
|
});
|
|
1047
|
-
var concurrencyConfigSchema =
|
|
1048
|
-
max_in_flight:
|
|
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:
|
|
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:
|
|
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:
|
|
1066
|
-
fail_open:
|
|
1067
|
-
max_consecutive_failures:
|
|
1068
|
-
max_window_seconds:
|
|
1069
|
-
recovery_threshold:
|
|
1070
|
-
degraded_mode:
|
|
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 =
|
|
1083
|
-
max_attempts:
|
|
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:
|
|
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:
|
|
1094
|
-
initial_ms:
|
|
1095
|
-
multiplier:
|
|
1096
|
-
jitter:
|
|
1097
|
-
total_budget_ms:
|
|
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 =
|
|
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:
|
|
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:
|
|
1118
|
-
source:
|
|
1119
|
-
target:
|
|
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 =
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
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 =
|
|
1132
|
-
var policyCompatibilitySchema =
|
|
1133
|
-
route_types:
|
|
1134
|
-
meters:
|
|
1135
|
-
auth_modes:
|
|
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 =
|
|
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:
|
|
1144
|
-
description:
|
|
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:
|
|
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
|
|
1158
|
-
var actionKindSchema =
|
|
1159
|
-
var actionAuditPolicySchema =
|
|
1160
|
-
var actionSubjectBindingSchema =
|
|
1161
|
-
type:
|
|
1162
|
-
from:
|
|
1163
|
-
name:
|
|
1164
|
-
});
|
|
1165
|
-
var actionResourceEffectSchema =
|
|
1166
|
-
resource:
|
|
1167
|
-
effect:
|
|
1168
|
-
});
|
|
1169
|
-
var actionSpecSchema =
|
|
1170
|
-
id:
|
|
1171
|
-
title:
|
|
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:
|
|
1176
|
+
actorType: z17.string().min(1).max(64).regex(/^[a-zA-Z0-9_.:-]+$/).optional(),
|
|
1174
1177
|
subject: actionSubjectBindingSchema.optional(),
|
|
1175
|
-
inputSchemaRef:
|
|
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 =
|
|
1182
|
-
method:
|
|
1183
|
-
path:
|
|
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 =
|
|
1202
|
-
|
|
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
|
-
|
|
1208
|
+
z18.array(z18.number().int().min(100).max(599)).min(1).max(100)
|
|
1206
1209
|
]);
|
|
1207
|
-
var routeDefinitionSchema =
|
|
1210
|
+
var routeDefinitionSchema = z18.object({
|
|
1208
1211
|
match: routeMatchSchema,
|
|
1209
|
-
metering:
|
|
1210
|
-
defaults:
|
|
1211
|
-
reports:
|
|
1212
|
-
estimates:
|
|
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:
|
|
1216
|
-
inheritDefaultMeters:
|
|
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:
|
|
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 =
|
|
1227
|
-
override_origin:
|
|
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 =
|
|
1230
|
-
rollout_key:
|
|
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:
|
|
1240
|
+
required_flags: z18.array(z18.string().min(1).max(120).regex(/^[a-z0-9_-]+$/)).max(10).optional()
|
|
1238
1241
|
});
|
|
1239
|
-
var routeLayerSchema =
|
|
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:
|
|
1245
|
-
description:
|
|
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:
|
|
1255
|
+
mutation_class: z18.enum(["runtime", "contractual"]).default("contractual"),
|
|
1253
1256
|
cacheProfile: cacheProfileSchema,
|
|
1254
|
-
routes:
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
1593
|
-
subdomain:
|
|
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 =
|
|
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 =
|
|
1602
|
-
key:
|
|
1603
|
-
display:
|
|
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:
|
|
1614
|
+
unit: z19.string().max(20).optional(),
|
|
1612
1615
|
/** Reusable pre-request estimate for routes that dynamically report this meter. */
|
|
1613
|
-
estimate:
|
|
1616
|
+
estimate: z19.number().finite().nonnegative().optional(),
|
|
1614
1617
|
/** Fixed per-request default applied by Product SDK helpers. */
|
|
1615
|
-
routeDefault:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
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
|
|
1703
|
-
source:
|
|
1704
|
-
|
|
1705
|
-
|
|
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 =
|
|
1690
|
+
var usageRatingSchema = z19.discriminatedUnion("source", [
|
|
1708
1691
|
fixedRatingSchema,
|
|
1709
|
-
providerCatalogRatingSchema
|
|
1710
|
-
upstreamReportedRatingSchema,
|
|
1711
|
-
externalRateApiRatingSchema,
|
|
1712
|
-
customRatingSchema
|
|
1692
|
+
providerCatalogRatingSchema
|
|
1713
1693
|
]);
|
|
1714
|
-
var usageMeterSchema =
|
|
1715
|
-
selector:
|
|
1716
|
-
measures:
|
|
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 =
|
|
1720
|
-
meters:
|
|
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 =
|
|
1723
|
-
defaults:
|
|
1724
|
-
reports:
|
|
1725
|
-
estimates:
|
|
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 =
|
|
1729
|
-
method:
|
|
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:
|
|
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:
|
|
1720
|
+
unmetered: z19.boolean().optional(),
|
|
1741
1721
|
metering: routeMeteringSchema.optional(),
|
|
1742
|
-
inheritDefaultMeters:
|
|
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 =
|
|
1728
|
+
var featureCatalogEntrySchema = z19.object({
|
|
1749
1729
|
// Optional human-friendly summary; surfaced in dashboards / settings UI.
|
|
1750
|
-
description:
|
|
1751
|
-
routes:
|
|
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:
|
|
1738
|
+
plans: z19.array(z19.string().min(1)).min(1).max(20)
|
|
1759
1739
|
});
|
|
1760
|
-
var featureCatalogSchema =
|
|
1761
|
-
var productCleanupPolicyModeSchema =
|
|
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 =
|
|
1745
|
+
var productChangeApprovalRiskSchema = z19.enum([
|
|
1766
1746
|
"safe",
|
|
1767
1747
|
"non_blocking",
|
|
1768
1748
|
"economic_risk",
|
|
1769
1749
|
"blocking"
|
|
1770
1750
|
]);
|
|
1771
|
-
var productOperatorPoliciesSchema =
|
|
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:
|
|
1778
|
-
enabled:
|
|
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:
|
|
1787
|
-
auto_merge_max_risk:
|
|
1788
|
-
require_human_for:
|
|
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 =
|
|
1780
|
+
var customerIdentityRequirementSchema = z19.enum([
|
|
1801
1781
|
"org_only",
|
|
1802
1782
|
"org_and_user"
|
|
1803
1783
|
]);
|
|
1804
|
-
var customerPortalAuthStrategySchema =
|
|
1784
|
+
var customerPortalAuthStrategySchema = z19.enum([
|
|
1805
1785
|
"clerk",
|
|
1806
1786
|
"test-personas"
|
|
1807
1787
|
]);
|
|
1808
|
-
var productCustomerContextSchema =
|
|
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:
|
|
1821
|
-
enabled:
|
|
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:
|
|
1808
|
+
portal_auth: z19.object({
|
|
1829
1809
|
strategy: customerPortalAuthStrategySchema
|
|
1830
1810
|
}).optional()
|
|
1831
1811
|
});
|
|
1832
|
-
var productSurfaceTypeSchema =
|
|
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 =
|
|
1843
|
-
key:
|
|
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:
|
|
1846
|
-
description:
|
|
1825
|
+
display: z19.string().min(1).max(100).optional(),
|
|
1826
|
+
description: z19.string().max(500).optional()
|
|
1847
1827
|
});
|
|
1848
|
-
var productEntitlementSchema =
|
|
1849
|
-
key:
|
|
1850
|
-
description:
|
|
1851
|
-
capabilities:
|
|
1852
|
-
featureGates:
|
|
1853
|
-
limits:
|
|
1854
|
-
meters:
|
|
1855
|
-
});
|
|
1856
|
-
var productSurfacesSchema =
|
|
1857
|
-
var productEntitlementsSchema =
|
|
1858
|
-
var productWorkflowKindSchema =
|
|
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 =
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
type:
|
|
1869
|
-
cron:
|
|
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
|
-
|
|
1872
|
-
type:
|
|
1873
|
-
event:
|
|
1851
|
+
z19.object({
|
|
1852
|
+
type: z19.literal("event"),
|
|
1853
|
+
event: z19.string().min(1).max(120)
|
|
1874
1854
|
}),
|
|
1875
|
-
|
|
1876
|
-
type:
|
|
1877
|
-
path:
|
|
1855
|
+
z19.object({
|
|
1856
|
+
type: z19.literal("api"),
|
|
1857
|
+
path: z19.string().min(1).max(240).regex(/^\//, "path must start with /")
|
|
1878
1858
|
}),
|
|
1879
|
-
|
|
1880
|
-
type:
|
|
1881
|
-
event:
|
|
1859
|
+
z19.object({
|
|
1860
|
+
type: z19.literal("lifecycle"),
|
|
1861
|
+
event: z19.string().min(1).max(120)
|
|
1882
1862
|
})
|
|
1883
1863
|
]);
|
|
1884
|
-
var productWorkflowSchema =
|
|
1885
|
-
key:
|
|
1886
|
-
title:
|
|
1887
|
-
description:
|
|
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:
|
|
1891
|
-
meters:
|
|
1892
|
-
estimates:
|
|
1893
|
-
metadata:
|
|
1894
|
-
});
|
|
1895
|
-
var productWorkflowsSchema =
|
|
1896
|
-
var productSpecSchema =
|
|
1897
|
-
product:
|
|
1898
|
-
name:
|
|
1899
|
-
displayName:
|
|
1900
|
-
description:
|
|
1901
|
-
baseUrl:
|
|
1902
|
-
sandboxBaseUrl:
|
|
1903
|
-
visibility:
|
|
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:
|
|
1906
|
-
primaryColor:
|
|
1885
|
+
logoUrl: z19.string().url().optional(),
|
|
1886
|
+
primaryColor: z19.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
|
|
1907
1887
|
// Environment
|
|
1908
|
-
envBranchPrefix:
|
|
1888
|
+
envBranchPrefix: z19.string().max(50).nullable().optional()
|
|
1909
1889
|
}),
|
|
1910
|
-
gateway:
|
|
1911
|
-
authHeader:
|
|
1912
|
-
upstreamAuth:
|
|
1913
|
-
type:
|
|
1914
|
-
token:
|
|
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:
|
|
1918
|
-
meters:
|
|
1919
|
-
billOn4xx:
|
|
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:
|
|
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
|
-
//
|
|
1957
|
-
// `
|
|
1958
|
-
|
|
1959
|
-
|
|
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:
|
|
1944
|
+
applyLimitUpgradesInstantly: z19.boolean().optional(),
|
|
1964
1945
|
subscriberChangePolicy: subscriberChangePolicySchema.default({
|
|
1965
|
-
default: "
|
|
1966
|
-
proration: "none",
|
|
1946
|
+
default: "period_end",
|
|
1967
1947
|
when: {
|
|
1968
|
-
price_increase: "
|
|
1969
|
-
price_decrease: "
|
|
1970
|
-
feature_added: "
|
|
1971
|
-
feature_removed: "
|
|
1972
|
-
limit_increased: "
|
|
1973
|
-
limit_reduced: "
|
|
1974
|
-
credit_increased: "
|
|
1975
|
-
credit_reduced: "
|
|
1976
|
-
rating_changed: "
|
|
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: "
|
|
1985
|
-
proration: "none",
|
|
1963
|
+
default: "period_end",
|
|
1986
1964
|
when: {
|
|
1987
|
-
price_increase: "
|
|
1988
|
-
price_decrease: "
|
|
1989
|
-
feature_added: "
|
|
1990
|
-
feature_removed: "
|
|
1991
|
-
limit_increased: "
|
|
1992
|
-
limit_reduced: "
|
|
1993
|
-
credit_increased: "
|
|
1994
|
-
credit_reduced: "
|
|
1995
|
-
rating_changed: "
|
|
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:
|
|
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:
|
|
2045
|
-
breaking_changes:
|
|
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:
|
|
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:
|
|
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:
|
|
2100
|
-
defaultPlan:
|
|
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 =
|
|
2090
|
+
var productPhaseSchema = z19.object({
|
|
2113
2091
|
product: productSpecSchema.shape.product
|
|
2114
2092
|
});
|
|
2115
|
-
var gatewayPhaseSchema =
|
|
2093
|
+
var gatewayPhaseSchema = z19.object({
|
|
2116
2094
|
gateway: productSpecSchema.shape.gateway
|
|
2117
2095
|
});
|
|
2118
|
-
var meteringPhaseSchema =
|
|
2096
|
+
var meteringPhaseSchema = z19.object({
|
|
2119
2097
|
metering: productSpecSchema.shape.metering
|
|
2120
2098
|
});
|
|
2121
|
-
var plansPhaseSchema =
|
|
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
|
|
2127
|
-
var capabilityLayerSchema =
|
|
2128
|
-
capability:
|
|
2129
|
-
description:
|
|
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:
|
|
2138
|
-
includes_features:
|
|
2139
|
-
includes_policies:
|
|
2140
|
-
includes_capabilities:
|
|
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
|
|
2123
|
+
import { z as z21 } from "zod";
|
|
2146
2124
|
var MANIFEST_IR_VERSION = 1;
|
|
2147
|
-
var manifestIrSchema =
|
|
2148
|
-
irVersion:
|
|
2125
|
+
var manifestIrSchema = z21.object({
|
|
2126
|
+
irVersion: z21.literal(MANIFEST_IR_VERSION),
|
|
2149
2127
|
/** Version of @farthershore/product that emitted this envelope. */
|
|
2150
|
-
sdkVersion:
|
|
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:
|
|
2132
|
+
routes: z21.array(routeLayerSchema).max(200).default([]),
|
|
2155
2133
|
/** Sorted by `name`. */
|
|
2156
|
-
policies:
|
|
2134
|
+
policies: z21.array(policyLayerSchema).max(200).default([]),
|
|
2157
2135
|
/** Sorted by `capability`. */
|
|
2158
|
-
capabilities:
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
|
2254
|
-
var subscriptionPricingOverrideSchema =
|
|
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:
|
|
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:
|
|
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:
|
|
2246
|
+
min_monthly_spend_cents: z22.number().int().nonnegative().optional(),
|
|
2266
2247
|
/** Override the maximum-spend ceiling. */
|
|
2267
|
-
max_monthly_spend_cents:
|
|
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:
|
|
2253
|
+
meters: z22.array(meterSchema).optional(),
|
|
2273
2254
|
/** Free-text notes about the deal. Surfaced in admin UI for audit. */
|
|
2274
|
-
notes:
|
|
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.
|
|
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 } : {},
|