@01.software/cli 0.10.6 → 0.11.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "kind": "01software.cli.mcp-build",
3
3
  "version": 1,
4
- "sourceHash": "3049a13135190c6d7869ded8bcf38425b8617fb93a7426103dfffa031c170fae",
4
+ "sourceHash": "83a6cd888f69eea5aa1ac867d5ac3d1c6d7749e1d211a412c1abfc6e12f1ad6a",
5
5
  "sourceInputs": [
6
6
  "apps/mcp/package.json",
7
7
  "apps/mcp/tsconfig.json",
@@ -757,14 +757,14 @@ var TOOL_POLICY_MANIFEST = {
757
757
  category: "mutation-order",
758
758
  oauthScope: MCP_SCOPES.write,
759
759
  consoleRole: "tenant-admin",
760
- consoleSurface: "POST /api/checkout",
760
+ consoleSurface: "POST /api/orders/checkout",
761
761
  annotationPolicy: DESTRUCTIVE_NON_IDEMPOTENT_MUTATION_ANNOTATION
762
762
  },
763
763
  "create-order": {
764
764
  category: "mutation-order",
765
765
  oauthScope: MCP_SCOPES.write,
766
766
  consoleRole: "tenant-admin",
767
- consoleSurface: "POST /api/orders",
767
+ consoleSurface: "POST /api/orders/create",
768
768
  annotationPolicy: DESTRUCTIVE_NON_IDEMPOTENT_MUTATION_ANNOTATION
769
769
  },
770
770
  "confirm-payment": {
@@ -779,7 +779,7 @@ var TOOL_POLICY_MANIFEST = {
779
779
  category: "mutation-order",
780
780
  oauthScope: MCP_SCOPES.write,
781
781
  consoleRole: "tenant-admin",
782
- consoleSurface: "PATCH /api/orders/{id}",
782
+ consoleSurface: "POST /api/orders/update",
783
783
  annotationPolicy: DESTRUCTIVE_IDEMPOTENT_MUTATION_ANNOTATION,
784
784
  exemptionReason: REASON_IDEMPOTENT_DESTRUCTIVE_UPDATE
785
785
  },
@@ -788,14 +788,14 @@ var TOOL_POLICY_MANIFEST = {
788
788
  category: "mutation-fulfillment",
789
789
  oauthScope: MCP_SCOPES.write,
790
790
  consoleRole: "tenant-admin",
791
- consoleSurface: "POST /api/orders/{id}/fulfillments",
791
+ consoleSurface: "POST /api/orders/create-fulfillment",
792
792
  annotationPolicy: NON_DESTRUCTIVE_MUTATION_ANNOTATION
793
793
  },
794
794
  "update-fulfillment": {
795
795
  category: "mutation-fulfillment",
796
796
  oauthScope: MCP_SCOPES.write,
797
797
  consoleRole: "tenant-admin",
798
- consoleSurface: "PATCH /api/fulfillments/{id}",
798
+ consoleSurface: "POST /api/orders/update-fulfillment",
799
799
  annotationPolicy: DESTRUCTIVE_IDEMPOTENT_MUTATION_ANNOTATION,
800
800
  exemptionReason: REASON_IDEMPOTENT_DESTRUCTIVE_UPDATE
801
801
  },
@@ -804,14 +804,14 @@ var TOOL_POLICY_MANIFEST = {
804
804
  category: "mutation-return",
805
805
  oauthScope: MCP_SCOPES.write,
806
806
  consoleRole: "tenant-admin",
807
- consoleSurface: "POST /api/returns",
807
+ consoleSurface: "POST /api/returns/create",
808
808
  annotationPolicy: DESTRUCTIVE_NON_IDEMPOTENT_MUTATION_ANNOTATION
809
809
  },
810
810
  "update-return": {
811
811
  category: "mutation-return",
812
812
  oauthScope: MCP_SCOPES.write,
813
813
  consoleRole: "tenant-admin",
814
- consoleSurface: "PATCH /api/returns/{id}",
814
+ consoleSurface: "POST /api/returns/update",
815
815
  annotationPolicy: DESTRUCTIVE_IDEMPOTENT_MUTATION_ANNOTATION,
816
816
  exemptionReason: REASON_IDEMPOTENT_DESTRUCTIVE_UPDATE
817
817
  },
@@ -819,7 +819,7 @@ var TOOL_POLICY_MANIFEST = {
819
819
  category: "mutation-return",
820
820
  oauthScope: MCP_SCOPES.write,
821
821
  consoleRole: "tenant-admin",
822
- consoleSurface: "POST /api/returns/with-refund",
822
+ consoleSurface: "POST /api/returns/return-refund",
823
823
  annotationPolicy: DESTRUCTIVE_NON_IDEMPOTENT_MUTATION_ANNOTATION
824
824
  },
825
825
  // ── Transaction mutations (mcp:write, tenant-admin) ──
@@ -827,7 +827,7 @@ var TOOL_POLICY_MANIFEST = {
827
827
  category: "mutation-transaction",
828
828
  oauthScope: MCP_SCOPES.write,
829
829
  consoleRole: "tenant-admin",
830
- consoleSurface: "PATCH /api/transactions/{id}",
830
+ consoleSurface: "POST /api/transactions/update",
831
831
  annotationPolicy: DESTRUCTIVE_NON_IDEMPOTENT_MUTATION_ANNOTATION
832
832
  },
833
833
  // ── Field-config mutations (mcp:write, tenant-admin) ──
@@ -1427,12 +1427,12 @@ var schema5 = {
1427
1427
  "delivered",
1428
1428
  "confirmed"
1429
1429
  ]).describe(
1430
- "New order status. Return-related statuses (return_requested, return_processing, returned) must be set via Return endpoints."
1430
+ "New operator-driven order status. The schema keeps SDK status values for compatibility, but the Console update endpoint rejects server-derived statuses (paid, canceled, returned, refunded); those must be set by payment/refund flows, and return-related statuses must be set via Return endpoints."
1431
1431
  )
1432
1432
  };
1433
1433
  var metadata5 = {
1434
1434
  name: "update-order",
1435
- description: "Update order status. Automatically adjusts stock on status changes (e.g., canceled restores stock).",
1435
+ description: "Update operator-driven order status. Console accepts transitions such as paid\u2192preparing/shipped/delivered/confirmed and rejects server-derived payment/refund statuses such as paid, canceled, returned, and refunded; the MCP schema keeps SDK status values for compatibility.",
1436
1436
  annotations: {
1437
1437
  title: "Update order status",
1438
1438
  readOnlyHint: false,
@@ -1686,12 +1686,12 @@ import { z as z11 } from "zod";
1686
1686
  var schema12 = {
1687
1687
  returnId: z11.string().min(1).describe("Return ID (required)"),
1688
1688
  status: z11.enum(["processing", "approved", "rejected", "completed"]).describe(
1689
- "New return status (required). Valid transitions: requested\u2192processing/rejected, processing\u2192approved/rejected, approved\u2192completed"
1689
+ "New operator-driven return status (required). The schema keeps SDK status values for compatibility, but Console accepts only requested\u2192processing/rejected and processing\u2192approved/rejected here; completed is server-derived and must be set by return-with-refund."
1690
1690
  )
1691
1691
  };
1692
1692
  var metadata12 = {
1693
1693
  name: "update-return",
1694
- description: "Update return status with FSM validation. Restores inventory on completion, reverts order status on rejection.",
1694
+ description: "Update operator-driven return status with FSM validation. Rejection can restore the order flow; completion and inventory restoration are handled by return-with-refund after provider-verified refund, while the MCP schema keeps SDK status values for compatibility.",
1695
1695
  annotations: {
1696
1696
  title: "Update return status",
1697
1697
  readOnlyHint: false,
@@ -1705,7 +1705,10 @@ async function updateReturn({
1705
1705
  }) {
1706
1706
  try {
1707
1707
  const client = getClient();
1708
- const result = await client.commerce.orders.updateReturn({ returnId, status });
1708
+ const result = await client.commerce.orders.updateReturn({
1709
+ returnId,
1710
+ status
1711
+ });
1709
1712
  return toolSuccess({ data: result });
1710
1713
  } catch (error) {
1711
1714
  return toolError(error);
@@ -1716,7 +1719,7 @@ async function updateReturn({
1716
1719
  var schema13 = ReturnWithRefundSchema.shape;
1717
1720
  var metadata13 = {
1718
1721
  name: "return-with-refund",
1719
- description: "Combined return + refund operation. Creates return, restores stock, cancels transaction, updates order status.",
1722
+ description: "Combined provider-verified return + refund operation. Creates a completed return, restores eligible stock, records the refund transaction, and advances the order to the returned state.",
1720
1723
  annotations: {
1721
1724
  title: "Return with refund",
1722
1725
  readOnlyHint: false,
@@ -1730,6 +1733,7 @@ async function returnWithRefund({
1730
1733
  reasonDetail,
1731
1734
  returnItems,
1732
1735
  refundAmount,
1736
+ returnShippingFee,
1733
1737
  pgPaymentId,
1734
1738
  paymentKey,
1735
1739
  refundReceiptUrl
@@ -1742,6 +1746,7 @@ async function returnWithRefund({
1742
1746
  reasonDetail,
1743
1747
  returnItems,
1744
1748
  refundAmount,
1749
+ returnShippingFee,
1745
1750
  pgPaymentId,
1746
1751
  paymentKey,
1747
1752
  refundReceiptUrl
@@ -2042,7 +2047,7 @@ var schema23 = {
2042
2047
  };
2043
2048
  var metadata23 = {
2044
2049
  name: "product-detail",
2045
- description: "Fetch full product detail by slug or id. Returns one resolver-ready product with variants, option slugs, option value slugs/media, brand, categories, tags, images, videos, and listing rollup, or null if missing/unpublished/wrong tenant/feature disabled.",
2050
+ description: "Fetch full product detail by slug or id. Returns one resolver-ready product with variants, option slugs, option value slugs/media, brand, categories, tags, images, videos, and listing rollup, or found:false with a reason if missing/unpublished/feature disabled. Permission/auth errors still throw.",
2046
2051
  annotations: {
2047
2052
  title: "Get product detail",
2048
2053
  readOnlyHint: true,
@@ -3672,8 +3677,9 @@ You can perform the "${goal}" task by following the patterns above.
3672
3677
  ### Product detail page (slug-based)
3673
3678
 
3674
3679
  \`\`\`typescript
3675
- const product = await client.commerce.product.detail({ slug })
3676
- if (!product) return notFound()
3680
+ const result = await client.commerce.product.detail({ slug })
3681
+ if (!result.found) return notFound()
3682
+ const product = result.product
3677
3683
  // product: { product, variants, options, brand, categories, tags, images, videos, listing }
3678
3684
  \`\`\`
3679
3685
 
@@ -3909,12 +3915,13 @@ const order = await client.commerce.orders.checkout({
3909
3915
  1. **Create Return** \u2192 \`create-return\` tool
3910
3916
  - Order must be \`delivered\` or \`confirmed\`
3911
3917
  - Specify returnItems and refundAmount
3912
- - Return status: requested \u2192 processing \u2192 approved \u2192 completed
3918
+ - Operator transitions: requested \u2192 processing/rejected, processing \u2192 approved/rejected
3919
+ - \`completed\` is server-derived and requires \`return-with-refund\`
3913
3920
 
3914
3921
  ### Option B: Return + Refund (atomic, recommended)
3915
3922
  1. **Return with Refund** \u2192 \`return-with-refund\` tool
3916
3923
  - Handles return + stock restoration + transaction update in one call
3917
- - Return immediately completed (bypasses FSM)
3924
+ - Sets the return to \`completed\` after provider-verified refund
3918
3925
  - Requires pgPaymentId and paymentKey for provider-verified refund
3919
3926
 
3920
3927
  ### Key Points
@@ -3930,8 +3937,9 @@ await client.commerce.orders.returnWithRefund({
3930
3937
  orderNumber: 'ORD-240101-001',
3931
3938
  reason: 'defective',
3932
3939
  reasonDetail: 'Product arrived damaged',
3933
- returnItems: [{ orderItem: 'oi-id', quantity: 1 }],
3940
+ returnItems: [{ orderItem: 'oi-id', quantity: 1, restockingFee: 500 }],
3934
3941
  refundAmount: 29900,
3942
+ returnShippingFee: 1500,
3935
3943
  pgPaymentId: 'pay_xxx',
3936
3944
  paymentKey: 'payment_key_xxx'
3937
3945
  })
@@ -5538,12 +5546,12 @@ const ret = await client.commerce.orders.createReturn({
5538
5546
  \`\`\`
5539
5547
 
5540
5548
  ### updateReturn()
5541
- Update return status.
5549
+ Update operator-driven return status. \`completed\` is server-derived and must go through \`returnWithRefund()\`.
5542
5550
 
5543
5551
  \`\`\`typescript
5544
5552
  const ret = await client.commerce.orders.updateReturn({
5545
5553
  returnId: 'return-id',
5546
- status: 'processing', // processing | approved | completed | rejected
5554
+ status: 'processing', // processing | approved | rejected
5547
5555
  })
5548
5556
  \`\`\`
5549
5557
 
@@ -5556,9 +5564,10 @@ const result = await client.commerce.orders.returnWithRefund({
5556
5564
  reason?: 'defective',
5557
5565
  reasonDetail?: 'Screen cracked on arrival',
5558
5566
  returnItems: [
5559
- { orderItem: 'order-item-id', quantity: 1 }
5567
+ { orderItem: 'order-item-id', quantity: 1, restockingFee?: 500 }
5560
5568
  ],
5561
5569
  refundAmount: 29900,
5570
+ returnShippingFee?: 1500,
5562
5571
  pgPaymentId: 'provider-payment-id', // required
5563
5572
  paymentKey: 'provider-payment-key', // required for provider refund
5564
5573
  refundReceiptUrl?: 'https://...',
@@ -6193,11 +6202,11 @@ var metadata49 = {
6193
6202
  function handler18() {
6194
6203
  return `# Webhooks
6195
6204
 
6196
- The platform dispatches HMAC-SHA256 signed webhook events to your registered URLs. Tenant developers own routing inside their webhook handler.
6205
+ The platform dispatches HMAC-SHA256 signed webhook events to registered URLs. Tenant developers own routing inside their webhook handler.
6197
6206
 
6198
6207
  ## Webhook Handling
6199
6208
 
6200
- Use the SDK \`handleWebhook\` helper to verify signatures. For customer auth events, use \`createCustomerAuthWebhookHandler\` to wire delivery behavior in your app.
6209
+ Use the SDK \`handleWebhook\` helper to verify signatures. For customer auth events, use \`createCustomerAuthWebhookHandler\`; for semantic events, use SDK guards before reading event-specific fields.
6201
6210
 
6202
6211
  \`\`\`typescript
6203
6212
  import { handleWebhook, createCustomerAuthWebhookHandler } from '@01.software/sdk/webhook'
@@ -6250,28 +6259,119 @@ export async function POST(request: Request) {
6250
6259
  }
6251
6260
  \`\`\`
6252
6261
 
6262
+ ## Commerce Notification Handler Example
6263
+
6264
+ \`\`\`typescript
6265
+ import {
6266
+ handleWebhook,
6267
+ isCommerceNotificationWebhookEvent,
6268
+ } from '@01.software/sdk/webhook'
6269
+
6270
+ function getWebhookSecret(): string {
6271
+ const secret = process.env.WEBHOOK_SECRET
6272
+ if (!secret) throw new Error('WEBHOOK_SECRET is required')
6273
+ return secret
6274
+ }
6275
+
6276
+ export async function POST(request: Request) {
6277
+ return handleWebhook(request, async (event) => {
6278
+ if (!isCommerceNotificationWebhookEvent(event)) return
6279
+
6280
+ const idempotencyKey = \`\${event.notification.intentId}:\${event.notification.dedupeKey}\`
6281
+ const processed = await processOnce(idempotencyKey, async () => {
6282
+ if (event.notification.event === 'orderPaid') {
6283
+ const orderId = event.notification.orderId ?? event.data.orderId
6284
+ if (orderId) await updateOrderWorkflow(orderId)
6285
+ }
6286
+
6287
+ if (event.notification.event === 'fulfillmentShipped') {
6288
+ const fulfillmentId =
6289
+ event.notification.fulfillmentId ?? event.data.fulfillmentId
6290
+ if (fulfillmentId) await updateFulfillmentWorkflow(fulfillmentId)
6291
+ }
6292
+ })
6293
+
6294
+ if (!processed) return
6295
+ }, {
6296
+ secret: getWebhookSecret(),
6297
+ })
6298
+ }
6299
+ \`\`\`
6300
+
6301
+ Back \`processOnce()\` with durable storage such as a database unique key or queue idempotency store; do not use process-local memory for webhook idempotency.
6302
+
6303
+ ## Headless Commerce Email Workers
6304
+
6305
+ For tenant-owned transactional email, use \`commerce.notification\` as the trigger and build a signed worker with \`createCommerceEmailWebhookHandler()\`. 01.software owns event timing, delivery retries, signing, and idempotency signals; the tenant worker owns template rendering, provider credentials, extra order/customer fetches via \`createServerClient\`, and final delivery through a tenant provider such as Resend.
6306
+
6307
+ Use \`getCommerceNotificationIdempotencyKey(event)\` or the \`idempotencyKey\` passed by \`createCommerceEmailWebhookHandler()\`, then guard provider sends with durable \`processOnce(idempotencyKey, ...)\` storage. The webhook payload is PII-light and is not enough for recipient data, so fetch recipient/template context with server SDK credentials. In the v1 headless path, do not store tenant ESP secrets in 01.software and do not ask 01.software to send directly through the tenant provider.
6308
+
6253
6309
  ## Webhook Payload Structure
6254
6310
 
6255
6311
  All webhook events share this envelope:
6256
6312
 
6257
6313
  \`\`\`typescript
6258
6314
  {
6259
- collection: string, // e.g. 'customers'
6260
- operation: string, // e.g. 'password-reset'
6261
- data: object, // event-specific payload
6315
+ collection: string,
6316
+ operation: string,
6317
+ data: object,
6318
+ eventType?: string,
6319
+ change?: object,
6320
+ notification?: object,
6321
+ timestamp?: string,
6322
+ deliveryId?: string,
6262
6323
  }
6263
6324
  \`\`\`
6264
6325
 
6265
6326
  ## Event Types
6266
6327
 
6328
+ ### Commerce Notification
6329
+
6330
+ V1 commerce notification webhooks use \`eventType: "commerce.notification"\` and \`operation: "notification"\`. The public SDK exports \`COMMERCE_NOTIFICATION_EVENT_TYPE\`, \`COMMERCE_NOTIFICATION_OPERATION\`, commerce notification types, and \`isCommerceNotificationWebhookEvent()\` from \`@01.software/sdk/webhook\`.
6331
+
6332
+ Canonical example (public operational identifiers only; no PII, payment/provider fields, metadata, tracking number, or tracking URL):
6333
+
6334
+ \`\`\`json
6335
+ {
6336
+ "eventType": "commerce.notification",
6337
+ "collection": "orders",
6338
+ "operation": "notification",
6339
+ "data": {
6340
+ "orderId": "order_123",
6341
+ "orderNumber": "ORD-1001",
6342
+ "status": "paid",
6343
+ "totalAmount": 5000,
6344
+ "currency": "KRW"
6345
+ },
6346
+ "notification": {
6347
+ "event": "orderPaid",
6348
+ "intentId": "intent_123",
6349
+ "dedupeKey": "orderPaid:order_123",
6350
+ "orderId": "order_123"
6351
+ },
6352
+ "timestamp": "2026-05-29T00:00:00.000Z",
6353
+ "deliveryId": "delivery_attempt_123"
6354
+ }
6355
+ \`\`\`
6356
+
6357
+ Use \`notification.intentId\` plus \`notification.dedupeKey\` for semantic idempotency. \`deliveryId\` is per delivery attempt / observability and may not be stable across semantic retries.
6358
+ Some normalized deliveries may include \`data.source\` or \`change\` metadata, but handlers should treat those fields as optional. Route from \`notification.orderId\`, \`notification.fulfillmentId\`, \`notification.returnId\`, or the matching typed \`data.*Id\` field.
6359
+
6360
+ V1 source subscription semantics:
6361
+
6362
+ - \`orderPaid\` and \`orderDelivered\` are delivered through \`orders\`-scoped endpoints.
6363
+ - \`fulfillmentShipped\` is delivered through \`fulfillments\`-scoped endpoints.
6364
+ - \`returnRequested\` and \`returnCompleted\` are delivered through \`returns\`-scoped endpoints.
6365
+ - Empty, unscoped, and all-collection endpoints do not receive v1 semantic commerce notifications.
6366
+
6267
6367
  ### Collection Order Changed
6268
6368
 
6269
6369
  Admin Panel manual ordering is delivered as a semantic collection update:
6270
6370
 
6271
6371
  - \`operation\` remains \`"update"\` for compatibility.
6272
6372
  - \`eventType\` is \`"collection.orderChanged"\`.
6273
- - \`change.scope\` identifies collection-level ordering versus join ordering.
6274
6373
  - SDK handlers should use \`isOrderChangedWebhookEvent()\` from \`@01.software/sdk/webhook\`.
6374
+ - \`change.scope\` identifies collection-level ordering versus join ordering.
6275
6375
  - Route on public semantics such as \`change.scope.collection\`, \`change.scope.field\`, and \`change.moved.id\`.
6276
6376
  - Do not branch on hidden Payload order fields or private backing collections; diagnostic fields may still be present.
6277
6377
  - Customer group member ordering is currently unsupported and does not emit a semantic order-change webhook.
@@ -6321,8 +6421,8 @@ Dispatched when a customer calls \`client.customer.forgotPassword(email)\`.
6321
6421
  customerId: string,
6322
6422
  email: string,
6323
6423
  name: string,
6324
- resetPasswordToken: string, // raw token to include in reset link
6325
- resetPasswordExpiresAt: string, // ISO 8601 expiry (1 hour from dispatch)
6424
+ resetPasswordToken: string,
6425
+ resetPasswordExpiresAt: string,
6326
6426
  }
6327
6427
  }
6328
6428
  \`\`\`
@@ -6337,11 +6437,13 @@ async function sendPasswordResetEmail(data: {
6337
6437
  resetPasswordToken: string
6338
6438
  resetPasswordExpiresAt: string
6339
6439
  }) {
6340
- const resetUrl = \`https://yourstore.com/reset-password?token=\${data.resetPasswordToken}\`
6440
+ const resetUrl = new URL('https://yourstore.com/reset-password')
6441
+ resetUrl.searchParams.set('token', data.resetPasswordToken)
6442
+
6341
6443
  await emailService.send({
6342
6444
  to: data.email,
6343
6445
  subject: 'Reset your password',
6344
- body: \`Reset link (expires \${data.resetPasswordExpiresAt}): \${resetUrl}\`,
6446
+ body: \`Reset link (expires \${data.resetPasswordExpiresAt}): \${resetUrl.toString()}\`,
6345
6447
  })
6346
6448
  }
6347
6449
  \`\`\`
@@ -6352,7 +6454,7 @@ Failed webhook deliveries are queued with automatic retries. Ensure your handler
6352
6454
 
6353
6455
  ## Webhook Configuration
6354
6456
 
6355
- Configure webhook URLs in the 01.software console under Tenant Settings > Webhooks. Multiple URLs can be registered; all receive every event.`;
6457
+ Configure webhook URLs in the 01.software console under Tenant Settings > Webhooks. Multiple URLs can be registered; scoped endpoints receive events for their configured source collection.`;
6356
6458
  }
6357
6459
 
6358
6460
  // src/resources/(docs)/product-detail.ts
@@ -6380,8 +6482,8 @@ const client = createClient({
6380
6482
  })
6381
6483
 
6382
6484
  const detail = await client.commerce.product.detail({ slug: 'my-product' })
6383
- if (!detail) return notFound()
6384
- const selection = resolveProductSelection(detail, {
6485
+ if (!detail.found) return notFound()
6486
+ const selection = resolveProductSelection(detail.product, {
6385
6487
  search: '?opt.option-color=color-black',
6386
6488
  })
6387
6489
  // selection.selectedVariant, selection.price, selection.stock, selection.media
@@ -6432,18 +6534,20 @@ export default async function ProductPage({ params }: { params: { slug: string }
6432
6534
  publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!,
6433
6535
  })
6434
6536
  const detail = await client.commerce.product.detail({ slug: params.slug })
6435
- if (!detail) return notFound()
6436
- return <ProductView detail={detail} />
6537
+ if (!detail.found) return notFound()
6538
+ return <ProductView detail={detail.product} />
6437
6539
  }
6438
6540
  \`\`\`
6439
6541
 
6440
- ## The \`null\` return contract
6542
+ ## The \`ProductDetailResult\` return contract
6543
+
6544
+ The endpoint returns 404 for \`not_found\`, \`not_published\`, or \`feature_disabled\`. The SDK maps those product-detail 404s to \`{ found: false, reason }\` so consumer UIs can render a standard 404, preview CTA, or feature-gating UI. Permission/auth errors, including 403 tenant mismatches, still throw typed SDK errors.
6441
6545
 
6442
- The endpoint returns 404 for \`not_found\`, \`not_published\`, \`tenant_mismatch\`, or \`feature_disabled\`. The SDK collapses all 404s to \`null\` so consumer UIs render one "not available" path.
6546
+ Successful product payloads expose inventory rollups without sentinel values: \`product.totalInventory\` is the tracked stock sum across non-unlimited variants, \`null\` when no variants are tracked, and \`product.hasUnlimitedVariant\` signals whether any variant is unlimited.
6443
6547
 
6444
6548
  ## Backend correlation
6445
6549
 
6446
- Log \`client.lastRequestId\` against backend logs \u2014 the endpoint records the exact 404 code alongside the request ID.`;
6550
+ Log \`client.lastRequestId\` against backend logs \u2014 the endpoint records the exact 404 reason alongside the request ID.`;
6447
6551
  }
6448
6552
 
6449
6553
  // src/server.ts
@@ -6849,4 +6953,4 @@ export {
6849
6953
  flushMcpTelemetrySummary,
6850
6954
  createServer
6851
6955
  };
6852
- //# sourceMappingURL=chunk-2ULP5WQH.js.map
6956
+ //# sourceMappingURL=chunk-3TIDAOYP.js.map