fix(core): ambient identity is per region, not per account - #1425
Merged
Conversation
Ambient (#1278) and dependency (#1273) resources are keyed by physical id and merged across stacks, on the reasoning that the default VPC's route table is one resource however many stacks route through it. That is right for a resource that really is account-level and wrong for a regional one: a VPC, subnet or security group in us-west-1 is a different resource from one in us-east-1, and merging them by id drops all but the first sighting. On AWS it cannot be observed — VPC, subnet and security-group ids are globally unique, so keying by id and keying by (region, id) give the same answer. Against a Floci emulator, which names the default VPC `vpc-default` and its subnets `subnet-default-a/b/c` in every region, a three-region estate's nine default subnets replayed as three and "how many subnets are empty" came back 2 instead of 8. The rule is not a table of regional kinds. A lexicon stamps a region on a resource when region is part of that resource's identity — the AWS lexicon does it in `stampRegion` — and `unqualifiedKey` reads that stamp back. A resource carrying no region is account-level as far as anything here can tell, and merges exactly as it did, which is why dependencies (never stamped) are untouched. Qualification happens where results are merged, not where they are recorded: `collectAmbient` across stacks and `replaySnapshots` across recorded stacks, each only when there is more than one to merge. One region is not a merge, so a single-region project's ids do not move and the first snapshot taken after this lands does not diff as "every ambient resource replaced". Managed resources are excluded outright. They join the declared canvas, which qualifies by stack or not at all, so re-keying `web` to `us-east-1::web` would unjoin it from its own declaration. Two things found while fixing it, both covered by the tests: - The live path had the same collapse, in `collectAmbient`, which #1416 did not mention. Fixing only the replay would have made `search --at` and `search --live` disagree. - Once ambient keys carry a region and dependency keys do not, a resource that is both stops colliding by key and would be counted twice. `observeResources` now matches those on physical id. `replay.ts` had no test file, which is part of why this survived. It has one now, and each assertion fails without the fix — checked by neutralising `unqualifiedKey` and re-running. Closes #1416 Claude-Session: https://claude.ai/code/session_014KoduiMJRyLqjFYBUbMskE Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
lex00
added a commit
to INTENTIUS/chant-bench
that referenced
this pull request
Aug 3, 2026
The chant arm vendored 0.33.1. It now carries INTENTIUS/chant#1425 and #1429, so its published g-series describes a build the arm no longer runs: different harness commit, different workspace fingerprint. By the rule method.md states, that is a different experiment. Three replicates rather than one. A single new run would have joined a set whose other two members are the old build, and the board ranks on the MEDIAN of three — so the figure would have described neither build. The set is now homogeneous: chant-r3 24/24 workspace 3912387ce569 harness 4f3ab36-dirty chant-r4 22/24 workspace 3912387ce569 harness 4f3ab36-dirty chant-r5 22/24 workspace 3912387ce569 harness 4f3ab36-dirty Median 0.9167, which is exactly what the g-series median was. **The two fixes do not move the board**, and that is the expected result rather than a disappointment: the arm's 0.33.1 read the estate through the CLI, which passed `--region` and signed it, so it already saw all three regions. #1429 is a regression against a build the board never ran. Where they do move the number is the negative set, which asks the question the board does not: 0/6 to 3/6. All three runs answered with zero account reads, entirely from the recorded snapshot, and all three passed the audit — 6/6, 24/24 and 24/24 trials using chant's own tooling, at 3%, 5% and 1% invocation failure rates. Two earlier attempts are not published. Both were voided for the same reason, before lex00/aws-bench#11 put the arm's CLI on PATH where a trial could see it. Claude-Session: https://claude.ai/code/session_014KoduiMJRyLqjFYBUbMskE Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ambient (#1278) and dependency (#1273) resources are keyed by physical id and merged across stacks, because the account's default VPC route table is one resource however many stacks route through it. Right for a resource that really is account-level; wrong for a regional one. A VPC, subnet or security group in
us-west-1is a different resource from one inus-east-1, and merging them by id drops all but the first sighting.On AWS it cannot be observed, since VPC, subnet and security-group ids are globally unique. Against a Floci emulator that names the default VPC
vpc-defaultand its subnetssubnet-default-a/b/cin every region (lex00/floci#21), a three-region estate's nine default subnets replayed as three, and "how many of my subnets are empty" came back 2 instead of 8.The rule
Not a table of regional kinds. A lexicon stamps a region on a resource when region is part of that resource's identity — the AWS lexicon does it in
stampRegion— andunqualifiedKeyreads that stamp back. A resource carrying no region is account-level as far as core can tell and merges exactly as before, which is why dependencies, never stamped, are untouched by this.Qualification happens where results are merged, not where they are recorded:
collectAmbientacross stacks,replaySnapshotsacross recorded stacks, each only when there is more than one to merge. One region is not a merge, so a single-region project's ids do not move — and the first snapshot taken after this lands does not diff as "every ambient resource replaced".Managed resources are excluded outright: they join the declared canvas, which qualifies by stack or not at all, so re-keying
webtous-east-1::webwould unjoin it from its own declaration.Two things the issue did not have
The live path had the same collapse.
collectAmbientmerges across stacks withfound[id] ??= meta, sochant search --livelost the same regions--atdid. #1416 named onlyreplay.ts; fixing that alone would have left the two paths disagreeing, which is the failure modesearch --atwas built to avoid.Ambient and dependency keys stopped colliding. Once ambient keys carry a region and dependency keys do not, a resource that is both — the default VPC an instance sits in — is no longer deduped by key and would be counted twice.
observeResourcesnow matches those on physical id.One thing from the issue was dropped: it proposed adding a
regionfield toLifecycleSnapshot. Not needed —stampRegionalready puts the region on the ambient metadata, which is the second option the issue offered, and it works for old snapshots too.Verification
replay.tshad no test file, which is part of why this survived. It has one now. Every assertion that asserts the fix fails without it, checked by neutralisingunqualifiedKeyand re-running: 3 of 6 fail, and the 3 that pass are the regression guards that should pass either way.Full suite: 10465 passed, 18 skipped, 0 failed. Typecheck and lint clean.
Note for anyone running the suite on a fresh clone: 108 tests fail until
just _ensure-genhas run, which is #1419 and not this.Closes #1416
🤖 Generated with Claude Code
https://claude.ai/code/session_014KoduiMJRyLqjFYBUbMskE