#2172 (issue #2160) added the claimed-field set: packages/core/src/claimed-fields.ts derives, from a declaration's own props, every path it ever set. A live value on a path outside that set now reports correctly instead of being read as drift. (#2181 is renaming that #2172 bucket from "held elsewhere" to "unclaimed": DeepDiffResult.unclaimed, heading UNCLAIMED. Using "unclaimed" throughout this issue.)
#2172's PR body names the counterpart-gated provider-default entries in the aws deep-observe hook as the largest shrink candidate for this lexicon, and says pruning and unclaimed report differently: pruned means the field never reaches the tree, unclaimed means it is reported with its live value under the UNCLAIMED heading. This issue is the per-lexicon follow-up its acceptance line asked for; nothing here has been shrunk yet.
What is in the file
awsDeepNormalizationHooks.prune (lexicons/aws/src/deep-observe.ts:379-419) checks, in order:
AWS_READ_ONLY_NAMES (deep-observe.ts:239-257): Arn, RoleId, PolicyId, GroupId, OwnerId, AttachmentCount, PermissionsBoundaryUsageCount, DefaultVersionId, IsAttachable, CreateDate, CreationDate, UpdateDate, LastModified, LastModifiedTime, DualStackDomainName, RegionalDomainName, WebsiteURL. Pruned on both sides unconditionally (checked at line 386).
schemaReadOnlyPatterns() (deep-observe.ts:334-344): generated at runtime from the CloudFormation schema's own readOnlyProperties, not hand-maintained. Also unconditional, both sides (line 385).
AWS_DEEP_BLIND_SPOTS (lexicons/aws/src/deep-topology.ts:223-237, applied at deep-observe.ts:392-398): AWS::EC2::VPC (4 names), AWS::EC2::Subnet (5 names). Declared-side only, and only when the live side cannot see the property at all.
isOwnershipTag() (defined deep-observe.ts:530-533, checked at line 410): chant's own ownership tag, counterpart-gated live+absent.
AWS_GENERATED_NAMES (deep-observe.ts:300-303, merged with EC2_TOPOLOGY_GENERATED_NAMES from deep-topology.ts:207-210): AWS::EC2::SecurityGroup.GroupName, AWS::EC2::Subnet (AvailabilityZone, AvailabilityZoneId), AWS::EC2::Instance.AvailabilityZone. 4 entries across 3 kinds, counterpart-gated live+absent (checked at line 413).
AWS_SERVICE_DEFAULTS (deep-observe.ts:266-289, merged with EC2_TOPOLOGY_SERVICE_DEFAULTS from deep-topology.ts:180-199): AWS::S3::Bucket (VersioningConfiguration.Status, AccelerateConfiguration.AccelerationStatus, ObjectLockEnabled, 3), AWS::IAM::Role (Path, MaxSessionDuration, 2), AWS::IAM::ManagedPolicy (Path, 1), AWS::EC2::SecurityGroup (GroupDescription, SecurityGroupEgress[].CidrIp, SecurityGroupEgress[].IpProtocol, 3), AWS::EC2::VPC (InstanceTenancy, IsDefault, 2), AWS::EC2::Subnet (MapPublicIpOnLaunch, AssignIpv6AddressOnCreation, EnableDns64, Ipv6Native, DefaultForAz, 5), AWS::EC2::Instance (Tenancy, Monitoring, EbsOptimized, SourceDestCheck, DisableApiTermination, 5). 21 entries across 7 kinds, gated the same way (checked at line 403).
Grepped every exported name across the lexicon and packages/core. AWS_READ_ONLY_NAMES and AWS_SERVICE_DEFAULTS are re-exported as public API from lexicons/aws/src/index.ts:61-62, but nothing internal or in examples/ imports them a second time. The aws lexicon has no export-resources.ts, so there is no import/export consumer to protect the way azure and fountain have.
Candidates versus must-stay
Candidates: AWS_SERVICE_DEFAULTS/EC2_TOPOLOGY_SERVICE_DEFAULTS (21 entries) and AWS_GENERATED_NAMES/EC2_TOPOLOGY_GENERATED_NAMES (4 entries). Both only fire when the declaration never set the property, the shape claimedFieldsOfProps now answers directly.
One caveat specific to this lexicon: deep-topology.ts:172-179's own comment ties EC2_TOPOLOGY_SERVICE_DEFAULTS's gating to keeping the topology fold facts (IsDefault, DefaultForAz, a live MapPublicIpOnLaunch) visible in the deep snapshot without them reading as drift on a provider-created resource. lexicons/aws/src/defaults.ts:44-46 reads IsDefault/DefaultForAz for the provider-default classification (providerDefault: true), but from a separate ambient property read, not from this deep-observe tree, so removing these two entries from the prune table looks safe for that consumer on inspection. This is not fully confirmed by reading the code alone; treat it as a required check rather than an assumption. A live value equal to the default (for example a non-default VPC's IsDefault: false) would newly report as UNCLAIMED on every clean read once the entry is gone, which is the exact tradeoff the rest of this table already makes.
Must stay:
Verification before merging
Run chant lifecycle diff --live against a real AWS account covering an AWS::S3::Bucket, an AWS::IAM::Role, an AWS::IAM::ManagedPolicy, an AWS::EC2::SecurityGroup, a AWS::EC2::VPC, an AWS::EC2::Subnet and an AWS::EC2::Instance, each declared with the relevant property left unset, before and after removing an entry. For the topology entries, additionally confirm the default-VPC and default-for-az classification in lexicons/aws/src/defaults.ts still resolves correctly on a real default VPC and a real non-default one.
Ref #2160, #2172, #2181.
Acceptance
#2172 (issue #2160) added the claimed-field set:
packages/core/src/claimed-fields.tsderives, from a declaration's own props, every path it ever set. A live value on a path outside that set now reports correctly instead of being read as drift. (#2181 is renaming that #2172 bucket from "held elsewhere" to "unclaimed":DeepDiffResult.unclaimed, headingUNCLAIMED. Using "unclaimed" throughout this issue.)#2172's PR body names the counterpart-gated provider-default entries in the aws deep-observe hook as the largest shrink candidate for this lexicon, and says pruning and unclaimed report differently: pruned means the field never reaches the tree, unclaimed means it is reported with its live value under the
UNCLAIMEDheading. This issue is the per-lexicon follow-up its acceptance line asked for; nothing here has been shrunk yet.What is in the file
awsDeepNormalizationHooks.prune(lexicons/aws/src/deep-observe.ts:379-419) checks, in order:AWS_READ_ONLY_NAMES(deep-observe.ts:239-257):Arn,RoleId,PolicyId,GroupId,OwnerId,AttachmentCount,PermissionsBoundaryUsageCount,DefaultVersionId,IsAttachable,CreateDate,CreationDate,UpdateDate,LastModified,LastModifiedTime,DualStackDomainName,RegionalDomainName,WebsiteURL. Pruned on both sides unconditionally (checked at line 386).schemaReadOnlyPatterns()(deep-observe.ts:334-344): generated at runtime from the CloudFormation schema's ownreadOnlyProperties, not hand-maintained. Also unconditional, both sides (line 385).AWS_DEEP_BLIND_SPOTS(lexicons/aws/src/deep-topology.ts:223-237, applied atdeep-observe.ts:392-398):AWS::EC2::VPC(4 names),AWS::EC2::Subnet(5 names). Declared-side only, and only when the live side cannot see the property at all.isOwnershipTag()(defineddeep-observe.ts:530-533, checked at line 410): chant's own ownership tag, counterpart-gated live+absent.AWS_GENERATED_NAMES(deep-observe.ts:300-303, merged withEC2_TOPOLOGY_GENERATED_NAMESfromdeep-topology.ts:207-210):AWS::EC2::SecurityGroup.GroupName,AWS::EC2::Subnet(AvailabilityZone,AvailabilityZoneId),AWS::EC2::Instance.AvailabilityZone. 4 entries across 3 kinds, counterpart-gated live+absent (checked at line 413).AWS_SERVICE_DEFAULTS(deep-observe.ts:266-289, merged withEC2_TOPOLOGY_SERVICE_DEFAULTSfromdeep-topology.ts:180-199):AWS::S3::Bucket(VersioningConfiguration.Status,AccelerateConfiguration.AccelerationStatus,ObjectLockEnabled, 3),AWS::IAM::Role(Path,MaxSessionDuration, 2),AWS::IAM::ManagedPolicy(Path, 1),AWS::EC2::SecurityGroup(GroupDescription,SecurityGroupEgress[].CidrIp,SecurityGroupEgress[].IpProtocol, 3),AWS::EC2::VPC(InstanceTenancy,IsDefault, 2),AWS::EC2::Subnet(MapPublicIpOnLaunch,AssignIpv6AddressOnCreation,EnableDns64,Ipv6Native,DefaultForAz, 5),AWS::EC2::Instance(Tenancy,Monitoring,EbsOptimized,SourceDestCheck,DisableApiTermination, 5). 21 entries across 7 kinds, gated the same way (checked at line 403).Grepped every exported name across the lexicon and
packages/core.AWS_READ_ONLY_NAMESandAWS_SERVICE_DEFAULTSare re-exported as public API fromlexicons/aws/src/index.ts:61-62, but nothing internal or inexamples/imports them a second time. The aws lexicon has noexport-resources.ts, so there is no import/export consumer to protect the way azure and fountain have.Candidates versus must-stay
Candidates:
AWS_SERVICE_DEFAULTS/EC2_TOPOLOGY_SERVICE_DEFAULTS(21 entries) andAWS_GENERATED_NAMES/EC2_TOPOLOGY_GENERATED_NAMES(4 entries). Both only fire when the declaration never set the property, the shapeclaimedFieldsOfPropsnow answers directly.One caveat specific to this lexicon:
deep-topology.ts:172-179's own comment tiesEC2_TOPOLOGY_SERVICE_DEFAULTS's gating to keeping the topology fold facts (IsDefault,DefaultForAz, a liveMapPublicIpOnLaunch) visible in the deep snapshot without them reading as drift on a provider-created resource.lexicons/aws/src/defaults.ts:44-46readsIsDefault/DefaultForAzfor the provider-default classification (providerDefault: true), but from a separate ambient property read, not from this deep-observe tree, so removing these two entries from the prune table looks safe for that consumer on inspection. This is not fully confirmed by reading the code alone; treat it as a required check rather than an assumption. A live value equal to the default (for example a non-default VPC'sIsDefault: false) would newly report asUNCLAIMEDon every clean read once the entry is gone, which is the exact tradeoff the rest of this table already makes.Must stay:
AWS_READ_ONLY_NAMESandschemaReadOnlyPatterns(): server-assigned identity and bookkeeping, pruned on both sides unconditionally. The read-only-name category feat(core): the claimed-field set, and a live value chant never set stops being drift #2172's body calls must-stay by name;schemaReadOnlyPatterns()is additionally not hand-written at all.AWS_DEEP_BLIND_SPOTS: runs the opposite direction from the claimed-field set. It prunes a declared property when the live side cannot see it, so a property chant does own never falsely reports absent. The claimed-field set only classifies live values against a declaration; it says nothing about a declared property with no live counterpart, so this table is untouched by feat(core): the claimed-field set, and a live value chant never set stops being drift #2172's mechanism.isOwnershipTag(): counterpart-gated the same way as the candidates, but it prunes chant's own ownership tag rather than a provider default. Removing it would make every managed resource's own tag report as unclaimed noise on every diff, which is not the finding lifecycle: the claimed-field set — a live value on a property you never declared is theirs, not drift #2160 exists to surface.Verification before merging
Run
chant lifecycle diff --liveagainst a real AWS account covering anAWS::S3::Bucket, anAWS::IAM::Role, anAWS::IAM::ManagedPolicy, anAWS::EC2::SecurityGroup, aAWS::EC2::VPC, anAWS::EC2::Subnetand anAWS::EC2::Instance, each declared with the relevant property left unset, before and after removing an entry. For the topology entries, additionally confirm the default-VPC and default-for-az classification inlexicons/aws/src/defaults.tsstill resolves correctly on a real default VPC and a real non-default one.Ref #2160, #2172, #2181.
Acceptance
AWS_SERVICE_DEFAULTS/EC2_TOPOLOGY_SERVICE_DEFAULTSandAWS_GENERATED_NAMES/EC2_TOPOLOGY_GENERATED_NAMESentry removed has a test inlexicons/aws/src/deep-observe.test.tsordeep-topology.test.tsasserting the field reports unclaimed with its live value.lexicons/aws/src/defaults.tsstill passes its existing tests after any topology entry moves, or gains a new test if none covered it.KNOWN_FAILURESentry added for any lexicon.lexicons/aws/src/deep-observe.test.tsandlexicons/aws/src/deep-topology.test.tspass in full.AWS_READ_ONLY_NAMES,schemaReadOnlyPatterns(),AWS_DEEP_BLIND_SPOTSandisOwnershipTag()are untouched, or the PR explains why one of them turned out to be coverable after all.