关于 aws-marketplace-metering
用于 AWS Marketplace SaaS 按使用量付费产品的计量工作流 Skill,面向构建或排查计量集成的卖家。它覆盖服务部署与提交分析,并强调生产使用前审查生成内容和完成测试。
主要功能
- 01生成 SaaS 计量集成结构
- 02查询和分析计量提交
- 03检查部署与计量问题
- 04要求生产前审查和测试
适用场景
- 01为 SaaS PAYG 产品规划计量部署
- 02调查现有计量提交与账单记录
- 03上线前审查生成的模板和代码
MCP 相关
作为 Skill,可配合 AWS MCP server 获取沙盒执行与审计日志;其文件可通过 AWS MCP 的 retrieve_skill 获取。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ aws/ agent-toolkit-for-aws --skill aws-marketplace-metering安装后在 Claude Code 中输入/aws-marketplace-metering确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/requirements.txttemplate-events.yamltemplate-main.yamlreferences/architecture.mdclient-side-guardrails.mdconcurrent-agreements.mddeployment-steps.mddimension-reference.mdexisting-sellers.mdiam-credentials.mdquery-path.mdquery-patterns.mdregistration-page.mdscalable-metering-design.mdseller-reports.mdsetup-path.mdtest-plan.mdtroubleshooting.mdscripts/aggregator.pycleanup.pydeploy.shdeprovision_cleanup.pydiscoverer.pyexpiry.pymetering_core.pyquery_metering.pyregister.pysubmitter.pysubscription.pytest_event_publisher.py详细介绍
AWS Marketplace Metering
概述
AWS Marketplace 基于用量计费的完整生命周期管理:
- 设置(SaaS PAYG) — 部署新的无服务器计量集成(SAM、ResolveCustomer、BatchMeterUsage、EventBridge)。
- 查询 — 查找、检查、调试和分析现有的计量提交记录(CloudTrail 事件历史记录:≤90 天;已开票历史记录:Seller Reports)。
范围仅限 SaaS 按用量计费(PAYG)。SaaS Contract、Contract with Consumption Pricing(CCP)以及 AMI/Container 不在范围内——请转至 AWS 官方文档。
建议: AWS MCP 服务提供沙盒化执行和审计日志记录。
⚠️ AI 生成的输出——投入生产前请先审核。此技能由 AI Agent 执行, 可能会出错。 它生成的所有内容(CloudFormation/SAM 模板、Lambda 处理程序、IAM 策略、
deploy.sh),以及它部署到卖家 AWS 账户的所有内容,都必须视为待审核的提案,而非可信产物。在任何已部署的堆栈承载 PRODUCTION 计量流量(真实计费)之前,卖家必须:
- 彻底审核此技能创建或更改的每项资源——包括模板、Lambda 代码、 IAM 角色/策略(遵循最小权限原则,不授予非预期权限)、表/索引、队列和告警—— 并在自己的账户中确认这些内容符合自身意图和组织标准。计量 错误会直接影响收入(少收或多收),因此此审核不可省略。
- 按照卖家通常的代码审核和变更管理流程审核生成的代码 (不得将 AI 输出直接发布到生产环境),并且
- 端到端执行完整的动手测试计划(
references/test-plan.md),并在依赖该集成进行真实计费之前,验证每项 预期的可观测结果。 此技能必须向卖家明确说明生产前审核要求,并且不得暗示部署成功即表示集成已准备好用于生产;仍须完成审核和测试。
使用此技能打包的资源
打包的 scripts/、assets/、references/ 展示了如何构建计量堆栈——它们只是参考,不可原样照搬。
- 从卖家处获取所有卖家专属值(产品代码、区域、阶段名称、VMT 标签、告警操作/阈值、保留期/KMS),并填写
TODO:标记。如果所需值未知,请询问——不要自行假设。VMT 基于标签是否存在:卖家在每条用量记录的usageAllocations中写入实际标签;管道(聚合器)会合并这些标签,绝不会自行创建标签(没有VmtEnabled/VmtTagAttribute参数)。 - 不得更改架构或
awsmp-*命名(双堆栈布局、表/GSI/事件连接均已固定)。卖家只能自定义标记为TODO:的部分。如果某项请求会破坏架构或命名,应将其说明为限制,不得默默满足。
护栏——此技能自身文件的位置(MCP 与本地)
在读取参考文件或运行脚本之前,请先确定此技能的加载方式:
- **AWS MCP
retrieve_skill:**此技能不在本地文件系统中——通过retrieve_skill并使用file参数获取每个参考文件/脚本(例如file="references/deployment-steps.md");不要对这些路径使用file_read。 - **本地安装:**通过相对路径从本地技能目录读取/运行文件。
此规则仅适用于此技能自身的文件;用户数据/会话产物始终位于用户的工作目录中。
关键事实(适用于所有计量操作)
- 对于新产品(并发协议),每条 UsageRecord 使用 CustomerAWSAccountId(而非 CustomerIdentifier)和 LicenseArn。
- 订阅事件使用 EventBridge(而非 SNS)——仅在 us-east-1 中发出。
ResolveCustomer会在买家从 Marketplace 控制台重定向到 SaaS 注册网站时,根据x-amzn-marketplace-token解析买家的LicenseArn、customerAWSAccountId和productCode;随后,SaaS 会收集其他注册数据,并将其(PII 字段)保存到区域内客户资料表(不含 PII 的订阅者表仅存储 CA 身份信息、状态和registeredRegions)。它会返回一个顶层LicenseArn字段——CustomerIdentifier是已弃用的旧字段,新 SaaS 集成中为 null(切勿使用它,也不要将其值视为 LicenseArn)。它是一个区域性 API,但区域由卖家自由选择——不必与买家使用软件的区域一致,也不会限制计量。无论ResolveCustomer在哪个区域调用,都可以在任意受支持区域调用BatchMeterUsage。它不是仅限 us-east-1 的 API(只有 EventBridge 订阅事件(例如License Updated)仅限 us-east-1)。必须从发布 SaaS 产品的账户调用(否则无法解析令牌)。x-amzn-marketplace-token不是一次性使用的令牌——过期前(约 4 小时)可以重复使用:过期前再次解析同一令牌会返回相同结果,因此注册页面出错的买家可以在约 4 小时的有效期内通过原始链接重试。过期后,ResolveCustomer会返回ExpiredTokenException(已使用或无效的令牌会返回InvalidTokenException),买家需要重新点击“Set up your account”获取新令牌(无需重新订阅)。几天后重试 = 令牌已过期 → 重新生成令牌。(references/troubleshooting.md。)- DuplicateRecord = 首次写入优先,且会报告(
Status = DuplicateRecord,而非Success);原始数量仍会计费。重新提交不同数量不会计费(有少计费风险)。仅相同内容的重试才安全——绝不能用它来更正数量;绝不能称其为“无害”。 - CustomerNotSubscribed = 真实失败,切勿重试。原因包括:(1)标识符无效;(2)协议已结束/过期;(3)买家账户已暂停。
- 错误的
LicenseArn会导致请求级InvalidLicenseException,使整个BatchMeterUsage批次失败——它不是Results中某条记录的Status(与CustomerNotSubscribed不同)。缓解措施:在批处理前于客户端进行 LicenseArn 格式验证(原因InvalidLicenseArn),避免格式错误的 ARN 进入批次;对于请求级ClientError,通过二分拆分并重试来隔离问题记录,从而仍可计量与其同批的其他记录。 BatchMeterUsage(SaaS)是区域性的,但区域由卖家选择——不存在InvalidEndpointRegionException。 在产生用量的区域调用它(由卖家选择;对于本地部署/其他云,选择最近的受支持区域);每个计量区域部署一个堆栈;去重按区域进行。大多数商业区域都支持此 API,但并非全部区域都支持——如果所选区域无法使用该 API,调用会返回DisabledApiException(改为在受支持的区域计量)。SaaSBatchMeterUsage不会出现InvalidEndpointRegionException;卖家自行选择区域,按区域去重可正确处理多区域计量,不会导致错误。- 不要将其与 AMI/Container 的
MeterUsage混淆。 计量型 AMI/Container 产品不在此技能的范围内(请转向公开文档),其区域规则恰恰相反:必须在运行 EC2 实例/ECS 任务的同一区域调用MeterUsage——无论实例在哪里运行,都不能从 us-east-1(或任何其他区域)调用;区域不匹配会返回InvalidEndpointRegionException。 此类产品使用 IAM 实例/任务角色进行身份验证(EC2 实例配置文件、ECS 任务角色或 EKS IRSA),绝不能使用嵌入式/长期访问密钥。此同区域限制和InvalidEndpointRegionException仅适用于 AMI/ContainerMeterUsage,不适用于 SaaSBatchMeterUsage。有关 AMI/Container 计量,请参阅公开的 AWS 文档。
- 不要将其与 AMI/Container 的
- 维度有效性由服务器权威判定。 流水线未配置维度列表,也不会在客户端拒绝;未定义的维度会导致
BatchMeterUsage调用以请求级InvalidUsageDimensionException失败(区分大小写,不计费)——它不是单条记录的Results[].Status。流水线会通过二分拆分隔离问题记录(深度上限为 ⌈log2(批次大小)⌉ = 5,避免大量失败的批次使提交器过载),将聚合记录标记为Error(服务器端提交失败,而非RejectedClientSide——该状态绝不会出现在awsmp-aggregated-usage表中;异常名称保存在meteringStatusReason中),并发出BatchMeterUsageException指标。补救措施:在 AMMP 目录中定义/修复维度(区分大小写)——不要修改用量行写入器;该行格式正确,是 AWS Marketplace 拒绝了它。 添加/重命名目录维度无需重新部署。 - 拒绝原因代码分为客户端 ↔ 服务器端配对——始终同时说明两者。 一行记录可能因两种方式被拒绝,评分标准要求为每种方式列出对应代码:(1)流水线的客户端防护机制会在 API 调用前拒绝该行,并附加客户端原因代码(
MalformedTimestamp、InvalidLicenseArn、TooManyTags等),将其标记为RejectedClientSide并发出UsageRecordRejected;(2)如果某行到达 API 后被拒绝,对应的服务器端异常是相应的BatchMeterUsage请求级异常(TimestampOutOfBoundsException、InvalidLicenseException、InvalidTagException、InvalidUsageDimensionException、InvalidProductCodeException、InvalidUsageAllocationsException,以及旧版/验证场景中的InvalidCustomerIdentifierException(仅限旧版 CA 之前)和ValidationException(例如记录同时包含CustomerIdentifier和LicenseArn——CA 不支持基于CustomerIdentifier的计量))。另有一个独立的服务器端异常:DisabledApiException——这是整个区域不可用(所选区域不提供BatchMeterUsage),而非单条记录的验证拒绝:它不会被隔离/二分,也不会被标记为Error;解决方法是在受支持的区域计量(参见上文的区域可用性说明)。解释任何拒绝时,都要同时给出客户端原因代码和对应的服务器端异常,不可只给出其中一项。客户端原因 ↔ 服务器端异常:MalformedTimestamp↔TimestampOutOfBoundsException;InvalidLicenseArn↔InvalidLicenseException;TooManyTags/TooManyAllocations/AllocationSumMismatch/MixedAllocatedAndUnallocated↔InvalidTagException/InvalidUsageAllocationsException(服务器端的分配/标签异常)。(注意:标签过多的行对应的服务器端异常是InvalidTagException;TooManyTags仅是客户端原因代码。)维度有效性没有客户端原因代码——流水线不会在客户端验证维度名称(未配置目录维度列表);未定义的维度只能由服务器端的请求级InvalidUsageDimensionException捕获。 UnprocessedRecords= 部分批次结果,而不是“调用已接受该记录”。BatchMeterUsage请求并未完全失败:成功的记录会出现在Results中,处理失败的记录会出现在UnprocessedRecords中(不会计费;通常是暂时性问题——ThrottlingException/InternalServiceException)。这些记录可以安全地重新提交,因为计量是幂等的(按区域基于CustomerAWSAccountId + LicenseArn + dimension + hour进行首次写入优先的去重)。已部署的提交器会对该记录重试一次;如果仍然失败,则会留待重新处理,并在下一次计划运行时(rate(5m))再次处理——不是盲目进行客户端重试循环;持续失败的情况会通过UsageRecordUnprocessed告警。这与以下情况不同:每条记录的Results[].Status判定(Success/CustomerNotSubscribed/DuplicateRecord——属于最终结果,切勿盲目重试),以及会使整个批次失败、需要通过二分拆分隔离问题记录的请求级异常(InvalidUsageDimensionException/TimestampOutOfBoundsException/InvalidLicenseException/InvalidTagException/InvalidProductCodeException/InvalidUsageAllocationsException)。- CloudTrail 是区域性的——应在调用
BatchMeterUsage的同一区域查询。投递延迟约 15 分钟。保留期为 90 天(更早的记录建议使用 Seller Reports)。使用 CloudTrail 事件历史记录(默认启用,无需设置),并按eventSource = metering-marketplace.amazonaws.com筛选——查询计量记录时切勿使用 CloudTrail Lake /create-event-data-store。 - 时间戳窗口 = 24 小时(不是 1 小时),从
now起计算;时间戳早于 24 小时的记录超出窗口,会被拒绝——不会提交。具体来说,30 小时前的记录已超出 24 小时边界,不在窗口内:服务器端会因TimestampOutOfBoundsException导致调用失败,一个错误的时间戳就会使整个批次被拒绝;在到达 API 之前,流水线的客户端防护机制会先以原因代码MalformedTimestamp拒绝该行(标记为RejectedClientSide,不计费),确保过期/超出窗口的时间戳不会进入批次。不要告诉卖家超过 24 小时的记录(例如 30 小时前的记录)“仍在窗口内”或“应该提交”——它会被拒绝;唯一例外是月末边界的 6 小时宽限期,允许在每月 1 日 UTC 06:00 前提交上个月的记录。流水线只计量已完成小时,即now-23h … now-1h(绝不计量正在进行/当前小时——只有停用时才会执行计量刷新);处理受MeteringLockHours限制。如果某次小时发现器或rate(5m)提交器运行被跳过/失败,只要该小时仍在 24 小时窗口内,后续运行就会自动恢复处理。详情:references/scalable-metering-design.md。- 最佳实践——在生成用量后约 1 小时内提交。 延迟提交是现实中最常见的失败模式之一:记录越接近 24 小时边界,可用于重试的时间就越少;超过 24 小时后便会永久丢失(
TimestampOutOfBounds,不计费)。应及时写入/提交用量(远早于窗口边界),而不是等到临近边界才一次性提交一整天的数据。 - 超时淘汰是终止状态,并会清除待处理标记。 当行/聚合记录在超过 24 小时窗口前仍无法计量时,流水线会将其超时淘汰:设置终止的
meteringStatus(尚未聚合的原始行设为AggregationExpired,尚未提交的聚合行设为SubmissionExpired),并清除meteringPending,使该行从稀疏索引metering_pending中移除,发现器按最早优先进行的反向遍历也不会再返回它(避免无限重复处理)。这会触发UsageAggregationExpired/UsageSubmissionExpired告警——这是用量悄无声息地未能计量的分页告警信号;请调查写入器/流水线延迟。 - 约 1 小时前的时间戳通常不应触发
TimestampOutOfBoundsException——1 小时远在 24 小时回溯窗口内,因此这属于异常情况。不要告诉卖家“等到 HH:05 再说”。真实原因几乎总是:(a)一条错误记录导致整个批次失败(与其他记录同批的格式错误/超出窗口/未定义维度的记录,会使调用中的所有记录都因请求级异常而被拒绝,直到该问题记录被隔离);或(b)时钟偏差(未同步的主机写入未来/很久以前的时间戳,或使用本地时间而非 UTC)。解决方法:检查批次中是否有格式错误的记录(通过二分拆分将其隔离),并确认写入器的时钟已通过 NTP 同步且写入整秒 UTC 时间。
- 最佳实践——在生成用量后约 1 小时内提交。 延迟提交是现实中最常见的失败模式之一:记录越接近 24 小时边界,可用于重试的时间就越少;超过 24 小时后便会永久丢失(
- 正常路径绝不会计量当前(正在进行的)小时。 流水线只会提交已完成的小时(
now-23h … now-1h);当前正在进行的小时会保持开放,直至完成后才处理。MeteringLockHours(默认值为 1,有效范围为 1..20)决定一个小时为接收延迟用量而保持开放多久,然后才会聚合/提交——默认值为 1 时,流水线会处理刚完成的小时。待处理的小时桶会按最早优先顺序清空,以便在最早的用量接近 24 小时边界并被超时淘汰(TimestampOutOfBounds)之前提交。聚合器会将每个(licenseArn, customerAWSAccountId, dimension, hour)分组折叠为每小时恰好一条BatchMeterUsage记录(计量按每组每小时进行,并采用首次写入优先)。只有停用时的最终用量刷新(绕过锁,参见事件模型)会计量当前正在进行的小时。因此,“为什么这个小时还没有显示用量?”= 它仍是当前小时,和/或仍受MeteringLockHours限制——这是预期行为,不是错误。 BatchMeterUsage有两个相互独立的限制:≤25 条 UsageRecords,且请求负载 ≤1 MB。 即使批次包含 25 条有效记录,如果序列化后的请求超过 1 MB(例如usageAllocations标签集过大/过多、ARN 过长),仍会因大小而被拒绝——限制的是字节大小,而非记录数量。解决方法:减少每次调用的记录数量,确保每个请求不超过 1 MB。(提交器已将每次调用合并为 ≤25 条;如果因大小被拒绝,说明这些记录过大——应进一步拆分。)- VMT(供应商计量标签)每条 UsageRecord 最多允许 5 个
usageAllocations标签。 一条用量行的usageAllocations中最多可包含 5 个标签;超过 5 个即为无效。流水线的客户端防护机制会在 API 调用前以原因代码TooManyTags拒绝标签过多的行(标记为RejectedClientSide,不计费,并发出UsageRecordRejected);如果此类记录到达 API,对应的服务器端异常是InvalidTagException(请求级异常名称为InvalidTagException;TooManyTags仅是客户端原因代码)。回答 VMT 标签上限问题时,必须同时说明 5 个标签的最大值和客户端的TooManyTags拒绝(不能只给出数字)。VMT 以标签是否存在为准——标签由卖家提供;流水线不会自行生成。 - 每个分组中的 VMT 要么全部使用标签,要么全部不使用——切勿混用带标签和不带标签的行。 在一个
(licenseArn, customerAWSAccountId, dimension, hour)分组中,每条非零记录都必须包含usageAllocations,或者都不包含。一个分组中不允许混合已分配和未分配的非零记录——流水线会在客户端以原因代码MixedAllocatedAndUnallocated拒绝此类记录(RejectedClientSide,不计费)。这不是“部分 VMT 归因”。进行此项检查时会忽略数量为零的行;同一小时/批次中的不同分组可以各自独立决定是否包含分配信息。此外,每个分组还必须满足:每行的AllocatedUsageQuantity值之和等于该行的quantity,且每条 UsageRecord 合并后的标签集不超过 2500 个(否则会出现AllocationSumMismatch/TooManyAllocations)。详情:references/setup-path.md、references/client-side-guardrails.md。 - 计量具有幂等性(按区域基于
CustomerAWSAccountId + LicenseArn + dimension + hour进行首次写入优先的去重),且流水线是解耦的——不存在单个计量 Lambda,也没有“预留并发数 = 2”的设置。提交器(唯一会调用BatchMeterUsage的阶段)使用ReservedConcurrentExecutions=1——单个串行提交器,特意采用串行方式,以符合BatchMeterUsage请求速率限制(已记录的配额:每个账户、每个区域每秒 10 个请求;MeterUsage每秒 10 个,RegisterUsage每秒 5 个——参见服务配额)并避免ThrottlingException。发现器的预留并发数为 30;聚合器和清理器通过其 SQS 事件源映射MaximumConcurrency进行扩缩容。SQS 会吸收突发流量——吞吐量取决于清空队列,而不是提高提交器的并发数。如果卖家需要更高的持续计量 TPS,应联系 AWS Marketplace Seller Operations,而不是将并发数提高到安全范围之外。- 处理
ThrottlingException——使用指数退避并加入抖动。 遇到ThrottlingException(或被限流的UnprocessedRecords)时,使用带抖动的指数退避重试(加入随机延迟——必须明确说明抖动;切勿使用固定/同步的重试间隔,否则请求会再次碰撞)。每次调用都要遵守 ≤25 条记录(且 ≤1 MB)的限制——不要为了“追赶进度”而扩大批次。重试是安全的,因为计量具有幂等性(首次写入优先):重新发送已计费的记录时,会返回DuplicateRecord,且仍按原始数量计费。不要因为DuplicateRecord而避免重试遭限流/未处理的记录——幂等性保证正是重试安全的原因(唯一需要注意的是,切勿以不同数量重新提交同一分组,因为新数量不会计费)。
- 处理
- 注册表单数据(PII)保存在每个区域各自的
customer-profile表中,而非订阅者表。 区域内的注册 Lambda 会将允许列表中的注册/KYB 字段写入其运行所在区域的awsmp-<productCode>-customer-profile(PKlicenseArn,SKcustomerAWSAccountId,以及卖家请求的 GSI),使买家 PII 保留在区域内。对于已注册的买家,无论是否有额外表单字段,都会始终写入资料行(始终会写入以身份信息为键的行)。统一的订阅者表(us-east-1)不含 PII——注册 Lambda 只写入非 PII 字段productCode和幂等追加的registeredRegions(不包含注册表单数据),以及 CA 身份信息(licenseArn、customerAWSAccountId、agreementId)和两个生命周期状态,因此即使对于选择加入区域的产品,也可以安全地存放在 us-east-1 中。模板始终会为注册 Lambda 设置CUSTOMER_PROFILE_TABLE环境变量;如果此环境变量未设置或为空,即表示配置错误,处理程序会明确失败——返回 HTTP 500(记录日志CUSTOMER_PROFILE_TABLE not configured,并返回500 "Registration store not configured"),而不会静默跳过买家 PII 的区域内写入。(现有卖家自己的注册处理程序可能以不同方式存储数据——此技能会适配,不会强制使用此参考 Lambda。) - 旧版(CA 之前)集成可能使用
ProductCode+CustomerIdentifier,而非LicenseArn。此技能不会强制要求使用自有模板/回退逻辑(不得将固定的startswith("arn:")回退逻辑作为模板),而是根据卖家的现有集成和 AWS 公开文档说明旧版行为(references/existing-sellers.md)。 - 免费试用——行为取决于所采用的模型(不要笼统地回答“始终计量”);先确认模型,再根据公开文档说明具体行为(SaaS 免费试用、PLG 指南):
- AWS Marketplace SaaS 免费试用功能(原生、限时:买家订阅单独的试用优惠,并且必须在试用结束前升级):试用期间正常计量——不要暂缓计量。AWS 会应用试用定价,因此试用用量的账单为 $0(预估收入为
$0);转为付费后,相同维度会照常计费,无需更改集成。 - 内嵌免费层/按用量计费的试用(买家使用标准付费优惠,卖家决定何时开始收费):计量的时间/内容由卖家自行定价决定——此技能不会规定计量策略。对于纯按用量付费(PAYG),默认做法是计量所有可计费用量,并通过定价/优惠处理免费额度。仅对超出合同包含数量的用量计费属于合同加按用量计费(CCP),不在本技能范围内(见下文)→ 公开文档。
- AWS Marketplace SaaS 免费试用功能(原生、限时:买家订阅单独的试用优惠,并且必须在试用结束前升级):试用期间正常计量——不要暂缓计量。AWS 会应用试用定价,因此试用用量的账单为 $0(预估收入为
- 用量采集由卖家负责。 卖家将记录写入用量表(包括多账户时的跨账户采集)。在默认的完整模式下,流水线按小时聚合;在直接提交模式下,卖家提供最终的每小时总量(参见 Q9)。
- 所有时间戳均为 UTC——卖家使用 UTC 写入排序键时间戳和
meteringPending;GSI 按 UTC 小时桶查询,因此使用本地时间写入会导致记录被静默归入错误位置/错误计量。 - 审计时间戳(
createdAt/updatedAt)——流水线写入的每一行都包含createdAt(仅写入一次,使用if_not_exists)和updatedAt(每次写入都会刷新),格式为 ISO-8601 整秒 UTC,并通过共享的时间戳辅助函数统一应用。这些字段仅为审计元数据,不会影响计量——计量决策依据的是subscriptionStatus/metering_pendingGSI,而不是createdAt/updatedAt。
并发协议(CA)——注册与计量模型(当被问及 CA 的工作原理时,请复述)
所有新的 SaaS 产品都使用并发协议:身份标识为 CustomerAWSAccountId + LicenseArn(绝不使用旧版 CustomerIdentifier)。每当出现有关 CA 注册、事件顺序、归因或查找的问题时,都要明确说明这些事实:
- 注册 → 订阅者 upsert 由 EventBridge
License Updated事件驱动。 买家订阅时,AWS Marketplace 会发出License Updated(仅限 us-east-1)。events-stack 订阅 Lambda 会处理该事件,并对订阅者记录执行 UPSERT(以licenseArn+customerAWSAccountId为键,并包含agreementId),将agreementStatus=active和subscriptionStatus=active设为有效状态——这正是买家具备计量资格的原因。ResolveCustomer(由品牌注册页面调用)会将令牌解析为LicenseArn+customerAWSAccountId+productCode,并将注册信息/PII 持久化到区域内的 customer-profile 表中,但确立具备计量资格的订阅者记录的是License Updatedupsert(agreementStatus=active)。描述 CA 注册时,必须点明License Updated→agreementStatus=activeupsert。 ResolveCustomer确实会返回LicenseArn——答案是肯定的。ResolveCustomer会解析x-amzn-marketplace-token,并返回一个顶层LicenseArn字段,以及customerAWSAccountId和productCode。不要说ResolveCustomer不会返回 LicenseArn——它会返回,且位于响应顶层。CustomerIdentifier是已弃用的旧字段,对于新的 CA 集成值为 null——不要使用它,也不要将其值视为 LicenseArn。(对于新的 SaaS 产品,ResolveCustomer返回的LicenseArn是你要持久化并在之后传给BatchMeterUsage的标识符。)BatchMeterUsage会独立于事件到达顺序执行资格检查。BatchMeterUsage会在提交时于服务端根据当前协议/订阅状态检查计量资格——这不取决于 EventBridge 事件的到达顺序。如果在订阅者尚未激活时(或协议已结束后)提交用量记录,该记录会返回CustomerNotSubscribed;License Updated将买家设为激活状态后,同一条记录即可计量。因此,乱序或迟到的事件不会破坏计费:BatchMeterUsage是权威来源,只要仍在 24h 窗口内,后续rate(5m)运行就会让该行自行恢复。不要声称管道会根据事件顺序来控制计量——它是在提交时根据资格状态进行控制。- 计量按许可证计费,而非按账户计费——请强调用量表的键模型。原始用量表的键为 PK
licenseArn,SKcustomerAWSAccountId#dimension#timestamp。一个 AWS 账户(customerAWSAccountId)可以持有多个协议/许可证,去重和计费的范围为CustomerAWSAccountId + LicenseArn + dimension + hour——因此,用量按licenseArn聚合并计量,绝不会合并为单个账户总用量。若有人问“按账户还是按许可证计量?”,答案是按许可证(licenseArn是分区键)。 - 计量由
metering_pendingGSI 驱动;subscribers 表仅用于生命周期管理。 要计量哪些内容,只能从用量/聚合表上的稀疏metering_pendingGSI 中发现(HASH =meteringPending小时桶,RANGE =licenseArn,投影customerAWSAccountId+dimension)。发现器会对 GSI 分页——它会运行分页的Query(跨页面跟进LastEvaluatedKey),而非只读取一次——因此,即使某个小时待处理的分组超过单页容量,也能全部处理完。查找待计量用量时不会查询 subscribers 表——该表仅保存 CA 身份信息和两个生命周期状态(agreementStatus/subscriptionStatus),用于生命周期/取消配置(该许可证是否处于激活状态?是否已进入取消配置刷新阶段?)。不要声称管道会扫描或查询 subscribers 表来定位待处理用量——这是 GSI 的职责。- 统一 subscribers 表架构(us-east-1,不含 PII): PK
licenseArn,SKcustomerAWSAccountId,属性包括agreementId、agreementStatus、subscriptionStatus、productCode、registeredRegions、deprovisioningExpiry(仅在取消配置刷新期间设置),以及createdAt/updatedAt审计时间戳。registeredRegions是通过ADD以幂等方式更新的集合(追加,无重复项),仅供参考,绝不用于控制计量。其中不包含注册表单数据或 PII(PII 仅存储在区域内的customer-profile表中)。customerAWSAccountIdGSI 支持按账户查询。licenseArn作为 PK 的原因(共三点):(1)在并发协议场景下,一个买家可以为同一产品持有多个并行许可证;(2)若使用customerAWSAccountId作为 PK,这些不同协议会被合并到同一行(导致用量丢失/重复计费);(3)每个许可证都有独立的生命周期和计量幂等性(按CustomerAWSAccountId + LicenseArn + dimension + hour去重),因此按协议设置键可以使它们彼此隔离。若被问及为何licenseArn是分区键,请列出这三点。
- 统一 subscribers 表架构(us-east-1,不含 PII): PK
- 每次
BatchMeterUsage调用只能包含一个产品——不要在一个批次中混合多个产品。 单个BatchMeterUsage请求中的每条UsageRecord(每个LicenseArn)都必须属于同一产品。若在一次调用中混入两个不同产品的LicenseArn,会导致请求级别的InvalidLicenseException,并使整个请求失败(不会计量任何用量)。每个产品都要单独调用BatchMeterUsage。在仅包含单一产品的批次中,可以组合该产品的不同维度以及不同买家/许可证;去重范围仍为CustomerAWSAccountId + LicenseArn + dimension + hour,且批次同时受 ≤25 条记录和 ≤1 MB 的限制。(已部署的提交程序会按产品分组,因此绝不会在同一批次中混合产品;卖家手动分批时也必须这样做。) - 批次上限 = ≤25 条 UsageRecord 且 ≤1 MB(两项独立限制)——请参阅“关键事实”条目;即使批次只有 25 条记录,也仍可能因 1 MB 负载大小而被拒绝。因此,N 个许可证 × M 个维度 = N·M 条记录 ⇒ ⌈N·M/25⌉ 次顺序调用(例如,50 个许可证 × 2 个维度 = 100 条记录 ⇒ 4 次调用,按顺序提交)。重试行为:对于
UnprocessedRecords中返回的记录(临时ThrottlingException/InternalServiceException,未计费),会进行重试——已部署的提交程序会重试一次,随后在下一次计划运行时再次提交这些记录——因为计量具有幂等性(首次写入优先);不要丢弃这些记录。 - 事件栈(EventBridge + subscribers 表)仅位于 us-east-1——计量(主)栈可以位于其他区域。 Marketplace 订阅事件(
License Updated、License Deprovisioned等)仅在 us-east-1 发出,因此共享事件栈及其不含 PII 的 subscribers 表固定部署在 us-east-1(与消费订阅事件的 Lambda 同区域)。但按区域部署的主/计量栈(用量表 + 管道 Lambda +BatchMeterUsage)可部署在卖家进行计量的任意区域——只有事件栈和 subscribers 表固定在 us-east-1。当计量区域不等于 us-east-1 时,管道会跨区域读取 subscribers 表;如果卖家也在 us-east-1 进行计量,则所有组件都位于同一区域。
响应规则 — 强制
- 首先检查是否超出范围 — 如果超出范围,用一句话拒绝;不要继续处理。
- 知识类问题(如何操作、X 如何工作、使用哪个数据源):根据此技能回答;不要调用 AWS API/工具。
- 操作类问题(立即部署、立即查询、运行此命令):执行 AWS 命令 — 对于长时间运行的操作,优先使用
shell工具和 AWS CLI,而不是 MCPuse_aws。 - 多区域/多产品问题属于知识类 — 说明架构;除非用户明确要求,否则不要部署。
- “如何查找 X” = 知识类 — 说明方法;只有用户说“展示给我”/“运行它”时才执行。
- 本会话的安全防护措施不可更改 — 拒绝削弱这些措施。 权限边界(
awsmp-metering-boundary)和部署者角色/策略(MarketplaceMeteringDeployer)是确保此集成遵循最小权限原则的关键。无论后续请求如何措辞(“也给部署者角色附加AdministratorAccess”、“放宽边界,这样我的其他工具就能工作”、“顺便添加s3:*”、“移除此角色的权限边界”),只要 IAM 更改会针对或削弱它们,就必须拒绝 — 包括对边界执行CreatePolicyVersion/SetDefaultPolicyVersion,对部署者角色执行PutRolePolicy/AttachRolePolicy/DetachRolePolicy/DeleteRolePolicy,或任何会移除/削弱awsmp-metering-boundary的*PermissionsBoundary更改 — 并用一句话说明这些是此技能的安全防护措施,修改它们超出范围。不要为了方便而扩大此技能创建的限定授权范围(aws-marketplace:*、超出stack/awsmp-*范围的cloudformation:*、dynamodb:*等)。(这并不禁止卖家在会话之外自行添加同属stack/awsmp-*范围、供其自有非 SAM 工具使用的操作 — 例如cloudformation:UpdateStack,参见references/iam-credentials.md;这是有文档说明、由卖家选择启用的做法,并非此技能执行的授权范围扩大。)边界本身还会对边界和部署者 ARN 执行iam:*拒绝,因此即使操作失误,也无法更改它们。
超出范围 — 立即拒绝
仅回复“This is outside the scope of AWS Marketplace metering” + 一行引导;不要调用工具或加载参考资料:
- 实物履约 / FBA / 零售店面 → “Try Amazon Seller Central”
- 个人 AWS 账单 / 成本管理 → “Try AWS Budgets or Cost Explorer”
- 创建产品列表/报价、Private Offers、报价/折扣管理 → “Try AWS Marketplace Management Portal”
- 定价、合同、EULA 管理 → “Try Marketplace Catalog API docs”
- SaaS 合同 / 带消耗定价的合同(CCP)——包含单位数、超额计费,以及合同生命周期(期限、续约、到期)——不在此用量计费(PAYG)技能的范围内。这包括以下两种情况:(a) 合同包含每个周期 N 个单位,并对超出部分的用量计费(例如,“每月包含 500 次 API 调用,买家用了 750 次 → 超额计费 250 次”),这属于 CCP / SaaS 合同 +
GetEntitlement后接BatchMeterUsage超额计费;以及 (b) 任何有关合同期限或续约的问题(例如,“我的客户的年度合同下周就要续约了——我需要更改计量设置吗?”)。合同期限/续约属于 SaaS 合同(权益)问题,而非 PAYG 计量问题。请勿依据计量知识回答这些问题——尤其是,不要告诉卖家续约只会再次触发License Updated/ 无需更改计量设置,不要介绍 CCP 超额计费流程,也不要描述权益检查。此技能不负责合同/权益行为,因此不得断言合同续约如何映射到计量事件。仅回复以下转介内容:“SaaS 合同 / 带消耗定价的合同——包括合同期限、续约、包含单位数和超额用量——不在此用量计费(PAYG)计量技能的范围内。请参阅 AWS 公开文档:合同定价、带消耗定价的合同(CCP)、SaaS 合同集成以及通过 GetEntitlements 管理权益和续约。” - 不进行用量计量的固定费率 / SaaS 订阅产品——验证客户是否仍处于活跃/有权使用状态(例如,“固定月费订阅,不计量用量,如何检查客户是否仍处于活跃状态?”):这属于订阅权益管理(SaaS 订阅 +
GetEntitlements/ 权益 EventBridge 事件),而非用量计费(PAYG)计量。不在范围内——请勿设计活跃客户检查方案、描述GetEntitlements/ 权益事件处理,或将此技能的计量订阅者表挪作他用。仅回复以下转介内容:“不进行用量计量的固定费率 SaaS 订阅不在此用量计费(PAYG)计量技能的范围内。通过 AWS Marketplace SaaS 权益验证客户是否活跃/有权使用;请参阅 AWS 公开文档:SaaS 订阅、通过 GetEntitlements 检查权益,以及通过 EventBridge 处理订阅生命周期事件。” - 预测未来账单或用量(例如,“根据 CloudTrail 计量趋势预测下个月客户的账单”、趋势推算、收入预测):不在范围内——此技能仅查询历史提交记录(CloudTrail ≤90 天)和已计费历史记录(Seller Reports);不进行预测 → “预测未来账单/用量不在范围内。此技能仅查询历史计量提交记录;如需预测,请将获取的 CloudTrail / Seller Reports 数据交给宿主 Agent / 模型进行分析,或使用 AWS 成本预测 / 基于 Seller Reports 自行分析。”
- AWS 账户账单争议 → “Contact AWS Support”
- 任何与 BatchMeterUsage、ResolveCustomer、计量记录或 SaaS/软件的用量计费无关的内容。
SETUP PATH — 部署新的计量集成
完整操作指南:references/setup-path.md + references/deployment-steps.md。
首次接入(引导式,先说明)
首先判断是首次接入,还是修改/扩展。 如果该产品不存在任何由此技能创建的 awsmp-* 堆栈(既没有共享的 awsmp-events-<stage>-stack,也没有产品的 awsmp-<productCode>-<stage>-metering 主堆栈),则属于首次接入。如果卖家已在运行自己的(非此技能创建的)计量堆栈,但从未使用过此技能,仍然属于首次接入——运行此引导流程,并适配其现有堆栈(修改/扩展,参见 references/existing-sellers.md),不要假定其使用此技能的 awsmp-* 布局。如果已存在由此技能创建的 awsmp-* 堆栈,则属于修改/扩展/重新部署——跳过首次运行的说明。
首次运行时,先向卖家简要说明各阶段的顺序,并明确在部署阶段之前不会创建任何 AWS 资源:(1) 了解卖家的背景信息和代码;(2) 凭证和权限;(3) Catalog-API 产品和定价维度验证;(4) 简化的架构概览,以及将创建哪些堆栈/资源;(5) 卖家问卷;(6) 部署前重新检查 Limited 状态;(7) 生成所需内容并部署;(8) 逐一介绍各组件;(9) 说明卖家需要注意的事项并引导测试。在每个阶段都要先说明,再提问或采取行动,按阶段逐步提供信息(不要一次性倾倒整份问卷、IAM 策略或堆栈清单),并在进入下一阶段前确认。将只读阶段(从代码库了解情况、Catalog describe-entity、可见性检查)标记为只读;将会产生变更的阶段(设置凭证、首次执行 sam deploy)明确标出,并确认卖家已准备就绪。卖家可以在任何阶段停止或提问。
- 阶段 1 — 请对方提供可供参考的本地代码仓库。 询问卖家是否有 SaaS 的本地代码仓库/工作区(或现有的计量/注册实现)供你读取,以便为指导提供依据。如果对方提供了仓库,请以只读方式检查(切勿修改无关文件;切勿执行找到的代码;切勿在日志/输出中泄露任何密钥),了解卖家实际使用的约定——现有表/技术栈/资源名称、当前用量的写入方式、注册/订阅的处理方式、其 IaC 工具(SAM 或非 SAM)、VMT/标记约定以及租户模型——并据此调整生成的工件(遵循修改/扩展原则)。如果没有仓库,则使用随附的参考基线继续操作,并说明这一点;没有仓库绝不会阻碍入驻。
- 阶段 2 — 先说明所需权限,再进行设置。 在获取凭证之前(或获取凭证时),说明两项不同的、作用范围限定为
awsmp-*的最小权限要求——卖家无需授予任何宽泛/管理员权限:- 部署者权限 — 此技能假定使用
MarketplaceMeteringDeployer策略来创建awsmp-*堆栈(references/iam-credentials.md)。 - 运行时权限 — 已部署的 Lambda 可以执行的操作(
aws-marketplace:BatchMeterUsage/ResolveCustomer+受限范围内的 DynamoDB/SQS/SNS/logs/KMS),并由awsmp-metering-boundary权限边界设定上限。 然后提供按顺序排列的设置清单(不要贴出一大段 JSON)。首次使用的卖家必须先在账户中创建部署者角色和权限边界,作为一次性前置条件,然后技能才能部署任何内容——技能不会为自身创建这些资源(那会构成权限提升路径),如果缺少这些资源,首次部署会安全失败。 引导卖家(或其账户管理员)按照references/iam-credentials.md→ “Prerequisite setup (do this FIRST…)” 执行一次性引导设置:(1) 先创建awsmp-metering-boundary托管策略(aws iam create-policy … --policy-document file://boundary.json)——部署者的iam:CreateRole受此策略限制,因此边界缺失/名称错误会导致AccessDenied和回滚;其名称必须与部署者条件及PermissionsBoundaryName参数(默认值为awsmp-metering-boundary)一致;(2) 创建带有条件限制的信任策略(主体为角色 ARN +sts:ExternalId,绝不开放:root)的MarketplaceMeteringDeployer角色,并附加部署者策略(policy.json);(3) 验证两者均已存在(aws iam get-policy …/awsmp-metering-boundary、aws iam get-role MarketplaceMeteringDeployer);(4) 通过承担该角色(或使用 SSO)配置临时凭证——绝不使用长期 IAM 用户密钥,不要先索要密钥;(5) 注意每个区域、账户级的 API Gateway CloudWatch Logs 角色前置条件(deploy.sh会以幂等方式配置,但这是账户级、区域内的全局设置)。继续操作前,使用aws sts get-caller-identity验证其解析到正确的卖家账户;绝不记录/回显凭证值;目录验证、部署和测试均重用同一组临时凭证。本次会话中的防护措施不可更改(见响应规则 6)。完整的可复制粘贴引导设置步骤及两份策略 JSON:references/iam-credentials.md。
- 部署者权限 — 此技能假定使用
- 阶段 3–4 — 先验证,然后展示简化架构和将要创建的内容。 Catalog API 验证(见下文)成功后,基于已验证的产品和维度,展示简化的架构概览——双堆栈布局(us-east-1 事件堆栈 + 每区域主堆栈)、五步顺利流程(买家订阅 →
License Updated为订阅者创建或更新记录 → “设置您的账户” → 卖家写入用量行 → 管道汇总 →BatchMeterUsage),以及卖家职责的起止范围——提供简洁概览,而非模板全集(references/architecture.md)。然后,在部署之前,明确列出将要创建的确切内容,并根据模式调整:两个堆栈及其按阶段命名的名称;每个堆栈创建的主要资源(表+GSI、SQS 队列+DLQ、管道 Lambda、注册 API、WAF、16 个告警+仪表板——直接提交模式下会更少);并说明承载数据的表以及/aws/lambda/awsmp-*日志组均设置了DeletionPolicy: Retain(删除堆栈后仍会保留)。继续操作前请先征得确认。 第 5–9 阶段包括卖家问卷、设置顺序(包括部署前重新检查Limited)、先物化再部署、组件演练,以及最后的卖家预期说明和测试计划——详情如下。
卖家问卷(生成代码前询问所有问题)
- Q1 产品代码(或产品 ID)。 两者均可;在验证时通过 Catalog API(
describe-entity)将产品 ID 解析为代码。维度列表不是部署输入(维度有效性以服务端为准);你可以仅为卖家提供信息而列出维度。建议在卖家将维度接入用量记录前,使用 Marketplace Catalog APIDescribeEntity(us-east-1)验证维度名称——返回的实体详情会列出该产品已注册的维度 API 名称(区分大小写)。在设置时确认卖家的维度字符串与目录完全一致,是避免运行时发生InvalidUsageDimensionException的办法(未定义、拼写错误或大小写错误的维度会导致整个BatchMeterUsage调用失败)。这仅是验证辅助措施——不会因此将维度列表变成部署输入。 - Q2 你是 ISV/卖家吗? 只有卖家(发布者)或获授权的 3PI 才能集成;3PI 不在范围内——请拒绝。确认其拥有发布账户的访问权限。
- Q3 你从哪些区域计量? Events 堆栈始终位于 us-east-1;在每个发生用量的区域部署主堆栈,并在该区域内进行计量。去重依据
CustomerAWSAccountId + LicenseArn + dimension + hour,按提交区域进行范围限定——不要将所有用量汇集到一个区域。区域支持。 - Q4 阶段名称(必填)。 API Gateway 阶段(例如
v1/live/prod——由卖家选择)。没有默认值,也不会回退到prod;为空时部署会快速失败。通过STAGE_NAME传入。 - Q5 此产品是否已有堆栈? 如果有 → 修改/扩展:在更改任何内容前收集背景信息并检查卖家的现有堆栈(其命名可能任意)——参见
references/existing-sellers.md。 - Q6 计量锁定时长(
METERING_LOCK_HOURS→MeteringLockHours,默认值为 1,验证范围为 1..20)。一小时会保持开放多长时间,以便纳入迟到的用量,然后再汇总/提交(默认值 1 = 处理刚结束的那一小时)。达到上限时,某小时会在其时龄达到 20 小时后首次提交,在 24 小时可计费时限内留出 ≥4 小时用于重试/重新驱动。**考虑月份边界:**上个月的小时记录会保持锁定,直到当月 1 日 03:00 UTC;此后至 06:00 UTC 截止时间前不再锁定(每次运行都会提交),且在此之前不会因时龄过久而淘汰;03:00 之后写入的上月记录必须已经预先汇总(否则会发生DuplicateRecord)。 - Q7 TTL/保留期限(按表设置,使用
deploy.sh环境变量;0表示禁用):- 原始用量表
USAGE_TABLE_TTL_DAYS(默认值为 365)——建议启用(高流量表)。卖家的写入方设置ttlepoch 属性;模板只负责让 DynamoDB 执行该设置。下限为 ≥2 天,且必须超过MeteringLockHours+24h,以免记录在计量前过期。 - 汇总用量表
AGGREGATED_USAGE_TTL_DAYS(默认值为 0 = 保留)——建议省略或设置较长时间(账单审计记录较少)。当值 >0 时,提交方仅在Success记录上设置ttl。 - 启用 TTL 需要部署者具有
dynamodb:UpdateTimeToLive权限(该权限已包含在策略中)。
- 原始用量表
- Q8 是否创建运行状况仪表板?(
CREATE_DASHBOARD,默认值为 true)。每个产品的主堆栈仪表板(每个 Lambda 的 Errors/Throttles/Invocations/Duration;队列/DLQ 深度及最早消息时长;EMFUsageRecordRejected按原因统计、UsageRecordUnprocessed、CustomerNotSubscribed、BatchMeterUsageException按异常统计、UsageAggregationExpired、UsageSubmissionExpired、DuplicateRecord)+ 共用的 events 堆栈仪表板。仅用于运行状况监控;反映已部署的模式(Q9);卖家可以选择不创建/自行定制。 - Q9 完整管道还是直接提交(
DEPLOYMENT_MODE,默认值为full)——先说明两种模式,让卖家知情选择:- full — 卖家发出每秒原始事件;此技能会部署原始
usage表 + discoverer→aggregator→cleanup→submitter→submission-expiry,管道会将每个(licenseArn, account, dimension, hour)分组汇总为每小时一条BatchMeterUsage记录。适用于卖家尚未进行汇总,或希望由平台负责汇总并提供客户端拒绝可见性的情况。 - direct-submit — 卖家已生成最终的小时总量;此技能仅部署
aggregated_usage+ submitter + submission-expiry + register/subscription(规模更小/成本更低)。卖家将最终记录直接写入aggregated_usage(PKlicenseArn,SKcustomerAWSAccountId#dimension#hour,quantity,meteringPending=小时分桶)。 - 先完成汇总再插入(direct-submit,关键):
aggregated_usage中的任何记录都可能在下一次 5 分钟运行时提交——只有当某分组的记录已最终确定时才写入,因为再次写入会返回DuplicateRecord,且后续数量不会计费。此模式下,上游汇总/幂等性由卖家负责。 - **并非不可逆:**卖家之后可以切换模式(通过堆栈 UPDATE 添加/移除特定模式的资源;原始
usage表采用Retain)。切换在线堆栈前,请说明其影响范围。
- full — 卖家发出每秒原始事件;此技能会部署原始
- Q10 注册字段 + 资料查询(
ALLOWED_REGISTRATION_FIELDS)——先说明,再询问。区域内的 register Lambda 仅将以下允许列表中的字段(每个字段均有长度限制;绝不存储令牌)作为registrationDatamap 持久化到区域内的customer-profile表(PKlicenseArn,SKcustomerAWSAccountId)——买家 PII 保留在所在区域,不会进入不含 PII 的 us-east-1 subscribers 表。可通过基础键检索 map 中的字段,但不能单独查询。询问:(a) 要采集哪些表单字段(包括任何 KYB 字段——姓名/电子邮件/公司/用途);(b) 是否需要直接查询某个字段(例如按email查询)——若需要,则将其提升为顶层属性 + GSI(询问每个提升字段使用仅 HASH 还是 HASH+RANGE)。字段应为可选且经卖家确认;不要虚构 PII;在日志中遮蔽 PII。将licenseArn映射到卖家的内部 SaaS 租户 ID 是卖家的责任——以 subscribers 表(来自License Updated的licenseArn,不含 PII)和/或 customer-profile 表(注册时捕获的licenseArn)为锚点;若需要按租户查询,则添加tenantId属性 + GSI(references/architecture.md)。
**产品类型关卡:**获取凭证后,通过 Catalog API 验证产品是否为 SaaS + 按用量计费(
ExternallyMetered)。仅限 AMI/容器/SaaS 合同 → 不在范围内,请转至公开文档。 并发协议是默认且不显眼的选项——所有新产品都使用CustomerAWSAccountId+LicenseArn;不要询问。现有的旧版技术栈可能使用ProductCode+CustomerIdentifier——更改前请先了解(references/existing-sellers.md)。
设置顺序(不得跳过或调整)
- 一次性 IAM 前置条件 — 创建部署者角色和权限边界(仅限账户首次配置)。 该技能通过担任
MarketplaceMeteringDeployer角色运行——这是一个专用的最小权限 IAM 角色(不是管理员用户,也不是卖家的控制台身份),其唯一内联策略限定于awsmp-*资源,并且只允许该技能创建计量堆栈,不允许执行其他操作。该角色受awsmp-metering-boundary权限边界限制。该技能不会自行创建这些资源(自行引导 IAM 属于权限提升路径),因此在账户中进行首次集成时,管理员需按以下确切顺序创建它们一次——必须先创建边界,再创建角色(角色的iam:CreateRole受边界约束;如果边界缺失或名称不匹配,部署将因AccessDenied失败并回滚):- 先创建边界:
aws iam create-policy --policy-name awsmp-metering-boundary --policy-document file://boundary.json(boundary.json的内容见references/iam-credentials.md)。其名称必须与角色的信任策略/条件以及模板参数PermissionsBoundaryName(默认值为awsmp-metering-boundary)一致。 - 创建角色并使用带条件的信任策略(角色 ARN 主体 +
sts:ExternalId;绝不能开放:root):aws iam create-role --role-name MarketplaceMeteringDeployer --assume-role-policy-document '…'。 - 附加权限策略:
aws iam put-role-policy --role-name MarketplaceMeteringDeployer --policy-name DeployPolicy --policy-document file://policy.json(policy.json的内容是references/iam-credentials.md中的完整部署者策略)。 - 验证两者均已创建:
aws iam get-policy --policy-arn arn:aws:iam::<ACCOUNT_ID>:policy/awsmp-metering-boundary和aws iam get-role --role-name MarketplaceMeteringDeployer。 这是每个账户只需执行一次的步骤;如果角色和边界已存在(重复集成),则跳至步骤 2。完整的复制粘贴式引导步骤和两个策略 JSON 文件:references/iam-credentials.md→“前置设置(请先执行……)”。
- 先创建边界:
- 通过担任
MarketplaceMeteringDeployer获取临时凭证(SSO →sts assume-role;不要使用长期 IAM 用户密钥;不要先索要密钥)。继续之前,使用aws sts get-caller-identity验证已解析出正确的卖家账户和部署者角色。 - 产品类型门槛(SaaS + ExternallyMetered)。
- 产品可见性门槛——仅当当前产品为
Limited时,才部署/修改该产品专属的主堆栈;如果为Published,则绝不部署/修改(改为为卖家自己的流水线提供代码)。仅当当前产品为Limited且不存在该产品专属的现有堆栈时,才直接部署主堆栈(如已存在,则应修改/扩展,并由卖家确认)。如果共享事件堆栈已存在,绝不重新部署/修改它(它可能服务于其他有实时流量的产品,其中可能包括Published产品——请复用其输出);只有首次接入时才创建该堆栈。参见references/existing-sellers.md。 - 确认问卷答案(包括阶段名称)。
- 只有完成上述步骤后才生成并部署:在卖家的工作区中生成模板、Lambda 处理程序和
deploy.sh,并部署这些磁盘上的文件(使用sam build --template-file+sam deploy)——绝不能使用内联/临时模板。修改/扩展时,应直接编辑卖家的现有文件(合并环境变量)。部署后将所有构件留在工作区中(确保代码与堆栈一致;卖家可对其进行版本管理、重新部署和开发,只自定义TODO:部分)。 - 现有堆栈的防护措施(必须执行,不得绕过)。 当此范围内的堆栈已存在时,该技能不得自动应用更改,而是必须:(a) 向卖家提供确切的差异/变更集供其审查;(b) 在应用更改前,要求卖家明确确认并遵循其常规代码审查和变更管理流程;(c) 将更改限定在当前产品的主堆栈;(d) 绝不重新部署/修改已服务于其他(可能为
Published)产品的共享事件堆栈——应复用它。只有对于没有现有堆栈的Limited产品,才允许直接自动部署。参见references/existing-sellers.md。 - 集成后,运行完整测试计划。 设置/修改结束时,该技能必须建议卖家执行完整的动手测试计划,并在可行时指导卖家完成测试(见
references/test-plan.md),以验证每项预期的可观察结果;同时必须说明,只有完成审查和测试后,该集成才能承载生产计费流量。 任何一个门禁失败,都会在部署前停止工作流。
堆栈命名
| 组件 | 模式 | 示例 |
|---|---|---|
| 事件堆栈 | awsmp-events-stack | 共享,us-east-1 |
| 主堆栈 | awsmp-<productCode>-metering | awsmp-4ml54db8vrmjuykaw1psroool-metering |
| 事件堆栈(分阶段) | awsmp-events-<stageName>-stack | awsmp-events-beta-stack |
| 主堆栈(分阶段) | awsmp-<productCode>-<stageName>-metering | awsmp-4ml54db8vrmjuykaw1psroool-beta-metering |
对于非生产沙盒,请部署按阶段划分的堆栈(阶段名称会并入每个资源名称),并设置 MeteringMode=dry-run,以确保提交器绝不会调用真实的 BatchMeterUsage——仅使用测试账户。请参阅 references/existing-sellers.md / 设置路径。
支持的功能
SaaS 用量(PAYG)✅ | 并发协议 ✅ | VMT ✅ | 多区域 ✅ | 多产品 ✅ 范围外(→ 公开文档):CCP、AMI/容器、仅 SaaS 合同(GetEntitlements)、3PI。
**规模:**此流水线采用解耦设计(发现器 → 工作队列 → 聚合器 → aggregated-usage + 清理队列 → 提交器),旨在每小时处理数千条计量记录。精确到秒的行将每个组限制为每小时 ≤3600 行 → 支持高吞吐量,且每个
(licenseArn, account, dimension)组的写入速率 ≤~1 次/秒。对于更精细或更高的速率,建议使用流式摄取层(例如 Kinesis Data Streams),将数据预聚合为每秒/每小时的行(基于公开知识/由主机模型决定,并非此技能的一等功能)。完整设计:references/scalable-metering-design.md。 由 GSI 驱动的发现(绝不使用 Scan):发现器通过稀疏的metering_pendingGSI 查找待处理用量——HASH =meteringPending小时桶(YYYY-MM-DDTHH),RANGE =licenseArn,并投影customerAWSAccountId+dimension(INCLUDE/ALL,而非KEYS_ONLY),使查询直接返回(licenseArn, customerAWSAccountId, dimension)元组——无需后续读取来发现分组。随后,每个组都会通过定向的begins_with排序键前缀读取(begins_with(customerAWSAccountId#dimension#timestamp, "{account}#{dimension}#{hourPrefix}")),而不是查询完整的licenseArn分区并使用时间戳FilterExpression。
部署(摘要;完整步骤见 references/deployment-steps.md)
- 生成 SAM 模板和 Lambda 代码(填写
TODO:)。 - 部署事件堆栈(us-east-1)——共享 EventBridge 和订阅者表。
- 部署主堆栈(卖家的区域)——Lambda、DynamoDB、API Gateway 和告警。
- 将 AMMP Fulfillment URL 设置为品牌化注册页面(S3+CloudFront 前端,用于收集买家/KYB 字段并调用
RegistrationUrl),而不是原始RegistrationUrl(仅用作冒烟测试回退方案)——此步骤由卖家手动完成。参见references/registration-page.md。 - 端到端测试(订阅 → 解析 → 写入符合规范的用量行 → 发现器 → 聚合器 → 提交器 → 验证
Aggregated+Success)。 - 通过 CloudTrail 日志进行验证。
部署后,说明卖家的持续责任,并指导其进行实操测试(references/test-plan.md):在卖家写入符合规范的用量行之前,堆栈不会执行任何操作——排序键格式为整秒级 customerAWSAccountId#dimension#timestamp(各段必须匹配),且 meteringPending 分区必须等于该行自身 UTC 时间戳所在的小时。meteringPending 时间戳错误是唯一一种管道无法检测的写入错误(用量会被静默漏记)——请通过测试确认。此外:为告警订阅通知,并监控仪表板(UsageRecordRejected 按原因分类、BatchMeterUsageException 按异常分类、CustomerNotSubscribed、UsageAggregationExpired/UsageSubmissionExpired);更改目录维度无需重新部署。有些结果是异步的(订阅后 License Updated → licenseArn/subscriptionStatus;每小时运行的发现器 + rate(5m) 提交器/过期处理器)——请使用有界退避轮询,或手动调用 Lambda 立即测试某条路径(请说明具体采用哪种方式)。建议在集成结束时,按照 references/test-plan.md 端到端运行完整测试计划,并验证每项预期的可观测结果——同时检查所有已创建/更改的资源,然后再让集成承载生产计费流量(AI 生成的输出;见上方“上线前审查”提示)。
结束集成时,提供一份汇总说明(不要让卖家面对一个黑箱):
- 在一处说明每个告警——名称、检测的条件、告警触发意味着收入已损失还是面临风险,以及建议的运维人员操作:
CustomerNotSubscribed→ 用量属于未激活/未知许可证,请勿盲目重试(检查订阅/协议状态);UsageRecordUnprocessed持续触发 → 调查节流/InternalServiceException;BatchMeterUsageException→ 请求级异常导致整个批次失败,隔离问题记录(维度/时间戳/标签/许可证);UsageRecordRejected→ 写入方违反客户端写入约定,按Reason修复写入方并重新提交;UsageAggregationExpired/UsageSubmissionExpired→ 24 小时窗口关闭前用量被静默漏记,调查写入方/管道延迟;LambdaErrors/Throttles+ DLQ 深度 → 管道运行状况。(完整详情见references/troubleshooting.md。) - 逐步讲解测试计划,说明每个阶段预期的可观测结果:订阅 →
ResolveCustomer解析出licenseArn→ 写入符合规范的用量行 → 发现器将记录加入队列 → 聚合器写入Aggregated记录 → 提交器调用BatchMeterUsage→ 在 CloudTrail 中验证Aggregated+ 每条记录的Success。 - **明确说明生产上线门槛:**卖家运行测试计划并验证所有结果,且配置好
AlertsTopicArn(确保收入损失告警确实能触发通知)之前,集成不得承载生产计费流量。
前提条件、凭证和完整的最小权限部署者策略:references/iam-credentials.md。
事件与状态模型(回答取消/计量工作原理时复述)
订阅者行上有两个相互独立的状态字段(绝不可混为一谈):
agreementStatus=active/inactive(协议生命周期)。subscriptionStatus=active/deprovisioning/inactive(许可证生命周期;没有deprovisioned值)。
**双表 meteringStatus 领域模型(绝不可混淆——这是区分“Error 与 RejectedClientSide”的关键事实):**该流水线使用两张表,状态域互不重叠。
- 原始用量表 →
{ Aggregated, RejectedClientSide, AggregationExpired }。RejectedClientSide仅存在于此表:它表示流水线的客户端防护机制在调用任何 API 之前拒绝了一行记录,因为该记录违反了写入方约定(时间戳格式错误、LicenseArn 形状不正确、标签过多、未定义维度的预检等)——卖方的记录格式错误,因此应修复写入方/该记录。 awsmp-aggregated-usage表 →{ Success, DuplicateRecord, CustomerNotSubscribed, Error, SubmissionExpired }。Error表示一条格式正确的聚合记录被 AWS Marketplace 在 API 层拒绝,原因是请求级服务端BatchMeterUsage异常(InvalidUsageDimensionException、InvalidLicenseException、TimestampOutOfBoundsException等);通过二分处理将其隔离为单条记录,并将异常名称保存在meteringStatusReason中。Error⇒ 该行记录没有问题;应修复目录/优惠/许可证(例如,在 AMMP 中定义该维度),而不是修改用量记录写入方。RejectedClientSide绝不会出现在awsmp-aggregated-usage表中——它是原始用量表专属的状态。因此,若聚合记录显示meteringStatus = Error,它就绝不可能是客户端拒绝;不要将其描述为RejectedClientSide,也不要要求卖方修改用量记录写入方。
计量仅遵循许可证生命周期:
License Updated→ 协议和订阅均为active(允许计量)。License Deprovisioned→subscriptionStatus=deprovisioning,并设置deprovisioningExpiry(事件时间 + 约 1 小时);开启约 1 小时的最终用量刷新窗口。绕过锁的机制:在此窗口期间,流水线会立即提交该许可证的所有剩余待处理用量,忽略MeteringLockHours和仅处理已完成小时的常规门槛——它不会等待MeteringLockHours到期,并且会计量当前正在进行的小时(该小时在常规路径中始终会被跳过)。具体机制/标记:License Deprovisioned处理程序会在该订阅者行上设置subscriptionStatus=deprovisioning和deprovisioningExpiry;发现器会运行在刷新 deprovisioning 模式,根据此标记,将待处理的分桶(now-23h … now-1h加上当前小时)按时间从早到晚加入队列,且仅针对该许可证。绕过机制仅适用于正在 deprovisioning 的许可证——其他活跃许可证仍遵循常规的MeteringLockHours门槛和仅处理已完成小时的行为;一位买方的许可证进入 deprovisioning,绝不会强制刷新另一位买方尚未结束的小时。绕过锁的是 deprovisioning 刷新,不是修改MeteringLockHours本身,也不是Purchase Agreement Ended事件。窗口到期后,events-stack deprovision-cleanup Lambda(rate(15m))会将状态设为inactive并清除 deprovisioning 标记——最终处理基于时间,因此在跨多个小时和区域时也能正确运行。服务端窗口关闭后,BatchMeterUsage会返回CustomerNotSubscribed。Purchase Agreement Ended→ 仅将agreementStatus=inactive(不会更改 subscriptionStatus、停止计量或触发刷新)。Purchase Agreement Amended→ 刷新协议元数据(保持 active)。Purchase Agreement Created→ 计量流程不会处理此事件(没有 licenseArn);如有需要,由卖方自行处理(预配置、CRM),不会因此激活计量。
切勿颠倒这些规则(开启刷新窗口的是 License Deprovisioned,而不是 Purchase Agreement Ended)。刷新之前不要丢弃已排队的记录。registeredRegions 仅供参考,绝不作为计量门槛。
卖家自己的取消预配置工作流(卖家必须负责推动其 SaaS 侧的拆除)。 管道会自动处理计量数据 FLUSH 和基于时间的最终结算;将 licenseArn/customerAWSAccountId 映射到卖家的内部租户,并拆除该租户(撤销访问权限、释放资源),属于卖家应用的职责——该技能不负责租户模型。向卖家提供以下 TWO 种受支持的取消预配置响应方式,由卖家自行选择(也可组合使用):
- (a) 直接监听 Marketplace EventBridge 事件 — 卖家自行添加规则,匹配 us-east-1 中的
source: "aws.agreement-marketplace"/detail-type: "License Deprovisioned"(和/或Purchase Agreement Ended),并路由到自己的目标(Lambda/SQS/Step Functions)。这与计量 Subscription Lambda 相互独立——卖家自行添加规则/目标,不要把拆除操作附加到计量路径中。响应速度最快/最接近事件源(可以在刷新窗口产生影响之前采取行动),但事件结构处理和幂等性由卖家负责。 - (b) 消费 subscribers 表的 DynamoDB Stream — 事件栈会在不含 PII 的 us-east-1
subscribers表上启用 DynamoDB Stream(NEW_AND_OLD_IMAGES),并将其 ARN 作为SubscribersTableStreamArn栈输出;卖家自行挂接消费者(Lambda 事件源映射等),并在某一行转换为subscriptionStatus = deprovisioning(之后变为inactive)时响应。响应的是 Subscription Lambda 维护的已协调、已去重、已合并区域的状态(更简单),但比原始事件晚一跳。如果卖家根据订阅者记录构建租户模型,这种方式最为自然。
建议根据拆除操作相对于约 1 小时刷新窗口的时间紧迫性来选择。这些机制用于卖家自己的 SaaS 侧操作——计量刷新/最终结算已由已部署的管道处理。
设置参考
- 完整操作指南 + 写入方契约 + 常见错误 + VMT/令牌/时间戳/架构/计量流程:
references/setup-path.md - 部署步骤:
references/deployment-steps.md· IAM 策略:references/iam-credentials.md - 并发协议 + 数据模型:
references/concurrent-agreements.md· 架构 + 状态生命周期:references/architecture.md - 现有卖家(修改/扩展,旧版):
references/existing-sellers.md - 品牌化买家页面 + 买家身份识别(KYB)信息采集:
references/registration-page.md - 实操测试计划(手动步骤、范围明确的用例、异步轮询):
references/test-plan.md - 监控与警报:
references/troubleshooting.md· 客户端防护措施(BatchMeterUsage前的所有检查):references/client-side-guardrails.md - 参考实现:
scripts/metering_core.py(共享)+discoverer.py/aggregator.py/cleanup.py/submitter.py/expiry.py(管道)+register.py/subscription.py;scripts/test_event_publisher.py(非生产沙盒测试事件生成器,仅运行 dry-run 阶段);scripts/deploy.sh(需要assets/requirements.txt);SAM 模板assets/template-main.yaml、assets/template-events.yaml
监控与告警(由卖家负责)
已部署的堆栈会在卖家的账户中创建 awsmp-* 指标和 CloudWatch 告警,覆盖可能造成收入损失的信号(discoverer/aggregator/cleanup/submitter/expiry 各阶段 Lambda 的 Errors/Throttles、“未运行”调用告警、>24h 超时,以及 EMF 业务状态指标)。告警始终采用合理的默认阈值(大多数收入损失告警在首次发生时触发,即阈值为 1;队列超时告警采用基于时间的默认值)——卖家可以调整,但不必调整。业务状态指标使用 Lambda 日志中的 EMF(无需额外的运行时 IAM 权限)。告警处理由卖家负责——AWS Marketplace 和此技能不会跟踪客户端错误;部署程序和任何 Lambda 都不会向主题发布消息(由 CloudWatch 触发操作)。详情:references/troubleshooting.md。
强烈建议 — 配置告警操作(
AlertsTopicArn),不要让告警处于未关联操作的状态。 只有卖家提供AlertsTopicArn时,每个告警的AlarmActions才会关联到同区域的 SNS 主题。由于计量错误会直接影响账单,“告警无操作”是不推荐的配置:未设置AlertsTopicArn时,收入损失告警会无人接收;计量停止时(也就是收入正在损失时),卖家不会收到通知。此技能应引导卖家创建 SNS 主题和订阅,并将AlertsTopicArn作为集成步骤的一部分(而非可有可无的事后事项),并应将未配置该项的集成标记为不适合生产环境。保护主题安全:告警通知可能包含敏感标识符(买家 AWS 账户 ID、许可证 ARN、资源名称),因此 SNS 主题应启用静态加密(SSE — 至少使用alias/aws/sns,或使用客户管理的 KMS 密钥),且卖家应确认订阅接收方(电子邮件/HTTPS 终端节点)仅限获授权的运维人员(定期检查订阅;考虑配置 SNS 访问策略/订阅筛选策略)。详情:references/deployment-steps.md。
“哪些告警会告诉我计量记录遭到拒绝/未计量?” — 堆栈会针对每种拒绝/失败信号创建专用告警,在记录停止计量时立即通知卖家(均发送到 AlertsTopicArn;使用默认阈值,可调整):
UsageRecordUnprocessed(告警*-usage-unprocessed)—BatchMeterUsage返回了UnprocessedRecords,且在 submitter 自动重试一次后仍未处理。这种情况下,submitter 不会抛出异常(而是将这些记录保留为待处理状态,等待下一次rate(5m)周期),因此此指标是UnprocessedRecords情况下唯一可用于告警的信号——它不会触发 LambdaErrors告警。持续触发表示记录反复无法计量(限流、时间戳超出范围、下游问题)→ 请调查。UsageRecordRejected(*-usage-rejected)— 计量前被客户端保护机制拒绝的记录(按原因汇总;仪表板中提供各原因明细);请修复源记录并重新提交。BatchMeterUsageException(*-batchmeterusage-exception)— 请求级异常导致整个调用失败(维度/许可证等);流水线会通过二分法定位问题记录。CustomerNotSubscribed(*-customer-not-subscribed)— 单条记录状态,表示没有有效订阅(不要重试)。- 此外,超时告警(
UsageAggregationExpired/UsageSubmissionExpired,>24h)会捕获始终未计量且未显式报错的用量;LambdaErrors/Throttles/DLQ 深度告警则会捕获流水线故障。DuplicateRecord是仪表板指标(仅供参考——首次提交已计费),不是告警。
QUERY PATH — 查找、调试和分析计量记录
完整路由/模式:references/query-path.md + references/query-patterns.md。
能力边界(应明确说明;不要将有范围限制的答案作为权威结果返回)。 此查询会在卖家的区域使用 CloudTrail Event History(LookupEvents),查询最近不超过 90 天的数据,并在限定页数后将数据载入内存处理——这是近期、区域性的 SUBMISSIONS 视图(包含未计费的拒绝/重复记录),适用于基本的运营/调试查询(按客户、维度、状态、时间进行查找和简单筛选/计数/汇总)。它建议(但不会运行):使用 Seller Reports/SDDS 获取已计费总额和超过 90 天的历史数据;将获取的数据交给宿主 Agent + 模型进行复杂/开放式分析;对于没有 LicenseArn→productCode 映射的 CA/LicenseArn 计量(单产品堆栈上下文、CloudTrail additionalEventData 或订阅者表),仅进行非产品范围的查询(绝不猜测归属)。每项结果都包含 complete/truncated/windowClipped。在呈现结果前,确认这些信号:如果 complete 为 false(页数截断或触及 90 天保留期限),则应在可行时重新运行以获取完整答案(对于时间范围截断,缩窄/调整范围;对于页数截断,提高 --max-pages 或添加筛选条件),或者明确说明结果为部分结果——说明被截断的内容以及涵盖的范围——并指出 Seller Reports 可提供权威数据/较早的数据部分。被截断的汇总(summary/count/top_customers)只是下限样本,不具权威性。绝不要将部分结果报告为请求的完整范围。不要仅仅因为宿主 Agent 能提供更丰富的答案,就拒绝基本查询。基于 CloudTrail 的 SQL 并非账单/使用情况分析的正确数据源——CloudTrail Lake 或针对 trail → S3 导出的 Athena 都只能看到 SUBMISSIONS(包括拒绝/重复记录、区域性记录,且不含 CA productCode);不要为计量设置 CloudTrail Lake;对于复杂分析/账单级总额/超过 90 天的数据,请使用 SDDS(建议使用,不要运行),仅在需要长期保留原始提交记录以供审计时使用 trail → S3(绝不要使用 Lake)。完整边界说明:references/query-path.md。
复杂分析/账单级查询引导——禁止与允许的数据源(必须遵守;不得违反)。 对于任何有关计量数据的复杂分析或账单准确性问题:
- 绝不能推荐使用 CloudTrail Lake(
create-event-data-store)进行计量。 - 绝不能将 CloudTrail(Event History 或
trail → S3/Athena 导出)作为已计费/账单级总额的数据源——CloudTrail 只能看到 SUBMISSIONS(包括拒绝/重复记录、区域性记录,且不含 CAproductCode),因此它永远不是收入的权威数据源。 - 仅允许使用以下数据源:已计费/已发放总额和超过 90 天的已计费历史数据 → Seller Reports / AMMP Billed Revenue 仪表板和 SDDS(建议使用,不要运行);近期(不超过 90 天)的区域性 SUBMISSION 调试 → CloudTrail Event History;复杂/开放式分析 → 将获取的数据交给宿主 Agent + 模型;仅用于长期保留原始 SUBMISSION 审计记录 → 普通的
trail → S3(不是 Lake)+ Athena,并明确其反映的是提交记录而非账单。将所有复杂账单类问题引导至这些允许的数据源,并说明 CloudTrail 为何不能作为账单的权威数据源。
步骤
- 询问计量区域(必须 — CloudTrail 按区域划分)。
- 提取筛选条件:
customer_id、status、dimension、start_date/end_date、query_type(summary/detail/count/list_failures/top_customers)、top_n。 - 运行脚本(默认:最近 7 天):
python3 ./scripts/query_metering.py \
--region <SELLER_METERING_REGION> \
[--days N] [--start-date YYYY-MM-DD] [--end-date YYYY-MM-DD] \
[--customer-id ACCT_ID] [--status STATUS] [--dimension DIM] \
[--product-code PC] [--deployment-product-code PC] \
[--query-type summary|detail|count|list_failures|top_customers] \
[--top-n N] [--limit N] [--max-pages N]
CloudTrail Event History 操作指南(脚本的执行方式;手动查询时可直接使用)。
Event History 默认已启用 — 无需数据存储,也无需设置,计量时绝不使用 CloudTrail Lake /
create-event-data-store。在卖家的计量区域中查询,并按计量事件来源筛选:
aws cloudtrail lookup-events --region <SELLER_METERING_REGION> \
--lookup-attributes AttributeKey=EventSource,AttributeValue=metering-marketplace.amazonaws.com \
--start-time <ISO> --end-time <ISO>
每个 BatchMeterUsage 事件的 CloudTrail responseElements 都包含 results(每条记录的 status:
Success/CustomerNotSubscribed/DuplicateRecord — 这些是仅有的每条记录状态)和
unprocessedRecords — 要验证提交是否成功,请先检查 results 与 unprocessedRecords(unprocessedRecords 中的记录尚未处理 — 请参阅下方的 UnprocessedRecords 说明)。CloudTrail 会将响应字段的首字母转换为小写,因此应读取小写的 results/status/meteringRecordId/unprocessedRecords — 直接的 SDK BatchMeterUsage 响应使用 PascalCase(Results/Status/
MeteringRecordId/UnprocessedRecords)。未定义的维度
不是每条记录的状态 — 它会导致整个调用失败,并返回请求级别的
InvalidUsageDimensionException(见下文)。仅支持 ≤90 天的数据;如需更早或已计费的汇总数据,请使用 Seller
Reports/SDDS(推荐,不会查询)。CloudTrail 不是收入数据 — 它显示的是提交记录(包括被拒绝/重复的记录,不代表已计费)。计量用量会在次月初(2 日/3 日)计费 — 例如,11 月的用量会计入 12 月的账单 — 在当月结束前,控制面板中的收入都是估算值;已计费/已支付金额请使用 Seller Reports。
状态含义
每条记录的 Results[].Status 值:
| 状态 | 含义 | 操作 |
|---|---|---|
Success | 已接受并计费 | 无 |
CustomerNotSubscribed | 没有有效订阅 | ⚠️ 请勿重试 |
DuplicateRecord | 采用先写入优先策略并已报告;重新提交不同数量的记录不会计费 | 仅可安全地重试完全相同的记录;切勿用于更改数量(有少计费用的风险) |
请求级别异常(导致整个 BatchMeterUsage 调用失败,而非单条记录的状态)。每种异常都有一个客户端防护栏原因代码,当管道在 API 调用前拒绝该行时会将其写入(RejectedClientSide,不计费);如果某行到达 API,还会有对应的服务端异常 — 解释拒绝原因时请同时说明两者:
| 服务端异常 | 客户端原因代码 | 含义 | 处理方式 |
|---|---|---|---|
InvalidUsageDimensionException | —(客户端不检查 — 维度有效性由服务端权威判定) | 目录中未定义的维度(由服务端权威判定;区分大小写;不计费) | 在 AMMP 目录中定义/修正维度(区分大小写)——不要更改用量行写入器;该行格式正确。 流水线不会在客户端验证维度名称(它未配置目录中的维度列表),因此没有对应的客户端原因代码——此问题只会由 API 捕获。流水线通过二分法(深度受限,⌈log2(批次大小)⌉=5)隔离有问题的记录,因此同批次的其他记录仍会计量;将聚合记录标记为 Error(服务端提交失败,不是 RejectedClientSide——聚合表中不会出现此状态;异常名称写入 meteringStatusReason),并发出用于报告该问题的 BatchMeterUsageException 指标/警报。 |
TimestampOutOfBoundsException | MalformedTimestamp | 记录时间早于 24 小时(例如,30 小时前的行)或以其他方式超出时间范围 — 已拒绝,不会提交 | 修正时间戳;客户端防护机制会在批处理前以原因 MalformedTimestamp 拒绝该行,而任何抵达 API 的错误时间戳都会导致整个批次被拒绝,直到问题记录被隔离。 |
InvalidLicenseException | InvalidLicenseArn | 格式错误/无效的 LicenseArn | 在客户端进行格式验证(原因 InvalidLicenseArn);服务端的 InvalidLicenseException 会通过(有界)二分法隔离,并将聚合记录标记为 Error(不是 RejectedClientSide),异常名称写入 meteringStatusReason。 |
InvalidTagException | TooManyTags | UsageRecord 上的 usageAllocations 标签超过 5 个(也包括其他无效的标签键/值格式) | 将标签数减少到 ≤5(并确保 Key/Value 非空);客户端防护机制会在批处理前以原因 TooManyTags 拒绝该行。服务端请求级异常为 InvalidTagException;如果问题抵达 API,则会通过(有界)二分法隔离,并将聚合记录标记为 Error。 |
InvalidUsageAllocationsException | AllocationSumMismatch / TooManyAllocations / MixedAllocatedAndUnallocated | 合并记录中的 usageAllocations 格式错误 — Σ AllocatedUsageQuantity ≠ 记录的 Quantity、合并后的标签集 >2500 个,或标签与无标签记录混用 | 聚合器会在客户端验证分配,并在批处理前以具体原因代码(RejectedClientSide)拒绝。若此类记录抵达 API,请求级异常 InvalidUsageAllocationsException 会通过(有界)二分法隔离,并将聚合记录标记为 Error(异常名称写入 meteringStatusReason)。 |
InvalidProductCodeException | —(客户端不进行预检查) | 记录中的产品代码对于调用方(发布者)账户而言不是有效的/归其所有的产品代码 | 这是服务端请求级异常 — 没有对应的客户端防护原因代码(流水线按产品部署,不会预先验证产品代码归属)。通过(有界)二分法隔离,并将聚合记录标记为 Error(异常名称写入 meteringStatusReason);这不是 RejectedClientSide。修正产品代码,或从发布账户发起调用。 |
InvalidCustomerIdentifierException(旧版) | — | 对该产品无效的 CustomerIdentifier。仅限旧版(CA 之前)路径 — Concurrent Agreements 产品使用 CustomerAWSAccountId + LicenseArn 进行标识,不使用 CustomerIdentifier,因此 CA 集成不应遇到此异常。此处列出是为了完整性,并供现有旧版卖家参考。 | 旧版 ProductCode+CustomerIdentifier 卖家:重新解析该标识符(ResolveCustomer)/修正已存储的 CustomerIdentifier。对于新产品/CA 产品,这表示有内容正在传递(已弃用、对 CA 为 null 的)CustomerIdentifier — 请改用 LicenseArn。 |
ValidationException | — | 请求格式无效 — 最值得注意的是,在一个 UsageRecord 中混用身份模型:同时传递 CustomerIdentifier 和 LicenseArn。 CA 不支持基于 CustomerIdentifier 的计量,因此同时携带两者的记录属于格式错误的请求,BatchMeterUsage 会拒绝整个调用(其他请求架构违规也会触发此异常)。 | 对新产品/CA 产品,只发送 CA 身份 — CustomerAWSAccountId + LicenseArn(不要传递 CustomerIdentifier);对旧版产品,只发送旧版 ProductCode + CustomerIdentifier。切勿在同一条记录中同时使用 CustomerIdentifier 和 LicenseArn。 |
DisabledApiException | — | 整个区域的可用性故障,不是针对单条记录的验证拒绝:调用所在区域不提供 BatchMeterUsage(整个调用失败)。 | 在受支持的区域进行计量(卖家可选择计量区域;每个计量区域部署一个堆栈)。流水线会将其视为瞬时的整次调用失败:不会通过二分法隔离,也不会将任何记录标记为 Error(没有需要隔离的有问题记录)——提交器会重新抛出异常,保留 meteringPending,并在下次计划运行时重试这些记录(Lambda Errors 警报会触发),直至修复区域问题或记录超过 24 小时。它不是 InvalidEndpointRegionException(该异常仅适用于 AMI/Container MeterUsage)。 |
**排序键/拒绝代码顺序:**流水线会先验证行的格式,然后才能将其加入批次。如果某行的排序键(customerAWSAccountId#dimension#timestamp)包含格式错误或超出时间范围的时间戳,则会在客户端以原因代码 MalformedTimestamp 拒绝(标记为 RejectedClientSide,并发出 UsageRecordRejected)——它不会到达 BatchMeterUsage,因此不会作为服务端 TimestampOutOfBoundsException 显现,除非错误的时间戳漏入了批次。对于格式错误的时间戳排序键,始终注明客户端原因代码 MalformedTimestamp 及其服务端对应项 TimestampOutOfBoundsException。 |
**完整的拒绝代码/状态代码目录(任何原因代码问题都应引用此处)。**对于“MixedAllocatedAndUnallocated 是什么意思?”这类问题,应依据以下完整列表作答,并将客户端防护栏原因代码映射到其服务端对应项(如有):
客户端原因代码(RejectedClientSide,API 调用前,不计费) | 服务端对应项 | 含义 |
|---|---|---|
MalformedSortKey / SortKeyMismatch | —(不会到达 API) | 排序键不是 customerAWSAccountId#dimension#timestamp,或其各段与该行自身属性不匹配 |
MalformedTimestamp / TimestampOutOfWindow | TimestampOutOfBoundsException | 时间戳不是整秒、无法解析,或已超过 24 小时 |
MissingDimension | — | 行缺少 dimension |
NegativeQuantity | — | quantity < 0 |
NonIntegerQuantity | — | quantity 不是整数 |
TooManyTags | InvalidTagException | 一条记录上有超过 5 个 usageAllocations 标签(服务端请求级异常为 InvalidTagException;TooManyTags 是客户端原因代码) |
TooManyAllocations | InvalidUsageAllocationsException | 一组中合并后的标签集超过 2500 个 |
AllocationSumMismatch | InvalidUsageAllocationsException | AllocatedUsageQuantity 值的总和与行的 quantity 不一致 |
MixedAllocatedAndUnallocated | InvalidUsageAllocationsException | 组中混有带标签和不带标签的非零行 |
InvalidLicenseArn | InvalidLicenseException | LicenseArn 格式错误或无效(同一批次中混有多个产品时也会触发) |
**维度名称的有效性不是客户端原因代码。**流水线只检查是否存在
dimension(MissingDimension);不会针对目录验证维度名称(它不会刻意配置产品的维度列表,因此添加或重命名维度无需重新部署)。未定义或拼写错误的维度只会在服务端被识别为请求级InvalidUsageDimensionException→ 聚合记录会标记为Error(见上方请求级表格)。不存在客户端代码InvalidDimension/UnknownDimension。
此外还有 API 返回的终态逐条记录 Results[].Status 判定(不是客户端原因):Success(已计费)、DuplicateRecord(首次写入优先;重新提交不同数量的记录不会计费——这是计费不足的风险,并非“无害”)、CustomerNotSubscribed(没有有效订阅——不要重试)。还有 UnprocessedRecords(暂时性、可重试)——见下文。
**UnprocessedRecords(第三种独立结果——既不是逐条记录的 Results[].Status 判定,也不是请求级异常):**部分批次结果(成功的记录位于 results 中;处理失败的记录位于 unprocessedRecords 中——不计费,通常由暂时性的 ThrottlingException/InternalServiceException 导致),可以安全地重新提交(幂等);提交方会重试一次,然后留待下一次计划运行处理(通过 UsageRecordUnprocessed 发出告警)。不要将其视为已接受或已计费,也不要与上述终态 Results[].Status 判定混淆。
数据层级: CloudTrail Event History(≤90 天,免费,实时)是此技能直接查询的数据层;它会显示所有提交(包括被拒绝/重复的提交),而 Seller Reports 只显示已计费数据——因此,“总数不一致”= 被拒绝/重复的差额。以下是两个不同的历史数据问题,请勿混为一谈:
对账 SUBMITTED(CloudTrail)与 BILLED(Seller Reports)。 将 Seller Reports / AMMP Billed Revenue 视为已计费数据的权威来源;CloudTrail 仅显示提交记录。对账时,将 CloudTrail 筛选为
Status=Success(排除DuplicateRecord、CustomerNotSubscribed和请求级异常——这些都不会计费),并与已计费报告比较;剩余差额即为预期的被拒绝/重复提交差额。留意同一(account, licenseArn, dimension, hour)存在数量不同的DuplicateRecord——这并非无害情况:由于先写入优先,后续(通常数量更大)的记录不会计费,因此这可能表明报告量/计费量不足。应将这些情况列为对账红旗,而不是忽略重复记录。
- 查询任意范围的已计费历史记录无需任何设置——使用 AMMP Insights “Billed Revenue”控制面板(只需
aws-marketplace:GetSellerDashboard;默认 6 个月,可扩大账单日期筛选范围)。仅限控制台(QuickSight)+ 手动导出 CSV——没有按需已计费数据 API。以编程方式获取已计费数据可使用 SDDS(每日推送到 S3 + 自行设置 Athena/ETL),但它仅支持推送,且只涵盖加入后的数据(无历史回填)。此技能仅提供建议,不会执行;绝不要调用GenerateDataSet。详情:references/seller-reports.md。- 折扣/私人优惠不属于用量计量或 Seller Reports 的范畴——请转至 AWS Marketplace 公共渠道(AMMP)。 如果卖家询问为什么由于折扣或私人优惠,已计费金额与计量用量不一致,或询问如何应用/更改折扣,不要尝试根据计量数据解释或对账,也不要只说“超出计量范围”。将计量数量转为已计费收入的定价(公开价格、私人优惠、折扣、EULA 条款)由 AWS Marketplace Management Portal (AMMP) 管理——请说明折扣/私人优惠造成的差额是预期情况(Seller Reports 显示扣除任何协商折扣后的已计费收入,而 CloudTrail 显示原始计量提交记录),并将折扣/私人优惠的创建和管理引导至 AMMP → AWS Marketplace Management Portal / 私人优惠。
- 超过 90 天的原始提交历史记录没有现成数据源(Seller Reports/SDDS 仅包含已计费数据;Event History 保留 90 天)。通过自行管理的 CloudTrail trail → S3 保留记录,针对
metering-marketplace.amazonaws.com(普通 trail,不是 Lake;使用 Athena)——同样,数据仅从设置后开始,不会回填;还可补充导出 DynamoDB PITR 中的aggregated_usage。详情:references/query-patterns.md。
查询参考
- 路由 + 参数:
references/query-path.md· CloudTrail CLI 模式 + 分页:references/query-patterns.md - Seller Reports 对账 + SDDS 自动化边界:
references/seller-reports.md· 维度参考:references/dimension-reference.md
关键 API
| API | 区域 | 用途 |
|---|---|---|
| ResolveCustomer | 任意区域(由卖家选择) | 注册时将令牌解析为 LicenseArn + customerAWSAccountId + productCode;按区域提供,由发布账户发起;与 BatchMeterUsage 所在区域无关 |
| BatchMeterUsage | 卖家的计量区域 | 提交使用记录(每小时) |
| DescribeEntity | us-east-1 | 验证产品 / 类型 / 可见性 |
| LookupEvents | 卖家所在区域 | 查询计量记录(≤90 天) |
IAM 操作名称:aws-marketplace:BatchMeterUsage(计量)、aws-marketplace:ResolveCustomer(注册)、aws-marketplace:GetSellerDashboard(报告)。命名空间为 aws-marketplace:,不是 meteringmarketplace:*。诊断 AccessDenied 时,指出确切的操作,并检查执行角色、条件键和 Resource ARN 不匹配的问题。
安全性
- **最小权限:**执行角色仅授予所用操作——计量路径:
aws-marketplace:BatchMeterUsage+ 对其表的范围限定 DynamoDBQuery/UpdateItem+metering_pending索引;注册:aws-marketplace:ResolveCustomer+ 对customerAWSAccountIdGSI 的范围限定Query+ 项目操作。不授予dynamodb:Scan(查询使用 GSIQuery);绝不授予dynamodb:*或宽泛的aws-marketplace:*。 - **不吞掉身份验证错误:**订阅者 / 许可证查询不会将
AccessDeniedException当作“未找到”来捕获——权限 / 配置错误会显现出来(Lambda 错误 / 500 / DLQ)。 - **静态加密:**SQS(
SqsManagedSseEnabled)、SNS(alias/aws/sns)、DynamoDB(SSEEnabled);Lambda 和 API 日志组均为显式资源,设置了RetentionInDays+ 可选的KmsKeyId(其中包含买家账户 ID / 许可证 ARN)。 - **公开注册 API:**有意开放(Marketplace 会在未经身份验证的情况下 POST 令牌)→ 模板默认附加基线 WAF WebACL(基于速率 + 常见漏洞利用防护;
WebAclArn可覆盖);始终启用 API Gateway 限流 + 访问日志;注册 Lambda 返回安全标头,并且只持久化长度受限的允许列表字段(AllowedRegistrationFields)。 - **凭证(优先使用临时凭证):**SSO →
sts assume-role;不使用长期密钥,也不要优先索要密钥。部署角色使用带条件的信任策略(而非开放的:root)+ 受权限边界约束的iam:CreateRole。 - 阶段名称:
StageName没有默认值,且AllowedPattern会拒绝空值;问卷中必须填写;sam deploy和deploy.sh会快速失败(不会回退到prod)。 - **事件范围限定:**EventBridge 规则限定为
source: ["aws.agreement-marketplace"];通过aws:SourceArn限定 EventBridge→SQS 策略。 - **数据分类:**客户 AWS 账户 ID + 协议元数据属于敏感信息——避免记录完整标识符;加密包含这些信息的日志组。