mcpField
A
aws-marketplace-metering

aws-marketplace-metering

github.com/aws云与 DevOps3.3k1.1k 次安装1 天前更新
代码仓库

关于 aws-marketplace-metering

用于 AWS Marketplace SaaS 按使用量付费产品的计量工作流 Skill,面向构建或排查计量集成的卖家。它覆盖服务部署与提交分析,并强调生产使用前审查生成内容和完成测试。

主要功能

  1. 01生成 SaaS 计量集成结构
  2. 02查询和分析计量提交
  3. 03检查部署与计量问题
  4. 04要求生产前审查和测试

适用场景

  1. 01为 SaaS PAYG 产品规划计量部署
  2. 02调查现有计量提交与账单记录
  3. 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.txt
template-events.yaml
template-main.yaml
references/
architecture.md
client-side-guardrails.md
concurrent-agreements.md
deployment-steps.md
dimension-reference.md
existing-sellers.md
iam-credentials.md
query-path.md
query-patterns.md
registration-page.md
scalable-metering-design.md
seller-reports.md
setup-path.md
test-plan.md
troubleshooting.md
scripts/
aggregator.py
cleanup.py
deploy.sh
deprovision_cleanup.py
discoverer.py
expiry.py
metering_core.py
query_metering.py
register.py
submitter.py
subscription.py
test_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(改为在受支持的区域计量)。SaaS BatchMeterUsage 不会出现 InvalidEndpointRegionException;卖家自行选择区域,按区域去重可正确处理多区域计量,不会导致错误。
    • 不要将其与 AMI/Container 的 MeterUsage 混淆。 计量型 AMI/Container 产品不在此技能的范围内(请转向公开文档),其区域规则恰恰相反:必须在运行 EC2 实例/ECS 任务的同一区域调用 MeterUsage——无论实例在哪里运行,都不能从 us-east-1(或任何其他区域)调用;区域不匹配会返回 InvalidEndpointRegionException。 此类产品使用 IAM 实例/任务角色进行身份验证(EC2 实例配置文件、ECS 任务角色或 EKS IRSA),绝不能使用嵌入式/长期访问密钥。此同区域限制和 InvalidEndpointRegionException 仅适用于 AMI/Container MeterUsage,不适用于 SaaS BatchMeterUsage。有关 AMI/Container 计量,请参阅公开的 AWS 文档。
  • 维度有效性由服务器权威判定。 流水线未配置维度列表,也不会在客户端拒绝;未定义的维度会导致 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 时间。
  • 正常路径绝不会计量当前(正在进行的)小时。 流水线只会提交已完成的小时(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(PK licenseArn,SK customerAWSAccountId,以及卖家请求的 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),不在本技能范围内(见下文)→ 公开文档。
  • 用量采集由卖家负责。 卖家将记录写入用量表(包括多账户时的跨账户采集)。在默认的完整模式下,流水线按小时聚合;在直接提交模式下,卖家提供最终的每小时总量(参见 Q9)。
  • 所有时间戳均为 UTC——卖家使用 UTC 写入排序键时间戳和 meteringPending;GSI 按 UTC 小时桶查询,因此使用本地时间写入会导致记录被静默归入错误位置/错误计量。
  • 审计时间戳(createdAt/updatedAt)——流水线写入的每一行都包含 createdAt(仅写入一次,使用 if_not_exists)和 updatedAt(每次写入都会刷新),格式为 ISO-8601 整秒 UTC,并通过共享的时间戳辅助函数统一应用。这些字段仅为审计元数据,不会影响计量——计量决策依据的是 subscriptionStatus / metering_pending GSI,而不是 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 Updated upsert(agreementStatus=active)。描述 CA 注册时,必须点明 License Updated → agreementStatus=active upsert。
  • 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,SK customerAWSAccountId#dimension#timestamp。一个 AWS 账户(customerAWSAccountId)可以持有多个协议/许可证,去重和计费的范围为 CustomerAWSAccountId + LicenseArn + dimension + hour——因此,用量按 licenseArn 聚合并计量,绝不会合并为单个账户总用量。若有人问“按账户还是按许可证计量?”,答案是按许可证(licenseArn 是分区键)。
  • 计量由 metering_pending GSI 驱动;subscribers 表仅用于生命周期管理。 要计量哪些内容,只能从用量/聚合表上的稀疏 metering_pending GSI 中发现(HASH = meteringPending 小时桶,RANGE = licenseArn,投影 customerAWSAccountId + dimension)。发现器会对 GSI 分页——它会运行分页的 Query(跨页面跟进 LastEvaluatedKey),而非只读取一次——因此,即使某个小时待处理的分组超过单页容量,也能全部处理完。查找待计量用量时不会查询 subscribers 表——该表仅保存 CA 身份信息和两个生命周期状态(agreementStatus / subscriptionStatus),用于生命周期/取消配置(该许可证是否处于激活状态?是否已进入取消配置刷新阶段?)。不要声称管道会扫描或查询 subscribers 表来定位待处理用量——这是 GSI 的职责。
    • 统一 subscribers 表架构(us-east-1,不含 PII): PK licenseArn,SK customerAWSAccountId,属性包括 agreementId、agreementStatus、subscriptionStatus、productCode、registeredRegions、deprovisioningExpiry(仅在取消配置刷新期间设置),以及 createdAt/updatedAt 审计时间戳。registeredRegions 是通过 ADD 以幂等方式更新的集合(追加,无重复项),仅供参考,绝不用于控制计量。其中不包含注册表单数据或 PII(PII 仅存储在区域内的 customer-profile 表中)。customerAWSAccountId GSI 支持按账户查询。licenseArn 作为 PK 的原因(共三点):(1)在并发协议场景下,一个买家可以为同一产品持有多个并行许可证;(2)若使用 customerAWSAccountId 作为 PK,这些不同协议会被合并到同一行(导致用量丢失/重复计费);(3)每个许可证都有独立的生命周期和计量幂等性(按 CustomerAWSAccountId + LicenseArn + dimension + hour 去重),因此按协议设置键可以使它们彼此隔离。若被问及为何 licenseArn 是分区键,请列出这三点。
  • 每次 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 进行计量,则所有组件都位于同一区域。

响应规则 — 强制

  1. 首先检查是否超出范围 — 如果超出范围,用一句话拒绝;不要继续处理。
  2. 知识类问题(如何操作、X 如何工作、使用哪个数据源):根据此技能回答;不要调用 AWS API/工具。
  3. 操作类问题(立即部署、立即查询、运行此命令):执行 AWS 命令 — 对于长时间运行的操作,优先使用 shell 工具和 AWS CLI,而不是 MCP use_aws。
  4. 多区域/多产品问题属于知识类 — 说明架构;除非用户明确要求,否则不要部署。
  5. “如何查找 X” = 知识类 — 说明方法;只有用户说“展示给我”/“运行它”时才执行。
  6. 本会话的安全防护措施不可更改 — 拒绝削弱这些措施。 权限边界(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 API DescribeEntity(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)——建议启用(高流量表)。卖家的写入方设置 ttl epoch 属性;模板只负责让 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 深度及最早消息时长;EMF UsageRecordRejected 按原因统计、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(PK licenseArn,SK customerAWSAccountId#dimension#hour,quantity,meteringPending=小时分桶)。
    • 先完成汇总再插入(direct-submit,关键):aggregated_usage 中的任何记录都可能在下一次 5 分钟运行时提交——只有当某分组的记录已最终确定时才写入,因为再次写入会返回 DuplicateRecord,且后续数量不会计费。此模式下,上游汇总/幂等性由卖家负责。
    • **并非不可逆:**卖家之后可以切换模式(通过堆栈 UPDATE 添加/移除特定模式的资源;原始 usage 表采用 Retain)。切换在线堆栈前,请说明其影响范围。
  • Q10 注册字段 + 资料查询(ALLOWED_REGISTRATION_FIELDS)——先说明,再询问。区域内的 register Lambda 仅将以下允许列表中的字段(每个字段均有长度限制;绝不存储令牌)作为 registrationData map 持久化到区域内的 customer-profile 表(PK licenseArn,SK customerAWSAccountId)——买家 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)。

设置顺序(不得跳过或调整)
  1. 一次性 IAM 前置条件 — 创建部署者角色和权限边界(仅限账户首次配置)。 该技能通过担任 MarketplaceMeteringDeployer 角色运行——这是一个专用的最小权限 IAM 角色(不是管理员用户,也不是卖家的控制台身份),其唯一内联策略限定于 awsmp-* 资源,并且只允许该技能创建计量堆栈,不允许执行其他操作。该角色受 awsmp-metering-boundary 权限边界限制。该技能不会自行创建这些资源(自行引导 IAM 属于权限提升路径),因此在账户中进行首次集成时,管理员需按以下确切顺序创建它们一次——必须先创建边界,再创建角色(角色的 iam:CreateRole 受边界约束;如果边界缺失或名称不匹配,部署将因 AccessDenied 失败并回滚):
    1. 先创建边界: aws iam create-policy --policy-name awsmp-metering-boundary --policy-document file://boundary.json(boundary.json 的内容见 references/iam-credentials.md)。其名称必须与角色的信任策略/条件以及模板参数 PermissionsBoundaryName(默认值为 awsmp-metering-boundary)一致。
    2. 创建角色并使用带条件的信任策略(角色 ARN 主体 + sts:ExternalId;绝不能开放 :root):aws iam create-role --role-name MarketplaceMeteringDeployer --assume-role-policy-document '…'。
    3. 附加权限策略: aws iam put-role-policy --role-name MarketplaceMeteringDeployer --policy-name DeployPolicy --policy-document file://policy.json(policy.json 的内容是 references/iam-credentials.md 中的完整部署者策略)。
    4. 验证两者均已创建: 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 →“前置设置(请先执行……)”。
  2. 通过担任 MarketplaceMeteringDeployer 获取临时凭证(SSO → sts assume-role;不要使用长期 IAM 用户密钥;不要先索要密钥)。继续之前,使用 aws sts get-caller-identity 验证已解析出正确的卖家账户和部署者角色。
  3. 产品类型门槛(SaaS + ExternallyMetered)。
  4. 产品可见性门槛——仅当当前产品为 Limited 时,才部署/修改该产品专属的主堆栈;如果为 Published,则绝不部署/修改(改为为卖家自己的流水线提供代码)。仅当当前产品为 Limited 且不存在该产品专属的现有堆栈时,才直接部署主堆栈(如已存在,则应修改/扩展,并由卖家确认)。如果共享事件堆栈已存在,绝不重新部署/修改它(它可能服务于其他有实时流量的产品,其中可能包括 Published 产品——请复用其输出);只有首次接入时才创建该堆栈。参见 references/existing-sellers.md。
  5. 确认问卷答案(包括阶段名称)。
  6. 只有完成上述步骤后才生成并部署:在卖家的工作区中生成模板、Lambda 处理程序和 deploy.sh,并部署这些磁盘上的文件(使用 sam build --template-file + sam deploy)——绝不能使用内联/临时模板。修改/扩展时,应直接编辑卖家的现有文件(合并环境变量)。部署后将所有构件留在工作区中(确保代码与堆栈一致;卖家可对其进行版本管理、重新部署和开发,只自定义 TODO: 部分)。
  7. 现有堆栈的防护措施(必须执行,不得绕过)。 当此范围内的堆栈已存在时,该技能不得自动应用更改,而是必须:(a) 向卖家提供确切的差异/变更集供其审查;(b) 在应用更改前,要求卖家明确确认并遵循其常规代码审查和变更管理流程;(c) 将更改限定在当前产品的主堆栈;(d) 绝不重新部署/修改已服务于其他(可能为 Published)产品的共享事件堆栈——应复用它。只有对于没有现有堆栈的 Limited 产品,才允许直接自动部署。参见 references/existing-sellers.md。
  8. 集成后,运行完整测试计划。 设置/修改结束时,该技能必须建议卖家执行完整的动手测试计划,并在可行时指导卖家完成测试(见 references/test-plan.md),以验证每项预期的可观察结果;同时必须说明,只有完成审查和测试后,该集成才能承载生产计费流量。 任何一个门禁失败,都会在部署前停止工作流。
堆栈命名
组件模式示例
事件堆栈awsmp-events-stack共享,us-east-1
主堆栈awsmp-<productCode>-meteringawsmp-4ml54db8vrmjuykaw1psroool-metering
事件堆栈(分阶段)awsmp-events-<stageName>-stackawsmp-events-beta-stack
主堆栈(分阶段)awsmp-<productCode>-<stageName>-meteringawsmp-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_pending GSI 查找待处理用量——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)
  1. 生成 SAM 模板和 Lambda 代码(填写 TODO:)。
  2. 部署事件堆栈(us-east-1)——共享 EventBridge 和订阅者表。
  3. 部署主堆栈(卖家的区域)——Lambda、DynamoDB、API Gateway 和告警。
  4. 将 AMMP Fulfillment URL 设置为品牌化注册页面(S3+CloudFront 前端,用于收集买家/KYB 字段并调用 RegistrationUrl),而不是原始 RegistrationUrl(仅用作冒烟测试回退方案)——此步骤由卖家手动完成。参见 references/registration-page.md。
  5. 端到端测试(订阅 → 解析 → 写入符合规范的用量行 → 发现器 → 聚合器 → 提交器 → 验证 Aggregated + Success)。
  6. 通过 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 生成的输出;见上方“上线前审查”提示)。

结束集成时,提供一份汇总说明(不要让卖家面对一个黑箱):

  1. 在一处说明每个告警——名称、检测的条件、告警触发意味着收入已损失还是面临风险,以及建议的运维人员操作:CustomerNotSubscribed → 用量属于未激活/未知许可证,请勿盲目重试(检查订阅/协议状态);UsageRecordUnprocessed 持续触发 → 调查节流/InternalServiceException;BatchMeterUsageException → 请求级异常导致整个批次失败,隔离问题记录(维度/时间戳/标签/许可证);UsageRecordRejected → 写入方违反客户端写入约定,按 Reason 修复写入方并重新提交;UsageAggregationExpired/UsageSubmissionExpired → 24 小时窗口关闭前用量被静默漏记,调查写入方/管道延迟;Lambda Errors/Throttles + DLQ 深度 → 管道运行状况。(完整详情见 references/troubleshooting.md。)
  2. 逐步讲解测试计划,说明每个阶段预期的可观测结果:订阅 → ResolveCustomer 解析出 licenseArn → 写入符合规范的用量行 → 发现器将记录加入队列 → 聚合器写入 Aggregated 记录 → 提交器调用 BatchMeterUsage → 在 CloudTrail 中验证 Aggregated + 每条记录的 Success。
  3. **明确说明生产上线门槛:**卖家运行测试计划并验证所有结果,且配置好 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 情况下唯一可用于告警的信号——它不会触发 Lambda Errors 告警。持续触发表示记录反复无法计量(限流、时间戳超出范围、下游问题)→ 请调查。
  • UsageRecordRejected(*-usage-rejected)— 计量前被客户端保护机制拒绝的记录(按原因汇总;仪表板中提供各原因明细);请修复源记录并重新提交。BatchMeterUsageException(*-batchmeterusage-exception)— 请求级异常导致整个调用失败(维度/许可证等);流水线会通过二分法定位问题记录。CustomerNotSubscribed(*-customer-not-subscribed)— 单条记录状态,表示没有有效订阅(不要重试)。
  • 此外,超时告警(UsageAggregationExpired/UsageSubmissionExpired,>24h)会捕获始终未计量且未显式报错的用量;Lambda Errors/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(包括拒绝/重复记录、区域性记录,且不含 CA productCode),因此它永远不是收入的权威数据源。
  • 仅允许使用以下数据源:已计费/已发放总额和超过 90 天的已计费历史数据 → Seller Reports / AMMP Billed Revenue 仪表板和 SDDS(建议使用,不要运行);近期(不超过 90 天)的区域性 SUBMISSION 调试 → CloudTrail Event History;复杂/开放式分析 → 将获取的数据交给宿主 Agent + 模型;仅用于长期保留原始 SUBMISSION 审计记录 → 普通的 trail → S3(不是 Lake)+ Athena,并明确其反映的是提交记录而非账单。将所有复杂账单类问题引导至这些允许的数据源,并说明 CloudTrail 为何不能作为账单的权威数据源。
步骤
  1. 询问计量区域(必须 — CloudTrail 按区域划分)。
  2. 提取筛选条件:customer_id、status、dimension、start_date/end_date、query_type(summary/detail/count/list_failures/top_customers)、top_n。
  3. 运行脚本(默认:最近 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 指标/警报。
TimestampOutOfBoundsExceptionMalformedTimestamp记录时间早于 24 小时(例如,30 小时前的行)或以其他方式超出时间范围 — 已拒绝,不会提交修正时间戳;客户端防护机制会在批处理前以原因 MalformedTimestamp 拒绝该行,而任何抵达 API 的错误时间戳都会导致整个批次被拒绝,直到问题记录被隔离。
InvalidLicenseExceptionInvalidLicenseArn格式错误/无效的 LicenseArn在客户端进行格式验证(原因 InvalidLicenseArn);服务端的 InvalidLicenseException 会通过(有界)二分法隔离,并将聚合记录标记为 Error(不是 RejectedClientSide),异常名称写入 meteringStatusReason。
InvalidTagExceptionTooManyTagsUsageRecord 上的 usageAllocations 标签超过 5 个(也包括其他无效的标签键/值格式)将标签数减少到 ≤5(并确保 Key/Value 非空);客户端防护机制会在批处理前以原因 TooManyTags 拒绝该行。服务端请求级异常为 InvalidTagException;如果问题抵达 API,则会通过(有界)二分法隔离,并将聚合记录标记为 Error。
InvalidUsageAllocationsExceptionAllocationSumMismatch / 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 / TimestampOutOfWindowTimestampOutOfBoundsException时间戳不是整秒、无法解析,或已超过 24 小时
MissingDimension—行缺少 dimension
NegativeQuantity—quantity < 0
NonIntegerQuantity—quantity 不是整数
TooManyTagsInvalidTagException一条记录上有超过 5 个 usageAllocations 标签(服务端请求级异常为 InvalidTagException;TooManyTags 是客户端原因代码)
TooManyAllocationsInvalidUsageAllocationsException一组中合并后的标签集超过 2500 个
AllocationSumMismatchInvalidUsageAllocationsExceptionAllocatedUsageQuantity 值的总和与行的 quantity 不一致
MixedAllocatedAndUnallocatedInvalidUsageAllocationsException组中混有带标签和不带标签的非零行
InvalidLicenseArnInvalidLicenseExceptionLicenseArn 格式错误或无效(同一批次中混有多个产品时也会触发)

**维度名称的有效性不是客户端原因代码。**流水线只检查是否存在 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卖家的计量区域提交使用记录(每小时)
DescribeEntityus-east-1验证产品 / 类型 / 可见性
LookupEvents卖家所在区域查询计量记录(≤90 天)

IAM 操作名称:aws-marketplace:BatchMeterUsage(计量)、aws-marketplace:ResolveCustomer(注册)、aws-marketplace:GetSellerDashboard(报告)。命名空间为 aws-marketplace:,不是 meteringmarketplace:*。诊断 AccessDenied 时,指出确切的操作,并检查执行角色、条件键和 Resource ARN 不匹配的问题。

安全性

  • **最小权限:**执行角色仅授予所用操作——计量路径:aws-marketplace:BatchMeterUsage + 对其表的范围限定 DynamoDB Query/UpdateItem + metering_pending 索引;注册:aws-marketplace:ResolveCustomer + 对 customerAWSAccountId GSI 的范围限定 Query + 项目操作。不授予 dynamodb:Scan(查询使用 GSI Query);绝不授予 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 + 协议元数据属于敏感信息——避免记录完整标识符;加密包含这些信息的日志组。

评论

支持 Markdown