<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fernando Azevedo</title>
    <description>The latest articles on DEV Community by Fernando Azevedo (@fernando_azevedo_6844e930).</description>
    <link>https://dev.to/fernando_azevedo_6844e930</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1787949%2F6b76fcdd-ad42-4051-833f-22554f10309f.jpg</url>
      <title>DEV Community: Fernando Azevedo</title>
      <link>https://dev.to/fernando_azevedo_6844e930</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fernando_azevedo_6844e930"/>
    <language>en</language>
    <item>
      <title>One listing, many legal entities: AWS Marketplace subsidiaries in practice</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Fri, 30 Oct 2026 17:14:07 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/one-listing-many-legal-entities-aws-marketplace-subsidiaries-in-practice-3i3o</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/one-listing-many-legal-entities-aws-marketplace-subsidiaries-in-practice-3i3o</guid>
      <description>&lt;p&gt;Anyone who has operated a group with one entity per country knows the hidden cost of AWS Marketplace: the same listing recreated in every seller account, with descriptions and pricing dimensions that drift silently. On 10/29/2026 AWS made it possible to publish the product once in the primary account and authorize subsidiaries to sell it through direct private offers, each under its own tax identity, currencies and bank account. The announcement sells this as the end of duplication. I read it differently: duplication leaves the catalog and becomes an authorization problem, with states, defaults and a revocation that does not do what the name suggests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The situation: the catalog was never the problem
&lt;/h2&gt;

&lt;p&gt;In a group with a US entity, a Brazilian Ltda and an Indian subsidiary, the reason for three seller accounts is not technical, it is fiscal. Each entity has to be the seller of record in its market: it holds the customer contract, invoices in local currency, pays its own Marketplace fees and gets paid into its own bank account. The documentation is explicit about this, and the new capability does not change that premise. It only removes the obligation to maintain the product inside every account.&lt;/p&gt;

&lt;p&gt;The expensive part was never creating the listing. It was keeping three copies in sync for years: every new pricing dimension, every description tweak, every product version multiplied by the number of entities, and every drift found by a customer before you found it. That is the maintenance cost this capability attacks, and it is real.&lt;/p&gt;

&lt;p&gt;What it puts in its place deserves the same care. The primary account now issues authorizations, one per product and per subsidiary, stating in which countries and with which pricing dimensions that entity can sell. That is access control over revenue. In a regulated environment, anything that decides who sells what, to whom and where belongs in the same bucket as an IAM role: it needs an owner, an audit trail and periodic review.&lt;/p&gt;

&lt;p&gt;One detail that tempers the optimism of anyone who already has duplicate listings: live agreements on a subsidiary's own listing stay there and cannot be moved to the primary account's product. The migration applies to new offers, not to the installed base. You will live with both models until the last legacy contract expires.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model: two layers and nothing shared by default
&lt;/h2&gt;

&lt;p&gt;Authorization has two layers, and mixing them up is the first mistake I would expect to see in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connection:&lt;/strong&gt; done in AWS Partner Central. The primary sends the request, with up to 10 account IDs per submission, and an administrator in the subsidiary accepts it. Acceptance has a side effect that needs legal sign-off: it records the subsidiary's consent to share partner attributed revenue and its Marketplace insights reports with the primary. The connection alone authorizes no sales at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authorization:&lt;/strong&gt; a &lt;code&gt;SubsidiaryAuthorization@1.0&lt;/code&gt; entity in the Catalog API, created by the primary with &lt;code&gt;StartChangeSet&lt;/code&gt; and the &lt;code&gt;CreateSubsidiaryAuthorization&lt;/code&gt; change type. It starts in &lt;code&gt;Draft&lt;/code&gt;, becomes &lt;code&gt;Active&lt;/code&gt; through &lt;code&gt;UpdateAvailability&lt;/code&gt; and ends in &lt;code&gt;Restricted&lt;/code&gt;. It carries the product ARN (immutable), the &lt;code&gt;Dimensions&lt;/code&gt; list, the &lt;code&gt;SubsidiaryAccountId&lt;/code&gt; (also immutable, it becomes the &lt;code&gt;SubsidiaryTargetingRule&lt;/code&gt;) and a &lt;code&gt;BuyerTargetingTerm&lt;/code&gt; with &lt;code&gt;CountryCodes&lt;/code&gt; and &lt;code&gt;OfferTargeting&lt;/code&gt;, which today accepts only &lt;code&gt;PrivateOffer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service role:&lt;/strong&gt; both accounts need &lt;code&gt;AWSMarketplaceSubsidiaryAuthorizationServiceRole&lt;/code&gt;, which AWS uses to create the resource share between them. Without it, in the console the authorization is created and never becomes active, and the subsidiary sees nothing. Through the API the error shows up as &lt;code&gt;INCOMPATIBLE_ACCOUNT_STATE&lt;/code&gt; on the primary or &lt;code&gt;INCOMPATIBLE_SUBSIDIARY_ACCOUNT_STATE&lt;/code&gt; on the subsidiary, and only in &lt;code&gt;DescribeChangeSet&lt;/code&gt;, because validation is asynchronous.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human IAM:&lt;/strong&gt; separate from the service role, and you need both. To authorize, the primary uses &lt;code&gt;AWSMarketplaceSellerFullAccess&lt;/code&gt; or &lt;code&gt;AWSMarketplaceSellerProductsFullAccess&lt;/code&gt;; to create an offer from the Private offers page, the subsidiary uses &lt;code&gt;AWSMarketplaceSellerOfferManagement&lt;/code&gt;. Those managed policies are broad. In production I would write a custom policy restricted to the Catalog API actions each team actually uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who controls what: product in the primary, contract and money in the subsidiary
&lt;/h2&gt;

&lt;p&gt;The diagram answers one question: where the control boundary sits between accounts. The primary owns the product and the authorization; the subsidiary is the seller of record, sets the price and gets paid. The dashed line is reporting visibility only and moves no money. Simplification: the Partner Central connection and the service role are implicit in the sharing arrow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Primary account
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Authorization repo Catalog API (ci)&lt;/li&gt;
&lt;li&gt;Product listing SaaSProduct (data)&lt;/li&gt;
&lt;li&gt;SubsidiaryAuthorization Draft, Active, Restricted (security)&lt;/li&gt;
&lt;li&gt;Marketplace insights consolidated (data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Subsidiary account
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Private offer seller of record (compute)&lt;/li&gt;
&lt;li&gt;Disbursement local bank, currency (storage)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Buyer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Buyer account tax address (user)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;pipe -&amp;gt; authz: StartChangeSet&lt;/li&gt;
&lt;li&gt;authz -&amp;gt; prod: references ARN&lt;/li&gt;
&lt;li&gt;authz -&amp;gt; offer: scopes countries and dimensions&lt;/li&gt;
&lt;li&gt;offer -&amp;gt; buyer: private offer&lt;/li&gt;
&lt;li&gt;buyer -&amp;gt; bank: pays via AWS&lt;/li&gt;
&lt;li&gt;offer -&amp;gt; insights: reports&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where the risk lives: revoking is not switching off
&lt;/h2&gt;

&lt;p&gt;The announcement says that revoking an authorization immediately stops the subsidiary from creating new offers. Correct, and incomplete for anyone designing a control. The documentation details four behaviors that change the runbook.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Published offers survive:&lt;/strong&gt; revoking does not cancel offers already published. The buyer can still accept an offer the subsidiary published before revocation, and existing agreements continue under their agreed terms, including renewals and disbursements to the subsidiary. If the reason for revoking is the divestiture of the entity, a restriction in a country or a corporate dispute, the block does not reach the exposure already out in the market.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Restricted&lt;/code&gt; is terminal:&lt;/strong&gt; there is no reactivation. To authorize again, you create a new authorization, which gets a new ID. Any system using the authorization ID as a business key breaks on the first accidental revocation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The connection takes everything down:&lt;/strong&gt; ending the connection in Partner Central ends every authorization between the two accounts. An analyst who removes a connection to fix revenue attribution takes down the selling capability of the whole entity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Narrowing breaks drafts:&lt;/strong&gt; removing a country or a dimension makes publishing fail for draft offers that used that scope. The offer stays parked and the local seller only finds out when trying to publish, usually the day before quarter close.&lt;/p&gt;

&lt;p&gt;The practical lesson: revocation is a process, not a click. Revoke the authorization, list the subsidiary's published offers not yet accepted, decide what happens to each one, and only then touch the connection. To shorten the exposure window, use short expiration dates on the private offers subsidiaries publish.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authorization as code: &lt;code&gt;UpdateInformation&lt;/code&gt; replaces everything
&lt;/h2&gt;

&lt;p&gt;With more than two subsidiaries, console-made authorizations drift within weeks. The Catalog API lets you treat each authorization as a versioned resource, and three details make that mandatory, not cosmetic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full replacement:&lt;/strong&gt; &lt;code&gt;UpdateInformation&lt;/code&gt; replaces &lt;code&gt;Name&lt;/code&gt;, &lt;code&gt;Description&lt;/code&gt;, &lt;code&gt;Resource&lt;/code&gt; and &lt;code&gt;Terms&lt;/code&gt; wholesale; anything you omit is removed. Synchronous validation returns 422 if &lt;code&gt;Dimensions&lt;/code&gt; is empty, but a &lt;code&gt;BuyerTargetingTerm&lt;/code&gt; without &lt;code&gt;CountryCodes&lt;/code&gt; passes, and an empty list means all countries. A script that only meant to change the description can open the Brazilian entity's sales to the whole world. The only safe approach is to always send the complete state, read from a versioned file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The default sits on the wrong side:&lt;/strong&gt; buyer eligibility is evaluated against the buyer's tax address. An authorization with no countries contradicts the fiscal premise that justified the local entity in the first place. In the pipeline, I would block the merge of any authorization with an empty &lt;code&gt;CountryCodes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Products do not propagate:&lt;/strong&gt; adding a dimension to the product does not reach the subsidiary; you have to call &lt;code&gt;UpdateInformation&lt;/code&gt; with the new list. If the product has &lt;code&gt;Metered&lt;/code&gt; or &lt;code&gt;ExternallyMetered&lt;/code&gt; dimensions, all of them must be in the authorization, otherwise the change set fails with &lt;code&gt;INCOMPATIBLE_PRODUCT_DIMENSIONS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The rest is idempotency and observability. &lt;code&gt;StartChangeSet&lt;/code&gt; is asynchronous: store the &lt;code&gt;ChangeSetId&lt;/code&gt;, poll &lt;code&gt;DescribeChangeSet&lt;/code&gt; until &lt;code&gt;SUCCEEDED&lt;/code&gt; or &lt;code&gt;FAILED&lt;/code&gt;, and read the &lt;code&gt;ErrorDetailList&lt;/code&gt;. Two authorizations for the same product and subsidiary cannot overlap countries and dimensions (&lt;code&gt;DUPLICATE_SUBSIDIARY_AUTHORIZATION&lt;/code&gt;), which gives you a natural key for your state: product, subsidiary and country set, not the ID. Reconcile daily with &lt;code&gt;ListEntities&lt;/code&gt; using &lt;code&gt;EntityType: SubsidiaryAuthorization&lt;/code&gt; and diff against the repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Playbook for tomorrow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Map entity, country and currency&lt;/strong&gt;: Build the entity by country by currency table and confirm every subsidiary has completed its tax interview, KYC and payment setup, with a bank account and at least one disbursement currency. Without payment setup the subsidiary cannot create offers, even with an active authorization. In India, the account must be standalone, outside AWS Organizations, disburses only in INR and sells only to buyers in India.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create the service role on both ends&lt;/strong&gt;: In each account, in Partner Central, Settings, Service roles tab, create &lt;code&gt;AWSMarketplaceSubsidiaryAuthorizationServiceRole&lt;/code&gt; before the first authorization. Without it the authorization never activates and the console gives no warning.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Accept the connection with legal sign-off&lt;/strong&gt;: Acceptance records consent to share attributed revenue and insights reports with the primary. In a group with minority shareholders or a joint venture, that is a corporate decision, not an IT one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Version every authorization&lt;/strong&gt;: One file per product and subsidiary, with explicit &lt;code&gt;CountryCodes&lt;/code&gt; and the complete &lt;code&gt;Dimensions&lt;/code&gt; list. The pipeline sends the whole state via &lt;code&gt;UpdateInformation&lt;/code&gt;, polls &lt;code&gt;DescribeChangeSet&lt;/code&gt; and fails the build on any non-empty &lt;code&gt;ErrorDetailList&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Release in two steps&lt;/strong&gt;: Create in &lt;code&gt;Draft&lt;/code&gt;, review &lt;code&gt;DescribeEntity&lt;/code&gt; against the file and only then call &lt;code&gt;UpdateAvailability&lt;/code&gt; with &lt;code&gt;Active&lt;/code&gt;. Activation takes a few minutes; do not promise the local team the offer goes out the same minute.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write the revocation runbook&lt;/strong&gt;: Revoke the authorization, list published and unaccepted offers, decide what happens to each one, and only then touch the connection. Include who approves, because &lt;code&gt;Restricted&lt;/code&gt; cannot be undone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reconcile daily&lt;/strong&gt;: &lt;code&gt;ListEntities&lt;/code&gt; with &lt;code&gt;SubsidiaryAuthorizationFilters&lt;/code&gt; by status and account, diffed against the repository. Use the primary's consolidated Marketplace insights to cross-check offers, agreements and renewals per subsidiary.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The authorization name is an interface:&lt;/strong&gt; The subsidiary picks the authorization by its &lt;code&gt;Name&lt;/code&gt; (1 to 100 characters) when creating an offer. Put the product and scope in the name, for example &lt;code&gt;Security Suite | BR, AR | users, api&lt;/code&gt;, and generate it from the versioned file. A hand-written name drifts from the real scope on the first change, and the local seller picks the wrong authorization without noticing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Three ways to sell through more than one entity
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Subsidiary authorization&lt;/th&gt;
&lt;th&gt;Resale authorization (CPPO)&lt;/th&gt;
&lt;th&gt;Duplicate listing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who sells&lt;/td&gt;
&lt;td&gt;Another seller account of your own group&lt;/td&gt;
&lt;td&gt;Third-party Channel Partner&lt;/td&gt;
&lt;td&gt;Each account, with its own product&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price&lt;/td&gt;
&lt;td&gt;Subsidiary sets it; the authorization carries no price&lt;/td&gt;
&lt;td&gt;Markup over the wholesale set by the ISV&lt;/td&gt;
&lt;td&gt;Each account sets it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Currency&lt;/td&gt;
&lt;td&gt;Those configured in the subsidiary itself&lt;/td&gt;
&lt;td&gt;The one the ISV defines in the authorization&lt;/td&gt;
&lt;td&gt;The account's own&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offer types&lt;/td&gt;
&lt;td&gt;Direct private only&lt;/td&gt;
&lt;td&gt;Private through the partner&lt;/td&gt;
&lt;td&gt;Public and private&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance cost&lt;/td&gt;
&lt;td&gt;1 listing + N versioned authorizations&lt;/td&gt;
&lt;td&gt;1 listing + one authorization per partner&lt;/td&gt;
&lt;td&gt;N listings synced by hand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use when&lt;/td&gt;
&lt;td&gt;Local entity must be seller of record and sells through private offers&lt;/td&gt;
&lt;td&gt;A third party resells and needs margin&lt;/td&gt;
&lt;td&gt;The local entity needs a public offer or its own CPPO&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Anti-patterns I would expect in the first quarter
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authorization without &lt;code&gt;CountryCodes&lt;/code&gt;&lt;/strong&gt;: an empty list means all countries, and the entity created for one fiscal market can now sell to any tax address.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treating revocation as a kill switch&lt;/strong&gt;: offers already published remain acceptable and agreements keep renewing; the block applies only to new offers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial updates by script&lt;/strong&gt;: &lt;code&gt;UpdateInformation&lt;/code&gt; removes everything not sent, so a description patch can wipe countries and dimensions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Removing the connection to fix attribution&lt;/strong&gt;: ending the connection ends every authorization between the two accounts at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization ID as a business key&lt;/strong&gt;: &lt;code&gt;Restricted&lt;/code&gt; is terminal and re-authorizing creates a new ID; key on product, subsidiary and countries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shipping a new dimension and forgetting the authorization&lt;/strong&gt;: the subsidiary keeps selling the old product version until someone calls &lt;code&gt;UpdateInformation&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Questions that will come up
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can the subsidiary create a CPPO for a local channel with the authorized product?
&lt;/h3&gt;

&lt;p&gt;No. The Catalog API rejects a resale authorization created from a product the primary authorized. The subsidiary can only create CPPOs for products it owns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I migrate agreements from the duplicate listing to the primary's product?
&lt;/h3&gt;

&lt;p&gt;No. They stay on the original listing. Plan for coexistence until expiry and use the authorization at renewal time, as a new offer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can the primary create offers or complete KYC on the subsidiary's behalf?
&lt;/h3&gt;

&lt;p&gt;No. Each account creates its own offers and completes its own tax interview and KYC. The service role also gives the primary no access to the subsidiary's offers, agreements or financial data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are Marketplace fees consolidated in the primary?
&lt;/h3&gt;

&lt;p&gt;No. Listing and transaction fees are charged to the transacting subsidiary and are not pooled, netted or rebilled across accounts. Your transfer pricing model still lives outside the Marketplace.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does it cost and where is it available?
&lt;/h3&gt;

&lt;p&gt;No additional cost, in all Regions where AWS Marketplace is available, according to the 10/29/2026 announcement.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Curator's note:&lt;/strong&gt; I would start with the lowest-volume subsidiary, a single product and two countries, and only move to the next one after running the revocation runbook end to end in a test setup. Before the first active authorization, the pipeline would already be blocking empty &lt;code&gt;CountryCodes&lt;/code&gt; and partial updates. The hard-won lesson I bring from financial platforms: the control that causes the most trouble is not the one that blocks, it is the one that looks like it blocks and does not. Here, that control is called revocation, and its name promises more than it delivers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Adopt subsidiary authorization when the group has local entities that must be seller of record, sells mostly through private offers and maintains duplicate listings today: the maintenance gain is direct. Do it with authorizations in a repository, &lt;code&gt;CountryCodes&lt;/code&gt; always explicit and a revocation runbook that handles published offers. Keep the local listing when the entity needs a public offer or its own CPPO, because neither is supported in this release.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Adote com runbook&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/10/aws-marketplace-subsidiary-account-authorization" rel="noopener noreferrer"&gt;AWS What's New: AWS Marketplace now enables partners to list once and sell through multiple subsidiary accounts (29/10/2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/marketplace/latest/userguide/subsidiary-offers.html" rel="noopener noreferrer"&gt;AWS Marketplace Seller Guide: Creating private offers as a subsidiary account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/partner-central/latest/getting-started/manage-subsidiary.html" rel="noopener noreferrer"&gt;AWS Partner Central: Managing AWS subsidiary account connections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/marketplace/latest/developerguide/work-with-subsidiary-authorizations.html" rel="noopener noreferrer"&gt;AWS Marketplace API: Work with subsidiary authorizations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/marketplace/latest/userguide/using-roles-for-subsidiary-authorization.html" rel="noopener noreferrer"&gt;AWS Marketplace Seller Guide: Subsidiary authorization service role&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/marketplace/latest/userguide/channel-partner-offers.html" rel="noopener noreferrer"&gt;AWS Marketplace Seller Guide: Creating private offers as an AWS Marketplace Channel Partner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/marketplace/latest/userguide/getting-started-seller-india.html" rel="noopener noreferrer"&gt;AWS Marketplace Seller Guide: Getting started as a seller in India&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/uma-listagem-varias-entidades-juridicas-subsidiarias-no-aws" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>awsmarketplace</category>
      <category>governance</category>
      <category>catalogapi</category>
    </item>
    <item>
      <title>AWS Builder Loft São Paulo: a new home for building together</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Fri, 30 Oct 2026 17:14:04 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/aws-builder-loft-sao-paulo-a-new-home-for-building-together-16eg</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/aws-builder-loft-sao-paulo-a-new-home-for-building-together-16eg</guid>
      <description>&lt;p&gt;São Paulo has a new home for builders. I left thinking about what we could build together. On the evening of October 29, the talks, conversations and community encounters at the AWS Builder Loft São Paulo opening left me with a welcome feeling: many people are ready to learn, share what they know and help an idea become something real.&lt;/p&gt;

&lt;p&gt;&lt;a href="/community-evidence/aws-builder-loft-sao-paulo-2026-10-29.jpg" class="article-body-image-wrapper"&gt;&lt;img src="/community-evidence/aws-builder-loft-sao-paulo-2026-10-29.jpg" alt="My record of the opening: the stage, the people, details of the space and the joy of being part of this beginning. São Paulo, October 29, 2026."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Fernando Azevedo&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took home from that evening
&lt;/h2&gt;

&lt;p&gt;As a solutions architect and an AWS Community Builder in AI Engineering, I spend much of my time connecting real problems with solutions that work. Good architecture takes questions, context and a willingness to reconsider choices. A good community needs those things too.&lt;/p&gt;

&lt;p&gt;That was what excited me most that evening. Being with people who want to create makes room for conversations that are difficult to fit into a presentation or a quick exchange of messages. A question reaches someone who has faced the problem before. One person's experience gives someone else the confidence to begin. An idea gains a second perspective.&lt;/p&gt;

&lt;p&gt;I left happy to have been part of this beginning and keen to keep contributing. The collage accompanying this piece preserves some of that memory: the talks, people gathering, the space and the small details that make an encounter memorable.&lt;/p&gt;

&lt;h2&gt;
  
  
  A place designed for continuity
&lt;/h2&gt;

&lt;p&gt;The idea behind the Loft was already present in the &lt;a href="https://aws.amazon.com/blogs/aws/in-the-works-aws-builder-lofts-in-berlin-hyderabad-and-sao-paulo/" rel="noopener noreferrer"&gt;official AWS announcement of the expansion to Berlin, Hyderabad and São Paulo&lt;/a&gt;: permanent spaces for learning, collaboration and community activities. For São Paulo, the announcement highlights connections with local communities, universities and startup networks.&lt;/p&gt;

&lt;p&gt;That continuity matters. Having somewhere to return gives a conversation a next chapter. Someone can arrive with a question, try an approach and come back with what worked, or what needs to change. Knowledge travels with the people involved.&lt;/p&gt;

&lt;p&gt;I see particular value in bringing beginners together with people who have production experience. Beginners ask questions that make us explain things better. People who have dealt with incidents and difficult decisions can share the precautions that do not always appear in a demonstration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A conversation can become a partnership. A question can open the way to a solution.&lt;br&gt;
Fernando Azevedo&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why this matters for Brazil
&lt;/h2&gt;

&lt;p&gt;In my view, a space like this can connect people working on similar challenges who have not yet met. Developers, architects, students, data professionals, security specialists and founders have much to learn from one another. Bringing those perspectives together makes it easier to understand the problem before choosing the technology.&lt;/p&gt;

&lt;p&gt;I also see an opportunity to create more knowledge grounded in our own circumstances. Costs, operations, accessibility, team development and the needs of Brazilian users belong in technical discussions. Sharing those experiences in Portuguese helps more people participate; publishing them in English as well broadens the exchange with the global community.&lt;/p&gt;

&lt;p&gt;The work can reach beyond people who are able to be physically present in São Paulo. A gathering can lead to a public repository, an accessible guide, a recording or an account that explains a decision. That is how I imagine the Loft's energy reaching other cities too: through useful materials, collaboration between groups and people willing to carry ideas forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would like to bring to the table
&lt;/h2&gt;

&lt;p&gt;I want to contribute what I have been studying and building in architecture, cloud and artificial intelligence, and learn from people with different experiences. In AI Engineering, I see room for sessions where we examine a real solution: the problem it solves, its limits, how to test it, what it costs to operate and what happens when it fails.&lt;/p&gt;

&lt;p&gt;One idea would be a hands-on session about agent tools, using a small example that anyone can reproduce. Another would be an architecture discussion where the group compares alternatives and records the decision. These are ideas I would like to develop with the community, not activities already scheduled at the Loft.&lt;/p&gt;

&lt;p&gt;My &lt;a href="https://fernando.moretes.com/mcp" rel="noopener noreferrer"&gt;open AWS architecture MCP&lt;/a&gt;, &lt;a href="https://fernando.moretes.com/studies" rel="noopener noreferrer"&gt;architecture studies&lt;/a&gt; and &lt;a href="https://fernando.moretes.com/blog" rel="noopener noreferrer"&gt;blog articles&lt;/a&gt; can provide starting points. The best outcome would be someone testing an example, finding a limitation, asking a better question and helping us improve the material.&lt;/p&gt;

&lt;h2&gt;
  
  
  From an encounter to a next step
&lt;/h2&gt;

&lt;p&gt;For someone just starting out, my invitation is simple: bring a real question. You do not need a finished project or knowledge of every service. Explaining what you tried and where you got stuck is already a good start to a conversation.&lt;/p&gt;

&lt;p&gt;For those with more experience, it is worth bringing the difficult choices too: the alternative you rejected, the unexpected cost, the failure that changed the design. These accounts help others move forward with a clearer understanding.&lt;/p&gt;

&lt;p&gt;When a conversation produces an idea, we can choose a small next step: write an example, review a diagram, test a hypothesis or open a repository discussion. Recording what we learn and crediting contributors keeps the encounter going. &lt;a href="https://builder.aws.com/" rel="noopener noreferrer"&gt;AWS Builder Center&lt;/a&gt; and &lt;a href="https://builder.aws.com/community/user-groups" rel="noopener noreferrer"&gt;AWS User Groups&lt;/a&gt; offer ways to continue that exchange. To attend future in-person activities, check the announcements and registration details for the local edition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thank you for being part of this beginning
&lt;/h2&gt;

&lt;p&gt;My thanks to the AWS team for the invitation and organization, to the speakers, AWS Heroes, community leaders and everyone who took part in that evening. It was a pleasure to be there with you.&lt;/p&gt;

&lt;p&gt;I left AWS Builder Loft São Paulo grateful and eager to keep the conversation going. I want to continue contributing alongside AWS builders, users and customers, sharing what I learn and listening to the problems we still need to solve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What idea, challenge or project would you bring to the table?&lt;/strong&gt; It could be an unanswered question, a prototype that needs another perspective or a topic you would like to teach. Let's start there.&lt;/p&gt;

&lt;p&gt;Let's learn, share and build together. There is much to look forward to in the AWS community in Brazil.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background and ways to stay involved
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/aws/in-the-works-aws-builder-lofts-in-berlin-hyderabad-and-sao-paulo/" rel="noopener noreferrer"&gt;AWS News Blog, anúncio da expansão dos Builder Lofts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://builder.aws.com/" rel="noopener noreferrer"&gt;AWS Builder Center&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://builder.aws.com/community/user-groups" rel="noopener noreferrer"&gt;AWS User Groups&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/aws-builder-loft-sao-paulo-comunidade-que-constroi" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>awsbuilderloft</category>
      <category>comunidadeaws</category>
      <category>sopaulo</category>
    </item>
    <item>
      <title>Your agent crashed after the write. Should you retry?</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Tue, 27 Oct 2026 16:46:39 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/your-agent-crashed-after-the-write-should-you-retry-232p</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/your-agent-crashed-after-the-write-should-you-retry-232p</guid>
      <description>&lt;p&gt;The hardest moment in an automation often sits between two acknowledgements: the target accepted the change, but the executor has not recorded completion. Restarting the agent restores process availability; it does not resolve uncertainty about the previous effect. In this lab, I terminate the process in that exact interval and inspect what survives. The next decision depends on persistent evidence, not the model's confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was verified
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Implementation:&lt;/strong&gt; lab-security-remediation, tag v0.4.0; local tests executed on October 27, 2026.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observed result:&lt;/strong&gt; 22 tests passed. In the receipt exercise: version 2, one receipt, and no second change after recovery and resubmission.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope:&lt;/strong&gt; Local SQLite, synthetic target, simulated review. No credentials or AWS calls required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not demonstrated:&lt;/strong&gt; Does not establish exactly-once execution across remote APIs, power-loss resilience, or production-ready authorization.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Start with the question the log cannot answer
&lt;/h2&gt;

&lt;p&gt;Imagine a tool that fixes a configuration. It receives a reviewed proposal, checks the target version, and applies the change. Before returning success, the process terminates. The client sees an interruption. The target may already have changed. A log line saying the call started cannot distinguish those possibilities.&lt;/p&gt;

&lt;p&gt;In this exercise, the target is only a SQLite row representing a synthetic bucket. The &lt;code&gt;blockPublicAccess&lt;/code&gt; property is a teaching device; this is not an S3 call. The proposal identifies the operation and the expected version. An intent record, called the ledger, stores the reviewed request. A receipt records that this request produced this target version.&lt;/p&gt;

&lt;p&gt;That separation creates a testable question: &lt;strong&gt;what evidence connects this operation to this change?&lt;/strong&gt; Seeing the desired configuration does not tell us who changed it. Finding a restarted process does not tell us whether its predecessor finished. A client error describes the client's observation, not necessarily the target's reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where evidence must survive
&lt;/h2&gt;

&lt;p&gt;Local lab architecture. The tables share one SQLite file, but intent, effect, and completion use separate transactions. The target update and its receipt are atomic with each other.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local CLI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Reviewed executor (compute)&lt;/li&gt;
&lt;li&gt;Recovery reader (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SQLite · separate commits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Operation ledger (storage)&lt;/li&gt;
&lt;li&gt;Target + receipt (data)&lt;/li&gt;
&lt;li&gt;Audit events (storage)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;executor -&amp;gt; ledger: INTENT&lt;/li&gt;
&lt;li&gt;executor -&amp;gt; target: EFFECT&lt;/li&gt;
&lt;li&gt;target -&amp;gt; recovery: EVIDENCE&lt;/li&gt;
&lt;li&gt;ledger -&amp;gt; recovery: REQUEST&lt;/li&gt;
&lt;li&gt;recovery -&amp;gt; ledger: CLASSIFIES&lt;/li&gt;
&lt;li&gt;recovery -&amp;gt; events: DECISION&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prepare a run another person can reproduce
&lt;/h2&gt;

&lt;p&gt;Use Node.js 22.13 or later with &lt;code&gt;node:sqlite&lt;/code&gt; available. The tagged source is the reference for this exercise; do not assume the published npm version is identical. Installing dependencies is not required to run this lab. Clone into a new directory, check the tag, and run the tests before changing a scenario.&lt;/p&gt;

&lt;p&gt;Choose fresh database names for each complete repetition. A reused database retains history, and that history changes the expected result. Preserve the original proposal too: generating another after the change can produce a different digest for the same identifier and should cause a conflict, not implicit permission to execute again.&lt;/p&gt;

&lt;p&gt;The intentional failures are separate from inspection commands. In a script using &lt;code&gt;set -e&lt;/code&gt;, explicitly capture exit code 86 so the expected termination does not end the investigation. The uncertainty exercise returns 2, another expected outcome. These codes are evidence; do not erase them with an indiscriminate &lt;code&gt;|| true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Setup: pinned source and local tests&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/fernando-moretes/lab-security-remediation.git
&lt;span class="nb"&gt;cd &lt;/span&gt;lab-security-remediation
git checkout v0.4.0
node &lt;span class="nt"&gt;--version&lt;/span&gt;
npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Experiment 1: the effect exists; the acknowledgement was lost
&lt;/h2&gt;

&lt;p&gt;Create the database and export the proposal. Running with &lt;code&gt;--approve&lt;/code&gt; simulates explicit review and records intent. The &lt;code&gt;after-target&lt;/code&gt; crash point terminates the process after the commit that updates the target and writes its receipt, before marking the operation complete. This is not an exception caught inside one function: the CLI exits, and subsequent commands open the database in other processes.&lt;/p&gt;

&lt;p&gt;After the crash, inspect three things together: operation status, target version, and receipt existence. In the October 27 run, the operation was &lt;code&gt;pending&lt;/code&gt;, the target was at version 2 with &lt;code&gt;blockPublicAccess: true&lt;/code&gt;, and one receipt matched the proposal digest. That combination demonstrates why retrying solely because the ledger is pending would be an uninformed decision.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Intentional crash: run exits 86; continue with inspect&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node bin/cli.mjs durable init ./receipt.sqlite
node bin/cli.mjs durable plan ./receipt.sqlite &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; receipt-proposal.json
node bin/cli.mjs durable run ./receipt.sqlite receipt-proposal.json &lt;span class="nt"&gt;--approve&lt;/span&gt; &lt;span class="nt"&gt;--crash&lt;/span&gt; after-target
&lt;span class="c"&gt;# Expected exit: 86&lt;/span&gt;
node bin/cli.mjs durable inspect ./receipt.sqlite
node bin/cli.mjs durable recover ./receipt.sqlite synthetic-finding-001
node bin/cli.mjs durable run ./receipt.sqlite receipt-proposal.json &lt;span class="nt"&gt;--approve&lt;/span&gt;
node bin/cli.mjs durable inspect ./receipt.sqlite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recovering the record does not require repeating the effect
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;recover&lt;/code&gt; command reads the operation, receipt, and current state. Here it changes the classification to &lt;code&gt;reconciled&lt;/code&gt; and records the decision in the event history. It does not mutate the target. Resubmitting the original proposal also returns &lt;code&gt;reconciled&lt;/code&gt;, with &lt;code&gt;retryable: false&lt;/code&gt;. In the final inspection, the version remains 2 and the receipt count remains 1.&lt;/p&gt;

&lt;p&gt;Those are the invariants worth checking. The test is not simply searching for the word success in the output. Compare the observable effect before and after recovery. Compare it again before and after resubmission. If the version increased again, an apparently correct status would hide a second execution.&lt;/p&gt;

&lt;p&gt;A receipt is historical evidence, not a permanent health certificate. If the target changes later, the earlier operation's history can remain valid while current state diverges. The implementation exposes &lt;code&gt;driftAfterReceipt&lt;/code&gt; to distinguish those questions. Restoring a desired configuration, when necessary, requires a new reviewed decision about current state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observed results from both experiments
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Inspection&lt;/th&gt;
&lt;th&gt;With receipt&lt;/th&gt;
&lt;th&gt;Without receipt, with drift&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;After interruption / drift&lt;/td&gt;
&lt;td&gt;pending; version 2; 1 receipt&lt;/td&gt;
&lt;td&gt;pending; version 2; 0 receipts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recovery&lt;/td&gt;
&lt;td&gt;reconciled; exit 0&lt;/td&gt;
&lt;td&gt;uncertain; exit 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Original proposal resubmission&lt;/td&gt;
&lt;td&gt;reconciled; version remains 2&lt;/td&gt;
&lt;td&gt;uncertain; version remains 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud calls&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Experiment 2: the state looks right; ownership is unknown
&lt;/h2&gt;

&lt;p&gt;Now use another database. Interrupt execution at &lt;code&gt;after-intent&lt;/code&gt;, before applying the effect. Then use the teaching command &lt;code&gt;drift&lt;/code&gt; to change the target through another path. During recovery, the desired property is true and the version is 2, just as in the first experiment. The decisive difference is the missing receipt.&lt;/p&gt;

&lt;p&gt;The observed result is &lt;code&gt;uncertain&lt;/code&gt;, &lt;code&gt;retryable: false&lt;/code&gt;, with exit code 2. Resubmitting the original proposal preserves that classification and does not mutate the target again. The system does not infer ownership from matching values. This behavior trades automatic progress for a decision that can be investigated.&lt;/p&gt;

&lt;p&gt;Do not delete the operation to unblock a queue. That removes the memory used to recognize duplicates. Preserve the database, proposal, and results; investigate the change and define the next step as a new decision. The lab offers no magic administrative resolution button and should not suggest that manually editing the ledger is a safe operating procedure.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The final configuration matches, but the receipt is missing&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node bin/cli.mjs durable init ./uncertain.sqlite
node bin/cli.mjs durable plan ./uncertain.sqlite &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; uncertain-proposal.json
node bin/cli.mjs durable run ./uncertain.sqlite uncertain-proposal.json &lt;span class="nt"&gt;--approve&lt;/span&gt; &lt;span class="nt"&gt;--crash&lt;/span&gt; after-intent
&lt;span class="c"&gt;# Expected exit: 86&lt;/span&gt;
node bin/cli.mjs durable drift ./uncertain.sqlite &lt;span class="nb"&gt;true
&lt;/span&gt;node bin/cli.mjs durable inspect ./uncertain.sqlite
node bin/cli.mjs durable recover ./uncertain.sqlite synthetic-finding-001
&lt;span class="c"&gt;# Expected exit: 2, status: uncertain&lt;/span&gt;
node bin/cli.mjs durable run ./uncertain.sqlite uncertain-proposal.json &lt;span class="nt"&gt;--approve&lt;/span&gt;
&lt;span class="c"&gt;# Expected exit: 2; no additional target change&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Three commits explain the failure windows
&lt;/h2&gt;

&lt;p&gt;The implementation persists reviewed intent in one transaction. In another, it compares the expected version and writes the target together with its receipt. In a third, it marks completion in the ledger. The receipt and change are atomic with each other; the whole execution is not one transaction. That choice deliberately creates the window we just tested.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt; serializes writers to the local file with a bounded contention wait. Storage uses WAL and &lt;code&gt;synchronous=FULL&lt;/code&gt;. These choices give the exercise transactional semantics, but process-termination tests are not power-loss, disk, corruption, or network-filesystem tests. The cost of serialization matters too: one local file does not represent distributed scalability.&lt;/p&gt;

&lt;p&gt;The suite covers other boundaries: crashes before the target commit, before the completion commit, and after completion; concurrent processes; repeated identifiers with different payloads; unreviewed proposals and state changes. The 22 passing tests check these properties in the lab. They are not an availability percentage or a production agent success rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  C4
&lt;/h2&gt;

&lt;p&gt;A person runs a recovery lab and inspects synthetic evidence. The model calls no AWS services and requires no LLM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Containers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CLI&lt;/strong&gt; (Node.js): Independent processes for plan, run, inspect, and recover.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent store&lt;/strong&gt; (SQLite local file): Operations, target, receipts, and history persisted in one file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Proposal validation&lt;/strong&gt;: Validates the request, simulated review, and expected version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditional mutation&lt;/strong&gt;: Updates target and receipt in the same commit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recovery classifier&lt;/strong&gt;: Reads evidence and records the classification without executing the mutation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What changes when the tool calls AWS
&lt;/h2&gt;

&lt;p&gt;In a real integration, recording intent in a database and calling a remote API are operations in different systems. The local commit does not encompass the API. The lab deliberately keeps target and receipt together so evidence is inspectable; moving the design to the cloud requires reassessing that boundary.&lt;/p&gt;

&lt;p&gt;The AWS Builders' Library explains the value of an explicit client-provided identifier for recognizing retry intent. Check the concrete operation's idempotency contract: scope, token lifetime, behavior with changed parameters, and repeated-call responses. Do not infer a universal guarantee because another service accepts a token.&lt;/p&gt;

&lt;p&gt;For each mutable tool, document which remote evidence is available, how long it remains queryable, and who resolves inconclusive outcomes. When the target cannot provide sufficient proof, retain a visible uncertainty state. Adding a queue, an agent framework, or an MCP server does not independently close the gap between effect and acknowledgement.&lt;/p&gt;

&lt;p&gt;Separate current authorization from historical evidence too. In this lab, &lt;code&gt;--approve&lt;/code&gt; is only a local review simulation. A real service must verify identity, scope, and validity before mutation; revoked approval must not become permission for a new execution just because an old record exists. A recovery read and a mutation attempt require different decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision: uncertainty is an operational outcome
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; accepted&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The client may lose the response after a committed effect at the target.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use persistent evidence to reconcile; block resubmission when the effect cannot be attributed to the operation. In this lab, recover never mutates the target.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consequences&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires an owner for investigating uncertain states.&lt;/li&gt;
&lt;li&gt;Receipt and proposal retention is part of the recovery contract.&lt;/li&gt;
&lt;li&gt;May require intervention even when current state looks desirable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checklist for reviewing your own tool
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Define intent&lt;/strong&gt;: Choose a stable identifier for the same operation and an explicit rule for conflicting payloads. Save the original proposal and contract version.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Locate the real commit&lt;/strong&gt;: Mark where the target changes and where acknowledgement is persisted. Inject an interruption between them, away from production resources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compare effects&lt;/strong&gt;: Observe version, receipts, and history before and after replay. A success status alone does not demonstrate the absence of duplicates.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Exercise ambiguity&lt;/strong&gt;: Have another actor change the target without the operation's receipt. Verify that matching values do not become assumed ownership.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Define retention and operations&lt;/strong&gt;: Document the evidence retention window, investigation owner, and consistent database-copy procedure. Close processes before copying the lab file.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A Well-Architected reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;operational-excellence&lt;/strong&gt;: Turn uncertain into an investigation queue with an owner and minimum evidence. Recovery needs a runbook and an exercise as well as observability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;security&lt;/strong&gt;: Local review authenticates nobody. In production, protect the ledger, minimize sensitive data, and revalidate authorization for the next mutation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reliability&lt;/strong&gt;: Test lost acknowledgement, not only unavailability before a call. Separate duplicate recognition, reconciliation, and a newly authorized attempt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;performance&lt;/strong&gt;: Measure contention and reconciliation latency in the real architecture. SQLite writer serialization is not evidence of distributed throughput.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cost&lt;/strong&gt;: The exercises make no AWS calls. For a remote implementation, estimate evidence storage and recovery queries; do not invent savings from this local test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sustainability&lt;/strong&gt;: Avoiding redundant effects and attempts reduces unnecessary work, but the lab measures neither energy nor emissions. Do not turn that hypothesis into a number.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Traps the exercise makes visible
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Retrying every interrupted call without consulting effect evidence.&lt;/li&gt;
&lt;li&gt;Treating the desired final value as proof that this operation produced it.&lt;/li&gt;
&lt;li&gt;Generating a new proposal after the change and calling it a retry of the original.&lt;/li&gt;
&lt;li&gt;Deleting receipts or ledger records without a policy that preserves the duplicate-recognition window.&lt;/li&gt;
&lt;li&gt;Presenting a local SQLite transaction as an exactly-once guarantee across remote services.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Diagnostic replay and mutation retry are different operations
&lt;/h2&gt;

&lt;p&gt;To share a reproducible case, include the code version, synthetic initial state, proposal, interruption point, exit codes, and before-and-after inspections. A terminal screenshot helps communicate; the files and commands make verification possible. Remove credentials and real data before opening an issue.&lt;/p&gt;

&lt;p&gt;The public evidence associated with this article contains results from both scenarios. The invitation is to discuss a concrete contract: which observation permits reconciliation, which permits another attempt, and which requires investigation? A small contribution, such as adding a failure case with an expected outcome, can be more useful than adding another tool to a catalog.&lt;/p&gt;

&lt;p&gt;If you want to experiment, run both scenarios in the repository and describe which failure boundary you would test next. To bring this into your AWS architecture, start with one mutable operation and document its idempotency and recovery contract. The test then becomes a property the team can review, beyond a generic agent demonstration.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fernando.moretes.com/community-evidence/2026-10-27-recovery-lab.json" rel="noopener noreferrer"&gt;Observed local results: October 27, 2026 (JSON)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fernando-moretes/lab-security-remediation/blob/v0.4.0/DURABILITY.md" rel="noopener noreferrer"&gt;Source and exercise, lab v0.4.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fernando-moretes/lab-security-remediation/blob/v0.4.0/test/durable.test.mjs" rel="noopener noreferrer"&gt;Executable recovery tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/" rel="noopener noreferrer"&gt;AWS Builders' Library: Making retries safe with idempotent APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sqlite.org/lang_transaction.html" rel="noopener noreferrer"&gt;SQLite transactions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/api/sqlite.html" rel="noopener noreferrer"&gt;Node.js SQLite API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The question to bring to your review
&lt;/h2&gt;

&lt;p&gt;Before authorizing another attempt, ask for evidence that distinguishes a confirmed effect, an absent effect, and an unknown effect. In this lab, a receipt allows reconciliation without another execution; its absence in the presence of drift requires stopping. The value of the exercise is making that decision reproducible and reviewable by someone else.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/agente-caiu-depois-da-mudanca-retry-recuperacao" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>idempotency</category>
      <category>crashrecovery</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Migrating agents from GPT-5.6 Terra to GPT-6.1 Terra on Bedrock</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Tue, 27 Oct 2026 16:46:37 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/migrating-agents-from-gpt-56-terra-to-gpt-61-terra-on-bedrock-1jod</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/migrating-agents-from-gpt-56-terra-to-gpt-61-terra-on-bedrock-1jod</guid>
      <description>&lt;p&gt;The GPT-6.1 Terra announcement on Amazon Bedrock, on 10/26/2026, sells the switch as obvious: 15 percentage points above GPT-5.6 Terra on OpenAI's Agents' Last Exam and token prices at least 50% lower. The question I care about is not whether the new model is better, it's what breaks in your AWS account the day you change the model ID. I read the model card line by line against the 5.6 Terra card, and the answer has names: source Region, API choice, caching and quota. This is the path I would follow to migrate a production agent without learning any of that on call.&lt;/p&gt;

&lt;h2&gt;
  
  
  The starting point
&lt;/h2&gt;

&lt;p&gt;The scenario I use as a reference is common in financial environments: an internal agent that reads policies, queries two or three systems through tool-use and returns structured JSON to a Step Functions workflow. It runs on &lt;code&gt;bedrock-runtime&lt;/code&gt; with the &lt;code&gt;us.openai.gpt-5.6-terra&lt;/code&gt; profile, because the risk team does not accept global routing, and the US geographic profile, sourced from N. Virginia, stays within &lt;code&gt;us-east-1&lt;/code&gt;, &lt;code&gt;us-east-2&lt;/code&gt; and &lt;code&gt;us-west-2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A typical run has about 40 thousand input tokens, 30 thousand of which are a stable prefix (instructions, tool definitions, policy excerpts), and 3 thousand output tokens. At 5.6 Terra list price on US CRIS (US$ 2.20 per million input, US$ 0.22 per million cache read and US$ 13.20 per million output), that run costs roughly US$ 0.068 in steady state. It looks small until you multiply by 200 thousand runs a month: about US$ 13.6 thousand in inference alone.&lt;/p&gt;

&lt;p&gt;These numbers are arithmetic on published prices, not customer measurements. They exist to size the decision. What actually matters is the calendar: the 5.6 Terra model card guarantees EOL no sooner than 07/13/2027, with a legacy period of at least six months. There is time, but there is no slack for a rushed migration in the last quarter, which is exactly when the team is busy with something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the model card says and the announcement doesn't
&lt;/h2&gt;

&lt;p&gt;The announcement talks about capability, speed and price. The model card talks about operations, and that is where the migration risk lives. Reading both cards side by side, four differences change the design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Region on &lt;code&gt;bedrock-mantle&lt;/code&gt;:&lt;/strong&gt; 5.6 Terra runs in-Region on the &lt;code&gt;bedrock-mantle&lt;/code&gt; endpoint in &lt;code&gt;us-east-1&lt;/code&gt;, &lt;code&gt;us-east-2&lt;/code&gt;, &lt;code&gt;us-west-2&lt;/code&gt; and both GovCloud Regions. 6.1 Terra, at launch, only in &lt;code&gt;us-east-1&lt;/code&gt;. If you point the OpenAI SDK at &lt;code&gt;bedrock-mantle.us-west-2.api.aws&lt;/code&gt;, you don't migrate by changing a string.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Region on &lt;code&gt;bedrock-runtime&lt;/code&gt;:&lt;/strong&gt; the 6.1 Terra table does not list &lt;code&gt;sa-east-1&lt;/code&gt; or &lt;code&gt;me-central-1&lt;/code&gt; as a source for the global profile, and it does not include the &lt;code&gt;in.&lt;/code&gt; geographic profile that 5.6 Terra offers for Mumbai and Hyderabad. If you call from São Paulo, this decides where the workload's client has to run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invoke API:&lt;/strong&gt; the 6.1 Terra API table marks &lt;code&gt;InvokeModel&lt;/code&gt; as supported on &lt;code&gt;bedrock-runtime&lt;/code&gt;; the 5.6 Terra table marks it as unsupported. That helps teams with legacy Invoke code, but it doesn't change the Guardrails and application inference profile restrictions, which remain tied to Converse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service tiers:&lt;/strong&gt; Standard only. No Priority, Flex or Reserved. If your capacity plan depended on reserving throughput, it does not exist for this model today.&lt;/p&gt;

&lt;p&gt;None of this is in the What's New post. An announcement is a trigger; the operational contract lives in the documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The journey, in six decisions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inventory how the model is called today&lt;/strong&gt;: Before touching code, pull four facts per application from invocation logs and CloudTrail: endpoint (&lt;code&gt;bedrock-runtime&lt;/code&gt; or &lt;code&gt;bedrock-mantle&lt;/code&gt;), API (Converse, Responses, Chat Completions), source Region and input size distribution. CloudTrail's &lt;code&gt;additionalEventData.inferenceRegion&lt;/code&gt; field, logged in the source Region, shows where each request was actually processed. Count how many requests exceed 272 thousand tokens: they move to a different price band.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prepare IAM and SCPs before the first request&lt;/strong&gt;: The model card requires &lt;code&gt;bedrock:InvokeModel&lt;/code&gt; on the account's default project (&lt;code&gt;arn:aws:bedrock:{region}:{account-id}:project/default&lt;/code&gt;) in addition to the inference profile. Missing it produces an &lt;code&gt;AccessDeniedException&lt;/code&gt; that looks like a credentials bug. For a geographic profile, the SCP must allow every destination Region in the profile; for a global profile, it must allow &lt;code&gt;aws:RequestedRegion&lt;/code&gt; equal to &lt;code&gt;unspecified&lt;/code&gt;, which many landing zones block by default.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Evaluate offline with real traffic&lt;/strong&gt;: Replay 300 to 500 logged, already sanitized runs against &lt;code&gt;us.openai.gpt-6.1-terra&lt;/code&gt;. Compare three signals per task: valid JSON rate against the schema with &lt;code&gt;strict: true&lt;/code&gt;, correctness of tool choice and arguments, and output tokens per task. OpenAI's benchmark is a hypothesis; your workload is the proof.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reposition the cache&lt;/strong&gt;: On Responses, mark the end of the stable prefix with &lt;code&gt;prompt_cache_breakpoint&lt;/code&gt; and use &lt;code&gt;prompt_cache_options.mode: explicit&lt;/code&gt; so the automatic breakpoint doesn't consume the four writes per request. The minimum prefix is 1,024 tokens and the TTL is 30 minutes. Track &lt;code&gt;cached_tokens&lt;/code&gt; against &lt;code&gt;cache_write_tokens&lt;/code&gt;: writes cost 1.25x uncached input.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Canary with a written promotion criterion&lt;/strong&gt;: Split traffic by flag (5%, 25%, 100%), with the model ID coming from configuration, not code. Write down beforehand what promotes and what rolls back: schema failures no higher than baseline, equal or lower p95 latency, cost per run and &lt;code&gt;InvocationThrottles&lt;/code&gt; within budget. A criterion decided after the result is rationalization.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Retire the old path on purpose&lt;/strong&gt;: Keep the 5.6 Terra profile as rollback for 30 days after reaching 100%. Then remove it from the IAM policy. A forgotten permission becomes an escape hatch: someone falls back to the old model in a hotfix and nobody notices until the invoice.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Converse or Responses: the decision that blocks the migration
&lt;/h2&gt;

&lt;p&gt;On &lt;code&gt;bedrock-runtime&lt;/code&gt;, the 6.1 Terra model card ties features to different APIs, and they don't add up. Guardrails only work through Converse. Application inference profiles, which is how you attribute cost per team with tags, are also Converse only. Explicit caching, on the other hand, only exists on Responses. On Converse you get implicit caching, which the documentation itself calls best effort.&lt;/p&gt;

&lt;p&gt;Converse has two more restrictions: it doesn't accept &lt;code&gt;document&lt;/code&gt; blocks, and it rejects an image inside &lt;code&gt;toolResult&lt;/code&gt; with a &lt;code&gt;ValidationException&lt;/code&gt;. An agent that returns a tool screenshot to the model breaks on the first call.&lt;/p&gt;

&lt;p&gt;The cost of this choice can be calculated. In the reference scenario, the 30-thousand-token prefix costs US$ 0.033 uncached and US$ 0.00165 as a cache read, at US$ 1.10 and US$ 0.055 per million. Every miss costs 20 times more on the prefix. If implicit caching hits 60% and explicit hits 95%, the gap across 200 thousand runs a month is close to US$ 2.2 thousand.&lt;/p&gt;

&lt;p&gt;Use Converse when AWS Guardrails and cost allocation by application profile are audit requirements, and measure the real hit rate through &lt;code&gt;cacheReadInputTokens&lt;/code&gt; before accepting the loss. Use Responses when the prefix is large and stable and content filtering already happens in another layer you control. Running half the fleet on each API without deciding is the worst case: two audit trails, two cost models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Canary migration backed by evidence
&lt;/h2&gt;

&lt;p&gt;The diagram answers one question: where control of the model switch lives. The router reads the model ID from a flag and splits traffic between the two US CRIS profiles. Dashed arrows are asynchronous: invocation logs arrive later, and promotion is a human decision based on the harness, not an automatic trigger. IAM, SCPs and the profile's destination Regions are simplified away.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Agent service ECS / Lambda (compute)&lt;/li&gt;
&lt;li&gt;Model router AppConfig flag (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Amazon Bedrock runtime
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GPT-5.6 Terra us.openai.gpt-5.6-terra (ai)&lt;/li&gt;
&lt;li&gt;GPT-6.1 Terra us.openai.gpt-6.1-terra (ai)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Evidence
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Invocation logs S3 + CloudWatch (storage)&lt;/li&gt;
&lt;li&gt;Eval harness replay + diff (ci)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;agent -&amp;gt; router: asks for model&lt;/li&gt;
&lt;li&gt;router -&amp;gt; old: 95% Converse&lt;/li&gt;
&lt;li&gt;router -&amp;gt; new: 5% canary&lt;/li&gt;
&lt;li&gt;old -&amp;gt; logs: logs&lt;/li&gt;
&lt;li&gt;new -&amp;gt; logs: logs&lt;/li&gt;
&lt;li&gt;logs -&amp;gt; eval: replays&lt;/li&gt;
&lt;li&gt;eval -&amp;gt; router: promotes or rolls back&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Before and after, at US CRIS list price
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;-50%&lt;/strong&gt;: Input per million tokens. US$ 2.20 on 5.6 Terra to US$ 1.10 on 6.1 Terra, context up to 272 thousand tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;-58%&lt;/strong&gt;: Output per million tokens. US$ 13.20 to US$ 5.50; cache reads drop 75%, from US$ 0.22 to US$ 0.055&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;-57%&lt;/strong&gt;: Cost per agent run. From ~US$ 0.068 to ~US$ 0.029 in the 40 thousand input (30 thousand cached) and 3 thousand output scenario, if output volume doesn't grow&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quota, long context and the cost that shows up later
&lt;/h2&gt;

&lt;p&gt;The price cut is the easy part. Quota is what sinks the migration. On &lt;code&gt;bedrock-runtime&lt;/code&gt;, limits are measured in tokens per minute with a 10x burndown on output: each output token consumes 10 from quota. Tokens read from cache don't count toward the input quota. In the reference scenario, one run consumes 10 thousand uncached input tokens plus 30 thousand output-equivalent tokens. Three quarters of the quota goes to output.&lt;/p&gt;

&lt;p&gt;This creates a silent failure mode. If the new model answers with 20% more output tokens, because it reasons more or writes more, cost still drops substantially and no FinOps alarm fires. Quota, however, rises 15% per run, and the first signal is a &lt;code&gt;ThrottlingException&lt;/code&gt; at the morning peak. That's why the canary watches &lt;code&gt;OutputTokenCount&lt;/code&gt; and &lt;code&gt;InvocationThrottles&lt;/code&gt; in CloudWatch, not just the invoice.&lt;/p&gt;

&lt;p&gt;The second late cost is long context. 6.1 Terra accepts a 1,050,000-token context (OpenAI's page states 922 thousand maximum input), but above 272 thousand input tokens the whole request moves to another band: input at US$ 2.20 and output at US$ 8.25 per million on US CRIS. An agent that accumulates history without summarizing crosses that line around turn twenty and doubles the input cost of every call from then on. Set an explicit history ceiling, around 250 thousand tokens, and summarize above it. Having a 1-million-token context doesn't oblige anyone to use 1 million.&lt;/p&gt;

&lt;h2&gt;
  
  
  Each model's operational contract
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;GPT-5.6 Terra&lt;/th&gt;
&lt;th&gt;GPT-6.1 Terra&lt;/th&gt;
&lt;th&gt;GPT-6.1 Sol&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input / output US CRIS (US$ per million)&lt;/td&gt;
&lt;td&gt;2.20 / 13.20&lt;/td&gt;
&lt;td&gt;1.10 / 5.50&lt;/td&gt;
&lt;td&gt;2.20 / 11.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache read (US$ per million)&lt;/td&gt;
&lt;td&gt;0.22&lt;/td&gt;
&lt;td&gt;0.055&lt;/td&gt;
&lt;td&gt;0.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In-Region on &lt;code&gt;bedrock-mantle&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;us-east-1, us-east-2, us-west-2, GovCloud&lt;/td&gt;
&lt;td&gt;us-east-1&lt;/td&gt;
&lt;td&gt;us-east-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;InvokeModel&lt;/code&gt; on &lt;code&gt;bedrock-runtime&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guardrails on &lt;code&gt;bedrock-runtime&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Converse only&lt;/td&gt;
&lt;td&gt;Converse only&lt;/td&gt;
&lt;td&gt;Chat Completions, InvokeModel and Converse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tiers beyond Standard&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Ultrafast (6x Standard)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lifecycle&lt;/td&gt;
&lt;td&gt;EOL no sooner than 07/13/2027&lt;/td&gt;
&lt;td&gt;EOL no sooner than 10/26/2027&lt;/td&gt;
&lt;td&gt;OpenAI terms, at least 6 months' notice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Risks I would handle before the canary:&lt;/strong&gt; &lt;strong&gt;São Paulo as source:&lt;/strong&gt; the 6.1 Terra table doesn't list &lt;code&gt;sa-east-1&lt;/code&gt; as a global profile source; if you call from there, move the client or wait. &lt;strong&gt;GovCloud and &lt;code&gt;bedrock-mantle&lt;/code&gt; outside &lt;code&gt;us-east-1&lt;/code&gt;:&lt;/strong&gt; no path at launch. &lt;strong&gt;Longer output:&lt;/strong&gt; with a 10x burndown, 20% more output becomes 15% more quota per run in the reference scenario, with no cost alarm. &lt;strong&gt;The 272-thousand-token line:&lt;/strong&gt; cross it and the whole request pays the long-context band. &lt;strong&gt;Vendor benchmark:&lt;/strong&gt; the 15 points and "three times faster" come from OpenAI's Agents' Last Exam, not from your workload.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Anti-patterns in this migration
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Changing the model ID and calling it a migration&lt;/strong&gt;: without an inventory of endpoint, API and source Region, the first failure comes from &lt;code&gt;bedrock-mantle&lt;/code&gt; in &lt;code&gt;us-west-2&lt;/code&gt; or a client in &lt;code&gt;sa-east-1&lt;/code&gt;, not from the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Promoting on the vendor benchmark&lt;/strong&gt;: Agents' Last Exam doesn't know your schema or your tools; promotion without replaying real traffic is a bet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measuring only the invoice&lt;/strong&gt;: cost drops even if output grows, and a quota with 10x burndown runs out before any FinOps alarm fires.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Switching to the global profile to dodge the Region table&lt;/strong&gt;: it fixes availability and creates a data residency problem, and it requires &lt;code&gt;aws:RequestedRegion&lt;/code&gt; &lt;code&gt;unspecified&lt;/code&gt; in the SCP.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Curator's note:&lt;/strong&gt; I would start this migration in November, not in June 2027. The first week would go entirely to the inventory and to the Converse versus Responses decision, because that decision defines auditing, caching and cost attribution for the next two years. After 16 years operating financial platforms, the lesson that keeps repeating in dependency swaps is this: the cost of a migration is not swapping the component, it's discovering late the contract that changed with it. With language models that doubles, because behavior changes too and no unit test catches it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/10/openai-gpt-6-1-terra-on-amazon-bedrock/" rel="noopener noreferrer"&gt;AWS What's New: OpenAI GPT-6.1 Terra is now generally available on Amazon Bedrock&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-openai-gpt-6-1-terra.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: GPT-6.1 Terra model card&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-openai-gpt-56-terra.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: GPT-5.6 Terra model card&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-openai-gpt-6-1-sol.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: GPT-6.1 Sol model card&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: Prompt caching for faster model inference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html" rel="noopener noreferrer"&gt;Amazon Bedrock User Guide: Cross-Region inference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/api/docs/models/gpt-6.1-terra" rel="noopener noreferrer"&gt;OpenAI API docs: GPT-6.1 Terra model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.classmethod.jp/en/articles/bedrock-openai-gpt61-terra/" rel="noopener noreferrer"&gt;Classmethod DevelopersIO: I tried the new GPT-6.1 model Terra on Amazon Bedrock&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Migrate, and start now. Use GPT-6.1 Terra when your workload runs on &lt;code&gt;bedrock-runtime&lt;/code&gt; with a &lt;code&gt;us.&lt;/code&gt; or &lt;code&gt;global.&lt;/code&gt; profile from a source Region listed in the model card, when replaying real traffic shows equal or better schema and tool-use results, and when quota can absorb the new output under a 10x burndown. Stay on 5.6 Terra for now if you depend on &lt;code&gt;bedrock-mantle&lt;/code&gt; outside &lt;code&gt;us-east-1&lt;/code&gt;, on GovCloud, or on &lt;code&gt;sa-east-1&lt;/code&gt; as a source with residency requirements; the 07/13/2027 EOL leaves room to wait for the Region table to grow. Either way, decide Converse or Responses before the canary: that is the choice you will maintain, audit and pay for over the next two years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Migrar com canário / Migrate with a cana&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/migrando-agentes-do-gpt-5-6-terra-para-o-gpt-6-1-terra-no-bedrock" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>amazonbedrock</category>
      <category>openai</category>
      <category>llmmigration</category>
    </item>
    <item>
      <title>Amazon Quick on a private network: PrivateLink's two planes and where they leak</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Fri, 23 Oct 2026 15:48:23 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/amazon-quick-on-a-private-network-privatelinks-two-planes-and-where-they-leak-2lk</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/amazon-quick-on-a-private-network-privatelinks-two-planes-and-where-they-leak-2lk</guid>
      <description>&lt;p&gt;When someone from compliance asks whether Amazon Quick "goes over the internet", the honest answer has two parts, and almost every design I have reviewed answers only one. User traffic into Quick uses PrivateLink interface endpoints. Quick's traffic out to your on-premises database, your private MCP server or your internal API uses something else: ENIs that Quick itself injects into your VPC. DNS, endpoint policy, IP/VPC restriction and IAM sit on top of those two planes, and each layer covers less than its name suggests. The useful question is not "is it private?", it's "which byte still leaves for the internet, and who decided that was fine?".&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;p&gt;While Quick was just BI, the VPC connection existed so Quick Sight could read PostgreSQL, SQL Server or Oracle behind a Direct Connect. The problem was well understood and the blast radius fit inside a dashboard. What changed is who uses that same VPC connection. It now carries Quick Agents calling MCP servers hosted on your network, Quick Flows and Quick Automate invoking action connectors, and Quick Research and Apps in Quick pulling from private sources. The ENI that used to run a &lt;code&gt;SELECT&lt;/code&gt; for a chart now carries an agent's tool call.&lt;/p&gt;

&lt;p&gt;In financial environments this matters in two places. &lt;strong&gt;Regulation:&lt;/strong&gt; CMN Resolution 4.893 and PCI-DSS expect you to show the data path and control who enters the perimeter. The AWS post lists HIPAA, FedRAMP and PCI-DSS as the usual triggers. &lt;strong&gt;Audit:&lt;/strong&gt; "traffic stays on the AWS backbone" only works as evidence if you can say which domains still leave through NAT, and some do.&lt;/p&gt;

&lt;p&gt;The 21 Oct 2026 post is a good walkthrough. What it doesn't do, and isn't meant to, is say where each control stops working. That's what this piece covers: the documented limits of each layer, and the cost of keeping all of it working for years, not the cost of building it once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two planes of private Quick access, and what stays public
&lt;/h2&gt;

&lt;p&gt;Ingress (user to Quick) over interface endpoints. Egress (Quick to your data) over VPC connection ENIs. Dashed lines are traffic no endpoint covers.&lt;/p&gt;

&lt;h3&gt;
  
  
  🏢 On-premises: rede corporativa
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;DNS corporativo forward de .quicksight.aws.amazon.com (network)&lt;/li&gt;
&lt;li&gt;Oracle / SQL Server porta do banco liberada só para o SG do Quick (data)&lt;/li&gt;
&lt;li&gt;Servidor MCP privado API interna por IP (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📡 Rede: Direct Connect ou Site-to-Site VPN
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Direct Connect rota para o CIDR da VPC (network)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS: VPC do cliente (2 AZs no mínimo)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Route 53 Resolver inbound 2+ ENIs (network)&lt;/li&gt;
&lt;li&gt;VPCE quicksight-website policy só com aws:PrincipalAccount / aws:OrgId (security)&lt;/li&gt;
&lt;li&gt;VPCE quicksight (API) policy por ação; 13 APIs fora (security)&lt;/li&gt;
&lt;li&gt;ENIs da VPC connection SG QuickSight-VPC, VPC ID imutável (network)&lt;/li&gt;
&lt;li&gt;VPCE secretsmanager / redshift / glue S3 por gateway (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🤖 AWS: Amazon Quick (Enterprise)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Quick Sight, Agents, Flows, Automate Identity Center, RLS/CLS (ai)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🌐 Externo: saída pública que continua obrigatória
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;*.cloudfront.net, *.execute-api, *.kinesisvideo sign-in do Identity Center (external)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;user -&amp;gt; dns: resolves the site name&lt;/li&gt;
&lt;li&gt;dns -&amp;gt; resolver: forwards the private zone&lt;/li&gt;
&lt;li&gt;user -&amp;gt; dx: HTTPS 443&lt;/li&gt;
&lt;li&gt;dx -&amp;gt; vpce-web: console and dashboards&lt;/li&gt;
&lt;li&gt;dx -&amp;gt; vpce-api: API calls&lt;/li&gt;
&lt;li&gt;vpce-web -&amp;gt; quick: PrivateLink (ingress)&lt;/li&gt;
&lt;li&gt;vpce-api -&amp;gt; quick: PrivateLink (ingress)&lt;/li&gt;
&lt;li&gt;quick -&amp;gt; eni: assumes execution role, uses ENIs&lt;/li&gt;
&lt;li&gt;eni -&amp;gt; dx: data and tool-call egress&lt;/li&gt;
&lt;li&gt;dx -&amp;gt; db: SQL query&lt;/li&gt;
&lt;li&gt;dx -&amp;gt; mcp: action connector / MCP&lt;/li&gt;
&lt;li&gt;eni -&amp;gt; sm: credentials without leaving the VPC&lt;/li&gt;
&lt;li&gt;user -&amp;gt; public: assets, metrics, sign-in via NAT/proxy&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ingress plane: interface endpoints, DNS and what the policy accepts
&lt;/h2&gt;

&lt;p&gt;Ingress uses two interface endpoints with different jobs: &lt;code&gt;com.amazonaws.&amp;lt;region&amp;gt;.quicksight-website&lt;/code&gt; for the site and &lt;code&gt;com.amazonaws.&amp;lt;region&amp;gt;.quicksight&lt;/code&gt; for the API (with a &lt;code&gt;-fips&lt;/code&gt; variant). If administrators sign in as IAM users, add &lt;code&gt;console&lt;/code&gt; and &lt;code&gt;signin&lt;/code&gt; too. Place each one in at least two AZs, with a security group that accepts only 443 from the corporate CIDR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DNS is where the first thing breaks.&lt;/strong&gt; The API endpoint, with private DNS on, takes over &lt;code&gt;quicksight.&amp;lt;region&amp;gt;.amazonaws.com&lt;/code&gt; by itself. The website endpoint doesn't: its private name is not the public URL. You create a private hosted zone for &lt;code&gt;&amp;lt;region&amp;gt;.quicksight.aws.amazon.com&lt;/code&gt; with an alias A record pointing at the endpoint. But a private hosted zone only answers associated VPCs, so corporate DNS has to forward that zone to a Route 53 Resolver inbound endpoint. Without the forward, the browser resolves the public IP, access still works, and nobody notices PrivateLink is being bypassed. Test with &lt;code&gt;nslookup&lt;/code&gt; from inside the corporate network and expect an IP from the VPC CIDR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The website endpoint policy is almost decorative.&lt;/strong&gt; &lt;code&gt;Principal&lt;/code&gt;, &lt;code&gt;Action&lt;/code&gt; and &lt;code&gt;Resource&lt;/code&gt; must be &lt;code&gt;"*"&lt;/code&gt;, and the condition only accepts &lt;code&gt;aws:PrincipalAccount&lt;/code&gt; or &lt;code&gt;aws:OrgId&lt;/code&gt;. It stops your network from becoming a doorway into another account's Quick, which helps against exfiltration, but it doesn't control what the user does. The API endpoint accepts action-level restriction, and that's where being specific pays off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Egress plane: the VPC connection is an injected ENI, not PrivateLink
&lt;/h2&gt;

&lt;p&gt;When Quick needs to fetch data, it doesn't go through the interface endpoint. To create a VPC connection (Enterprise only), you supply a VPC ID, at least two subnets in different AZs, at least one security group, and an execution role that trusts &lt;code&gt;quicksight.amazonaws.com&lt;/code&gt;. Quick assumes that role and creates ENIs in your subnets with &lt;code&gt;ec2:CreateNetworkInterface&lt;/code&gt;. From then on your network decides: route table, NACL, security group and the Direct Connect route.&lt;/p&gt;

&lt;p&gt;Three details turn into incidents if you don't know them. &lt;strong&gt;The VPC ID can't change after creation:&lt;/strong&gt; reorganizing the network means recreating the connection and repointing every data source. &lt;strong&gt;The &lt;code&gt;PARTIALLY AVAILABLE&lt;/code&gt; status&lt;/strong&gt; means one ENI is down and the connection still works. It's exactly the state nobody watches until the second AZ fails. &lt;strong&gt;Connections created before 27 Apr 2023 use a non-stateful security group&lt;/strong&gt; and need an inbound rule that lets return traffic in on every port (0–65535) from the source's SG. If yours dates from then, recreate it.&lt;/p&gt;

&lt;p&gt;For on-premises database host names, enter the Resolver inbound IPs in the DNS resolver endpoints field. Without them Quick resolves through public DNS and can't find the host. Credentials go to Secrets Manager through an interface endpoint in the same VPC as the ENIs, so that even the password lookup doesn't leave through NAT.&lt;/p&gt;

&lt;p&gt;One limitation changes the design: the Quick Index knowledge base VPC connection currently supports only Amazon Redshift. Confluence, SharePoint, Box, Google Drive, OneDrive and S3 can't use it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The ENI security group is now the agent's network allow-list:&lt;/strong&gt; The same VPC connection serves Quick Sight and Quick Agents calling private MCP. In practice, the egress of the &lt;code&gt;QuickSight-VPC&lt;/code&gt; SG and the routes on those subnets define everything an agent can reach on your network. If the SG opens the whole on-premises CIDR because "BI needed it", the agent inherits that reach. Split subnets and security groups by purpose (analytical data on one side, action tools on the other), even if it costs a second VPC connection.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Enforcing endpoint-only access: where the restriction stops
&lt;/h2&gt;

&lt;p&gt;Quick's IP/VPC restriction (&lt;code&gt;update-ip-restriction --enabled --vpc-endpoint-id-restriction-rule-map vpce-...=Name&lt;/code&gt;) blocks anything that doesn't come from the approved endpoint. It has four properties you need to know before turning it on in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It covers web, embedded and mobile, but not the public API.&lt;/strong&gt; Anyone with credentials can call any operation from any IP. For the API the control is IAM: an &lt;code&gt;aws:SourceVpce&lt;/code&gt; or &lt;code&gt;aws:SourceIp&lt;/code&gt; condition in a deny policy, ideally in an SCP. Here's the catch: 13 APIs don't work through the interface endpoint (the 8 action connector ones, such as &lt;code&gt;CreateActionConnector&lt;/code&gt;, and 5 Flows ones, such as &lt;code&gt;ListFlows&lt;/code&gt;). A blanket &lt;code&gt;aws:SourceVpce&lt;/code&gt; deny breaks the automation that manages connectors. You write the exception with &lt;code&gt;NotAction&lt;/code&gt; for those operations and run that automation from a controlled path with public egress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;UpdateIpRestriction&lt;/code&gt; replaces the whole map.&lt;/strong&gt; A script that sends only the new rule deletes the others. Run &lt;code&gt;describe-ip-restriction&lt;/code&gt; first, or better, keep the map versioned and always apply the full state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A rule takes up to 10 minutes to apply and is global&lt;/strong&gt;, enforced in every Region and managed only from the account's Region. The limit is 100 rules combined, with IP and VPC rules OR'ed together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lockout is real.&lt;/strong&gt; Without a rule covering your own access, the console won't let you save. But anyone on a source outside the list loses access the moment you turn it on. Publicly embedded dashboards then work only through the endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What stays public, and what keeping the rest private costs
&lt;/h2&gt;

&lt;p&gt;The documentation lists the domains the browser reaches, and several have no endpoint: &lt;code&gt;*.cloudfront.net&lt;/code&gt; (CSS and JS), &lt;code&gt;*.execute-api.&amp;lt;region&amp;gt;.amazonaws.com&lt;/code&gt; (client metrics), &lt;code&gt;*.kinesisvideo.amazonaws.com&lt;/code&gt; (automation streaming), &lt;code&gt;&amp;lt;region&amp;gt;.signin.aws&lt;/code&gt; and, with IAM Identity Center, browser sign-in, which requires the public sign-in and Identity Center endpoints. No public IP is needed, but NAT or a proxy is. Your compliance report should say "the data and API path is private; static assets, telemetry and authentication leave through an inspected proxy". Writing "zero internet" claims something the documentation itself contradicts.&lt;/p&gt;

&lt;p&gt;Now the recurring cost, in &lt;code&gt;us-east-1&lt;/code&gt;. An interface endpoint costs US$ 0.01 per hour per ENI, plus US$ 0.01 per GB for the first 1 PB. Website and API in 2 AZs are 4 ENIs, about US$ 29/month. Add &lt;code&gt;console&lt;/code&gt; and &lt;code&gt;signin&lt;/code&gt;, another US$ 29. Secrets Manager, Redshift and Glue in 2 AZs, roughly US$ 44 more. The most expensive piece is the one nobody budgets: a Resolver inbound endpoint costs US$ 0.125 per ENI per hour, with at least two, so about US$ 182/month, plus US$ 0.40 per million queries. If you already have a shared Resolver inbound in a central network VPC, reuse it: it's the biggest line item and there's nothing Quick-specific about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-patterns I find in this design
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website endpoint without on-premises DNS forwarding:&lt;/strong&gt; PrivateLink exists, the browser resolves the public IP and access works from outside. It's a silent failure that only shows up in &lt;code&gt;nslookup&lt;/code&gt; or VPC Flow Logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treating IP/VPC restriction as an API control:&lt;/strong&gt; it doesn't cover the public API. Without &lt;code&gt;aws:SourceVpce&lt;/code&gt; in IAM or an SCP, a leaked credential calls everything from anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blanket &lt;code&gt;aws:SourceVpce&lt;/code&gt; deny with no exception:&lt;/strong&gt; the 13 action connector and Flows APIs don't go through the endpoint, and the automation that manages connectors stops working without an obvious error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A script that calls &lt;code&gt;UpdateIpRestriction&lt;/code&gt; with only the new rule:&lt;/strong&gt; the map is replaced whole, the other rules disappear and an entire branch office loses access within 10 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One VPC connection for BI and agents with the whole on-premises CIDR open:&lt;/strong&gt; the agent inherits BI's network reach, and the blast radius of a prompt injection becomes the corporate network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeping a VPC connection from before 27 Apr 2023:&lt;/strong&gt; the non-stateful SG requires return traffic open on 0–65535, a rule no auditor accepts without questions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Well-Architected lens
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;security&lt;/strong&gt;: Layered defense with clear roles: website endpoint policy scoped by &lt;code&gt;aws:OrgId&lt;/code&gt;, API endpoint policy by action, IP/VPC restriction for web and mobile, an SCP with &lt;code&gt;aws:SourceVpce&lt;/code&gt; and &lt;code&gt;NotAction&lt;/code&gt; for the 13 APIs outside the endpoint, RLS and CLS on the data. Restriction map changes land in CloudTrail; alarm on &lt;code&gt;UpdateIpRestriction&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reliability&lt;/strong&gt;: Two AZs on every endpoint and on the VPC connection, an alarm whenever status leaves &lt;code&gt;AVAILABLE&lt;/code&gt; (including &lt;code&gt;PARTIALLY AVAILABLE&lt;/code&gt;), and Direct Connect with a backup VPN, because the VPC connection has no other path to on-premises.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What I would do:&lt;/strong&gt; I'd start with the report I'll hand the auditor, and only then create the first endpoint: which domains leave through the proxy, which controls cover the API and which cover the browser. Then I'd build two security groups and two VPC connections, one for analytical data and one for agent tools, because I've seen a "temporary" BI security group last four years. The restriction map would go into code from day one, applied as full state. The lesson I carry from financial platforms: the expensive control isn't the one you configure, it's the one you think is active and isn't, and in Quick that control is DNS forwarding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Use the full design (website and API endpoints, VPC connection, Resolver inbound, IP/VPC restriction and an SCP with &lt;code&gt;aws:SourceVpce&lt;/code&gt;) when you have regulated on-premises data, agents calling private tools, and an auditor who will ask for the path of every byte. If Quick only reads Redshift and S3 in the same account and sign-in goes through Identity Center, a VPC connection with service endpoints covers most of the risk, and IP restriction on the corporate egress covers the browser for well under US$ 300/month. Either way, document what stays public. That sentence is what separates a defensible design from a promise the documentation contradicts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Recomendado com ressalvas / Recommended &lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/machine-learning/securing-quick-access-from-corporate-networks-with-aws-privatelink/" rel="noopener noreferrer"&gt;AWS Machine Learning Blog: Securing Quick access from corporate networks with AWS PrivateLink&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/quicksight/latest/developerguide/vpc-interface-endpoints.html" rel="noopener noreferrer"&gt;Quick and interface VPC endpoints (AWS PrivateLink)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/quicksight/latest/user/enabling-ip-restrictions.html" rel="noopener noreferrer"&gt;Managing IP/VPC restrictions in Amazon Quick&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/quick/latest/userguide/working-with-aws-vpc.html" rel="noopener noreferrer"&gt;Configuring VPC connections in Amazon Quick Sight&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/privatelink/pricing/" rel="noopener noreferrer"&gt;AWS PrivateLink pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/route53/pricing/" rel="noopener noreferrer"&gt;Amazon Route 53 pricing (Resolver endpoints)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/amazon-quick-em-rede-privada-os-dois-planos-do-privatelink-e-onde" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>amazonquick</category>
      <category>privatelink</category>
      <category>vpcendpoints</category>
    </item>
    <item>
      <title>After login: per-operation authorization, revocation and recovery for agent tools</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Wed, 21 Oct 2026 16:01:25 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/after-login-per-operation-authorization-revocation-and-recovery-for-agent-tools-45i0</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/after-login-per-operation-authorization-revocation-and-recovery-for-agent-tools-45i0</guid>
      <description>&lt;h2&gt;
  
  
  A valid login is only the starting point
&lt;/h2&gt;

&lt;p&gt;An agent may continue a conversation after a user loses a permission. The operational question is whether the next tool call can still change a resource. This article proposes an architecture exercise, not a report of an AWS deployment or a completed cloud experiment. The AWS scenarios below are test proposals; the local recovery lab has a narrower purpose.&lt;/p&gt;

&lt;p&gt;The starting points are Carlos Biagolini's articles on &lt;a href="https://builder.aws.com/content/3Kuk63NZmMt59SD8ONcrmAmf3mE/primeiros-passos-com-amazon-bedrock-agentcore-identity" rel="noopener noreferrer"&gt;AgentCore Identity&lt;/a&gt; and &lt;a href="https://builder.aws.com/content/3Ga6SwtCJCXBaQgy1sxBi5Qw8um/construindo-um-servidor-mcp-serverless-na-aws-com-autenticacao-iam" rel="noopener noreferrer"&gt;a serverless MCP server with IAM authentication&lt;/a&gt;. They motivate a useful follow-up: once identity is established, which component enforces the right to perform each operation, and what happens if execution is interrupted?&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate three decisions
&lt;/h2&gt;

&lt;p&gt;Authentication establishes the caller's identity. Authorization decides whether that caller may perform a particular operation on a particular resource and tenant. Approval records consent to a concrete proposed change. A valid token, a visible tool or an old approval does not answer all three questions.&lt;/p&gt;

&lt;p&gt;Imagine a remediation tool shared by two tenants. A caller may have permission to invoke the endpoint and still lack permission to change the selected resource. Similarly, approval of an earlier proposal should not authorize a modified proposal. These checks must remain explicit even when the model produces a convincing explanation.&lt;/p&gt;

&lt;h2&gt;
  
  
  A route is a boundary, not the whole policy
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-access-control.html" rel="noopener noreferrer"&gt;API Gateway HTTP APIs&lt;/a&gt; support IAM, JWT authorizers and Lambda authorizers. This exercise compares IAM-based invocation with application-defined Lambda authorization. With AWS_IAM, a signed request and invocation permission control entry. A Lambda authorizer applies application logic: format 1.0 requires an IAM policy; format 2.0 also supports a simple Boolean response when enabled.&lt;/p&gt;

&lt;p&gt;If several MCP tools share &lt;code&gt;POST /mcp&lt;/code&gt;, the tool name and arguments live in the body. Permission to invoke that route does not establish permission for every operation carried by it. In this design, the executor enforces fine-grained authorization. Another trusted layer could do so if it understands the operation, cannot be bypassed and must run before the effect. Separate routes help entry controls but do not remove resource and tenant checks.&lt;/p&gt;

&lt;p&gt;Do not accept a model-supplied tenant as proof of authority. In a JWT flow, use validated claims and their authorized relationship to the resource. In an IAM/SigV4 flow, use the authenticated principal and a service-controlled authorization mapping. Carry that trusted context to the execution boundary. A Lambda role's ability to access DynamoDB does not automatically prove that every caller may read every item the function can access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Revocation has an observable window
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_control-access_disable-perms.html" rel="noopener noreferrer"&gt;IAM documentation&lt;/a&gt; explains that temporary-credential permissions are evaluated for AWS requests, while policy updates may take minutes to propagate. Per-request evaluation therefore does not imply instantaneous revocation.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html" rel="noopener noreferrer"&gt;cached Lambda-authorizer result&lt;/a&gt; introduces another window. Include the cache key, route separation and TTL in the test design. Denying future work does not reverse a completed effect. Sensitive operations also need an explicit policy for in-flight work and a check near the effect boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approval and completion are different facts
&lt;/h2&gt;

&lt;p&gt;Bind approval to reviewer identity, proposal digest, scope, expiry and expected resource state. After a worker restart, a &lt;code&gt;pending&lt;/code&gt; record does not prove whether an effect happened. Look for durable target evidence before replaying. If evidence is insufficient, preserve uncertainty and request review instead of guessing.&lt;/p&gt;

&lt;p&gt;The public &lt;a href="https://github.com/fernando-moretes/lab-security-remediation/releases/tag/v0.4.0" rel="noopener noreferrer"&gt;v0.4.0 lab&lt;/a&gt; explores that recovery boundary with SQLite and a synthetic target. Its &lt;a href="https://github.com/fernando-moretes/lab-security-remediation/blob/v0.4.0/DURABILITY.md" rel="noopener noreferrer"&gt;reproduction guide&lt;/a&gt; requires Node.js 22.13+ and creates local files. No AWS account is needed. CLI approval is simulated; the lab does not implement authenticated approval, IAM or revocation. Atomic local target-and-receipt writes do not establish exactly-once effects across a remote API.&lt;/p&gt;

&lt;p&gt;In the documented exercise, a deliberate crash occurs after the synthetic target and receipt are committed but before the execution ledger acknowledges completion. Recovery uses that receipt to reconcile the operation. Keeping the original proposal matters: generating a new proposal after the target changes produces a different digest. This is a concrete way to discuss recovery without implying a cloud guarantee the exercise cannot demonstrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed test matrix
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Valid principal requests another tenant&lt;/strong&gt;: Deny before protected data access. Evidence: Synthetic identity and decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared MCP endpoint receives an unauthorized tool&lt;/strong&gt;: Deny at the operation boundary. Evidence: Tool name, scope and decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permission changes during a session&lt;/strong&gt;: Measure when new calls stop succeeding. Evidence: Change time and decision timeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorizer result is cached&lt;/strong&gt;: Test before and after TTL across routes and tenants. Evidence: Cache settings and observations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proposal changes after approval&lt;/strong&gt;: Reject the changed digest. Evidence: Original and changed synthetic digests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource changes before execution&lt;/strong&gt;: Reject the stale precondition. Evidence: Expected and observed version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Process stops after effect, before acknowledgement&lt;/strong&gt;: Reconcile using evidence. Evidence: Ledger, target version and receipt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For every scenario, distinguish expected from observed results. These AWS authorization tests are proposed, not completed. Use synthetic inputs and avoid storing tokens, secrets or customer data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs and the next experiment
&lt;/h2&gt;

&lt;p&gt;Fresh authorization checks add work and latency. Caching can reduce both while extending the stale-decision window. Choose the design using operation impact, revocation policy and measurements. A low-risk read and a destructive remediation may justify different controls.&lt;/p&gt;

&lt;p&gt;Before adding another agent, ask the team to demonstrate which identity drives the decision, which component enforces it and how interrupted work is recovered. A future isolated experiment can add authenticated approval and revocation, with a cost ceiling and cleanup procedure defined before deployment.&lt;/p&gt;

&lt;p&gt;What would you test first: cross-tenant access, revocation during a session, or recovery after the effect? A useful answer includes the evidence needed to prove the outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-access-control.html" rel="noopener noreferrer"&gt;HTTP API access controls&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-access-control-iam.html" rel="noopener noreferrer"&gt;IAM authorization for HTTP APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html" rel="noopener noreferrer"&gt;Lambda authorizers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_control-access_disable-perms.html" rel="noopener noreferrer"&gt;Temporary-credential permissions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://builder.aws.com/content/3Kuk63NZmMt59SD8ONcrmAmf3mE/primeiros-passos-com-amazon-bedrock-agentcore-identity" rel="noopener noreferrer"&gt;AgentCore Identity article&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://builder.aws.com/content/3Ga6SwtCJCXBaQgy1sxBi5Qw8um/construindo-um-servidor-mcp-serverless-na-aws-com-autenticacao-iam" rel="noopener noreferrer"&gt;Serverless MCP and IAM article&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fernando-moretes/lab-security-remediation/blob/v0.4.0/DURABILITY.md" rel="noopener noreferrer"&gt;Local recovery exercise and limitations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Authorization boundaries
&lt;/h2&gt;

&lt;p&gt;Proposed design; identity and policy must reach the effect boundary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trusted application boundary
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Authenticated caller (user)&lt;/li&gt;
&lt;li&gt;Route entry control (edge)&lt;/li&gt;
&lt;li&gt;Tool contract (compute)&lt;/li&gt;
&lt;li&gt;Operation + resource + tenant (security)&lt;/li&gt;
&lt;li&gt;Proposal approval (security)&lt;/li&gt;
&lt;li&gt;Constrained executor (compute)&lt;/li&gt;
&lt;li&gt;Durable evidence (storage)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;caller -&amp;gt; entry&lt;/li&gt;
&lt;li&gt;entry -&amp;gt; contract&lt;/li&gt;
&lt;li&gt;contract -&amp;gt; policy&lt;/li&gt;
&lt;li&gt;policy -&amp;gt; approval&lt;/li&gt;
&lt;li&gt;approval -&amp;gt; executor&lt;/li&gt;
&lt;li&gt;executor -&amp;gt; receipt&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  C4
&lt;/h2&gt;

&lt;p&gt;A user requests remediation through an agent; the model proposes, the application enforces boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Containers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API / MCP endpoint&lt;/strong&gt; (API Gateway + application): Authenticates the caller and controls entry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool executor&lt;/strong&gt; (Application service): Checks operation, resource, tenant and approval before the effect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence store&lt;/strong&gt; (SQLite in local lab): Preserves synthetic proposal, state and receipt for recovery.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Policy check&lt;/strong&gt;: Uses trusted context independent of model arguments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recovery reconciler&lt;/strong&gt;: Reconciles using evidence; preserves uncertainty without proof.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AWS Well-Architected
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;security&lt;/strong&gt;: Deny out-of-scope operations before effects; do not trust model-supplied tenant identity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reliability&lt;/strong&gt;: Recover using receipts and preconditions without promising remote exactly-once effects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;operational-excellence&lt;/strong&gt;: Record expected and observed results with versions and synthetic evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cost&lt;/strong&gt;: Run the local lab first; define a ceiling and cleanup before an AWS experiment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checklist before automation
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bind identity, digest, scope and expiry to approval.&lt;/strong&gt;: Document the evidence, owner and known limitation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test the wrong tenant before data access.&lt;/strong&gt;: Document the evidence, owner and known limitation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Measure propagation and caching during revocation.&lt;/strong&gt;: Document the evidence, owner and known limitation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simulate a crash after the effect and inspect the receipt.&lt;/strong&gt;: Document the evidence, owner and known limitation.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Anti-patterns
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Treating authentication or old approval as universal authorization.&lt;/li&gt;
&lt;li&gt;Repeating an effect merely because the ledger still says pending.&lt;/li&gt;
&lt;li&gt;Promising instant revocation or remote exactly-once execution without measurement.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/depois-do-login-autorizacao-revogacao-recuperacao-agentes" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>securityresilience</category>
      <category>aws</category>
      <category>iam</category>
      <category>agenticai</category>
    </item>
    <item>
      <title>Retro: EMR Serverless on IPv6-only and the subnet left without DNS64</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Tue, 20 Oct 2026 15:42:40 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/retro-emr-serverless-on-ipv6-only-and-the-subnet-left-without-dns64-355a</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/retro-emr-serverless-on-ipv6-only-and-the-subnet-left-without-dns64-355a</guid>
      <description>&lt;p&gt;The dangerous question about IPv6 on EMR Serverless is not "do my jobs run on IPv6?", it is "what does each subnet in the application know how to do when a job asks for an address that only exists in IPv4?". The 10/16/2026 announcement is short: Spark and Hive can now run on dual-stack or IPv6-only subnets, at no additional cost, in every Region and release version of the service. The documentation is more honest than the announcement: the addressing mode is derived from the subnets, and getting IPv6 traffic to your data is your responsibility. This piece is a pre-emptive retro, an incident that did not happen, built from the documentation and the failure modes it lists itself. I would rather write the postmortem before than after.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario: /22 subnets that no longer fit three applications
&lt;/h2&gt;

&lt;p&gt;The starting point is what AWS itself uses as a planning example. Each EMR Serverless worker consumes an IP address in the subnet where it launches, through an ENI. An application with 4 vCPU workers and a 4,000 vCPU ceiling reaches 1,000 workers, meaning 1,000 ENIs. A /22 subnet has 1,024 addresses and 1,019 usable ones, because AWS reserves five. With three applications sharing the same subnets and a 12,000 vCPU account quota, the documentation's recommendation becomes 3,000 free addresses per subnet, in at least two Availability Zones.&lt;/p&gt;

&lt;p&gt;In a bank's data platform this usually hits a wall: the VPC IPv4 block was negotiated with corporate networking years ago, does not overlap with on-premises and does not grow without an approval round. That is exactly the case IPv6 solves. A VPC IPv6 subnet is a /64, and the address conversation is over.&lt;/p&gt;

&lt;p&gt;In the scenario, the team goes straight to IPv6-only on three subnets, one per AZ, created by an IaC module. Two were built by hand during the pilot and got DNS64. The third came from the module, which did not know DNS64 existed. No integration test caught it, because the test job ran twice and landed on the two good subnets both times.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers that drive the decision
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1.000&lt;/strong&gt;: ENIs per application. 4 vCPU workers with a 4,000 vCPU ceiling: one IP per worker&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1.019&lt;/strong&gt;: Usable IPs in a /22. Barely fits one application; three need 3,000 per subnet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;US$ 0,045/GB&lt;/strong&gt;: NAT gateway processing. Pricing page example (US East Ohio): every byte through NAT64 pays&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Timeline of the simulated incident
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;D-7: subnet swap&lt;/strong&gt;: The team stops the application, runs &lt;code&gt;update-application&lt;/code&gt; with the three IPv6-only subnets in &lt;code&gt;--network-configuration&lt;/code&gt; and starts it again. The mode is evaluated at every start, so the change only applies after the restart. Two test jobs pass.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;02:00: the nightly batch starts&lt;/strong&gt;: About a third of the jobs fail with &lt;code&gt;UnknownHostException&lt;/code&gt; when calling endpoints that only publish an A record. Each job runs in a single subnet picked from the set, and the ones that landed on the subnet without DNS64 cannot resolve anything that is not IPv6.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;02:40: retries hide the pattern&lt;/strong&gt;: The orchestrator's retry policy resubmits the jobs. Some pass because they land on another subnet. The failure looks intermittent, and intermittent becomes "flaky network" in the on-call's first hypothesis.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;04:10: correlation by subnet&lt;/strong&gt;: Someone opens each job run detail, which shows the subnet used, and crosses it with success and failure. 100% of the failures sit on the same subnet. &lt;code&gt;modify-subnet-attribute --enable-dns64&lt;/code&gt; fixes name resolution.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;05:00: the second failure&lt;/strong&gt;: An ingestion job with an old JDBC driver fails with &lt;code&gt;UnresolvedAddressException&lt;/code&gt;. It carried &lt;code&gt;-Djava.net.preferIPv4Stack=true&lt;/code&gt; in &lt;code&gt;spark.driver.extraJavaOptions&lt;/code&gt;, a workaround from years ago. The documentation forbids forcing the IPv4 stack in any mode.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;D+5: the bill arrives&lt;/strong&gt;: Cost Explorer shows a jump in NAT gateway data processing. S3 reads, which used to leave through the gateway endpoint, now resolve through DNS64 to &lt;code&gt;64:ff9b::/96&lt;/code&gt; and cross NAT64.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; EMR Serverless derives the addressing mode from the subnets and does not validate whether the network is ready for it. Each subnet's readiness (IPv6 CIDR, auto-assign, DNS64, a &lt;code&gt;64:ff9b::/96&lt;/code&gt; route to the NAT gateway, a &lt;code&gt;::/0&lt;/code&gt; route to the egress-only gateway) was not encoded in the IaC module, and one-subnet-per-job selection turned a deterministic defect into an apparently random failure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why the failure looked random: the addressing mode rules
&lt;/h2&gt;

&lt;p&gt;It is worth understanding the rules before blaming the network. An IPv4-only subnet runs as it always did. A dual-stack subnet runs IPv4 until you turn on &lt;code&gt;spark.emr-serverless.networking.ipv6.enabled=true&lt;/code&gt; in the application's &lt;code&gt;spark-defaults&lt;/code&gt; (for Hive, &lt;code&gt;hive.emr-serverless.networking.ipv6.enabled&lt;/code&gt; in &lt;code&gt;hive-site&lt;/code&gt;). An IPv6-only subnet runs IPv6 with no property at all.&lt;/p&gt;

&lt;p&gt;When the set mixes types, the whole application runs in the one mode every subnet supports. IPv4-only with IPv6-only is rejected at validation. IPv6-only with dual-stack runs everything IPv6-only, including the dual-stack subnets, which must then meet the same prerequisites. IPv4-only with dual-stack runs everything on IPv4.&lt;/p&gt;

&lt;p&gt;Three details produce incidents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The mode applies at start:&lt;/strong&gt; a running application keeps the mode it started with. You fix the subnet and nothing changes until the next &lt;code&gt;stop&lt;/code&gt;/&lt;code&gt;start&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One job, one subnet:&lt;/strong&gt; a job runs in a single subnet picked from the list. One bad subnet out of three fails a third of the time, and retries mask it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual-stack does not prefer IPv6 by default:&lt;/strong&gt; even with the opt-in on, the engine uses IPv4 until you set &lt;code&gt;spark.emr-serverless.driverEnv.SPARK_PREFER_IPV6&lt;/code&gt; and &lt;code&gt;spark.executorEnv.SPARK_PREFER_IPV6&lt;/code&gt;. Setting those on an IPv4-only subnet kills the job.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lesson: the addressing mode is derived state, not configured state. The API does not let you declare the mode; it lets you declare subnets and infers the rest. Derived state needs a test that proves the derivation on every subnet, not on average.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where each byte of a job on an IPv6-only subnet leaves
&lt;/h2&gt;

&lt;p&gt;The path depends on the destination's DNS record: AAAA leaves directly, A becomes &lt;code&gt;64:ff9b::/96&lt;/code&gt; and pays NAT64.&lt;/p&gt;

&lt;h3&gt;
  
  
  🟧 AWS: subnet IPv6-only (uma por AZ)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Spark driver SPARK_PREFER_IPV6 automático (compute)&lt;/li&gt;
&lt;li&gt;Executors 1 ENI IPv6 por worker (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📡 VPC: resolução e rotas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Route 53 Resolver DNS64 na subnet (network)&lt;/li&gt;
&lt;li&gt;NAT gateway rota 64:ff9b::/96 (NAT64) (network)&lt;/li&gt;
&lt;li&gt;Egress-only IGW rota ::/0 (network)&lt;/li&gt;
&lt;li&gt;Interface endpoints Glue, STS, KMS, Logs (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🌐 Destinos
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;S3 dual-stack s3.dualstack. (storage)&lt;/li&gt;
&lt;li&gt;Endpoint só IPv4 JDBC legado, API pública (external)&lt;/li&gt;
&lt;li&gt;Endpoint IPv6 AAAA publicado (external)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;drv -&amp;gt; r53: DNS query&lt;/li&gt;
&lt;li&gt;exe -&amp;gt; r53: DNS query&lt;/li&gt;
&lt;li&gt;r53 -&amp;gt; nat: A record only: synthesizes 64:ff9b::&lt;/li&gt;
&lt;li&gt;nat -&amp;gt; v4: translates to IPv4, billed per GB&lt;/li&gt;
&lt;li&gt;exe -&amp;gt; eigw: destination with AAAA&lt;/li&gt;
&lt;li&gt;eigw -&amp;gt; s3ds: data reads and writes&lt;/li&gt;
&lt;li&gt;eigw -&amp;gt; v6: egress without ingress&lt;/li&gt;
&lt;li&gt;drv -&amp;gt; vpce: catalog, credentials, keys&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Network remediation: turning prerequisites into invariants
&lt;/h2&gt;

&lt;p&gt;The DNS64 fix took one command. The remediation that prevents recurrence took more work, and it is the one that matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subnet module with a contract:&lt;/strong&gt; the IaC module now requires, for every subnet tagged as an EMR Serverless target, &lt;code&gt;assign_ipv6_address_on_creation = true&lt;/code&gt;, &lt;code&gt;enable_dns64 = true&lt;/code&gt;, a &lt;code&gt;64:ff9b::/96&lt;/code&gt; route to the NAT gateway and a &lt;code&gt;::/0&lt;/code&gt; route to the egress-only internet gateway. Auto-assign only applies to resources created after the change, so turning it on late does not fix an ENI that already exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation before apply:&lt;/strong&gt; a pipeline check (policy as code, or a test that reads &lt;code&gt;describe-subnets&lt;/code&gt; and &lt;code&gt;describe-route-tables&lt;/code&gt;) fails the deploy if any subnet in the set breaks the contract. Validation that runs after the incident is a postmortem, not a control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-subnet canary:&lt;/strong&gt; since the job picks the subnet and you do not, I create a canary application with a single subnet, one per AZ. A minimal Spark job resolves and connects to the endpoints the batch depends on (catalog, STS, KMS, the data bucket, the source database) and fails loudly. It runs before the batch window and after any network change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clean JVM:&lt;/strong&gt; a sweep across every &lt;code&gt;sparkSubmitParameters&lt;/code&gt; and &lt;code&gt;runtimeConfiguration&lt;/code&gt; looking for &lt;code&gt;preferIPv4Stack&lt;/code&gt;. A 2019 workaround cannot decide the network stack in 2026.&lt;/p&gt;

&lt;p&gt;None of this is sophisticated. Its cost is not writing it, it is keeping the contract when someone creates the fourth subnet eighteen months from now without reading this document.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost the announcement does not mention: NAT64 and S3
&lt;/h2&gt;

&lt;p&gt;"No additional cost" is true for the feature. It is not true for the path the traffic now takes.&lt;/p&gt;

&lt;p&gt;On an IPv6-only subnet, every destination that only publishes an A record gets a synthesized address in &lt;code&gt;64:ff9b::/96&lt;/code&gt;, and that route points to the NAT gateway. The NAT gateway bills per hour and per GB processed; the VPC pricing page uses US$ 0.045 per GB as its example in US East (Ohio). A platform reading 8 TB a night from S3 and sending it through NAT64 pays around US$ 360 a night in processing alone, roughly US$ 10,800 over 30 nights. The S3 gateway endpoint on the workload subnet does not catch this traffic, because the destination is no longer an IPv4 prefix.&lt;/p&gt;

&lt;p&gt;S3 only serves IPv6 through its dual-stack endpoints (&lt;code&gt;s3.dualstack.&amp;lt;region&amp;gt;.amazonaws.com&lt;/code&gt;), and the client has to ask for them explicitly. Do not trust it before measuring: compare the NAT gateway's &lt;code&gt;BytesOutToDestination&lt;/code&gt; before and after the switch.&lt;/p&gt;

&lt;p&gt;Other services need the same care. In the AWS IPv6 support table, Glue has dual-stack and a private endpoint with IPv6, but not the public endpoint; Lake Formation also only has IPv6 through its private endpoint. On IPv6-only, that means interface endpoints for catalog and permissions, or paid NAT64 on every call.&lt;/p&gt;

&lt;p&gt;And security: the S3 documentation warns that policies with IPv4-only &lt;code&gt;aws:SourceIp&lt;/code&gt; make clients incorrectly lose or gain access once they start using IPv6. Review every bucket policy before the switch, not after.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three modes side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;IPv4-only&lt;/th&gt;
&lt;th&gt;Dual-stack with opt-in&lt;/th&gt;
&lt;th&gt;IPv6-only&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IPv4 consumption&lt;/td&gt;
&lt;td&gt;1 per worker&lt;/td&gt;
&lt;td&gt;1 per worker, still consumed&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Activation&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ipv6.enabled&lt;/code&gt; on the application + &lt;code&gt;SPARK_PREFER_IPV6&lt;/code&gt; on the job&lt;/td&gt;
&lt;td&gt;Automatic from the subnets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IPv4-only destinations&lt;/td&gt;
&lt;td&gt;Direct&lt;/td&gt;
&lt;td&gt;Direct over IPv4&lt;/td&gt;
&lt;td&gt;Only with DNS64 + NAT64, billed per GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incident risk&lt;/td&gt;
&lt;td&gt;Exhausting the subnet at peak&lt;/td&gt;
&lt;td&gt;Low: failure falls back to IPv4&lt;/td&gt;
&lt;td&gt;High if any subnet or dependency is not ready&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When to use&lt;/td&gt;
&lt;td&gt;Address headroom and no IPv6 mandate&lt;/td&gt;
&lt;td&gt;Transition and dependency validation&lt;/td&gt;
&lt;td&gt;Compliance mandate or real exhaustion, with dependencies mapped&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Well-Architected reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;security&lt;/strong&gt;: An IPv6 address is globally unique: egress only through an egress-only gateway, never a &lt;code&gt;::/0&lt;/code&gt; route to the internet gateway on worker subnets. EMR Serverless already refuses security groups with ports open to &lt;code&gt;0.0.0.0/0&lt;/code&gt; or &lt;code&gt;::/0&lt;/code&gt;. Review &lt;code&gt;aws:SourceIp&lt;/code&gt; in bucket policies and prefer &lt;code&gt;aws:SourceVpce&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reliability&lt;/strong&gt;: Spread subnets across at least two AZs, as the documentation recommends, but treat each one as its own failure unit: a canary per subnet and an alarm per subnet. Automatic retries without per-subnet correlation turn a deterministic defect into noise.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Anti-patterns this retro tries to prevent
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validating with two jobs:&lt;/strong&gt; with three subnets and one picked per job, two green jobs prove nothing about the third.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Going straight to IPv6-only without a dependency inventory:&lt;/strong&gt; every driver, SDK and endpoint the job touches must support IPv6 or go through NAT64; EMR Serverless does not change your code or third-party endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turning on &lt;code&gt;SPARK_PREFER_IPV6&lt;/code&gt; as a global default:&lt;/strong&gt; on an IPv4-only subnet the preference kills the job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixing the subnet without restarting the application:&lt;/strong&gt; the mode was fixed at start, and on-call concludes the fix did not work.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What I would do:&lt;/strong&gt; I would start with dual-stack with the opt-in, on a new application, and keep the IPv6 preference on only for jobs whose dependencies I have already measured. IPv6-only waits for a mandate or real exhaustion, and only after two weeks of green per-subnet canaries and a NAT gateway bytes chart I can explain line by line. The lesson I took from other network migrations is that the defect is almost never in the new service, it is in the subnet someone created with the old module. That is why the contract lives in IaC, not in the wiki.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lessons and recommendation
&lt;/h2&gt;

&lt;p&gt;Adopt IPv6 on EMR Serverless when the IPv4 space in shared subnets already limits your vCPU ceiling or when there is a formal IPv6 requirement. Use dual-stack with the opt-in while there is any IPv4-only dependency that matters in volume; use IPv6-only when every dependency publishes AAAA or sits behind an interface endpoint, and NAT64 is limited to control calls. Either way, encode DNS64, routes and auto-assign in the subnet module, run a per-subnet canary and measure the NAT gateway before declaring the migration done. The feature is free; an unprepared network bills per GB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Adote com dual-stack primeiro / Adopt wi&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/10/amazon-emr-serverless/" rel="noopener noreferrer"&gt;Amazon EMR Serverless now supports IPv6 and dual-stack connectivity (AWS What's New, 16/10/2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-Serverless-UserGuide/ipv6-subnets.html" rel="noopener noreferrer"&gt;Configure IPv6 subnets for EMR Serverless applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-Serverless-UserGuide/vpc-access.html" rel="noopener noreferrer"&gt;Configuring VPC access for EMR Serverless applications (subnet planning)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/nat-gateway-nat64-dns64.html" rel="noopener noreferrer"&gt;DNS64 and NAT64 (Amazon VPC User Guide)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/egress-only-internet-gateway.html" rel="noopener noreferrer"&gt;Egress-only internet gateway (Amazon VPC User Guide)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/aws-ipv6-support.html" rel="noopener noreferrer"&gt;AWS services that support IPv6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/developerguide/ipv6-access.html" rel="noopener noreferrer"&gt;Making requests to Amazon S3 over IPv6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/vpc/pricing/" rel="noopener noreferrer"&gt;Amazon VPC pricing (NAT gateway)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/retro-emr-serverless-em-ipv6-only-e-a-subnet-que-ficou-sem-dns64" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>emrserverless</category>
      <category>ipv6</category>
      <category>vpc</category>
    </item>
    <item>
      <title>CloudFront dual-stack VPC origins: IPv6 to private origins, with caveats</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Fri, 16 Oct 2026 17:37:34 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/cloudfront-dual-stack-vpc-origins-ipv6-to-private-origins-with-caveats-1bj0</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/cloudfront-dual-stack-vpc-origins-ipv6-to-private-origins-with-caveats-1bj0</guid>
      <description>&lt;p&gt;For almost two years, anyone who adopted CloudFront VPC origins and had an IPv6 target lived with a bad choice: keep the ALB in a private subnet and stay on IPv4 between edge and origin, or expose the origin to the internet to get end-to-end IPv6. The 10/15/2026 announcement removes that dilemma. The question that matters now is not 'can I get IPv6 to a private origin?', it's 'what in my VPC, my security group and my change process breaks when I turn it on?'. That is the question this analysis answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The launch in numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;US$ 0&lt;/strong&gt;: Additional cost. Dual-stack VPC origins carry no extra charge; the ENI addresses can be private.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;34&lt;/strong&gt;: Commercial Regions. Every Region with VPC origins, including sa-east-1; four have one excluded AZ.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;55/60&lt;/strong&gt;: Prefix list weight. The CloudFront managed list weighs 55 against the default 60 rules per security group.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What actually changed
&lt;/h2&gt;

&lt;p&gt;VPC origins, launched in November 2024, places an ALB, NLB or EC2 instance in a private subnet and makes CloudFront the single point of entry. The connection arrives through a service-managed ENI created inside your subnet, and access is controlled by a security group AWS creates with the naming pattern &lt;code&gt;CloudFront-VPCOrigins-Service-SG&lt;/code&gt;. Until yesterday that ENI spoke IPv4 only.&lt;/p&gt;

&lt;p&gt;In September 2025 CloudFront gained IPv6 for custom origins, with three options: IPv4 only (default), IPv6 only and dual-stack. That announcement excluded S3 and VPC origins. What shipped now closes half of that gap: VPC origins accept dual-stack. The other half stays open, because the current CloudFront IPv6 documentation still lists IPv6 only as "excluding Amazon S3 and VPC origins".&lt;/p&gt;

&lt;p&gt;In practice, the change is one field: when you create the VPC origin, the console asks for &lt;strong&gt;Origin type&lt;/strong&gt;, and the default is still IPv4. For dual-stack to work, the private subnet needs at least one free IPv6 address in addition to the IPv4 one, because that is where the service ENI is born. Without it, ENI creation fails.&lt;/p&gt;

&lt;p&gt;What does not change: gRPC is still out, Lambda@Edge origin request and origin response triggers are still out, a Gateway Load Balancer cannot be an origin, and neither can an NLB with a TLS listener. If you ruled out VPC origins for one of those reasons, you have no new reason to revisit.&lt;/p&gt;

&lt;h2&gt;
  
  
  End-to-end IPv6 path with a private origin
&lt;/h2&gt;

&lt;p&gt;Every hop where IPv6 can silently fall back to IPv4, and the control that governs that hop.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌐 DNS e borda
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Route 53 alias A + alias AAAA (network)&lt;/li&gt;
&lt;li&gt;CloudFront edge IPv6 de viewer ligado (edge)&lt;/li&gt;
&lt;li&gt;Origin-facing escolhe IPv4 ou IPv6 (edge)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS: VPC privada (sa-east-1)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;ENI do serviço 1 IPv4 + 1 IPv6 livres (network)&lt;/li&gt;
&lt;li&gt;ALB interno IpAddressType: dualstack (compute)&lt;/li&gt;
&lt;li&gt;Target group IPv4 ou IPv6 (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔐 Controles
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;CloudFront-VPCOrigins-Service-SG fonte da regra de entrada (security)&lt;/li&gt;
&lt;li&gt;NACL de saída efêmeras 1024–65535 IPv4 e IPv6 (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;viewer -&amp;gt; r53: AAAA lookup&lt;/li&gt;
&lt;li&gt;viewer -&amp;gt; edge: HTTPS over IPv6&lt;/li&gt;
&lt;li&gt;edge -&amp;gt; ofs: cache miss&lt;/li&gt;
&lt;li&gt;ofs -&amp;gt; eni: private connection, family picked by the service&lt;/li&gt;
&lt;li&gt;eni -&amp;gt; alb: allowed by service SG&lt;/li&gt;
&lt;li&gt;alb -&amp;gt; tg: new connection, target group family&lt;/li&gt;
&lt;li&gt;svcsg -&amp;gt; alb: inbound rule&lt;/li&gt;
&lt;li&gt;nacl -&amp;gt; alb: return path&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where it shines
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The trade-off is gone:&lt;/strong&gt; IPv6 to the origin no longer requires a public subnet, an internet-facing ALB and an IP list to maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero license cost:&lt;/strong&gt; no additional charge, and the ENI IPv6 addresses do not need to be public.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same security model:&lt;/strong&gt; the service SG still restricts the origin to your distributions, now across both families.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brazil included:&lt;/strong&gt; &lt;code&gt;sa-east-1&lt;/code&gt; is on the list, so a regulated workload that must stay in-country is not left out.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dual-stack is not guaranteed IPv6
&lt;/h2&gt;

&lt;p&gt;This is the point the announcement does not make explicit, and it matters to whoever has to answer the audit. The CloudFront documentation describes dual-stack mode this way: the service automatically picks IPv4 or IPv6 for the origin connection to prioritize performance and availability. In other words, you don't decide the family of each connection. CloudFront does.&lt;/p&gt;

&lt;p&gt;For most sites that is exactly the right behavior. For a team that has to prove to a regulator or a contract that traffic runs over IPv6 all the way to the origin, it is an evidence problem. Turning on dual-stack produces no proof at all; measurement does.&lt;/p&gt;

&lt;p&gt;The same holds on the viewer side. The documentation itself warns that CloudFront may answer a viewer over IPv4 when its data says the experience will be better, and that the IPv6 share should remain a minority because not every access network supports the protocol. Two automatic decisions, one at each end, and neither is yours.&lt;/p&gt;

&lt;p&gt;What I would do: treat "end-to-end IPv6" as a measured SLO, not a setting. At the edge, the &lt;code&gt;c-ip&lt;/code&gt; field in standard logs shows the viewer family. At the origin, ALB access logs show the address that opened the connection, which for VPC origins is the service ENI. The daily ratio of IPv6 connections to total is the number that goes into the report. If the requirement is IPv6-only, VPC origins still doesn't meet it: IPv6 only remains excluded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network prerequisites that fail silently
&lt;/h2&gt;

&lt;p&gt;The CloudFront side is one field. The real work is in the VPC, and much of it fails without a clear message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IPv6 in the VPC and subnets:&lt;/strong&gt; the ALB only accepts &lt;code&gt;IpAddressType: dualstack&lt;/code&gt; if the VPC and its subnets have an associated IPv6 CIDR block, the route tables route IPv6, and the SG and NACL allow IPv6. In an older account built IPv4-only, that is a project, not a click.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An address for the ENI:&lt;/strong&gt; the subnet where the VPC origin lives needs a free IPv6 address. A /28 that runs out of IPv4 is already a known problem; with dual-stack the same math repeats in the other family.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Origin security group:&lt;/strong&gt; the documentation gives two options. Managed prefix lists, and for dual-stack that means both, &lt;code&gt;com.amazonaws.global.cloudfront.origin-facing&lt;/code&gt; and &lt;code&gt;com.amazonaws.global.ipv6.cloudfront.origin-facing&lt;/code&gt;. The VPC documentation assigns the CloudFront list a weight of 55, against the default of 60 rules per security group, which leaves 5 rules for everything else. The other option is to reference &lt;code&gt;CloudFront-VPCOrigins-Service-SG&lt;/code&gt;: it takes one rule and restricts the origin to your distributions, not to any CloudFront distribution. That is the one I use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outbound NACL:&lt;/strong&gt; inbound NACL rules are not evaluated for traffic from CloudFront, but outbound rules are, on the return path, on ephemeral ports 1024–65535. A NACL keeps IPv6 rules separate from IPv4 ones. If you only allowed &lt;code&gt;0.0.0.0/0&lt;/code&gt;, you will watch the IPv6 handshake die on the way back.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Limits that still apply:&lt;/strong&gt; IPv6 only is still unavailable for VPC origins. gRPC and Lambda@Edge origin triggers are not supported. A Gateway Load Balancer and an NLB with a TLS listener cannot be origins, and an NLB must have a security group attached. And IPv6 ends at the ALB: the connection to targets uses the target group's family, so a dual-stack ALB with an IPv4 target group is IPv6 to the ALB, not to the application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Migrating a VPC origin already in production
&lt;/h2&gt;

&lt;p&gt;Here is the cost the announcement does not show. Per the documented procedure, updating a VPC origin requires first making sure it is not the default origin of any cache behavior, then disassociating it from &lt;strong&gt;every&lt;/strong&gt; distribution that uses it, editing, waiting for the status to return to &lt;strong&gt;Deployed&lt;/strong&gt; (up to 15 minutes), and only then associating it again. In production, that is a scheduled outage window, multiplied by the number of distributions sharing the origin.&lt;/p&gt;

&lt;p&gt;I would not edit the existing VPC origin. The safer path is to create a second, dual-stack VPC origin pointing at the same ALB ARN, and swap origins through CloudFront continuous deployment: a staging distribution gets the new origin, you test with a slice of traffic, and you promote the configuration when the signals are clean. Rollback is promoting the previous configuration back, without waiting 15 minutes for a VPC origin to propagate.&lt;/p&gt;

&lt;p&gt;The signals I would watch on staging: the distribution's &lt;code&gt;5xxErrorRate&lt;/code&gt; and &lt;code&gt;OriginLatency&lt;/code&gt; in CloudWatch, the ALB's &lt;code&gt;TargetConnectionErrorCount&lt;/code&gt; and &lt;code&gt;HTTPCode_ELB_5XX_Count&lt;/code&gt;, and the share of IPv6 connections in the ALB access logs. If that share stays at zero, the cause is almost always an SG or NACL missing the IPv6 rule, and CloudFront falls back to IPv4 without complaining, which is good for availability and terrible for the target.&lt;/p&gt;

&lt;p&gt;If you share VPC origins across accounts through AWS RAM, add coordination with the owner of every consuming distribution.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to adopt without an outage window
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prepare the VPC&lt;/strong&gt;: Associate an IPv6 CIDR with the VPC and private subnets, adjust route tables and confirm each subnet has a free IPv6 address for the ENI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Move the ALB to dual-stack&lt;/strong&gt;: Run &lt;code&gt;aws elbv2 set-ip-address-type --ip-address-type dualstack&lt;/code&gt; on the internal ALB; decide separately whether the target group also moves to IPv6.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lock down SG and NACL&lt;/strong&gt;: ALB inbound from &lt;code&gt;CloudFront-VPCOrigins-Service-SG&lt;/code&gt;; NACL outbound on ephemeral 1024–65535 for both IPv4 and IPv6.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create a new VPC origin&lt;/strong&gt;: Origin type dual-stack, same ALB ARN. Don't edit the existing one; wait for Deployed status.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test on staging and promote&lt;/strong&gt;: Staging distribution with the new origin, partial traffic, 5xx signals and IPv6 share; promote only when both are clean.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Finish the edge&lt;/strong&gt;: Enable viewer IPv6 and add a second AAAA alias in Route 53 for each alternate domain name; review signed URLs using a custom &lt;code&gt;IpAddress&lt;/code&gt; policy.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Anti-patterns
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Editing the VPC origin in production:&lt;/strong&gt; the procedure requires disassociating from every distribution, and the wait of up to 15 minutes becomes an outage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claiming compliance because the field says dual-stack:&lt;/strong&gt; CloudFront picks the family per connection; without log measurement, there is no evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allowing the prefix lists when the origin only serves your own CloudFront:&lt;/strong&gt; it consumes 55 of the default 60 rules and accepts any distribution, not just yours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting the target group:&lt;/strong&gt; a dual-stack ALB with IPv4 targets delivers IPv6 to the load balancer, not to the application.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Curator's note:&lt;/strong&gt; In a financial environment I would turn this on first for a low-risk distribution, with staging and an IPv6 share target written down before the change. I have seen an IP family migration pass functional testing and fail the first audit report, because the fallback to IPv4 is silent by design. The lesson: in networking, whatever degrades without an error needs its own metric, otherwise you find out from the wrong person. And I would not edit an existing VPC origin under any circumstances; creating another one and swapping via continuous deployment costs an afternoon and saves a 2 AM window.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Adopt it when you have an end-to-end IPv6 requirement, contractual, regulatory or platform-driven, and the origin already is or needs to be in a private subnet: there is no additional cost and the security model does not get worse. Wait if the requirement is IPv6-only, because IPv6 only is still out for VPC origins, or if the VPC has no IPv6 CIDR yet, because then the project is a network project, not a CDN one. Either way, create a new VPC origin and swap via staging; do not edit the one serving traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; 4/5&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/10/cloudfront-vpc-origin-dualstack/" rel="noopener noreferrer"&gt;AWS What's New: Amazon CloudFront announces dual-stack support for VPC origins (15/10/2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-vpc-origins.html" rel="noopener noreferrer"&gt;Amazon CloudFront Developer Guide: Restrict access with VPC origins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-enable-ipv6.html" rel="noopener noreferrer"&gt;Amazon CloudFront Developer Guide: Enable IPv6 for CloudFront distributions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/09/amazon-cloudfront-ipv6-origins/" rel="noopener noreferrer"&gt;AWS What's New: Amazon CloudFront announces support for IPv6 origins (09/2025)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/working-with-aws-managed-prefix-lists.html" rel="noopener noreferrer"&gt;Amazon VPC User Guide: AWS-managed prefix lists and weights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-ip-address-type.html" rel="noopener noreferrer"&gt;Elastic Load Balancing: Update the IP address types for your Application Load Balancer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/networking-and-content-delivery/optimizing-application-performance-the-strategic-benefits-of-combining-amazon-cloudfront-with-application-load-balancers" rel="noopener noreferrer"&gt;AWS Networking &amp;amp; Content Delivery Blog: Combining Amazon CloudFront with Application Load Balancers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/cloudfront-vpc-origins-dual-stack-ipv6-ate-a-origem-privada-com" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>cloudfront</category>
      <category>vpcorigins</category>
      <category>ipv6</category>
    </item>
    <item>
      <title>Well-Architected Agent: architecture review becomes a weekly process</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Tue, 13 Oct 2026 18:24:40 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/well-architected-agent-architecture-review-becomes-a-weekly-process-58o</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/well-architected-agent-architecture-review-becomes-a-weekly-process-58o</guid>
      <description>&lt;p&gt;For years, the Well-Architected review was an event: a week-long workshop, a spreadsheet of high and medium risks, and then six months with nobody opening the report. On 10/01/2026 AWS put AWS Well-Architected Agent into preview, which replaces the event with a cycle: scheduled scans, up to 30 recommendations per run, weekly refresh. The question that interests me is not "is the agent right?", it's "who triages 30 findings a week, and what happens to what it cannot see?". After 16 years operating financial platforms on AWS, I've learned that a findings tool without a triage owner becomes just another red dashboard everyone learns to ignore.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers that shape the design
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;100&lt;/strong&gt;: accounts per profile. scanning every commercial Region from us-east-1, us-east-2 or us-west-2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;30&lt;/strong&gt;: recommendations per run. with a 7-day cooldown between scheduled runs of the same profile&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5/dia&lt;/strong&gt;: IaC reviews per profile. zip up to 25 MB; S3 folder up to 100 MB, file up to 1 MB&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The signal: an aggregator with an opinion
&lt;/h2&gt;

&lt;p&gt;Strip the AI layer and what AWS shipped is an aggregator with an opinion. The related-services documentation is explicit: the agent ingests &lt;strong&gt;Trusted Advisor&lt;/strong&gt; findings as baseline signals, uses &lt;strong&gt;Compute Optimizer&lt;/strong&gt; rightsizing signals for EC2, Lambda and RDS, consumes &lt;strong&gt;Security Hub CSPM&lt;/strong&gt;, &lt;strong&gt;Resilience Hub&lt;/strong&gt; assessment data (with its RTO/RPO) and &lt;strong&gt;Cost Optimization Hub&lt;/strong&gt;. On top of that it adds three things: prioritization against goals you write, cross-pillar trade-off analysis, and ready remediation (console, updated IaC, &lt;code&gt;aws&lt;/code&gt; CLI commands and Systems Manager runbooks).&lt;/p&gt;

&lt;p&gt;Recommendations come at three levels. &lt;strong&gt;Resource:&lt;/strong&gt; one instance, one function, one database, with dollar impact where applicable. &lt;strong&gt;Application:&lt;/strong&gt; consolidated findings across related resources, a format the documentation itself marks as beta. &lt;strong&gt;Architecture:&lt;/strong&gt; generated from CloudFormation, Terraform or CDK templates sent as a zip or S3 URI, before deployment.&lt;/p&gt;

&lt;p&gt;Two operational details change result quality. Without &lt;strong&gt;Cost Explorer&lt;/strong&gt; enabled, the agent estimates cost from public pricing, which ignores Savings Plans, Reserved Instances and contractual discounts; in an account with an EDP, the "dollar impact" can be inflated enough to invert prioritization. And access requires a &lt;strong&gt;Business+&lt;/strong&gt; Support plan or higher: Business+ gets 2 profiles and 7 applications per profile; Enterprise On-Ramp, Enterprise and Unified Operations get 10 and 30. Developer and Business are excluded.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes for architects
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goals became configuration:&lt;/strong&gt; up to 10 goals per profile, plain text, one pillar each, and they order the queue. A badly written goal mis-prioritizes without raising any error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous review has fixed throughput:&lt;/strong&gt; 30 recommendations per run and a 7-day cooldown mean a triage ceiling per profile. The bottleneck becomes the team, not the tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shift-left with a quota:&lt;/strong&gt; 5 IaC reviews per day per profile cannot back a gate on every pull request. They fit on merge to the release branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Criticality is declared, not inferred:&lt;/strong&gt; application context accepts &lt;code&gt;MISSION_CRITICAL&lt;/code&gt;, &lt;code&gt;BUSINESS_CRITICAL&lt;/code&gt;, &lt;code&gt;NON_CRITICAL&lt;/code&gt; and &lt;code&gt;TEST_DEVELOPMENT&lt;/code&gt;. Whoever skips it gets recommendations tuned for the average case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silence is not compliance:&lt;/strong&gt; the unsupported type list includes &lt;code&gt;AWS::WAFv2::WebACL&lt;/code&gt;, &lt;code&gt;AWS::Organizations::Policy&lt;/code&gt; and &lt;code&gt;AWS::IAM::RolePolicy&lt;/code&gt;. No finding there proves nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The blind spot that matters in a bank
&lt;/h2&gt;

&lt;p&gt;The quotas page carries a long table of CloudFormation resource types the agent does not analyze. Most are peripheral (IoT Analytics, DeviceFarm, Evidently). But a few are exactly the controls an auditor checks first in an environment under BACEN 4.893 or PCI-DSS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AWS::WAFv2::WebACL&lt;/code&gt;, &lt;code&gt;WebACLAssociation&lt;/code&gt;, &lt;code&gt;IPSet&lt;/code&gt; and &lt;code&gt;RuleGroup&lt;/code&gt;: the entire WAF edge.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS::Organizations::Policy&lt;/code&gt;: SCPs, that is, the organization's data perimeter.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS::IAM::RolePolicy&lt;/code&gt;, &lt;code&gt;UserPolicy&lt;/code&gt; and &lt;code&gt;GroupPolicy&lt;/code&gt;: inline policies, where the &lt;code&gt;"Action": "*"&lt;/code&gt; nobody remembers writing usually lives.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS::OpenSearchService::Domain&lt;/code&gt;, &lt;code&gt;AWS::Shield::Protection&lt;/code&gt; and Security Hub's own configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The failure mode here is silent. The report arrives clean on the security pillar, someone takes the screenshot to the risk committee, and the absence of WAF findings is read as a well-configured WAF. It isn't: it's an unanalyzed WAF.&lt;/p&gt;

&lt;p&gt;The mitigation is cheap and has to be explicit. Keep those controls covered by AWS Config conformance packs and Security Hub CSPM directly, outside the agent, and write in the governance document which types the agent does not cover and which tool covers each one. The list is marked preview and should shrink, so review it every quarter instead of assuming it is stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the agent reads, where it writes
&lt;/h2&gt;

&lt;p&gt;Role chain in a dedicated account, input signals and remediation paths. Only the IaC path goes back through the pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔐 AWS: Conta de tooling dedicada
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Agent profile us-east-1 · 10 metas (ai)&lt;/li&gt;
&lt;li&gt;Execution role trust wellarchitected.amazonaws.com (security)&lt;/li&gt;
&lt;li&gt;CloudTrail API wellarchitected:* (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS: Contas de workload (até 100)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Access role WellArchitectedAgentResourceScanning (security)&lt;/li&gt;
&lt;li&gt;Recursos todas as regiões comerciais (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📡 AWS: Sinais de entrada
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Trusted Advisor Security Hub CSPM (data)&lt;/li&gt;
&lt;li&gt;Compute Optimizer Cost Optimization Hub (data)&lt;/li&gt;
&lt;li&gt;Cost Explorer sem ele: preço público (data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📤 Saída: Remediação
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;IaC atualizado PR no repositório (ci)&lt;/li&gt;
&lt;li&gt;AWS CLI / SSM runbook só em sandbox (external)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;profile -&amp;gt; exec: assumes&lt;/li&gt;
&lt;li&gt;exec -&amp;gt; access: sts:AssumeRole, ExternalId = profile ARN&lt;/li&gt;
&lt;li&gt;access -&amp;gt; res: config read&lt;/li&gt;
&lt;li&gt;ta -&amp;gt; profile: baseline findings&lt;/li&gt;
&lt;li&gt;co -&amp;gt; profile: rightsizing and cost&lt;/li&gt;
&lt;li&gt;ce -&amp;gt; profile: actual cost per resource&lt;/li&gt;
&lt;li&gt;profile -&amp;gt; ct: audit&lt;/li&gt;
&lt;li&gt;profile -&amp;gt; owner: 30 recommendations/week&lt;/li&gt;
&lt;li&gt;owner -&amp;gt; iac: accepts and versions&lt;/li&gt;
&lt;li&gt;owner -&amp;gt; cli: tests outside prod&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  IAM: the design is good, the default less so
&lt;/h2&gt;

&lt;p&gt;The access model deserves credit. Instead of a service-linked role, the agent uses customer-managed roles in a chain: an &lt;strong&gt;execution role&lt;/strong&gt; in the profile account, trusting &lt;code&gt;wellarchitected.amazonaws.com&lt;/code&gt; and holding only &lt;code&gt;sts:AssumeRole&lt;/code&gt;, and an &lt;strong&gt;access role&lt;/strong&gt; in each workload account, with the read-only &lt;code&gt;WellArchitectedAgentResourceScanning&lt;/code&gt; managed policy. The profile ARN is used as the external ID, which closes the confused deputy, and there is an &lt;code&gt;iam:PassRole&lt;/code&gt; check at creation. The documentation states neither role allows modifying resources. Onboarding is opt-in per account.&lt;/p&gt;

&lt;p&gt;That said, three adjustments I'd make before turning it on in production:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dedicated account:&lt;/strong&gt; AWS itself recommends hosting the profile in an account with no workloads. In a Control Tower environment, that is the tooling or audit account, never the management account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trust by exact ARN:&lt;/strong&gt; use the execution role ARN, with the &lt;code&gt;service-role/&lt;/code&gt; path the console creates, not the account root. Roll out access roles via StackSets with the same name in every account (the common-name pattern the profile accepts) so that revoking is one operation, not a hundred.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Processing residency:&lt;/strong&gt; the profile can only live in &lt;code&gt;us-east-1&lt;/code&gt;, &lt;code&gt;us-east-2&lt;/code&gt; or &lt;code&gt;us-west-2&lt;/code&gt;, even though it scans &lt;code&gt;sa-east-1&lt;/code&gt;. Configuration metadata and tags leave the Region for analysis. If your tagging policy carries customer names or CPF numbers in tags (I've seen it), that becomes a conversation with the DPO under LGPD before it becomes one with the auditor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four remediation paths, four maintenance costs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Console&lt;/th&gt;
&lt;th&gt;Updated IaC&lt;/th&gt;
&lt;th&gt;AWS CLI&lt;/th&gt;
&lt;th&gt;SSM runbook&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Creates drift&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes, unless the runbook updates the code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review trail&lt;/td&gt;
&lt;td&gt;CloudTrail only&lt;/td&gt;
&lt;td&gt;Pull request with approval&lt;/td&gt;
&lt;td&gt;CloudTrail only&lt;/td&gt;
&lt;td&gt;SSM execution history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where I use it&lt;/td&gt;
&lt;td&gt;Understanding the finding&lt;/td&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;td&gt;Sandbox&lt;/td&gt;
&lt;td&gt;Repeatable action with no IaC state, e.g. orphaned snapshot&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How I'd position it today
&lt;/h2&gt;

&lt;p&gt;The agent doesn't replace a human Well-Architected review, it changes what the human review has to cover. The mechanical work (oversized instance, bucket without versioning, single-AZ RDS tagged &lt;code&gt;MISSION_CRITICAL&lt;/code&gt;) leaves the workshop agenda. What remains for people is what the agent admittedly cannot see: SCPs, WAF, inline policies, and architecture decisions that show up in no configuration at all, like why a settlement flow is synchronous.&lt;/p&gt;

&lt;p&gt;In practice, three design decisions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One profile per regulatory domain, not per team:&lt;/strong&gt; with 10 profiles on Enterprise, separate what has distinct SLOs and regimes (payments, data, internal platform). On Business+, with 2 profiles and 7 applications, the choice is harder: regulated production in one, everything else in the other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goals versioned in a repository:&lt;/strong&gt; plain-text goals are a ranking parameter. Treat them as code: a file in the platform repository, applied through the &lt;code&gt;wellarchitected&lt;/code&gt; namespace API, with review. A goal like "reduce cost" is weak; "reduce non-production compute cost by 20% without touching the core's 15-minute RTO" gives the agent a trade-off to respect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A queue with an owner and an SLA:&lt;/strong&gt; 30 recommendations a week is about 120 a month per profile. Without an owner who marks items complete or suppresses them with a reason, the backlog grows linearly and the tool loses credibility in two months. I'd turn on cost and resilience first, where the cost of error is low, and security only later, with the unsupported list beside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-patterns you can already predict
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Running the CLI command in production:&lt;/strong&gt; it fixes the finding and creates drift; the next &lt;code&gt;terraform apply&lt;/code&gt; reverts the fix and nobody notices until the next scan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reading a clean security pillar as attestation:&lt;/strong&gt; with WAF, SCPs and inline policies out of scope, a clean report is partial coverage, not compliance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritizing by dollars without Cost Explorer:&lt;/strong&gt; the agent falls back to public pricing and ignores Savings Plans and EDP; the cost queue ends up sorted by a number that isn't yours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Profile in the management account:&lt;/strong&gt; it concentrates the role that reaches 100 accounts in the place with the largest blast radius in the organization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IaC gate on every PR:&lt;/strong&gt; 5 reviews per day per profile run out before lunch; the gate either fails open or blocks the team.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The goal is the new prompt:&lt;/strong&gt; The goals text defines the order of the remediation queue across 100 accounts. That is production configuration with effects on money and risk, written in natural language with no validation. Whoever edits goals needs the same change control as whoever edits SCPs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Curator's note:&lt;/strong&gt; I'd turn the agent on this week in a dedicated tooling account, cost and resilience only, with Cost Explorer enabled before the first run. Access roles go out through StackSets, trust pinned to the exact execution role ARN, and goals live in a versioned file. The lesson I carry from years of Trusted Advisor and Security Hub: the cost of a findings tool is not the license, it's the weekly triage someone has to sustain for years. If there is no named owner for the queue, I'd rather not turn it on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Adopt now if you already have a Business+ plan or higher, Control Tower with a tooling account, and a platform team able to triage about 120 items a month per profile: the agent removes the mechanical work from reviews and delivers IaC remediation that fits your pipeline. Wait if your main need is security evidence for regulatory audit: while WAF, SCPs and inline policies are out of scope, it complements AWS Config and Security Hub CSPM, it does not replace them. Either way, production remediation goes only through the IaC path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Adotar com escopo / Adopt with scope&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/aws/announcing-aws-well-architected-agent-an-ai-powered-intelligence-to-optimize-your-cloud-environment-preview/" rel="noopener noreferrer"&gt;AWS News Blog: Announcing AWS Well-Architected Agent (preview)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/userguide/agent.html" rel="noopener noreferrer"&gt;What is AWS Well-Architected Agent (preview)?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/userguide/security-iam-agent.html" rel="noopener noreferrer"&gt;AWS Well-Architected Agent access model (IAM)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/userguide/agent-quotas.html" rel="noopener noreferrer"&gt;AWS Well-Architected Agent quotas and limits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/userguide/agent-related-services.html" rel="noopener noreferrer"&gt;AWS Well-Architected Agent related services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/userguide/agent-concepts.html" rel="noopener noreferrer"&gt;AWS Well-Architected Agent concepts and terminology&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sdxcentral.com/news/aws-unveils-well-architected-ai-agent-that-acts-like-a-cloud-architect/" rel="noopener noreferrer"&gt;SDxCentral: AWS unveils Well-Architected AI agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/well-architected-agent-a-revisao-de-arquitetura-vira-processo-semanal" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>wellarchitected</category>
      <category>aiagents</category>
      <category>finops</category>
    </item>
    <item>
      <title>Sharing a GPU cluster on HyperPod: three designs and when each one pays off</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Sat, 10 Oct 2026 16:19:29 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/sharing-a-gpu-cluster-on-hyperpod-three-designs-and-when-each-one-pays-off-3o50</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/sharing-a-gpu-cluster-on-hyperpod-three-designs-and-when-each-one-pays-off-3o50</guid>
      <description>&lt;p&gt;The question that usually opens the shared GPU cluster discussion is "how do I split the machines across teams?". The question that decides whether the design survives is a different one: when the research team's training job gets evicted at 2 AM to make room for an inference endpoint, who finds out, how many GPU-hours were thrown away, and which cost center absorbs them? The reference architecture AWS published on 10/08/2026 for SageMaker HyperPod on EKS answers a good part of that. Here I put it side by side with the two alternatives I see most often and say under which conditions each one is worth its maintenance cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real choice: three ways to split GPUs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Option A, one shared HyperPod EKS cluster with Task Governance:&lt;/strong&gt; the design in the post. Each team gets a &lt;code&gt;hyperpod-ns-&amp;lt;team&amp;gt;&lt;/code&gt; namespace, its own SageMaker domain for Studio users, and a permission set in IAM Identity Center for CLI users. Fairness across teams belongs to Task Governance, built on Kueue: guaranteed quota per team, lending of idle capacity, and priority classes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B, one cluster per team, ideally in separate accounts:&lt;/strong&gt; the boundary is the AWS account. There is no noisy neighbor because there is no neighbor. In exchange, each team carries its own capacity floor, and GPUs idle in one cluster cannot rescue a full queue in another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option C, a shared cluster with namespaces and &lt;code&gt;ResourceQuota&lt;/code&gt; only:&lt;/strong&gt; this is what shows up when the platform starts as a single-team cluster and other teams trickle in. It works until the first week of contention, when the quota becomes a hard ceiling and arrival order decides who gets to train.&lt;/p&gt;

&lt;p&gt;The difference between the three is not the cost of standing up the cluster. It is what each one demands in continuous operation: reviewing quotas, debugging Kueue admission, maintaining the UID/GID webhook, reconciling chargeback. That is the cost that stays for years, and it is the one I compare on.&lt;/p&gt;

&lt;h2&gt;
  
  
  From login to GPU: where each boundary in design A is enforced
&lt;/h2&gt;

&lt;p&gt;Identity decides the namespace, the namespace decides the queue, the queue decides whether the pod runs on its own or borrowed quota, and the namespace becomes the chargeback key.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔐 IAM Identity Center: identidade
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Permission set AWSReservedSSO_TeamA_* (security)&lt;/li&gt;
&lt;li&gt;SageMaker domain TeamA-domain + TeamA-role (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS: HyperPod EKS control plane
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;EKS access entries escopo: hyperpod-ns-team-a (security)&lt;/li&gt;
&lt;li&gt;Admission webhook UID/GID via sessionName (compute)&lt;/li&gt;
&lt;li&gt;DynamoDB mapa POSIX (data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🤖 Task Governance: Kueue
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;LocalQueue hyperpod-ns-team-a-localqueue (messaging)&lt;/li&gt;
&lt;li&gt;ClusterQueue cota garantida + borrow limit (messaging)&lt;/li&gt;
&lt;li&gt;Idle resource sharing IdleResourceSharing: Enabled (messaging)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS: pool de GPU compartilhado
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Nós HyperPod health checks + auto-recovery (compute)&lt;/li&gt;
&lt;li&gt;FSx for Lustre /fsx/TeamA (storage)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📤 Saída: custo e visibilidade
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Kubecost custo por namespace (data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;idp -&amp;gt; ps: federates groups&lt;/li&gt;
&lt;li&gt;user -&amp;gt; ps: aws sso login&lt;/li&gt;
&lt;li&gt;user -&amp;gt; dom: Studio&lt;/li&gt;
&lt;li&gt;ps -&amp;gt; ae: kubectl&lt;/li&gt;
&lt;li&gt;dom -&amp;gt; ae: execution role&lt;/li&gt;
&lt;li&gt;ae -&amp;gt; wh: pod created&lt;/li&gt;
&lt;li&gt;wh -&amp;gt; ddb: resolves UID/GID&lt;/li&gt;
&lt;li&gt;wh -&amp;gt; lq: queue-name label&lt;/li&gt;
&lt;li&gt;lq -&amp;gt; cq: admission&lt;/li&gt;
&lt;li&gt;cq -&amp;gt; idle: over quota: borrows&lt;/li&gt;
&lt;li&gt;cq -&amp;gt; gpu: admits pod&lt;/li&gt;
&lt;li&gt;gpu -&amp;gt; fsx: checkpoint&lt;/li&gt;
&lt;li&gt;gpu -&amp;gt; cost: usage per namespace&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Isolation: a namespace is an organizational boundary, not a security one
&lt;/h2&gt;

&lt;p&gt;The post itself states it plainly: a namespace is an isolation boundary, not a hard security boundary, and the design targets several teams within one company, not tenants who do not trust each other. I agree, and I add what that means in a regulated environment.&lt;/p&gt;

&lt;p&gt;Design A stacks four controls: EKS access entries scoped per namespace (one for the domain role, another for the &lt;code&gt;AWSReservedSSO_&amp;lt;permission-set&amp;gt;_&amp;lt;id&amp;gt;&lt;/code&gt; role), RBAC that returns &lt;code&gt;Forbidden&lt;/code&gt; on cross-namespace calls, a default-deny &lt;code&gt;NetworkPolicy&lt;/code&gt; per namespace, and S3 restricted to the team's prefix. All four run on the same kernel, the same NVIDIA drivers and the same control plane.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Silent failure number one:&lt;/strong&gt; a &lt;code&gt;NetworkPolicy&lt;/code&gt; without a CNI that enforces it is just YAML. If the Amazon VPC CNI does not have network policy enabled, &lt;code&gt;kubectl apply&lt;/code&gt; succeeds, the object exists, and not a single packet is blocked. Test with a team B pod trying to open a connection to a team A service before you call the boundary done.&lt;/p&gt;

&lt;p&gt;My rule is this. If the teams share the same regulatory regime and the same data classification, a namespace is enough. If one team trains on customer data under LGPD and another on synthetic data, or if a BACEN 4.893 auditor is going to ask for demonstrable segregation, the right boundary is the account, and the design goes back to B.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fairness: quota, borrowing, and who gets evicted
&lt;/h2&gt;

&lt;p&gt;Task Governance splits two policies. &lt;strong&gt;Compute prioritization&lt;/strong&gt; decides how idle capacity is handed out (first-come first-serve or fair-share) and how tasks are queued (arrival order or task ranking with priority classes). &lt;strong&gt;Compute allocation&lt;/strong&gt; sets, per team, the guaranteed quota, a fair-share weight from 0 to 100, and the lending posture: Lend and Borrow, Lend, or Don't Lend. The borrow limit can be a percentage, up to 10,000% of quota, or absolute per instance type.&lt;/p&gt;

&lt;p&gt;The documentation example shows the mechanics: team A with a quota of 6 using 2, team B with a quota of 5 using 4, a 4-unit job lands on team B and 3 units are borrowed from A. What the documentation also says, and what defines operations, is that a task running on borrowed quota can be preempted by a higher-priority task when capacity runs out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Silent failure number two:&lt;/strong&gt; a job without &lt;code&gt;kueue.x-k8s.io/priority-class&lt;/code&gt; gets the lowest priority. An inference endpoint deployed without the label becomes the first eviction candidate in the cluster.&lt;/p&gt;

&lt;p&gt;Preemption has a price in GPU-hours. A 16-GPU training run on borrowed capacity with a checkpoint every 30 minutes loses up to 8 GPU-hours per eviction, 4 on average. If the cluster evicts that job twice a day, that is roughly 240 GPU-hours a month thrown away without tripping a single alert. That is why I treat borrowed capacity like spot: only jobs with checkpoints on FSx and idempotent resume run on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chargeback: who pays for idle and borrowed GPUs
&lt;/h2&gt;

&lt;p&gt;The post uses Kubecost grouped by namespace, and since the namespace is already the team, no extra workload tagging is needed. That works for the question "how much did team A consume". On its own it does not answer two questions that show up at the first month-end close.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The borrowed GPU:&lt;/strong&gt; when team B runs on team A's quota, the pod lives in B's namespace. Charging B is correct, but team A will look at the report and see it paid for a guaranteed quota someone else used. Decide upfront whether the guaranteed quota is a fixed price (team A pays for the reservation, used or not) or usage-based. Both rules are defensible; changing the rule mid-quarter is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The idle GPU:&lt;/strong&gt; capacity not allocated to any quota needs an owner in the report. Split Cost Allocation Data for EKS, which started covering GPU, Trainium and Inferentia on 09/02/2025, splits cost per pod and reports unused capacity separately, with a 9:1 GPU versus CPU and memory weighting in the documentation example. That example is an EC2 instance. HyperPod nodes are &lt;code&gt;ml.*&lt;/code&gt; instances billed through SageMaker, so confirm on your bill whether that data shows up before you swap Kubecost for CUR. I would keep Kubecost as the chargeback source and use CUR to reconcile the total.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three designs side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;A: shared HyperPod + Task Governance&lt;/th&gt;
&lt;th&gt;B: one cluster per team&lt;/th&gt;
&lt;th&gt;C: namespaces + ResourceQuota&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Isolation boundary&lt;/td&gt;
&lt;td&gt;Namespace + access entry + NetworkPolicy&lt;/td&gt;
&lt;td&gt;AWS account&lt;/td&gt;
&lt;td&gt;Namespace + RBAC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One team's idle GPU&lt;/td&gt;
&lt;td&gt;Lent, with limits and preemption&lt;/td&gt;
&lt;td&gt;Stays idle&lt;/td&gt;
&lt;td&gt;Stays idle (quota is a hard cap)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contention&lt;/td&gt;
&lt;td&gt;Fair-share by 0–100 weight + task ranking&lt;/td&gt;
&lt;td&gt;None across teams&lt;/td&gt;
&lt;td&gt;Arrival order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chargeback&lt;/td&gt;
&lt;td&gt;Per namespace, needs a borrowing rule&lt;/td&gt;
&lt;td&gt;Account bill, trivial&lt;/td&gt;
&lt;td&gt;Per namespace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parts to maintain&lt;/td&gt;
&lt;td&gt;Quota policies, UID/GID webhook, Kubecost, dashboards&lt;/td&gt;
&lt;td&gt;N clusters, N upgrades, N pipelines&lt;/td&gt;
&lt;td&gt;Few, until the first dispute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When it fails badly&lt;/td&gt;
&lt;td&gt;Job without priority-class or checkpoint&lt;/td&gt;
&lt;td&gt;Low utilization becomes fixed cost&lt;/td&gt;
&lt;td&gt;Large team monopolizes the cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Decision matrix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A: shared HyperPod + Task Governance
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One team's idle GPU becomes another's capacity, with &lt;code&gt;IdleResourceSharing&lt;/code&gt; covering even unallocated capacity&lt;/li&gt;
&lt;li&gt;Explicit priority: production inference preempts experiments&lt;/li&gt;
&lt;li&gt;One driver and EKS upgrade for every team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A namespace will not pass a strong-segregation audit&lt;/li&gt;
&lt;li&gt;The UID/GID webhook and its DynamoDB map become yours to run&lt;/li&gt;
&lt;li&gt;Preemption wastes GPU-hours when jobs do not checkpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Default for teams in one company under one regime&lt;/p&gt;

&lt;h3&gt;
  
  
  B: one cluster per team
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Segregation provable by account, IAM and bill&lt;/li&gt;
&lt;li&gt;A bad upgrade's blast radius stays within one team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each team pays its own GPU floor, even when idle&lt;/li&gt;
&lt;li&gt;Platform effort multiplied by N&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; When data or regulation differ across teams&lt;/p&gt;

&lt;h3&gt;
  
  
  C: namespaces + ResourceQuota
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nothing new to learn&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No borrowing, no cross-team priority, no fair-share&lt;/li&gt;
&lt;li&gt;GPU disputes become meetings, not policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Only as a step until the second team arrives&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Guaranteed quota is not guaranteed capacity:&lt;/strong&gt; The documentation says to keep the sum of quotas below cluster capacity: 20 instances, quotas summing to under 20. But HyperPod pulls unhealthy nodes out of rotation, and idle resource sharing already excludes &lt;code&gt;NotReady&lt;/code&gt; or &lt;code&gt;unschedulable&lt;/code&gt; nodes from its math. Team quotas do not shrink with them. In a 16-node cluster with 2 nodes recovering, quotas summing to 16 promise capacity that does not exist at that moment. I keep at least one node per instance type outside any quota.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I would do:&lt;/strong&gt; I would start with design A, but with three rules written down before the first job: any manifest without &lt;code&gt;kueue.x-k8s.io/priority-class&lt;/code&gt; is rejected by an admission policy, every job running on borrowed quota writes checkpoints to FSx at a known interval, and the billing rule for borrowed quota is signed off by the cost center owners. I would also make the UID/GID webhook's &lt;code&gt;failurePolicy&lt;/code&gt; an explicit decision: &lt;code&gt;Fail&lt;/code&gt; so the cluster stops scheduling when DynamoDB does not answer, &lt;code&gt;Ignore&lt;/code&gt; only if you accept pods with the wrong POSIX identity writing to &lt;code&gt;/fsx&lt;/code&gt;. I have seen shared platforms die not from a technical limit, but because the first unexplained eviction made a team ask for its own cluster. The lesson: in a shared cluster, predictability is worth more than utilization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Use &lt;strong&gt;Option A (shared HyperPod with Task Governance)&lt;/strong&gt; when the teams belong to the same company, handle data with the same classification, and someone owns the quota policies and Kueue observability (&lt;code&gt;kubectl describe workload&lt;/code&gt; has to become routine, not archaeology). Use &lt;strong&gt;Option B (cluster per account)&lt;/strong&gt; when one team operates under LGPD with customer data and another does not, or when the audit requires segregation by account; accept paying for idle GPUs as the price of isolation. Treat Option C as a transitional state: as soon as a second team competes for GPUs, move to A. The gain in design A is not having a single cluster, it is lending idle GPUs without negotiating in a meeting, and it only pays off if evictions are predictable and chargeback is agreed upfront.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; A, com regras de admissão e checkpoint /&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/machine-learning/share-gpu-clusters-across-teams-with-isolation-and-fairness-using-amazon-sagemaker-hyperpod/" rel="noopener noreferrer"&gt;AWS ML Blog: Share GPU clusters across teams with isolation and fairness using Amazon SageMaker HyperPod (08/10/2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/sagemaker/latest/dg/sagemaker-hyperpod-eks-operate-console-ui-governance-policies.html" rel="noopener noreferrer"&gt;Amazon SageMaker AI docs: HyperPod task governance policies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/machine-learning/best-practices-for-amazon-sagemaker-hyperpod-task-governance/" rel="noopener noreferrer"&gt;AWS ML Blog: Best practices for Amazon SageMaker HyperPod task governance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cur/latest/userguide/example-accelerated-instances.html" rel="noopener noreferrer"&gt;AWS Data Exports docs: Example of split cost allocation data for accelerated instances&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/09/split-cost-allocation-data-amazon-eks-nvidia-amd-gpu-trainium-inferentia-ec2" rel="noopener noreferrer"&gt;AWS What's New: Split Cost Allocation Data for Amazon EKS supports NVIDIA &amp;amp; AMD GPU, Trainium, and Inferentia (02/09/202&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/cluster-de-gpu-compartilhado-no-hyperpod-tres-desenhos-e-quando-cada" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>sagemakerhyperpod</category>
      <category>eks</category>
      <category>gpu</category>
    </item>
    <item>
      <title>Critical or non-critical: the failure contract of ECS daemons</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Thu, 08 Oct 2026 18:00:58 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/critical-or-non-critical-the-failure-contract-of-ecs-daemons-505i</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/critical-or-non-critical-the-failure-contract-of-ecs-daemons-505i</guid>
      <description>&lt;p&gt;Every container cluster carries a layer of software nobody asked for and everybody needs: the log shipper, the EDR agent, the metrics exporter, the mesh proxy. For years that layer was treated as a packaging detail, one more sidecar in the task definition, a DaemonSet on Kubernetes. The launch of non-critical daemons in Amazon ECS Managed Daemons, on September 3, 2026, exposes the question that was hiding underneath the packaging: when that agent dies, what should happen to the machine hosting it? The answer is now a parameter (&lt;code&gt;critical&lt;/code&gt;), and parameters get versioned, reviewed, and gotten wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: cross-cutting agents are coupling disguised as convenience
&lt;/h2&gt;

&lt;p&gt;The sidecar-per-task model solves distribution and fails at economics and governance. An instance running 40 tasks runs 40 copies of the log shipper; at 128 MiB each, that is 5 GiB of RAM spent duplicating one function. Worse than the waste is the drift: the agent version becomes the property of each application team, and the security team finds out during an audit that three services still run a seven-month-old image.&lt;/p&gt;

&lt;p&gt;ECS already had the &lt;code&gt;DAEMON&lt;/code&gt; scheduling strategy for the EC2 launch type, but that is placement only, one daemon per instance, with no guarantee that it is up &lt;em&gt;before&lt;/em&gt; the first application task serves traffic. For telemetry that is a hole in the graph. For a runtime security agent, it is a window in which the instance processes transactions without coverage.&lt;/p&gt;

&lt;p&gt;Managed Daemons, GA on April 1, 2026 for ECS Managed Instances capacity providers, closed that hole by inverting the relationship: the instance only transitions the application task to &lt;code&gt;RUNNING&lt;/code&gt; after the daemon is running. The price of that guarantee was rigid coupling, any daemon failure drained and replaced the instance. Correct for a compliance agent. Absurd for a metrics exporter. The September launch does not add a new capability; it undoes a coupling that should never have been the only option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anatomy: three parts, two guarantees, one switch
&lt;/h2&gt;

&lt;p&gt;The pattern has three distinct resources. First, a &lt;strong&gt;daemon task definition&lt;/strong&gt;: its own resource type with its own ARN (&lt;code&gt;daemon-task-definition/...&lt;/code&gt;), separate from your application task definition. Second, a &lt;strong&gt;daemon&lt;/strong&gt;, created with &lt;code&gt;CreateDaemon&lt;/code&gt;, binding that definition to a cluster and to one or more ECS Managed Instances capacity providers. Third, the EC2 instances provisioned by those capacity providers, on each of which ECS keeps exactly one daemon task.&lt;/p&gt;

&lt;p&gt;The first guarantee is &lt;strong&gt;ordering&lt;/strong&gt;: ECS provisions the instance, starts the daemon task, and only then promotes the application task to &lt;code&gt;RUNNING&lt;/code&gt;. Independent measurements published in April 2026 showed the daemon reaching &lt;code&gt;RUNNING&lt;/code&gt; 18 to 21 seconds ahead of the application, with the instance held in &lt;code&gt;REGISTERING&lt;/code&gt; during that window. &lt;strong&gt;That ordering applies to every daemon, critical or not.&lt;/strong&gt; This is the point I most often see misread: marking a daemon non-critical does not cost you the guarantee that the agent comes up before traffic; it only costs you the guarantee that it &lt;em&gt;stays&lt;/em&gt; up.&lt;/p&gt;

&lt;p&gt;The second guarantee is &lt;strong&gt;remediation&lt;/strong&gt;, and that is what the &lt;code&gt;critical&lt;/code&gt; switch governs. Note how ECS remediates: it does not restart the container, it drains and replaces the whole instance. That is deliberately blunt, an agent that died may have left eBPF probes detached, network rules half-applied, buffers lost. Replacing the instance is the only cheap way back to a known state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instance lifecycle and the criticality branch
&lt;/h2&gt;

&lt;p&gt;The startup ordering (left) is identical in both modes. The &lt;code&gt;critical&lt;/code&gt; parameter only decides what happens after the daemon fails, and the non-critical branch is only safe if the observability arm is wired up.&lt;/p&gt;

&lt;h3&gt;
  
  
  🟦 AWS: Provisioning &amp;amp; startup order
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Managed Instances capacity provider (compute)&lt;/li&gt;
&lt;li&gt;EC2 instance state: REGISTERING (compute)&lt;/li&gt;
&lt;li&gt;Daemon task exactly 1 per instance (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔀 Failure contract, critical parameter
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;critical = true | false per-daemon, default true (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟥 critical=true, fail closed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Registration blocked instance never goes ACTIVE (security)&lt;/li&gt;
&lt;li&gt;DRAINING + replace ~5 min per wave, 25% default (compute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟩 critical=false, fail open
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Instance stays ACTIVE running tasks untouched (compute)&lt;/li&gt;
&lt;li&gt;Placement continues registration never blocked (compute)&lt;/li&gt;
&lt;li&gt;Coverage gap silent unless observed (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 Observability, the other half of the pattern
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;EventBridge Daemon service action (messaging)&lt;/li&gt;
&lt;li&gt;Service action log critical + non-critical (data)&lt;/li&gt;
&lt;li&gt;CloudWatch alarm + bake time rollback (data)&lt;/li&gt;
&lt;li&gt;Coverage SLO % instances with healthy daemon (data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;svc -&amp;gt; cp: 1. pending task&lt;/li&gt;
&lt;li&gt;cp -&amp;gt; ec2: 2. provisions EC2&lt;/li&gt;
&lt;li&gt;ec2 -&amp;gt; daemon: 3. daemon first (~18-21s)&lt;/li&gt;
&lt;li&gt;daemon -&amp;gt; gate: 4. failed / stopped / unhealthy&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; block: fails to start&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; drain: fails after ACTIVE&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; active: critical=false&lt;/li&gt;
&lt;li&gt;active -&amp;gt; place: new tasks land&lt;/li&gt;
&lt;li&gt;place -&amp;gt; gap: no agent coverage&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; eb: event in both modes&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; log: service action log&lt;/li&gt;
&lt;li&gt;eb -&amp;gt; alarm: rule → SNS/Lambda&lt;/li&gt;
&lt;li&gt;alarm -&amp;gt; drain: circuit breaker rollback&lt;/li&gt;
&lt;li&gt;eb -&amp;gt; slo: feeds the coverage error budget&lt;/li&gt;
&lt;li&gt;gap -&amp;gt; slo: only visible if measured&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What actually differs between the two modes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Critical (default)&lt;/th&gt;
&lt;th&gt;Non-critical (critical=false)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Daemon fails to start&lt;/td&gt;
&lt;td&gt;Instance never completes registration; no application task is placed on it&lt;/td&gt;
&lt;td&gt;Registration is never blocked; application tasks launch immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daemon stops or goes unhealthy later&lt;/td&gt;
&lt;td&gt;ECS drains and replaces the whole instance (auto-repair)&lt;/td&gt;
&lt;td&gt;Instance stays ACTIVE; existing tasks are untouched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Placement of new tasks&lt;/td&gt;
&lt;td&gt;Stops on the affected instance&lt;/td&gt;
&lt;td&gt;Continues normally, agent or no agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup ordering&lt;/td&gt;
&lt;td&gt;Daemon before the application&lt;/td&gt;
&lt;td&gt;Daemon before the application, identical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signal emitted on failure&lt;/td&gt;
&lt;td&gt;EventBridge event + service action log&lt;/td&gt;
&lt;td&gt;EventBridge event + service action log, identical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Residual risk you take on&lt;/td&gt;
&lt;td&gt;Fleet churn: an unstable agent turns into cascading instance replacement&lt;/td&gt;
&lt;td&gt;Blind spot: coverage degrades silently if nobody consumes the event&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When to use each mode: the rule I apply
&lt;/h2&gt;

&lt;p&gt;The rule fits in one line: &lt;strong&gt;mark as critical the agent whose absence changes what the system is allowed to do; mark as non-critical the agent whose absence only changes what you can see.&lt;/strong&gt; A runtime EDR, a network policy enforcer, an mTLS sidecar, or a file-integrity agent required by PCI DSS fall in the first group, without them the instance should not be eligible to process a transaction. An OTel metrics collector, a node exporter, a continuous profiler fall in the second.&lt;/p&gt;

&lt;p&gt;The nuance lives in the log router. In financial environments the log often &lt;em&gt;is&lt;/em&gt; the audit trail: "what you can see" becomes "what you can prove", and Fluent Bit stops being auxiliary. Do not classify by tool category; classify by the regulatory obligation that stream underwrites. If the agent's local buffer survives the outage window without dropping events, non-critical is defensible; if it drops them, it is critical.&lt;/p&gt;

&lt;p&gt;The design corollary is what pays off most in practice: because criticality is per daemon, and a capacity provider accepts multiple daemons, &lt;strong&gt;do not package agents with different contracts into the same task definition&lt;/strong&gt;. A task definition bundling the EDR and the metrics collector forces the stricter contract on both, and turns a collector memory leak into fleet replacement. Two daemons, two release cycles, two owners, two criticalities.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Non-critical" is a control-plane term, not a business term. The parameter describes what ECS does to the instance, not how much that agent matters to you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What you pay for classifying wrong, in both directions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Too critical.&lt;/strong&gt; A bad agent revision makes the task flap, and every flap becomes an instance replacement. On a 60-instance fleet with the drain percentage at its default of 25%, that is 15 simultaneous replacements per wave; at the measured ~4m50s per wave, a one-line agent change costs roughly 20 minutes of fleet rotation. The direct bill impact is modest and computable: the Managed Instances management fee for a &lt;code&gt;c6a.2xlarge&lt;/code&gt; is US$0.037/hour per instance (Oregon), so 60 instances add up to ~US$1,620/month in fee alone, plus EC2 and the overlap during draining. The expensive bill is elsewhere: application task rescheduling, drained connections, cold caches, cold JIT.&lt;/p&gt;

&lt;p&gt;Two defaults conspire here. Bake time defaults to &lt;code&gt;0&lt;/code&gt; minutes, meaning that once the last wave finishes, the window in which the circuit breaker watches alarms to roll back is nil. A regression that only shows up under load sails right through.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Too non-critical.&lt;/strong&gt; Fail-open emits no pain: the instance stays ACTIVE, the application SLO does not move, and agent coverage decays in silence. Add the documented operational trap: daemon configuration settings are &lt;strong&gt;not persisted between updates&lt;/strong&gt;, and &lt;code&gt;critical&lt;/code&gt; defaults to &lt;code&gt;true&lt;/code&gt;. An &lt;code&gt;UpdateDaemon&lt;/code&gt; fired by a pipeline without an explicit &lt;code&gt;--no-critical&lt;/code&gt; re-arms fleet-wide churn that nobody asked for. It is exactly the kind of change that only surfaces at the next incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-patterns this launch makes easy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Marking everything non-critical to "stop the churn", you did not remove the failure, you just muted the alarm it used to pull.&lt;/li&gt;
&lt;li&gt;Flipping to non-critical without first wiring the EventBridge rule for daemon events to a destination that wakes somebody up. Fail-open with no event consumer is a compliance blind spot that looks like stability.&lt;/li&gt;
&lt;li&gt;Packaging the security agent and the telemetry agent in the same daemon task definition, forcing a single failure contract onto software with opposite risk profiles.&lt;/li&gt;
&lt;li&gt;Managing daemons via ad-hoc CLI calls: since &lt;code&gt;UpdateDaemon&lt;/code&gt; does not persist settings and &lt;code&gt;critical&lt;/code&gt; reverts to the &lt;code&gt;true&lt;/code&gt; default, every partial call is a silent contract change.&lt;/li&gt;
&lt;li&gt;Keeping bake time at 0 while configuring alarms, paying the cognitive cost of the circuit breaker without buying the observation window that makes it useful.&lt;/li&gt;
&lt;li&gt;Treating the critical daemon as a substitute for application health checking. It repairs agent coverage, not business health; a healthy agent on a disk-full instance is still "healthy".&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Well-Architected reading of the decision
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;security&lt;/strong&gt;: Fail-closed remains the correct default for enforcement agents: a critical daemon keeps the instance from registering without coverage. Govern who may loosen it with IAM on &lt;code&gt;ecs:CreateDaemon&lt;/code&gt;/&lt;code&gt;ecs:UpdateDaemon&lt;/code&gt;, scoped by daemon ARN and conditioned on tags, so the security team owns the security daemon and the platform team owns the rest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reliability&lt;/strong&gt;: Non-critical removes a real correlated failure mode: an agent regression hitting every instance at once used to produce simultaneous fleet replacement. In exchange, agent recovery stops being automatic, plan explicit repair.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Reference configuration I would take to production:&lt;/strong&gt; Two daemons per capacity provider: &lt;code&gt;sec-runtime&lt;/code&gt; with &lt;code&gt;critical: true&lt;/code&gt; and &lt;code&gt;obs-otel&lt;/code&gt; with &lt;code&gt;critical: false&lt;/code&gt;. Drain percentage at 10 (not 25) so a bad wave hits 10% of the fleet, and bake time between 15 and 30 minutes with at least two CloudWatch alarms wired to the circuit breaker, one on application errors, one on the agent's own health. One EventBridge rule capturing daemon service action and daemon deployment state change events, targeting SNS for the critical daemon and a queue/Lambda feeding the coverage SLO for the non-critical one. Every daemon declared in full in IaC, never by partial CLI call, with drift detection, because &lt;code&gt;UpdateDaemon&lt;/code&gt; reverts whatever you omit. And check the instance profile: it needs &lt;code&gt;AmazonECSInstanceRolePolicyForManagedInstances&lt;/code&gt;; the legacy &lt;code&gt;AmazonEC2ContainerServiceforEC2Role&lt;/code&gt; policy stops the daemon from starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Curator's note:&lt;/strong&gt; I have watched a memory leak in an observability agent turn into node rotation during a settlement window, and the post-mortem did not name the leak as root cause, it named the fact that nobody had ever written down which agents were allowed to die. Since then I require an explicit criticality field in the ADR for any agent before it lands on the platform, with the owner and the regulatory obligation it underwrites next to it. This launch finally gives me a place in the control plane to materialize that decision instead of leaving it implicit in a runbook. What I would not do is use &lt;code&gt;critical: false&lt;/code&gt; as a painkiller: if an agent fails often enough to hurt, the agent is the problem, and marking it non-critical merely trades a noisy incident for a silent gap. My default stays critical, and every exception needs a coverage SLO to get approved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Adopt it, and treat &lt;code&gt;critical&lt;/code&gt; as an architectural decision, not a tuning flag. Keep the critical default for enforcement agents (EDR, network policy, mTLS, file integrity) and for log routers that underwrite an audit trail. Move to non-critical only metrics, profiling and telemetry whose temporary loss is not a loss of evidence, and only after the EventBridge rule has a consumer and the coverage SLO is published. Split agents with different contracts into different daemons, drop the drain percentage to 10, raise bake time to 15–30 minutes, and declare everything in IaC, because &lt;code&gt;UpdateDaemon&lt;/code&gt; silently reverts omissions to &lt;code&gt;critical: true&lt;/code&gt;. If your cluster is not on Managed Instances yet, this launch does not change your roadmap; if it is, it closes the last reasonable excuse for keeping an agent sidecar in every task definition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Adopt, with the switch written down&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/09/ecs-managed-daemons-non-critical/" rel="noopener noreferrer"&gt;AWS What's New: Amazon ECS Managed Daemons now support non-critical daemons (Sep 3, 2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/managed-daemons.html" rel="noopener noreferrer"&gt;Amazon ECS Developer Guide: Amazon ECS Managed Daemons (daemon criticality)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/managed-daemons-create-manage.html" rel="noopener noreferrer"&gt;Amazon ECS Developer Guide: Creating and managing daemons (drain percentage, bake time, UpdateDaemon)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs_cwe_events.html" rel="noopener noreferrer"&gt;Amazon ECS Developer Guide: Amazon ECS events (daemon service action, daemon deployment state change)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/04/amazon-ecs-managed-daemons/" rel="noopener noreferrer"&gt;AWS What's New: Amazon ECS announces Managed Daemons for ECS Managed Instances (Apr 1, 2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/ecs/pricing/" rel="noopener noreferrer"&gt;Amazon ECS pricing: ECS Managed Instances management fee&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shinyaz.com/en/blog/2026/04/02/ecs-managed-daemons-verification" rel="noopener noreferrer"&gt;shinyaz: Measuring ECS Managed Daemons startup order guarantees and rolling deployment behavior&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/ecs/managed-instances/" rel="noopener noreferrer"&gt;Amazon ECS Managed Instances, product overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/daemon-critico-ou-nao-critico-o-contrato-de-falha-do-ecs-amazon-ecs-m" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>ecs</category>
      <category>managedinstances</category>
      <category>containers</category>
    </item>
    <item>
      <title>AMIs with a contract: anatomy of EC2's new instance-type block</title>
      <dc:creator>Fernando Azevedo</dc:creator>
      <pubDate>Thu, 08 Oct 2026 18:00:24 +0000</pubDate>
      <link>https://dev.to/fernando_azevedo_6844e930/amis-with-a-contract-anatomy-of-ec2s-new-instance-type-block-3nfc</link>
      <guid>https://dev.to/fernando_azevedo_6844e930/amis-with-a-contract-anatomy-of-ec2s-new-instance-type-block-3nfc</guid>
      <description>&lt;p&gt;Um AMI sempre foi um contrato verbal. O artefato sabe exatamente de que hardware ele precisa, driver ENA, NVMe, arquitetura do binário, extensão de CPU que a JVM compilou, e nada disso estava escrito em lugar nenhum que o &lt;code&gt;RunInstances&lt;/code&gt; pudesse ler. A partir de 4 de setembro de 2026 dá para escrever: o dono do AMI declara &lt;code&gt;SupportedInstanceTypes&lt;/code&gt;, &lt;code&gt;UnsupportedInstanceTypes&lt;/code&gt; ou os dois, e o EC2 bloqueia o lançamento fora da lista. Sem custo adicional, em todas as regiões. A leitura fácil é "menos launch quebrado". A leitura que interessa é outra, o ponto de enforcement mudou de lugar, e com ele mudou quem paga a conta da manutenção.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: the contract existed, it just wasn't executable
&lt;/h2&gt;

&lt;p&gt;Every platform team that publishes golden AMIs has lived this scene. The hardened image was built for &lt;code&gt;x86_64&lt;/code&gt;, with a statically compiled EDR agent and a kernel that only loads its network driver on a Nitro host. Someone creates an Auto Scaling group with attribute-based instance type selection asking for 4 vCPUs and 16 GiB, leaves Spot diversification wide, which is the correct recommendation for filling capacity, and EC2 picks a Graviton family because it matches the attributes and is the cheapest right now.&lt;/p&gt;

&lt;p&gt;The instance &lt;strong&gt;comes up&lt;/strong&gt;. That detail is what makes the failure mode hurt. It doesn't die at &lt;code&gt;RunInstances&lt;/code&gt;; it dies later, with no console, no network, nothing but a health check running out. The ASG waits the default 300-second grace period, marks the instance unhealthy, terminates it and tries again, and since the allocation decision hasn't changed, it picks the same pool. You get a launch/terminate loop billed at a 60-second minimum per instance created, an alarm firing at 2 AM, and a graph that looks exactly like a capacity shortage.&lt;/p&gt;

&lt;p&gt;Every defense available lived on the caller side: an &lt;code&gt;ec2:InstanceType&lt;/code&gt; condition in an IAM policy, an SCP at the Organization, &lt;code&gt;ExcludedInstanceTypes&lt;/code&gt; inside a fleet's &lt;code&gt;InstanceRequirements&lt;/code&gt;. All correct, all sharing one structural defect, they describe what &lt;strong&gt;that principal&lt;/strong&gt; may launch, not what &lt;strong&gt;that image&lt;/strong&gt; can survive. Share the AMI with 40 accounts and you replicate the rule 40 times, in 40 places that drift apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  The contract travels with the artifact, and fails at the gate, not at the health check
&lt;/h2&gt;

&lt;p&gt;Reference design: the pipeline derives the list from a capability query, writes the specification onto the AMI, and &lt;code&gt;RunInstances&lt;/code&gt; becomes the single evaluation point for every account that received the image.&lt;/p&gt;

&lt;h3&gt;
  
  
  🏗️ Pipeline de imagem, build time
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;EC2 Image Builder pipeline do golden AMI (ci)&lt;/li&gt;
&lt;li&gt;DescribeInstanceTypes filtro por arquitetura + hypervisor (compute)&lt;/li&gt;
&lt;li&gt;ReplaceImageInstanceTypeSpecification lista explícita, gerada (ci)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟧 AWS, plano de controle do EC2
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AMI InstanceTypeSpecification (storage)&lt;/li&gt;
&lt;li&gt;CopyImage / share especificação preservada (storage)&lt;/li&gt;
&lt;li&gt;RunInstances avalia tipo × contrato (security)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🚀 Consumidores, launch time
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Auto Scaling group ABIS + Spot diversificado (compute)&lt;/li&gt;
&lt;li&gt;Launch template versão do AMI fixada (compute)&lt;/li&gt;
&lt;li&gt;Instância lançada tipo dentro do contrato (compute)&lt;/li&gt;
&lt;li&gt;InvalidParameterCombination lançamento bloqueado (external)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔍 Sinais, o que você tem que instrumentar
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;CloudTrail errorCode em RunInstances (data)&lt;/li&gt;
&lt;li&gt;Scaling activity falha registrada no ASG (messaging)&lt;/li&gt;
&lt;li&gt;CloudWatch metric filter por imageId (data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;plat -&amp;gt; builder: publishes the golden AMI&lt;/li&gt;
&lt;li&gt;builder -&amp;gt; capq: asks what the hardware does&lt;/li&gt;
&lt;li&gt;capq -&amp;gt; setspec: compatible types, enumerated&lt;/li&gt;
&lt;li&gt;setspec -&amp;gt; ami: writes both lists&lt;/li&gt;
&lt;li&gt;ami -&amp;gt; copy: cross-region / cross-account&lt;/li&gt;
&lt;li&gt;ami -&amp;gt; gate: contract read at launch&lt;/li&gt;
&lt;li&gt;asg -&amp;gt; lt: scales out&lt;/li&gt;
&lt;li&gt;lt -&amp;gt; gate: RunInstances&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; ok: type allowed&lt;/li&gt;
&lt;li&gt;gate -&amp;gt; blocked: type off the list&lt;/li&gt;
&lt;li&gt;blocked -&amp;gt; ct: auditable error&lt;/li&gt;
&lt;li&gt;blocked -&amp;gt; sa: capacity not delivered&lt;/li&gt;
&lt;li&gt;ct -&amp;gt; alarm: metric filter&lt;/li&gt;
&lt;li&gt;sa -&amp;gt; alarm&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Anatomy: two lists, one evaluation order, zero state
&lt;/h2&gt;

&lt;p&gt;The attribute is &lt;code&gt;InstanceTypeSpecification&lt;/code&gt; and it carries &lt;code&gt;SupportedInstanceTypes&lt;/code&gt; and &lt;code&gt;UnsupportedInstanceTypes&lt;/code&gt;. The logic is short enough to fit in an ADR: with no specification, everything passes (that's the default, and that's why nothing broke on the 4th); with only the unsupported list, everything passes except what matches; with the supported list set, the type must match the supported list &lt;strong&gt;and&lt;/strong&gt; must not match the unsupported one. Denial always wins.&lt;/p&gt;

&lt;p&gt;You write it with &lt;code&gt;aws ec2 replace-image-instance-type-specification --image-id ami-... --instance-type-specification '{"SupportedInstanceTypes": ["t3.*","a2.*"], "UnsupportedInstanceTypes": ["t3.micro"]}'&lt;/code&gt;. Omitting the parameter removes the restriction. You read it back through &lt;code&gt;describe-images&lt;/code&gt;, which returns the &lt;code&gt;InstanceTypeSpecification&lt;/code&gt; field with the patterns as stored. A blocked launch returns &lt;code&gt;InvalidParameterCombination&lt;/code&gt; from &lt;code&gt;RunInstances&lt;/code&gt;, with a message pointing at &lt;code&gt;DescribeImages&lt;/code&gt; as the place to look.&lt;/p&gt;

&lt;p&gt;Four properties change the design, and none of them are in the announcement:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Replace&lt;/code&gt;, not &lt;code&gt;Modify&lt;/code&gt;:&lt;/strong&gt; the operation swaps the whole specification. There is no add or remove, it's read-modify-write with no conditional version. Two pipelines patching the same image in parallel produce last-writer-wins &lt;strong&gt;silently&lt;/strong&gt;, and the loser only finds out when a legitimate launch gets blocked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only the owner writes.&lt;/strong&gt; An account that received the shared AMI consumes the contract and does not negotiate it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;CopyImage&lt;/code&gt; preserves the specification.&lt;/strong&gt; That's what makes the pattern worth it in a multi-Region topology: you declare once and the &lt;code&gt;sa-east-1&lt;/code&gt; replica is born with the contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Marketplace is mutually exclusive.&lt;/strong&gt; You can't set a specification on an AMI listed in AWS Marketplace, and you can't list an AMI that already has one. If you're an ISV shipping an appliance, this feature does not exist for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to constrain instance type, and what each one actually covers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;IAM / SCP &lt;code&gt;ec2:InstanceType&lt;/code&gt; condition&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;ExcludedInstanceTypes&lt;/code&gt; in ABIS&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;InstanceTypeSpecification&lt;/code&gt; on the AMI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where the rule lives&lt;/td&gt;
&lt;td&gt;In the principal's policy, per account or OU&lt;/td&gt;
&lt;td&gt;In the fleet or ASG configuration&lt;/td&gt;
&lt;td&gt;On the artifact, once, in the owning account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Applies to launches from another account&lt;/td&gt;
&lt;td&gt;No, that policy is theirs, not yours&lt;/td&gt;
&lt;td&gt;No, the consumer configures the fleet&lt;/td&gt;
&lt;td&gt;Yes: EC2 evaluates it regardless of caller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Survives CopyImage and sharing&lt;/td&gt;
&lt;td&gt;Irrelevant, it doesn't follow the image&lt;/td&gt;
&lt;td&gt;Irrelevant, it doesn't follow the image&lt;/td&gt;
&lt;td&gt;Yes, preserved on copy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When the failure shows up&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;UnauthorizedOperation&lt;/code&gt; at launch&lt;/td&gt;
&lt;td&gt;Never, the type simply isn't chosen&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;InvalidParameterCombination&lt;/code&gt; at launch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance cost&lt;/td&gt;
&lt;td&gt;High: replicate and audit across N accounts&lt;/td&gt;
&lt;td&gt;Medium: one edit per fleet that exists&lt;/td&gt;
&lt;td&gt;Low per place, high per hardware generation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where the glob betrays you: a string is not an architecture predicate
&lt;/h2&gt;

&lt;p&gt;Both lists accept &lt;code&gt;*&lt;/code&gt;. The docs give the examples: &lt;code&gt;t3.*&lt;/code&gt; matches the whole t3 family, &lt;code&gt;g5.*&lt;/code&gt; matches g5, &lt;code&gt;*.12xlarge&lt;/code&gt; matches any 12xlarge in any family, &lt;code&gt;*xlarge&lt;/code&gt; matches any type xlarge or larger. It looks expressive. This is where I'd stop and ask the question that matters: &lt;strong&gt;are you describing hardware, or are you describing names?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What the AMI actually requires is a capability: &lt;code&gt;arm64&lt;/code&gt; versus &lt;code&gt;x86_64&lt;/code&gt;, Nitro versus Xen hypervisor, presence of a GPU, presence of local NVMe. What the specification evaluates is a pattern over the instance type's string. The two coincide &lt;em&gt;today&lt;/em&gt; because AWS names Graviton families with a &lt;code&gt;g&lt;/code&gt; and local-storage families with a &lt;code&gt;d&lt;/code&gt;. They coincide by convention, and a naming convention is not a contract: &lt;code&gt;c6gn&lt;/code&gt;, &lt;code&gt;c7gd&lt;/code&gt;, &lt;code&gt;m7i-flex&lt;/code&gt;, &lt;code&gt;u7in-32tb&lt;/code&gt; and &lt;code&gt;mac2-m2pro&lt;/code&gt; already show a scheme full of exceptions, suffixes and infixes that no simple glob covers. A &lt;code&gt;*g.*&lt;/code&gt; written to exclude Graviton is a heuristic, and a hand-written heuristic, in a file nobody revisits, is debt that accrues interest.&lt;/p&gt;

&lt;p&gt;The second trap is temporal, and it's the expensive one. &lt;strong&gt;An explicit allow-list blocks every future generation by construction.&lt;/strong&gt; New families reach new Regions constantly; the day the next generation shows up in your Spot pool, the fleet you deliberately left wide, because wide diversification is the correct practice for Spot capacity, will skip exactly the cheapest, most available family. You get no error: the ASG simply doesn't choose it, price creeps up, interruption rate creeps up, and nobody connects that to a JSON written eight months ago. It's the same tension behind &lt;code&gt;OnDemandMaxPricePercentageOverLowestPrice&lt;/code&gt;, which ships with a 20% default headroom precisely so it doesn't narrow your pool without telling you.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Generate the list, don't write the list:&lt;/strong&gt; The only way to keep the contract honest is to derive it from a capability query in the same build that produced the image: &lt;code&gt;aws ec2 describe-instance-types --filters Name=processor-info.supported-architectures,Values=x86_64 Name=hypervisor,Values=nitro --query 'InstanceTypes[].InstanceType'&lt;/code&gt;, feeding the result straight into &lt;code&gt;replace-image-instance-type-specification&lt;/code&gt;. For fleets that already think in attributes, &lt;code&gt;GetInstanceTypesFromInstanceRequirements&lt;/code&gt; plays the same role and lets you preview the set before writing it. The list ends up explicit, verbose and ugly, which is exactly what you want, because it is &lt;strong&gt;regenerated on every build&lt;/strong&gt;, and your lag behind the AWS catalog becomes your pipeline cadence instead of the memory of whoever wrote the glob.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The hard block on incident day, and the signal nobody instruments
&lt;/h2&gt;

&lt;p&gt;The documentation is explicit: EC2 enforces the specification as a hard block, and launch templates and Auto Scaling groups that reference the AMI can fail if the configured instance type isn't allowed. That isn't an implementation detail, it's the pattern's central trade. You converted a slow, expensive failure (zombie instance, health check, loop) into a fast, cheap one (&lt;code&gt;InvalidParameterCombination&lt;/code&gt; at &lt;code&gt;RunInstances&lt;/code&gt;). Fast and cheap wins on 95% of days. On the other 5%, you're in the middle of a zonal capacity event, &lt;code&gt;price-capacity-optimized&lt;/code&gt; has finally found a pool with room, and your contract says no.&lt;/p&gt;

&lt;p&gt;In financial systems that has a name: you traded correctness risk for availability risk, and you did it on purpose. When the AMI genuinely won't boot on that hardware, the trade is trivially good, the instance was never going to serve traffic. When the list is merely stale, you invented an unavailability mode that didn't exist before, in exactly the window where elasticity matters. That's why a &lt;strong&gt;generated allow-list&lt;/strong&gt; and a &lt;strong&gt;minimal deny-list&lt;/strong&gt; aren't aesthetic preferences: one errs toward letting through, the other errs toward blocking.&lt;/p&gt;

&lt;p&gt;The hole is observability. The specification doesn't affect running instances, emits no metric of its own, and shows up on no dashboard by default. The auditable signal is the &lt;code&gt;RunInstances&lt;/code&gt; event in CloudTrail carrying an &lt;code&gt;errorCode&lt;/code&gt;, build a metric filter grouped by &lt;code&gt;requestParameters.imageId&lt;/code&gt; and alarm on any block, because in steady state the expected rate is &lt;strong&gt;zero&lt;/strong&gt;. A recurring block isn't the control working; it's a launch template pointing at an AMI whose contract changed underneath it. And add the read side to the runbook: &lt;code&gt;describe-images&lt;/code&gt; with its &lt;code&gt;InstanceTypeSpecification&lt;/code&gt; field is the first thing to check when a launch fails on a parameter, the error message itself sends you there, and whoever is on call at 2 AM needs to know that before opening a support case.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to get this wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Applying the contract to an already-shared AMI without an inventory of consumers&lt;/strong&gt;: you can't see the launch templates in the other 40 accounts, and the block only surfaces on their next scale-out, which is probably a traffic spike, not a deploy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hand-writing the list&lt;/strong&gt;: a human-curated glob is a snapshot of the instance catalog on the day it was written, and the catalog changes every week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two pipelines writing the same AMI's specification&lt;/strong&gt;: &lt;code&gt;ReplaceImageInstanceTypeSpecification&lt;/code&gt; replaces everything and has no conditional write; the second one erases the first with no error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using this as a cost control&lt;/strong&gt;: blocking &lt;code&gt;*.24xlarge&lt;/code&gt; to contain spend is FinOps policy disguised as compatibility, that belongs in an SCP with &lt;code&gt;ec2:InstanceType&lt;/code&gt; or in &lt;code&gt;OnDemandMaxPricePercentageOverLowestPrice&lt;/code&gt;, where exceptions have an owner and a process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mistaking this for pipeline validation&lt;/strong&gt;: the contract fails at launch, not at build. It does not replace a test that boots the image on each allowed family before promoting the AMI to production.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reading the pattern through the pillars
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;operational-excellence&lt;/strong&gt;: The contract becomes generated code in the same pipeline as the AMI, versioned with it. Add &lt;code&gt;describe-images&lt;/code&gt; to the runbook: the error message points there, and it's the first diagnostic step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;security&lt;/strong&gt;: Constraining the list is neither isolation nor least privilege, only the owner writes it and the rule follows the image, but it does not replace an IAM condition or an SCP for governing who launches what.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reliability&lt;/strong&gt;: A real gain by killing the launch/terminate loop from incompatible AMIs; a real risk of failing to scale during a capacity event if the list is stale. An alarm with an expected rate of zero is what separates the two.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cost&lt;/strong&gt;: The feature costs nothing and cuts the waste of instances that booted only to die at the health check, but a narrow allow-list pushes the fleet into pricier pools while emitting no signal at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What I'd actually do:&lt;/strong&gt; I'd turn this on first for the AMIs that carry a genuine physical limit, an &lt;code&gt;arm64&lt;/code&gt; image, an image whose driver only exists on Nitro, an inference image that assumes a GPU, and leave general-purpose AMIs alone, where the permissive default is already right. On every image I restrict, I'd generate the list from &lt;code&gt;describe-instance-types&lt;/code&gt; inside the build and fail the pipeline if it comes back empty, because an empty list from a broken query blocks everything silently. And I'd put a zero-expected-rate alarm on the &lt;code&gt;RunInstances&lt;/code&gt; &lt;code&gt;errorCode&lt;/code&gt; in CloudTrail the same day, not next sprint. The lesson that cost me, on financial-grade platforms: any control that blocks something and doesn't count how often it blocked eventually becomes a root cause nobody can name. The block is the easy part; the counter is what saves you at 2 AM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/09/ec2-images-supported-instances/" rel="noopener noreferrer"&gt;AWS What's New: Amazon EC2 now supports specifying compatible instance types on AMIs (Sep 4, 2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ami-allowed-instance-types.html" rel="noopener noreferrer"&gt;Amazon EC2 User Guide: AMI allowed instance types (evaluation logic, wildcards, considerations)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ReplaceImageInstanceTypeSpecification.html" rel="noopener noreferrer"&gt;Amazon EC2 API Reference: ReplaceImageInstanceTypeSpecification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/reference/ec2/replace-image-instance-type-specification.html" rel="noopener noreferrer"&gt;AWS CLI Reference, ec2 replace-image-instance-type-specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/create-mixed-instances-group-attribute-based-instance-type-selection.html" rel="noopener noreferrer"&gt;EC2 Auto Scaling, attribute-based instance type selection, price and performance protection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-fleet-attribute-based-instance-type-selection.html" rel="noopener noreferrer"&gt;Amazon EC2 User Guide, attribute-based instance type selection for EC2 Fleet and Spot Fleet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_GetInstanceTypesFromInstanceRequirements.html" rel="noopener noreferrer"&gt;Amazon EC2 API Reference: GetInstanceTypesFromInstanceRequirements&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Adopt it, narrowly scoped, with the list generated. Use &lt;code&gt;InstanceTypeSpecification&lt;/code&gt; when three conditions hold together: the AMI has a real hardware incompatibility rather than a cost preference, you don't control every account that launches the image, and the pipeline that builds the AMI can derive the list from a capability query on every build. Missing any one of them, an &lt;code&gt;ec2:InstanceType&lt;/code&gt; condition in an SCP remains the right tool, it errs toward people who know how to ask for an exception. Don't use this as a FinOps control, don't hand-write globs, and don't enable it on a shared AMI before running &lt;code&gt;describe-images&lt;/code&gt; against the launch templates already pointing at it. The feature costs nothing and the permissive default guarantees nothing breaks until you act; the price is entirely maintenance, and it comes due every time AWS ships a new family. If your team has nowhere to hang that maintenance, the pattern is still correct and the answer is still no.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating:&lt;/strong&gt; Adopt with scope&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fernando.moretes.com/blog/ami-com-contrato-anatomia-do-novo-bloqueio-de-tipos-no-ec2-amazon-ec2-n" rel="noopener noreferrer"&gt;fernando.moretes.com&lt;/a&gt;. By Fernando F. Azevedo: Senior Solutions Architect.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>awscloud</category>
      <category>ec2</category>
      <category>ami</category>
      <category>goldenimage</category>
    </item>
  </channel>
</rss>
