Skip to content

A multi-region watch silently watches only one region — and regions is never read #1

Description

@scttfrdmn

Found during the Go-parity audit. This is the highest-priority finding: the watch model advertises a capability it does not have.

The defect

Watch.regions (src/core/types.ts:31) is documented as "Regions to search. At least one." A grep for regions across src/ outside tests finds only that declaration and two comments. CapacityWatcher.check() never reads it.

The region a check actually queries comes from somewhere else entirely: truffleFinderAdapter(finder, region) (src/live/truffle-adapter.ts:45) takes a single region string and stamps it onto every result. So:

const watcher = new CapacityWatcher({ finder: truffleFinderAdapter(aws, "us-east-1") });
await watcher.check({ watchId: "w1", instanceTypePattern: "p5.*",
                     regions: ["us-east-1", "us-west-2", "eu-west-1"] });   // ← 2 of 3 ignored

…returns a match stamped region: "us-east-1" and never looks at us-west-2 or eu-west-1. Nothing warns. The caller asked to watch three regions and got one, with a plausible-looking result.

For a capacity tool this is the worst possible failure shape: the whole point of watching multiple regions is that scarce capacity (p5, g6e) appears somewhere first. A watch that silently searches one region will sit "waiting" while capacity is free two regions over.

Go's searchBestMatch passes w.Regions (plural) straight into SearchInstanceTypes(ctx, w.Regions, matcher, …) — the region set is honoured, and the returned InstanceTypeResult carries its own Region.

Root cause

truffle-ts's LiveFinder.search dedupes across regions by instance type and drops region identity (truffle-ts/src/live/live-finder.ts:111-113), and its InstanceType has no region field. The adapter's header comment says so honestly. So lagotto-ts couldn't have done this correctly with the upstream API as it stands.

Blocked on truffle-ts#31 (region identity in multi-region search). Once InstanceType carries region, the adapter can stop stamping and check() can iterate watch.regions.

Fix

  1. check() iterates watch.regions and evaluates candidates per region, keeping the global cheapest (Go's behaviour).
  2. The adapter takes the region set, or is dropped once truffle-ts returns per-region results.
  3. Until then, do not fail silently. If a watch names more regions than the finder can serve, throw or emit a warning — the #63 invariant: a narrowed search must not be indistinguishable from a completed one.

Also missing while here: availableAZs is never populated (truffle-ts doesn't produce it, tracked in truffle-ts#33), so orderAZs always receives [], candidateAzs is always empty, and a watch with availabilityZones pinned matches nothing on the on-demand path — evaluate returns null when a pin is set and no offered AZ matches. That's a second silent-empty-result path worth its own test.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: livelive capacity watcher + AWS seambugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions