@farthershore/product 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/codegen.js CHANGED
@@ -28,6 +28,9 @@ var TOP_LEVEL_HANDLED = /* @__PURE__ */ new Set([
28
28
  "policies",
29
29
  "customer_context",
30
30
  "resources",
31
+ "surfaces",
32
+ "entitlements",
33
+ "workflows",
31
34
  "plans"
32
35
  ]);
33
36
  var PLAN_HANDLED_KEYS = /* @__PURE__ */ new Set([
@@ -58,8 +61,8 @@ function generateManifestSource(ir) {
58
61
  lines.push(`import { fs } from "@farthershore/product";`);
59
62
  lines.push("");
60
63
  lines.push(
61
- `const business = fs.business(${lit(product.product.name)}, ${lit(
62
- businessOptions(product),
64
+ `const product = fs.product(${lit(product.product.name)}, ${lit(
65
+ productOptions(product),
63
66
  0
64
67
  )});`
65
68
  );
@@ -68,7 +71,7 @@ function generateManifestSource(ir) {
68
71
  lines.push("");
69
72
  for (const meter of meters) {
70
73
  const { key, ...rest } = meter;
71
- lines.push(`business.meter(${lit(key)}, ${lit(rest, 0)});`);
74
+ lines.push(`product.meter(${lit(key)}, ${lit(rest, 0)});`);
72
75
  }
73
76
  }
74
77
  const resources = product.resources ?? [];
@@ -76,7 +79,15 @@ function generateManifestSource(ir) {
76
79
  lines.push("");
77
80
  for (const resource of resources) {
78
81
  const { name, ...rest } = resource;
79
- lines.push(`business.resource(${lit(name)}, ${lit(rest, 0)});`);
82
+ lines.push(`product.resource(${lit(name)}, ${lit(rest, 0)});`);
83
+ }
84
+ }
85
+ const surfaces = product.surfaces ?? [];
86
+ if (surfaces.length) {
87
+ lines.push("");
88
+ for (const surface of surfaces) {
89
+ const { type, key, ...rest } = surface;
90
+ lines.push(`product.surface(${lit(type)}, ${lit({ key, ...rest }, 0)});`);
80
91
  }
81
92
  }
82
93
  if (ir.capabilities.length) {
@@ -93,7 +104,7 @@ function generateManifestSource(ir) {
93
104
  if (cap.includes_capabilities?.length)
94
105
  options.includesCapabilities = cap.includes_capabilities;
95
106
  lines.push(
96
- `business.capability(${lit(cap.capability)}, ${lit(options, 0)});`
107
+ `product.capability(${lit(cap.capability)}, ${lit(options, 0)});`
97
108
  );
98
109
  }
99
110
  }
@@ -118,7 +129,7 @@ function generateManifestSource(ir) {
118
129
  ...cw.auth_modes ? { authModes: cw.auth_modes } : {}
119
130
  };
120
131
  }
121
- lines.push(`business.policy(${lit(policy.name)}, ${lit(options, 0)});`);
132
+ lines.push(`product.policy(${lit(policy.name)}, ${lit(options, 0)});`);
122
133
  }
123
134
  }
124
135
  if (ir.routes.length) {
@@ -155,43 +166,59 @@ function generateManifestSource(ir) {
155
166
  ...route.inheritDefaultMeters !== void 0 ? { inheritDefaultMeters: route.inheritDefaultMeters } : {},
156
167
  ...route.action !== void 0 ? { action: route.action } : {}
157
168
  }));
158
- lines.push(`business.feature(${lit(file.feature)}, ${lit(options, 0)});`);
169
+ lines.push(`product.feature(${lit(file.feature)}, ${lit(options, 0)});`);
159
170
  }
160
171
  }
161
172
  if (product.frontend !== void 0) {
162
173
  lines.push("");
163
- lines.push(`business.frontend.manifest(${lit(product.frontend, 0)});`);
174
+ lines.push(`product.frontend.manifest(${lit(product.frontend, 0)});`);
175
+ }
176
+ const entitlements = product.entitlements ?? [];
177
+ if (entitlements.length) {
178
+ lines.push("");
179
+ for (const entitlement of entitlements) {
180
+ const { key, ...rest } = entitlement;
181
+ lines.push(`product.entitlement(${lit(key)}, ${lit(rest, 0)});`);
182
+ }
183
+ }
184
+ const workflows = product.workflows ?? [];
185
+ if (workflows.length) {
186
+ lines.push("");
187
+ for (const workflow of workflows) {
188
+ const { key, ...rest } = workflow;
189
+ lines.push(`product.workflow(${lit(key)}, ${lit(rest, 0)});`);
190
+ }
164
191
  }
165
192
  if (product.migrations?.length) {
166
193
  lines.push("");
167
- lines.push(`business.lifecycle.migrations(${lit(product.migrations, 0)});`);
194
+ lines.push(`product.lifecycle.migrations(${lit(product.migrations, 0)});`);
168
195
  }
169
196
  const plans = product.plans ?? [];
170
197
  if (plans.length) {
171
198
  lines.push("");
172
199
  for (const plan of plans) {
173
200
  lines.push(
174
- `business.plan(${lit(plan.key)}, ${lit(planOptions(plan), 0)});`
201
+ `product.plan(${lit(plan.key)}, ${lit(planOptions(plan), 0)});`
175
202
  );
176
203
  }
177
204
  }
178
205
  const patch = productPatch(product);
179
206
  if (Object.keys(patch).length) {
180
207
  lines.push("");
181
- lines.push(`business.raw.productPatch(${lit(patch, 0)});`);
208
+ lines.push(`product.raw.productPatch(${lit(patch, 0)});`);
182
209
  }
183
210
  lines.push("");
184
- lines.push("export default business;");
211
+ lines.push("export default product;");
185
212
  lines.push("");
186
213
  return lines.join("\n");
187
214
  }
188
- function businessOptions(product) {
215
+ function productOptions(product) {
189
216
  const block = product.product;
190
- const options = { baseUrl: block.baseUrl };
217
+ const options = { origin: block.baseUrl };
191
218
  if (block.displayName !== void 0) options.displayName = block.displayName;
192
219
  if (block.description !== void 0) options.description = block.description;
193
220
  if (block.sandboxBaseUrl !== void 0)
194
- options.sandboxBaseUrl = block.sandboxBaseUrl;
221
+ options.sandboxOrigin = block.sandboxBaseUrl;
195
222
  if (block.visibility !== void 0) options.visibility = block.visibility;
196
223
  if (block.logoUrl !== void 0) options.logoUrl = block.logoUrl;
197
224
  if (block.primaryColor !== void 0)
@@ -224,7 +251,7 @@ function customerContextOptions(context) {
224
251
  return {
225
252
  ...context.identity_requirement !== void 0 ? { identityRequirement: context.identity_requirement } : {},
226
253
  ...context.context_tokens !== void 0 ? { contextTokens: context.context_tokens } : {},
227
- ...context.portal_auth !== void 0 ? { portalAuth: context.portal_auth } : {}
254
+ ...context.portal_auth !== void 0 ? { customerAuth: context.portal_auth } : {}
228
255
  };
229
256
  }
230
257
  function planOptions(plan) {
package/dist/index.js CHANGED
@@ -1385,7 +1385,7 @@ var customerPortalAuthStrategySchema = z13.enum([
1385
1385
  var productCustomerContextSchema = z13.object({
1386
1386
  /**
1387
1387
  * Edge credential identity policy. This is intentionally Product-scoped:
1388
- * B7 keeps Product as the business boundary and avoids customer-side
1388
+ * B7 keeps Product as the product boundary and avoids customer-side
1389
1389
  * Workspace/subject models until per-subject entitlements need them.
1390
1390
  */
1391
1391
  identity_requirement: customerIdentityRequirementSchema.optional(),
@@ -1406,6 +1406,70 @@ var productCustomerContextSchema = z13.object({
1406
1406
  strategy: customerPortalAuthStrategySchema
1407
1407
  }).optional()
1408
1408
  });
1409
+ var productSurfaceTypeSchema = z13.enum([
1410
+ "frontend",
1411
+ "api",
1412
+ "docs",
1413
+ "widget",
1414
+ "dashboard",
1415
+ "webhook",
1416
+ "worker",
1417
+ "agent"
1418
+ ]);
1419
+ var productSurfaceSchema = z13.object({
1420
+ key: z13.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Surface key must be lowercase alphanumeric with hyphens/underscores"),
1421
+ type: productSurfaceTypeSchema,
1422
+ display: z13.string().min(1).max(100).optional(),
1423
+ description: z13.string().max(500).optional()
1424
+ });
1425
+ var productEntitlementSchema = z13.object({
1426
+ key: z13.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Entitlement key must be lowercase alphanumeric with hyphens/underscores"),
1427
+ description: z13.string().max(500).optional(),
1428
+ capabilities: z13.array(z13.string().min(1).max(100)).max(100).optional(),
1429
+ featureGates: z13.record(z13.string().min(1), z13.boolean()).optional(),
1430
+ limits: z13.array(planLimitRuleSchema).max(100).optional(),
1431
+ meters: z13.array(z13.string().min(1).max(64)).max(100).optional()
1432
+ });
1433
+ var productSurfacesSchema = z13.array(productSurfaceSchema).max(20).default([]);
1434
+ var productEntitlementsSchema = z13.array(productEntitlementSchema).max(100).default([]);
1435
+ var productWorkflowKindSchema = z13.enum([
1436
+ "async_job",
1437
+ "agent_task",
1438
+ "scheduled",
1439
+ "lifecycle",
1440
+ "background"
1441
+ ]);
1442
+ var productWorkflowTriggerSchema = z13.discriminatedUnion("type", [
1443
+ z13.object({ type: z13.literal("manual") }),
1444
+ z13.object({
1445
+ type: z13.literal("schedule"),
1446
+ cron: z13.string().min(1).max(120)
1447
+ }),
1448
+ z13.object({
1449
+ type: z13.literal("event"),
1450
+ event: z13.string().min(1).max(120)
1451
+ }),
1452
+ z13.object({
1453
+ type: z13.literal("api"),
1454
+ path: z13.string().min(1).max(240).regex(/^\//, "path must start with /")
1455
+ }),
1456
+ z13.object({
1457
+ type: z13.literal("lifecycle"),
1458
+ event: z13.string().min(1).max(120)
1459
+ })
1460
+ ]);
1461
+ var productWorkflowSchema = z13.object({
1462
+ key: z13.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Workflow key must be lowercase alphanumeric with hyphens/underscores"),
1463
+ title: z13.string().min(1).max(120).optional(),
1464
+ description: z13.string().max(1e3).optional(),
1465
+ kind: productWorkflowKindSchema,
1466
+ trigger: productWorkflowTriggerSchema,
1467
+ capabilities: z13.array(z13.string().min(1).max(100)).max(100).optional(),
1468
+ meters: z13.array(z13.string().min(1).max(64)).max(100).optional(),
1469
+ estimates: z13.record(z13.string().min(1).max(64), z13.number().finite()).optional(),
1470
+ metadata: z13.record(z13.string().min(1), z13.unknown()).optional()
1471
+ });
1472
+ var productWorkflowsSchema = z13.array(productWorkflowSchema).max(100).default([]);
1409
1473
  var productSpecSchema = z13.object({
1410
1474
  product: z13.object({
1411
1475
  name: z13.string().min(1).max(100),
@@ -1439,6 +1503,9 @@ var productSpecSchema = z13.object({
1439
1503
  resources: countedResourcesSchema,
1440
1504
  policies: productOperatorPoliciesSchema,
1441
1505
  customer_context: productCustomerContextSchema.optional(),
1506
+ surfaces: productSurfacesSchema,
1507
+ entitlements: productEntitlementsSchema,
1508
+ workflows: productWorkflowsSchema,
1442
1509
  /**
1443
1510
  * Legacy/internal declarative frontend surface. New product repos customize
1444
1511
  * the generated `frontend/` project directly; this remains for existing IR
@@ -2148,6 +2215,9 @@ var productSpecV2Schema = z20.object({
2148
2215
  resources: countedResourcesSchema,
2149
2216
  policies: productOperatorPoliciesSchema,
2150
2217
  customer_context: productCustomerContextSchema.optional(),
2218
+ surfaces: productSurfacesSchema,
2219
+ entitlements: productEntitlementsSchema,
2220
+ workflows: productWorkflowsSchema,
2151
2221
  billing: z20.object({
2152
2222
  gracePeriodDays: z20.number().int().nonnegative().default(3),
2153
2223
  // When true (default), a plan limit INCREASE re-projects onto active
@@ -2348,10 +2418,10 @@ function canonicalIrJson(ir) {
2348
2418
  }
2349
2419
 
2350
2420
  // src/version.ts
2351
- var SDK_VERSION = true ? "0.3.1" : "0.0.0-dev";
2421
+ var SDK_VERSION = true ? "0.4.0" : "0.0.0-dev";
2352
2422
 
2353
- // src/business.ts
2354
- var BUSINESS_BRAND = Symbol.for("farthershore.product.business");
2423
+ // src/product.ts
2424
+ var PRODUCT_BRAND = Symbol.for("farthershore.product.product");
2355
2425
  function isCapabilityGrant(value) {
2356
2426
  return typeof value === "object" && value !== null && value.kind === "capability_grant";
2357
2427
  }
@@ -2396,8 +2466,8 @@ function parseRouteMatch(match) {
2396
2466
  }
2397
2467
  return { method, path };
2398
2468
  }
2399
- var Business = class {
2400
- [BUSINESS_BRAND] = true;
2469
+ var Product = class {
2470
+ [PRODUCT_BRAND] = true;
2401
2471
  name;
2402
2472
  options;
2403
2473
  graph = new ManifestResourceGraph();
@@ -2408,15 +2478,16 @@ var Business = class {
2408
2478
  api;
2409
2479
  frontend;
2410
2480
  lifecycle;
2481
+ offering;
2411
2482
  /** Escape hatches — raw platform-schema JSON, validated at toIR(). */
2412
2483
  raw;
2413
2484
  constructor(name, options) {
2414
2485
  if (!name || typeof name !== "string") {
2415
- throw new ManifestBuilderError("fs.business(name, \u2026): name is required");
2486
+ throw new ManifestBuilderError("fs.product(name, \u2026): name is required");
2416
2487
  }
2417
- if (!options?.baseUrl) {
2488
+ if (!options?.origin) {
2418
2489
  throw new ManifestBuilderError(
2419
- `fs.business("${name}", \u2026): options.baseUrl is required (the upstream origin the gateway proxies to)`
2490
+ `fs.product("${name}", \u2026): options.origin is required (the business logic origin Farther Shore calls for customer-facing actions)`
2420
2491
  );
2421
2492
  }
2422
2493
  this.name = name;
@@ -2431,7 +2502,7 @@ var Business = class {
2431
2502
  const file = this.getFeatureFile(featureKey);
2432
2503
  if (!file) {
2433
2504
  throw new ManifestBuilderError(
2434
- `api.route("${match}"): feature "${featureKey}" is not declared \u2014 call business.feature("${featureKey}", \u2026) first`
2505
+ `api.route("${match}"): feature "${featureKey}" is not declared \u2014 call product.feature("${featureKey}", \u2026) first`
2435
2506
  );
2436
2507
  }
2437
2508
  file.routes.push(this.buildRoute(match, options2));
@@ -2524,6 +2595,9 @@ var Business = class {
2524
2595
  return this;
2525
2596
  }
2526
2597
  };
2598
+ this.offering = {
2599
+ plan: (key, options2) => this.plan(key, options2)
2600
+ };
2527
2601
  this.raw = {
2528
2602
  productPatch: (patch) => {
2529
2603
  this.productPatch = deepMerge(this.productPatch, patch);
@@ -2694,6 +2768,57 @@ var Business = class {
2694
2768
  this.graph.register("policy", name, file, this.policyDependsOn(file));
2695
2769
  return { kind: "policy", key: name };
2696
2770
  }
2771
+ surface(type, options = {}) {
2772
+ const key = options.key ?? type;
2773
+ this.assertNewKey("surface", key, "surface");
2774
+ const surface = {
2775
+ key,
2776
+ type,
2777
+ ...options.display !== void 0 ? { display: options.display } : {},
2778
+ ...options.description !== void 0 ? { description: options.description } : {}
2779
+ };
2780
+ this.graph.register("surface", key, surface);
2781
+ return { kind: "surface", key };
2782
+ }
2783
+ workflow(key, options = {}) {
2784
+ this.assertNewKey("workflow", key, "workflow");
2785
+ const workflow = {
2786
+ key,
2787
+ kind: options.kind ?? "async_job",
2788
+ trigger: options.trigger ?? { type: "manual" },
2789
+ ...options.title !== void 0 ? { title: options.title } : {},
2790
+ ...options.description !== void 0 ? { description: options.description } : {},
2791
+ ...options.capabilities?.length ? { capabilities: options.capabilities.map(keyOf) } : {},
2792
+ ...options.meters?.length ? { meters: options.meters.map(keyOf) } : {},
2793
+ ...options.estimates !== void 0 ? { estimates: options.estimates } : {},
2794
+ ...options.metadata !== void 0 ? { metadata: options.metadata } : {}
2795
+ };
2796
+ this.graph.register(
2797
+ "workflow",
2798
+ key,
2799
+ workflow,
2800
+ this.workflowDependsOn(workflow)
2801
+ );
2802
+ return { kind: "workflow", key };
2803
+ }
2804
+ entitlement(key, options = {}) {
2805
+ this.assertNewKey("entitlement", key, "entitlement");
2806
+ const entitlement = {
2807
+ key,
2808
+ ...options.description !== void 0 ? { description: options.description } : {},
2809
+ ...options.capabilities?.length ? { capabilities: options.capabilities.map(keyOf) } : {},
2810
+ ...options.featureGates !== void 0 ? { featureGates: options.featureGates } : {},
2811
+ ...options.limits?.length ? { limits: options.limits } : {},
2812
+ ...options.meters?.length ? { meters: options.meters.map(keyOf) } : {}
2813
+ };
2814
+ this.graph.register(
2815
+ "entitlement",
2816
+ key,
2817
+ entitlement,
2818
+ this.entitlementDependsOn(entitlement)
2819
+ );
2820
+ return { kind: "entitlement", key };
2821
+ }
2697
2822
  plan(key, options) {
2698
2823
  this.assertNewKey("plan", key, "plan");
2699
2824
  const capabilityKeys = (options.capabilities ?? []).map(keyOf);
@@ -2760,16 +2885,14 @@ var Business = class {
2760
2885
  /** Assemble + validate the Manifest IR. Throws ManifestValidationError
2761
2886
  * with structured issues when the declared state is invalid. */
2762
2887
  toIR() {
2888
+ const routes = this.materializeFeatureFiles();
2889
+ this.assertRouteMeteringValid(routes);
2763
2890
  this.assertGraphDependenciesSatisfied();
2764
- this.assertRouteMeteringValid();
2765
2891
  const candidate = {
2766
2892
  irVersion: 1,
2767
2893
  sdkVersion: SDK_VERSION,
2768
2894
  product: this.buildProductSpec(),
2769
- routes: this.graph.sortedValues(
2770
- "feature",
2771
- (file) => file.feature
2772
- ),
2895
+ routes,
2773
2896
  policies: this.graph.sortedValues(
2774
2897
  "policy",
2775
2898
  (file) => file.name
@@ -2789,10 +2912,10 @@ var Business = class {
2789
2912
  const base = {
2790
2913
  product: {
2791
2914
  name: this.name,
2792
- baseUrl: options.baseUrl,
2915
+ baseUrl: options.origin,
2793
2916
  ...options.displayName !== void 0 ? { displayName: options.displayName } : {},
2794
2917
  ...options.description !== void 0 ? { description: options.description } : {},
2795
- ...options.sandboxBaseUrl !== void 0 ? { sandboxBaseUrl: options.sandboxBaseUrl } : {},
2918
+ ...options.sandboxOrigin !== void 0 ? { sandboxBaseUrl: options.sandboxOrigin } : {},
2796
2919
  ...options.visibility !== void 0 ? { visibility: options.visibility } : {},
2797
2920
  ...options.logoUrl !== void 0 ? { logoUrl: options.logoUrl } : {},
2798
2921
  ...options.primaryColor !== void 0 ? { primaryColor: options.primaryColor } : {},
@@ -2809,6 +2932,24 @@ var Business = class {
2809
2932
  ...options.billing !== void 0 ? { billing: options.billing } : {},
2810
2933
  ...options.operatorPolicies !== void 0 ? { policies: options.operatorPolicies } : {},
2811
2934
  ...options.customerContext !== void 0 ? { customer_context: buildCustomerContext(options.customerContext) } : {},
2935
+ ...this.graph.values("surface").length ? {
2936
+ surfaces: this.graph.sortedValues(
2937
+ "surface",
2938
+ (surface) => surface.key
2939
+ )
2940
+ } : {},
2941
+ ...this.graph.values("entitlement").length ? {
2942
+ entitlements: this.graph.sortedValues(
2943
+ "entitlement",
2944
+ (entitlement) => entitlement.key
2945
+ )
2946
+ } : {},
2947
+ ...this.graph.values("workflow").length ? {
2948
+ workflows: this.graph.sortedValues(
2949
+ "workflow",
2950
+ (workflow) => workflow.key
2951
+ )
2952
+ } : {},
2812
2953
  ...this.graph.has("frontend", "manifest") ? {
2813
2954
  frontend: this.graph.get(
2814
2955
  "frontend",
@@ -2844,6 +2985,9 @@ var Business = class {
2844
2985
  }
2845
2986
  routeValueMeterKeys() {
2846
2987
  const keys = /* @__PURE__ */ new Set();
2988
+ for (const cost of this.defaultMeterCosts) {
2989
+ keys.add(cost.meter);
2990
+ }
2847
2991
  for (const file of this.graph.values("feature")) {
2848
2992
  for (const route of file.routes) {
2849
2993
  for (const meter of Object.keys(route.metering?.defaults ?? {})) {
@@ -2854,6 +2998,11 @@ var Business = class {
2854
2998
  }
2855
2999
  }
2856
3000
  }
3001
+ for (const workflow of this.graph.values("workflow")) {
3002
+ for (const meter of workflow.meters ?? []) keys.add(meter);
3003
+ for (const meter of Object.keys(workflow.estimates ?? {}))
3004
+ keys.add(meter);
3005
+ }
2857
3006
  return keys;
2858
3007
  }
2859
3008
  buildRoute(match, options) {
@@ -2878,11 +3027,6 @@ var Business = class {
2878
3027
  buildRouteMetering(options) {
2879
3028
  if (options.unmetered === true) return void 0;
2880
3029
  const defaults = {};
2881
- if (options.inheritDefaultMeters !== false) {
2882
- for (const cost of this.defaultMeterCosts) {
2883
- defaults[cost.meter] = (defaults[cost.meter] ?? 0) + cost.value;
2884
- }
2885
- }
2886
3030
  for (const cost of this.normalizeMeterCosts(options.costs)) {
2887
3031
  defaults[cost.meter] = (defaults[cost.meter] ?? 0) + cost.value;
2888
3032
  }
@@ -2910,6 +3054,32 @@ var Business = class {
2910
3054
  out.onStatusCodes = options.onStatusCodes;
2911
3055
  return Object.keys(out).length ? out : void 0;
2912
3056
  }
3057
+ materializeFeatureFiles() {
3058
+ return this.graph.sortedValues("feature", (file) => file.feature).map((file) => ({
3059
+ ...file,
3060
+ routes: file.routes.map((route) => this.materializeRoute(route))
3061
+ }));
3062
+ }
3063
+ materializeRoute(route) {
3064
+ if (route.unmetered === true) return route;
3065
+ const defaults = {
3066
+ ...route.metering?.defaults ?? {}
3067
+ };
3068
+ if (route.inheritDefaultMeters !== false) {
3069
+ for (const cost of this.defaultMeterCosts) {
3070
+ defaults[cost.meter] = (defaults[cost.meter] ?? 0) + cost.value;
3071
+ }
3072
+ }
3073
+ const hasMetering = route.metering !== void 0 || Object.keys(defaults).length > 0;
3074
+ const metering = hasMetering ? {
3075
+ ...route.metering ?? {},
3076
+ ...Object.keys(defaults).length ? { defaults } : {}
3077
+ } : void 0;
3078
+ return {
3079
+ ...route,
3080
+ ...metering ? { metering } : {}
3081
+ };
3082
+ }
2913
3083
  normalizeMeterCost(cost) {
2914
3084
  if (!cost || cost.kind !== "meter_cost") {
2915
3085
  throw new ManifestBuilderError(
@@ -3034,6 +3204,27 @@ var Business = class {
3034
3204
  policyDependsOn(file) {
3035
3205
  return this.dependenciesFor("meter", file.compatible_with?.meters);
3036
3206
  }
3207
+ entitlementDependsOn(entitlement) {
3208
+ const limitDimensions = (entitlement.limits ?? []).map(
3209
+ (limit) => limit.dimension
3210
+ );
3211
+ return [
3212
+ ...this.dependenciesFor("capability", entitlement.capabilities),
3213
+ ...this.existingDependenciesFor("meter", [
3214
+ ...entitlement.meters ?? [],
3215
+ ...limitDimensions
3216
+ ])
3217
+ ];
3218
+ }
3219
+ workflowDependsOn(workflow) {
3220
+ return [
3221
+ ...this.dependenciesFor("capability", workflow.capabilities),
3222
+ ...this.dependenciesFor("meter", [
3223
+ ...workflow.meters ?? [],
3224
+ ...Object.keys(workflow.estimates ?? {})
3225
+ ])
3226
+ ];
3227
+ }
3037
3228
  featureDependsOn(file) {
3038
3229
  const meterKeys = file.routes.flatMap(
3039
3230
  (route) => this.routeMeterDependencyKeys(route)
@@ -3056,12 +3247,26 @@ var Business = class {
3056
3247
  }
3057
3248
  return [...keys];
3058
3249
  }
3059
- assertRouteMeteringValid() {
3060
- for (const file of this.graph.values("feature")) {
3250
+ assertRouteMeteringValid(files) {
3251
+ const declaredMeters = new Set(
3252
+ this.graph.values("meter").map((meter) => meter.key)
3253
+ );
3254
+ for (const file of files) {
3061
3255
  file.routes.forEach((route, routeIndex) => {
3062
3256
  if (route.unmetered === true) return;
3063
3257
  const defaults = new Set(Object.keys(route.metering?.defaults ?? {}));
3258
+ for (const meter of defaults) {
3259
+ if (declaredMeters.has(meter)) continue;
3260
+ throw new ManifestBuilderError(
3261
+ `feature "${file.feature}" route ${routeIndex}: fixed meter "${meter}" is not declared \u2014 declare it with product.meter("${meter}", ...) first`
3262
+ );
3263
+ }
3064
3264
  for (const meter of route.metering?.reports ?? []) {
3265
+ if (!declaredMeters.has(meter)) {
3266
+ throw new ManifestBuilderError(
3267
+ `feature "${file.feature}" route ${routeIndex}: reported meter "${meter}" is not declared \u2014 declare it with product.meter("${meter}", ...) first`
3268
+ );
3269
+ }
3065
3270
  if (defaults.has(meter)) {
3066
3271
  throw new ManifestBuilderError(
3067
3272
  `feature "${file.feature}" route ${routeIndex}: meter "${meter}" cannot be both a fixed route cost and a dynamic report`
@@ -3084,6 +3289,12 @@ var Business = class {
3084
3289
  );
3085
3290
  }
3086
3291
  }
3292
+ for (const meter of Object.keys(route.metering?.estimates ?? {})) {
3293
+ if (declaredMeters.has(meter)) continue;
3294
+ throw new ManifestBuilderError(
3295
+ `feature "${file.feature}" route ${routeIndex}: estimate meter "${meter}" is not declared \u2014 declare it with product.meter("${meter}", ...) first`
3296
+ );
3297
+ }
3087
3298
  });
3088
3299
  }
3089
3300
  }
@@ -3139,13 +3350,13 @@ var Business = class {
3139
3350
  return [...new Set(keys)].filter((key) => this.graph.has(kind, key)).map((key) => resourceDependency(kind, key));
3140
3351
  }
3141
3352
  };
3142
- function isBusiness(value) {
3143
- return typeof value === "object" && value !== null && value[BUSINESS_BRAND] === true;
3353
+ function isProduct(value) {
3354
+ return typeof value === "object" && value !== null && value[PRODUCT_BRAND] === true;
3144
3355
  }
3145
- function business(name, options, configure) {
3146
- const product2 = new Business(name, options);
3147
- if (configure) product2.use(configure);
3148
- return product2;
3356
+ function product(name, options, configure) {
3357
+ const instance = new Product(name, options);
3358
+ if (configure) instance.use(configure);
3359
+ return instance;
3149
3360
  }
3150
3361
  function isPlainObject(value) {
3151
3362
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -3154,7 +3365,7 @@ function buildCustomerContext(options) {
3154
3365
  return {
3155
3366
  ...options.identityRequirement !== void 0 ? { identity_requirement: options.identityRequirement } : {},
3156
3367
  ...options.contextTokens !== void 0 ? { context_tokens: options.contextTokens } : {},
3157
- ...options.portalAuth !== void 0 ? { portal_auth: options.portalAuth } : {}
3368
+ ...options.customerAuth !== void 0 ? { portal_auth: options.customerAuth } : {}
3158
3369
  };
3159
3370
  }
3160
3371
  function deepMerge(base, patch) {
@@ -3204,19 +3415,17 @@ var price = {
3204
3415
  };
3205
3416
 
3206
3417
  // src/index.ts
3207
- var product = business;
3208
- var fs = { business, product, price };
3418
+ var fs = { product, price };
3209
3419
  export {
3210
- BUSINESS_BRAND,
3211
- Business,
3212
3420
  ManifestBuilderError,
3213
3421
  ManifestValidationError,
3422
+ PRODUCT_BRAND,
3423
+ Product,
3214
3424
  SDK_VERSION,
3215
- business,
3216
3425
  canonicalIrJson,
3217
3426
  fs,
3218
3427
  hashIr,
3219
- isBusiness,
3428
+ isProduct,
3220
3429
  price,
3221
3430
  product,
3222
3431
  validateManifestIr
@@ -5,7 +5,7 @@ export type ManifestIssue = {
5
5
  message: string;
6
6
  };
7
7
  /**
8
- * Thrown by `business.toIR()` (and the manifest-build bin) when the
8
+ * Thrown by `product.toIR()` (and the manifest-build bin) when the
9
9
  * assembled manifest fails schema validation. Plain-data `issues` so the
10
10
  * error survives serialization across the runner callback boundary.
11
11
  */
@@ -1,20 +1,18 @@
1
- import { business } from "./business.js";
2
- export declare const product: typeof business;
1
+ import { product } from "./product.js";
3
2
  export declare const fs: {
4
- business: typeof business;
5
- product: typeof business;
3
+ product: typeof product;
6
4
  price: {
7
5
  monthly(dollars: number): import("./price.js").PriceSpec;
8
6
  yearly(dollars: number): import("./price.js").PriceSpec;
9
7
  free(): import("./price.js").PriceSpec;
10
8
  };
11
9
  };
12
- export { business, Business, isBusiness, BUSINESS_BRAND } from "./business.js";
10
+ export { product, Product, isProduct, PRODUCT_BRAND } from "./product.js";
13
11
  export { price } from "./price.js";
14
12
  export { validateManifestIr, hashIr, canonicalIrJson } from "./validate.js";
15
13
  export { ManifestValidationError, ManifestBuilderError } from "./errors.js";
16
14
  export { SDK_VERSION } from "./version.js";
17
- export type { BusinessOptions, MeterOptions, RequestMeterOptions, MeterCost, ResourceOptions, CapabilityOptions, ActionOptions, FrontendNavItemOptions, FrontendPageOptions, FrontendComponentOptions, MigrationOptions, FeatureOptions, RouteOptions, PolicyOptions, PlanOptions, MeterRef, ResourceRef, ActionRef, PolicyRef, PlanRef, FeatureRef, CapabilityRef, PlanCapabilityGrant, ProductModule, } from "./business.js";
15
+ export type { ProductOptions, MeterOptions, RequestMeterOptions, MeterCost, ResourceOptions, CapabilityOptions, ActionOptions, FrontendNavItemOptions, FrontendPageOptions, FrontendComponentOptions, MigrationOptions, FeatureOptions, RouteOptions, PolicyOptions, PlanOptions, SurfaceOptions, EntitlementOptions, MeterRef, ResourceRef, ActionRef, PolicyRef, PlanRef, SurfaceRef, EntitlementRef, FeatureRef, CapabilityRef, PlanCapabilityGrant, ProductModule, } from "./product.js";
18
16
  export type { ManifestResourceGraphSnapshot, ManifestResourceKind, ManifestResourceUrn, } from "./resource-graph.js";
19
17
  export type { PriceSpec } from "./price.js";
20
18
  export type { ManifestIssue } from "./errors.js";