@farthershore/product 0.3.1 → 0.5.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,13 @@ 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.5.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");
2425
+ var PRODUCT_MANIFEST_COMPILER = Symbol.for(
2426
+ "farthershore.product.manifestCompiler"
2427
+ );
2355
2428
  function isCapabilityGrant(value) {
2356
2429
  return typeof value === "object" && value !== null && value.kind === "capability_grant";
2357
2430
  }
@@ -2396,8 +2469,8 @@ function parseRouteMatch(match) {
2396
2469
  }
2397
2470
  return { method, path };
2398
2471
  }
2399
- var Business = class {
2400
- [BUSINESS_BRAND] = true;
2472
+ var Product = class {
2473
+ [PRODUCT_BRAND] = true;
2401
2474
  name;
2402
2475
  options;
2403
2476
  graph = new ManifestResourceGraph();
@@ -2408,15 +2481,16 @@ var Business = class {
2408
2481
  api;
2409
2482
  frontend;
2410
2483
  lifecycle;
2411
- /** Escape hatches — raw platform-schema JSON, validated at toIR(). */
2484
+ offering;
2485
+ /** Escape hatches — raw platform-schema JSON, validated by the compiler. */
2412
2486
  raw;
2413
2487
  constructor(name, options) {
2414
2488
  if (!name || typeof name !== "string") {
2415
- throw new ManifestBuilderError("fs.business(name, \u2026): name is required");
2489
+ throw new ManifestBuilderError("fs.product(name, \u2026): name is required");
2416
2490
  }
2417
- if (!options?.baseUrl) {
2491
+ if (!options?.origin) {
2418
2492
  throw new ManifestBuilderError(
2419
- `fs.business("${name}", \u2026): options.baseUrl is required (the upstream origin the gateway proxies to)`
2493
+ `fs.product("${name}", \u2026): options.origin is required (the business logic origin Farther Shore calls for customer-facing actions)`
2420
2494
  );
2421
2495
  }
2422
2496
  this.name = name;
@@ -2431,7 +2505,7 @@ var Business = class {
2431
2505
  const file = this.getFeatureFile(featureKey);
2432
2506
  if (!file) {
2433
2507
  throw new ManifestBuilderError(
2434
- `api.route("${match}"): feature "${featureKey}" is not declared \u2014 call business.feature("${featureKey}", \u2026) first`
2508
+ `api.route("${match}"): feature "${featureKey}" is not declared \u2014 call product.feature("${featureKey}", \u2026) first`
2435
2509
  );
2436
2510
  }
2437
2511
  file.routes.push(this.buildRoute(match, options2));
@@ -2524,6 +2598,9 @@ var Business = class {
2524
2598
  return this;
2525
2599
  }
2526
2600
  };
2601
+ this.offering = {
2602
+ plan: (key, options2) => this.plan(key, options2)
2603
+ };
2527
2604
  this.raw = {
2528
2605
  productPatch: (patch) => {
2529
2606
  this.productPatch = deepMerge(this.productPatch, patch);
@@ -2694,6 +2771,57 @@ var Business = class {
2694
2771
  this.graph.register("policy", name, file, this.policyDependsOn(file));
2695
2772
  return { kind: "policy", key: name };
2696
2773
  }
2774
+ surface(type, options = {}) {
2775
+ const key = options.key ?? type;
2776
+ this.assertNewKey("surface", key, "surface");
2777
+ const surface = {
2778
+ key,
2779
+ type,
2780
+ ...options.display !== void 0 ? { display: options.display } : {},
2781
+ ...options.description !== void 0 ? { description: options.description } : {}
2782
+ };
2783
+ this.graph.register("surface", key, surface);
2784
+ return { kind: "surface", key };
2785
+ }
2786
+ workflow(key, options = {}) {
2787
+ this.assertNewKey("workflow", key, "workflow");
2788
+ const workflow = {
2789
+ key,
2790
+ kind: options.kind ?? "async_job",
2791
+ trigger: options.trigger ?? { type: "manual" },
2792
+ ...options.title !== void 0 ? { title: options.title } : {},
2793
+ ...options.description !== void 0 ? { description: options.description } : {},
2794
+ ...options.capabilities?.length ? { capabilities: options.capabilities.map(keyOf) } : {},
2795
+ ...options.meters?.length ? { meters: options.meters.map(keyOf) } : {},
2796
+ ...options.estimates !== void 0 ? { estimates: options.estimates } : {},
2797
+ ...options.metadata !== void 0 ? { metadata: options.metadata } : {}
2798
+ };
2799
+ this.graph.register(
2800
+ "workflow",
2801
+ key,
2802
+ workflow,
2803
+ this.workflowDependsOn(workflow)
2804
+ );
2805
+ return { kind: "workflow", key };
2806
+ }
2807
+ entitlement(key, options = {}) {
2808
+ this.assertNewKey("entitlement", key, "entitlement");
2809
+ const entitlement = {
2810
+ key,
2811
+ ...options.description !== void 0 ? { description: options.description } : {},
2812
+ ...options.capabilities?.length ? { capabilities: options.capabilities.map(keyOf) } : {},
2813
+ ...options.featureGates !== void 0 ? { featureGates: options.featureGates } : {},
2814
+ ...options.limits?.length ? { limits: options.limits } : {},
2815
+ ...options.meters?.length ? { meters: options.meters.map(keyOf) } : {}
2816
+ };
2817
+ this.graph.register(
2818
+ "entitlement",
2819
+ key,
2820
+ entitlement,
2821
+ this.entitlementDependsOn(entitlement)
2822
+ );
2823
+ return { kind: "entitlement", key };
2824
+ }
2697
2825
  plan(key, options) {
2698
2826
  this.assertNewKey("plan", key, "plan");
2699
2827
  const capabilityKeys = (options.capabilities ?? []).map(keyOf);
@@ -2757,19 +2885,17 @@ var Business = class {
2757
2885
  resourceGraph() {
2758
2886
  return this.graph.snapshot();
2759
2887
  }
2760
- /** Assemble + validate the Manifest IR. Throws ManifestValidationError
2761
- * with structured issues when the declared state is invalid. */
2762
- toIR() {
2888
+ /** @internal Internal platform compiler entrypoint. Builder code exports the
2889
+ * Product; the bot/CLI/build-runner decide when to compile and apply it. */
2890
+ [PRODUCT_MANIFEST_COMPILER]() {
2891
+ const routes = this.materializeFeatureFiles();
2892
+ this.assertRouteMeteringValid(routes);
2763
2893
  this.assertGraphDependenciesSatisfied();
2764
- this.assertRouteMeteringValid();
2765
2894
  const candidate = {
2766
2895
  irVersion: 1,
2767
2896
  sdkVersion: SDK_VERSION,
2768
2897
  product: this.buildProductSpec(),
2769
- routes: this.graph.sortedValues(
2770
- "feature",
2771
- (file) => file.feature
2772
- ),
2898
+ routes,
2773
2899
  policies: this.graph.sortedValues(
2774
2900
  "policy",
2775
2901
  (file) => file.name
@@ -2789,10 +2915,10 @@ var Business = class {
2789
2915
  const base = {
2790
2916
  product: {
2791
2917
  name: this.name,
2792
- baseUrl: options.baseUrl,
2918
+ baseUrl: options.origin,
2793
2919
  ...options.displayName !== void 0 ? { displayName: options.displayName } : {},
2794
2920
  ...options.description !== void 0 ? { description: options.description } : {},
2795
- ...options.sandboxBaseUrl !== void 0 ? { sandboxBaseUrl: options.sandboxBaseUrl } : {},
2921
+ ...options.sandboxOrigin !== void 0 ? { sandboxBaseUrl: options.sandboxOrigin } : {},
2796
2922
  ...options.visibility !== void 0 ? { visibility: options.visibility } : {},
2797
2923
  ...options.logoUrl !== void 0 ? { logoUrl: options.logoUrl } : {},
2798
2924
  ...options.primaryColor !== void 0 ? { primaryColor: options.primaryColor } : {},
@@ -2809,6 +2935,24 @@ var Business = class {
2809
2935
  ...options.billing !== void 0 ? { billing: options.billing } : {},
2810
2936
  ...options.operatorPolicies !== void 0 ? { policies: options.operatorPolicies } : {},
2811
2937
  ...options.customerContext !== void 0 ? { customer_context: buildCustomerContext(options.customerContext) } : {},
2938
+ ...this.graph.values("surface").length ? {
2939
+ surfaces: this.graph.sortedValues(
2940
+ "surface",
2941
+ (surface) => surface.key
2942
+ )
2943
+ } : {},
2944
+ ...this.graph.values("entitlement").length ? {
2945
+ entitlements: this.graph.sortedValues(
2946
+ "entitlement",
2947
+ (entitlement) => entitlement.key
2948
+ )
2949
+ } : {},
2950
+ ...this.graph.values("workflow").length ? {
2951
+ workflows: this.graph.sortedValues(
2952
+ "workflow",
2953
+ (workflow) => workflow.key
2954
+ )
2955
+ } : {},
2812
2956
  ...this.graph.has("frontend", "manifest") ? {
2813
2957
  frontend: this.graph.get(
2814
2958
  "frontend",
@@ -2844,6 +2988,9 @@ var Business = class {
2844
2988
  }
2845
2989
  routeValueMeterKeys() {
2846
2990
  const keys = /* @__PURE__ */ new Set();
2991
+ for (const cost of this.defaultMeterCosts) {
2992
+ keys.add(cost.meter);
2993
+ }
2847
2994
  for (const file of this.graph.values("feature")) {
2848
2995
  for (const route of file.routes) {
2849
2996
  for (const meter of Object.keys(route.metering?.defaults ?? {})) {
@@ -2854,6 +3001,11 @@ var Business = class {
2854
3001
  }
2855
3002
  }
2856
3003
  }
3004
+ for (const workflow of this.graph.values("workflow")) {
3005
+ for (const meter of workflow.meters ?? []) keys.add(meter);
3006
+ for (const meter of Object.keys(workflow.estimates ?? {}))
3007
+ keys.add(meter);
3008
+ }
2857
3009
  return keys;
2858
3010
  }
2859
3011
  buildRoute(match, options) {
@@ -2878,11 +3030,6 @@ var Business = class {
2878
3030
  buildRouteMetering(options) {
2879
3031
  if (options.unmetered === true) return void 0;
2880
3032
  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
3033
  for (const cost of this.normalizeMeterCosts(options.costs)) {
2887
3034
  defaults[cost.meter] = (defaults[cost.meter] ?? 0) + cost.value;
2888
3035
  }
@@ -2910,6 +3057,32 @@ var Business = class {
2910
3057
  out.onStatusCodes = options.onStatusCodes;
2911
3058
  return Object.keys(out).length ? out : void 0;
2912
3059
  }
3060
+ materializeFeatureFiles() {
3061
+ return this.graph.sortedValues("feature", (file) => file.feature).map((file) => ({
3062
+ ...file,
3063
+ routes: file.routes.map((route) => this.materializeRoute(route))
3064
+ }));
3065
+ }
3066
+ materializeRoute(route) {
3067
+ if (route.unmetered === true) return route;
3068
+ const defaults = {
3069
+ ...route.metering?.defaults ?? {}
3070
+ };
3071
+ if (route.inheritDefaultMeters !== false) {
3072
+ for (const cost of this.defaultMeterCosts) {
3073
+ defaults[cost.meter] = (defaults[cost.meter] ?? 0) + cost.value;
3074
+ }
3075
+ }
3076
+ const hasMetering = route.metering !== void 0 || Object.keys(defaults).length > 0;
3077
+ const metering = hasMetering ? {
3078
+ ...route.metering ?? {},
3079
+ ...Object.keys(defaults).length ? { defaults } : {}
3080
+ } : void 0;
3081
+ return {
3082
+ ...route,
3083
+ ...metering ? { metering } : {}
3084
+ };
3085
+ }
2913
3086
  normalizeMeterCost(cost) {
2914
3087
  if (!cost || cost.kind !== "meter_cost") {
2915
3088
  throw new ManifestBuilderError(
@@ -3034,6 +3207,27 @@ var Business = class {
3034
3207
  policyDependsOn(file) {
3035
3208
  return this.dependenciesFor("meter", file.compatible_with?.meters);
3036
3209
  }
3210
+ entitlementDependsOn(entitlement) {
3211
+ const limitDimensions = (entitlement.limits ?? []).map(
3212
+ (limit) => limit.dimension
3213
+ );
3214
+ return [
3215
+ ...this.dependenciesFor("capability", entitlement.capabilities),
3216
+ ...this.existingDependenciesFor("meter", [
3217
+ ...entitlement.meters ?? [],
3218
+ ...limitDimensions
3219
+ ])
3220
+ ];
3221
+ }
3222
+ workflowDependsOn(workflow) {
3223
+ return [
3224
+ ...this.dependenciesFor("capability", workflow.capabilities),
3225
+ ...this.dependenciesFor("meter", [
3226
+ ...workflow.meters ?? [],
3227
+ ...Object.keys(workflow.estimates ?? {})
3228
+ ])
3229
+ ];
3230
+ }
3037
3231
  featureDependsOn(file) {
3038
3232
  const meterKeys = file.routes.flatMap(
3039
3233
  (route) => this.routeMeterDependencyKeys(route)
@@ -3056,12 +3250,26 @@ var Business = class {
3056
3250
  }
3057
3251
  return [...keys];
3058
3252
  }
3059
- assertRouteMeteringValid() {
3060
- for (const file of this.graph.values("feature")) {
3253
+ assertRouteMeteringValid(files) {
3254
+ const declaredMeters = new Set(
3255
+ this.graph.values("meter").map((meter) => meter.key)
3256
+ );
3257
+ for (const file of files) {
3061
3258
  file.routes.forEach((route, routeIndex) => {
3062
3259
  if (route.unmetered === true) return;
3063
3260
  const defaults = new Set(Object.keys(route.metering?.defaults ?? {}));
3261
+ for (const meter of defaults) {
3262
+ if (declaredMeters.has(meter)) continue;
3263
+ throw new ManifestBuilderError(
3264
+ `feature "${file.feature}" route ${routeIndex}: fixed meter "${meter}" is not declared \u2014 declare it with product.meter("${meter}", ...) first`
3265
+ );
3266
+ }
3064
3267
  for (const meter of route.metering?.reports ?? []) {
3268
+ if (!declaredMeters.has(meter)) {
3269
+ throw new ManifestBuilderError(
3270
+ `feature "${file.feature}" route ${routeIndex}: reported meter "${meter}" is not declared \u2014 declare it with product.meter("${meter}", ...) first`
3271
+ );
3272
+ }
3065
3273
  if (defaults.has(meter)) {
3066
3274
  throw new ManifestBuilderError(
3067
3275
  `feature "${file.feature}" route ${routeIndex}: meter "${meter}" cannot be both a fixed route cost and a dynamic report`
@@ -3084,6 +3292,12 @@ var Business = class {
3084
3292
  );
3085
3293
  }
3086
3294
  }
3295
+ for (const meter of Object.keys(route.metering?.estimates ?? {})) {
3296
+ if (declaredMeters.has(meter)) continue;
3297
+ throw new ManifestBuilderError(
3298
+ `feature "${file.feature}" route ${routeIndex}: estimate meter "${meter}" is not declared \u2014 declare it with product.meter("${meter}", ...) first`
3299
+ );
3300
+ }
3087
3301
  });
3088
3302
  }
3089
3303
  }
@@ -3139,13 +3353,13 @@ var Business = class {
3139
3353
  return [...new Set(keys)].filter((key) => this.graph.has(kind, key)).map((key) => resourceDependency(kind, key));
3140
3354
  }
3141
3355
  };
3142
- function isBusiness(value) {
3143
- return typeof value === "object" && value !== null && value[BUSINESS_BRAND] === true;
3356
+ function isProduct(value) {
3357
+ return typeof value === "object" && value !== null && value[PRODUCT_BRAND] === true;
3144
3358
  }
3145
- function business(name, options, configure) {
3146
- const product2 = new Business(name, options);
3147
- if (configure) product2.use(configure);
3148
- return product2;
3359
+ function product(name, options, configure) {
3360
+ const instance = new Product(name, options);
3361
+ if (configure) instance.use(configure);
3362
+ return instance;
3149
3363
  }
3150
3364
  function isPlainObject(value) {
3151
3365
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -3154,7 +3368,7 @@ function buildCustomerContext(options) {
3154
3368
  return {
3155
3369
  ...options.identityRequirement !== void 0 ? { identity_requirement: options.identityRequirement } : {},
3156
3370
  ...options.contextTokens !== void 0 ? { context_tokens: options.contextTokens } : {},
3157
- ...options.portalAuth !== void 0 ? { portal_auth: options.portalAuth } : {}
3371
+ ...options.customerAuth !== void 0 ? { portal_auth: options.customerAuth } : {}
3158
3372
  };
3159
3373
  }
3160
3374
  function deepMerge(base, patch) {
@@ -3204,19 +3418,17 @@ var price = {
3204
3418
  };
3205
3419
 
3206
3420
  // src/index.ts
3207
- var product = business;
3208
- var fs = { business, product, price };
3421
+ var fs = { product, price };
3209
3422
  export {
3210
- BUSINESS_BRAND,
3211
- Business,
3212
3423
  ManifestBuilderError,
3213
3424
  ManifestValidationError,
3425
+ PRODUCT_BRAND,
3426
+ Product,
3214
3427
  SDK_VERSION,
3215
- business,
3216
3428
  canonicalIrJson,
3217
3429
  fs,
3218
3430
  hashIr,
3219
- isBusiness,
3431
+ isProduct,
3220
3432
  price,
3221
3433
  product,
3222
3434
  validateManifestIr
@@ -5,9 +5,9 @@ export type ManifestIssue = {
5
5
  message: string;
6
6
  };
7
7
  /**
8
- * Thrown by `business.toIR()` (and the manifest-build bin) when the
9
- * assembled manifest fails schema validation. Plain-data `issues` so the
10
- * error survives serialization across the runner callback boundary.
8
+ * Thrown by the platform manifest compiler when the assembled manifest fails
9
+ * schema validation. Plain-data `issues` so the error survives serialization
10
+ * across the runner callback boundary.
11
11
  */
12
12
  export declare class ManifestValidationError extends Error {
13
13
  readonly issues: ManifestIssue[];
@@ -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";