Skip to content

fix(test): align live domain suites with staging sender-identity parity - #923

Merged
jiashuoz merged 2 commits into
mainfrom
fix/live-teardown-receipt-expectation
Aug 21, 2026
Merged

fix(test): align live domain suites with staging sender-identity parity#923
jiashuoz merged 2 commits into
mainfrom
fix/live-teardown-receipt-expectation

Conversation

@jiashuoz

Copy link
Copy Markdown
Member

Why

The v1.7.9 release pipeline (e2a-ops run 32447588478) went red on two gates — both stale test expectations unmasked by staging finally running sender_identity for real (e2a-ops #318, the 2026-08-18 incident prevention work). Server behavior is correct and documented in both cases; no server code changes here.

  1. SDK live gates — TS e2e-domains.test.ts (and the identical Python assertion, which sits one step later and never ran) asserted domain-delete returns sending_teardown: "confirmed". That was only ever true with no provider configured: BeginDomainTeardownReceiptTx starts every receipt at pending when a provider is configured, and the in-request best-effort deprovision can only upgrade it to confirmed by proving provider absence. Staging's IAM fence denies SES calls for out-of-namespace names, so a throwaway .example.com fixture can never confirm there.
  2. Conformance gate — suite 22 minted dl-*.<zone> fixtures outside the staging fence (identity/*.staging.trymnexa.com). Its delete receipt fail-closed to pending, so the suite — correctly honoring "keep DNS published unless confirmed" — left the fixture's 2 DNS records in place and recorded dns-cleanup-failed. All 230+ tests passed; the one finding failed the run.

What

  • TS + Python SDK live suites now accept both contract-valid receipt states (pending | confirmed) with comments explaining why confirmed cannot be forced from those suites.
  • Hoisted suite 35's environment-aware fixtureDomainSuffix() (staging → staging.<zone>, prod → <zone>) into harness/fixtures.ts; suite 22 now uses it for both fixture domains. In-fence, delete-time deprovision proves absence (NotFound) and the receipt confirms synchronously — same as production. Also corrects suite 35's now-stale comment claiming suite 22 "has nothing for the fence to deny."

Notes

  • Prod path unaffected: isProductionTarget keeps prod fixtures at the bare zone, and prod's unfenced SES user confirms teardown as before.
  • Follow-up (separate issue): out-of-namespace domain deletions on staging leave a permanently-retrying async teardown job (AccessDenied can never become NotFound). This PR stops the conformance/SDK suites from generating those, but the server-side behavior for unprovable absence may deserve a read-only status probe.

🤖 Generated with Claude Code

Jace added 2 commits August 20, 2026 21:54
…2e suites

The TS and Python SDK live domain suites asserted that deleting a
never-verified throwaway domain returns sending_teardown:"confirmed".
That was only ever true on deployments with no sending-identity provider
configured (the receipt's synchronous-confirmed arm). With a provider
configured, BeginDomainTeardownReceiptTx always starts the receipt at
"pending" and the response only upgrades to "confirmed" when the
in-request best-effort deprovision proves provider absence.

Staging now runs with sender_identity enabled for prod parity, and its
provider IAM policy intentionally denies identity calls outside the
staging namespace — so the deprovision probe for a throwaway
.example.com domain fails AccessDenied (not NotFound), the receipt
fail-closes to "pending", and both live gates go red against correct,
documented server behavior. First seen on the v1.7.9 release pipeline
(TS gate failed; the Python gate carries the same assertion one step
later).

Accept both contract-valid receipt states, with a comment explaining
why "confirmed" cannot be forced from these suites.
…IAM fence

Suite 22 minted throwaway domains as <slug>.<zone> (dl-*.trymnexa.com).
Since staging gained sender_identity parity (e2a-ops #318), every
registered domain touches SES: verify auto-enqueues identity
provisioning and delete runs a best-effort deprovision. For an
out-of-fence name both calls AccessDeny against the staging policy
(identity/*.staging.trymnexa.com only), the delete's teardown receipt
fail-closes at "pending", and the suite — correctly honoring the
contract's 'keep DNS published unless confirmed' — leaves the fixture's
DNS records in place and records a dns-cleanup-failed FAIL finding.
First seen on the v1.7.9 release pipeline: fail 0 tests, one finding,
gate red.

Hoist suite 35's environment-aware fixtureDomainSuffix() (staging ->
staging.<zone>, prod -> <zone>) into harness/fixtures.ts and use it for
both of suite 22's fixture domains. Suite 35's comment claiming suite 22
'has nothing for the fence to deny' predated staging sender-identity
parity and is corrected. In-fence, the delete-time deprovision proves
absence (NotFound), the receipt confirms synchronously, and DNS cleanup
proceeds — same as production.
@jiashuoz
jiashuoz merged commit a41a580 into main Aug 21, 2026
24 checks passed
@jiashuoz
jiashuoz deleted the fix/live-teardown-receipt-expectation branch August 21, 2026 05:24
jiashuoz added a commit that referenced this pull request Aug 24, 2026
@e2a/sdk 5.7.0 was cut on 2026-08-16 and has been four commits behind the repo
ever since, including a SECURITY fix that has sat in an "## Unreleased"
changelog section the whole time:

  #909  reject dot-segment path params that retarget requests
  #918  shared base-URL resolution (E2A_API_URL, WSListener parity)
  #923  live domain suite alignment
  #926  dependency bump

The gap is my error in #932. I scoped the TypeScript SDK by diffing against
v1.7.10 and concluded "devDependency bump only, no republish". The correct
baseline is the last PUBLISHED tag, ts-sdk-v5.7.0, which predates v1.7.10 by
four days. So the Python client shipped its dot-segment guard as 5.8.0 while the
TypeScript client kept serving 5.7.0 without one — and @e2a/cli depends on
@e2a/sdk, so every CLI install carried the unfixed URL builder underneath.

Minor rather than patch, matching the Python client: the guard throws
E2AValidationError on path parameter values that previously produced a real,
misdirected request.

The changelog also gains the two user-visible #918 changes it never documented —
the E2A_API_URL rename with E2A_BASE_URL deprecated, and WSListener no longer
ignoring the environment (it honoured only an explicit opts.baseUrl and
otherwise went straight to the hosted service, so a self-hosted deployment's
listener silently pointed at api.e2a.dev).

Note this is NOT the fail-closed change #918 made elsewhere: the TypeScript
client still falls back to https://api.e2a.dev by design.

No CLI republish needed — cli 2.5.0 requires @e2a/sdk ^5.7.0, which 5.8.0
satisfies, so installs pick the fix up on resolution.

Co-authored-by: Jace <jace@team.tokencanopy.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant