From 0bf71c9890b50e82de49c78da6da858a4b2f19e3 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 13 Nov 2026 00:56:39 -0800 Subject: [PATCH 1/2] fix(aws-marketplace): check the container license's uncounted usage entitlement so every replica turns Enterprise on --- .../aws-marketplace/container-license.test.ts | 6 ++-- .../aws-marketplace/container-license.ts | 36 ++++++++++--------- helm/sim/Chart.yaml | 2 +- helm/sim/examples/values-aws-marketplace.yaml | 3 +- 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/apps/sim/lib/billing/aws-marketplace/container-license.test.ts b/apps/sim/lib/billing/aws-marketplace/container-license.test.ts index d9c817ba494..d78fc17dae0 100644 --- a/apps/sim/lib/billing/aws-marketplace/container-license.test.ts +++ b/apps/sim/lib/billing/aws-marketplace/container-license.test.ts @@ -50,7 +50,7 @@ describe('applyAwsMarketplaceContainerLicense', () => { checkoutRequests.push(command.input) const answer = answers.length > 1 ? answers.shift() : answers[0] if (answer !== 'entitled') throw awsError(answer ?? 'ServerInternalException') - return { EntitlementsAllowed: [{ Name: 'enterprise_license', Unit: 'None' }] } + return { EntitlementsAllowed: [{ Name: 'AWS::Marketplace::Usage', Unit: 'None' }] } }) }) @@ -68,7 +68,7 @@ describe('applyAwsMarketplaceContainerLicense', () => { expect(mockSend).not.toHaveBeenCalled() }) - it('turns Enterprise on with an uncounted checkout of the entitlement', async () => { + it('turns Enterprise on with an uncounted checkout of the Marketplace usage entitlement', async () => { answers = ['entitled'] await applyAwsMarketplaceContainerLicense() expect(enterpriseEnabled()).toBe(true) @@ -77,7 +77,7 @@ describe('applyAwsMarketplaceContainerLicense', () => { ProductSKU: 'product-placeholder', CheckoutType: 'PROVISIONAL', KeyFingerprint: 'aws:294406891311:AWS/Marketplace:issuer-fingerprint', - Entitlements: [{ Name: 'enterprise_license', Unit: 'None' }], + Entitlements: [{ Name: 'AWS::Marketplace::Usage', Unit: 'None' }], }), ]) }) diff --git a/apps/sim/lib/billing/aws-marketplace/container-license.ts b/apps/sim/lib/billing/aws-marketplace/container-license.ts index fd4d53d6ab9..50201a3c7be 100644 --- a/apps/sim/lib/billing/aws-marketplace/container-license.ts +++ b/apps/sim/lib/billing/aws-marketplace/container-license.ts @@ -8,14 +8,13 @@ import { awsRequestHandler } from '@/lib/core/network/proxy-agent.server' /** * Enterprise entitlement for Sim installed from the AWS Marketplace container listing. * - * The listing sells an annual contract whose one dimension is `enterprise_license`. AWS Marketplace - * grants the buyer a license in AWS License Manager, and the running product checks it the way AWS - * documents for a contract that unlocks a tier of features rather than metering a quantity: a - * provisional `CheckoutLicense` of the entitlement with unit `None`. Such a checkout is not counted - * against the contract, so every replica can check at once, and it needs only the permission AWS's - * managed `AWSLicenseManagerConsumptionPolicy` grants. While the license is active, the Enterprise - * suite is on (`ENTERPRISE_ENABLED`). Without one, Sim runs as the open-source core, so a lapsed - * renewal or an unreachable License Manager never locks a buyer out of their deployment. + * The listing sells an annual contract. The buyer's AWS License Manager license carries the + * contract's counted dimensions plus an uncounted `AWS::Marketplace::Usage` entitlement held while + * the agreement is active. Sim checks the uncounted one with a provisional `CheckoutLicense`, so + * every replica can check at once (a counted unit would go to one replica only), using only the + * permission in AWS's managed `AWSLicenseManagerConsumptionPolicy`. While the license is active, the + * Enterprise suite is on (`ENTERPRISE_ENABLED`). Without one, Sim runs as the open-source core, so a lapsed renewal or + * an unreachable License Manager never locks a buyer out of their deployment. * * Opt-in: nothing here runs unless {@link PRODUCT_SKU_ENV} is set, which only the Marketplace * Helm profile does. Enterprise flags are read once at startup by both the server and the @@ -38,8 +37,8 @@ const LICENSE_REGION_ENV = 'AWS_MARKETPLACE_LICENSE_REGION' /** The issuer every AWS Marketplace license is signed by (a fixed value, not a placeholder). */ const MARKETPLACE_KEY_FINGERPRINT = 'aws:294406891311:AWS/Marketplace:issuer-fingerprint' -/** The listing's contract dimension API name. */ -const ENTERPRISE_ENTITLEMENT = 'enterprise_license' +/** The uncounted entitlement every AWS Marketplace license holds while its agreement is active. */ +const MARKETPLACE_USAGE_ENTITLEMENT = 'AWS::Marketplace::Usage' /** AWS recommends re-checking every 15 minutes to pick up renewals, upgrades, and cancellations. */ const RECHECK_INTERVAL_MS = 15 * 60 * 1000 @@ -68,7 +67,10 @@ const NO_ENTITLEMENT_ERRORS = new Set([ 'ResourceNotFoundException', ]) -/** The ServiceAccount's IAM role does not allow the check: a setup problem, never a verdict. */ +/** + * The check was refused: the ServiceAccount's IAM role lacks the permission, or License Manager's + * service-linked role is missing from the account. A setup problem, never a verdict. + */ const PERMISSION_ERRORS = new Set(['AccessDeniedException', 'AuthorizationException']) /** `unknown` is a failed check (network, permissions, throttling), never a verdict on the license. */ @@ -93,10 +95,10 @@ function classifyFailure(error: unknown): LicenseState { return 'not-entitled' } if (name && PERMISSION_ERRORS.has(name)) { - logger.error( - 'The IAM role cannot check the AWS Marketplace license; allow license-manager:CheckoutLicense', - { error: name } - ) + logger.error('AWS License Manager refused the license check', { + error: name, + message: getErrorMessage(error, 'Unknown error'), + }) return 'unknown' } logger.warn('Could not check the AWS Marketplace Enterprise license', { @@ -116,12 +118,12 @@ async function checkLicense( ProductSKU: productSku, CheckoutType: 'PROVISIONAL', KeyFingerprint: MARKETPLACE_KEY_FINGERPRINT, - Entitlements: [{ Name: ENTERPRISE_ENTITLEMENT, Unit: 'None' }], + Entitlements: [{ Name: MARKETPLACE_USAGE_ENTITLEMENT, Unit: 'None' }], ClientToken: generateId(), }) ) const allowed = (checkout.EntitlementsAllowed ?? []).some( - (entitlement) => entitlement.Name === ENTERPRISE_ENTITLEMENT + (entitlement) => entitlement.Name === MARKETPLACE_USAGE_ENTITLEMENT ) return allowed ? 'entitled' : 'not-entitled' } catch (error) { diff --git a/helm/sim/Chart.yaml b/helm/sim/Chart.yaml index 6b50f65e446..7f2a4efbba6 100644 --- a/helm/sim/Chart.yaml +++ b/helm/sim/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: sim description: A Helm chart for Sim - the open-source AI workspace where teams build, deploy, and manage AI agents type: application -version: 1.11.17 +version: 1.11.18 appVersion: "v0.8.26" kubeVersion: ">=1.25.0-0" home: https://sim.ai diff --git a/helm/sim/examples/values-aws-marketplace.yaml b/helm/sim/examples/values-aws-marketplace.yaml index b4cee0b9083..07f1a452a65 100644 --- a/helm/sim/examples/values-aws-marketplace.yaml +++ b/helm/sim/examples/values-aws-marketplace.yaml @@ -23,7 +23,8 @@ # CloudFormation template creates both in your VPC. # 3. An IAM role for the ServiceAccount below that allows license-manager:CheckoutLicense (the # CloudFormation template creates it; AWS's managed AWSLicenseManagerConsumptionPolicy -# grants it too). +# grants it too). License Manager must be set up in the account: open its console once, or run +# aws iam create-service-linked-role --aws-service-name license-manager.amazonaws.com # 4. An ACM certificate for the two hostnames below. # 5. Two Secrets in the install namespace (no secret ever goes in this file). The template's # DatabasePasswordArn and CacheAuthTokenArn outputs are the ARNs of the Secrets Manager From 616bba117b94a87b90db283d17a0185e8109ad0c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 13 Nov 2026 01:06:49 -0800 Subject: [PATCH 2/2] fix(aws-marketplace): keep the license check's log message searchable --- apps/sim/lib/billing/aws-marketplace/container-license.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/sim/lib/billing/aws-marketplace/container-license.ts b/apps/sim/lib/billing/aws-marketplace/container-license.ts index 50201a3c7be..b190c924b9b 100644 --- a/apps/sim/lib/billing/aws-marketplace/container-license.ts +++ b/apps/sim/lib/billing/aws-marketplace/container-license.ts @@ -96,14 +96,14 @@ function classifyFailure(error: unknown): LicenseState { } if (name && PERMISSION_ERRORS.has(name)) { logger.error('AWS License Manager refused the license check', { - error: name, - message: getErrorMessage(error, 'Unknown error'), + errorName: name, + error: getErrorMessage(error, 'Unknown error'), }) return 'unknown' } logger.warn('Could not check the AWS Marketplace Enterprise license', { - error: name, - message: getErrorMessage(error, 'Unknown error'), + errorName: name, + error: getErrorMessage(error, 'Unknown error'), }) return 'unknown' }