Repository navigation
feat: direct Lexe gateway client — remove lexe-sidecar and L402 hop (v2) - #2
Conversation
- outer TLS pinned to the embedded Lexe root CA (prod/staging) - CONNECT run.lexe.app with the credentials' long-lived proxy token - inner mTLS: revocable client cert, node cert chained to the eph CA - node JSON REST commands, amounts in sats; pay polls to terminal state - local analyze (prefix classify + BOLT11 hrpart amount), LNURL refused - drop lexe.my_account, agenticmail/L402 machinery, stateDir, webhook - hermetic fake-gateway+node TLS tests pin the wire protocol - docs/config/systemd updated; sidecar unit removed Co-Authored-By: goose <goose@aaif.ai>
- waitPayment: tolerate transient poll errors (submitted payments must not surface as retryable bare failures — double-pay guard); surface index + check_payment guidance on unknown status - invoice matching tightened: created_at + outbound + invoice kind + amount - typed errPaymentNotFound sentinel replaces string matching - http.Server read timeouts + 1 MiB body cap (slowloris) - nodeCall: body-read errors on 2xx fail instead of parsing truncation - bolt11 amount: overflow-safe bounds (21M BTC cap, portable ints) Co-Authored-By: goose <goose@aaif.ai>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4127512cd9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| m := asMap(out) | ||
| for _, item := range asSlice(m["payments"]) { |
There was a problem hiding this comment.
Read the bare updated-payments array
The direct API contract recorded in docs/plans/2026-09-15-direct-gateway-client.md:24 returns a bare [BasicPaymentV2…] array, but this casts the response to a map and looks for a payments property. Consequently every BOLT11 payment is submitted but never found by the poller, eventually returning an error after roughly 150 seconds and potentially prompting a duplicate payment; iterate over asSlice(out) instead.
AGENTS.md reference: AGENTS.md:L174-L181
Useful? React with 👍 / 👎.
| body := map[string]any{ | ||
| "description": description, | ||
| "min_amount": strconv.Itoa(minAmountSats), | ||
| "expiration_secs": ttl, |
There was a problem hiding this comment.
Send the offer TTL as
expiry_secs
The direct-node create_offer contract documented in docs/plans/2026-09-15-direct-gateway-client.md:20 names this field expiry_secs, not the sidecar-era expiration_secs. As written, the node can reject every create request as malformed or ignore the field, so configured offerTtlSecs values do not control the generated offer's lifetime.
AGENTS.md reference: AGENTS.md:L203-L210
Useful? React with 👍 / 👎.
| case 'p': | ||
| return 0, false // sub-sat precision — treat as amountless |
There was a problem hiding this comment.
Recognize whole-sat pico-BTC invoice amounts
The p multiplier does not always imply a sub-sat amount: for example, 10000p is exactly one sat. Returning false for every pico-denominated BOLT11 invoice makes valid fixed-amount invoices appear amountless, so lexe.pay rejects them unless the caller redundantly supplies amount_sats and lexe.analyze omits their amount; accept p values representing whole sats and reject only non-integral sat values.
AGENTS.md reference: AGENTS.md:L168-L180
Useful? React with 👍 / 👎.
Summary
lexe-node-clientfrom lexe-public@bcabbd3: outer TLS pinned to the embedded Lexe root CA (prod/staging, valid to 2034),CONNECT run.lexe.app:443with the credentials' long-livedProxy-Authorizationtoken, inner mTLS presenting the revocable client certificate and chaining the node certificate to the credentials' ephemeral CAlexe.paypollspayments/id/payments/updated(250 ms → 4 s backoff, ~150 s cap) to terminal status, mints BOLT12 payer proofs after settled offer pays; post-submission poll errors never surface as retryable bare failures (double-pay guard)lexe.analyzeis now a local decode (prefix classification + BOLT11 hrpart amount, overflow-guarded); LNURL/Lightning-address inputs are refused with guidancelexe.my_account, the L402/AgenticMail hop,stateDirpersistence,/webhook,lexe-sidecar.service; config reduced tolexeNetwork(mainnet|testnet3), credentials, port/host, payMaxSats, offer defaultsgateway_test.go) pin the full wire protocol: CA pinning, CONNECT auth, inner mTLS (incl. cross-credential rejection), JSON shapes, pollingReview
Production-safety review (multi-model panel degraded → two independent reviewer passes): all 5 findings fixed in 4127512 (double-pay window, invoice matching, server read timeouts + 1 MiB body cap, body-read truncation, BOLT11 overflow); verification pass returned SHIP.
Test plan
go vet . && go test -race . && go build— green locally (14 tests incl. hermetic TLS stack)/health, legacyinitialize,OPTIONS204,tools/list,lexe.payidentity guard