Merge remote-tracking branch 'origin/main'
This commit is contained in:
18
.gitleaks.toml
Normal file
18
.gitleaks.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
title = "nick-doc gitleaks config"
|
||||||
|
|
||||||
|
[extend]
|
||||||
|
useDefault = true
|
||||||
|
|
||||||
|
# 'Pangolin/Newt' is the literal product name of a self-hosted tunneling tool
|
||||||
|
# mentioned in operational handoff docs, not a secret. The generic-api-key
|
||||||
|
# rule fires on entropy heuristics for the surrounding line.
|
||||||
|
[[allowlists]]
|
||||||
|
description = "Documentation-only false positives"
|
||||||
|
regexes = [
|
||||||
|
'''Pangolin/Newt''',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Pin the known historical finding so we don't mask future leaks in the file.
|
||||||
|
[[allowlists]]
|
||||||
|
description = "Pre-existing FP in Telegram Mini App handoff doc, 2026-05-24"
|
||||||
|
commits = ["940ad0c655777e3bf6d5416fd2829be77bdfc4f8"]
|
||||||
157
01 - Architecture/Request Network Integration Constraints.md
Normal file
157
01 - Architecture/Request Network Integration Constraints.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
# Request Network Integration — Constraints and Design Implications
|
||||||
|
|
||||||
|
**Date:** 2026-05-27
|
||||||
|
**Status:** Active concerns; mitigations partially designed, partially blocked on RN clarifications
|
||||||
|
**Owners:** Backend payments (Amanat), product
|
||||||
|
|
||||||
|
This document captures four payment-flow issues that surfaced while integrating Request Network (RN) into the Amanat escrow stack. Each one is either a show-stopper or a non-trivial architectural constraint. Listed in priority order.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. RN does not support Rabby — show-stopper for our wallet user base
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
RN's hosted payment page (the `pay.request.network/?token=…` UI returned by `/v2/secure-payments`) does not detect / connect to Rabby. A meaningful slice of Amanat's user base pays from Rabby. Sending them to a screen that won't even let them connect is a hard block.
|
||||||
|
|
||||||
|
### Mitigation (designed, not yet implemented)
|
||||||
|
|
||||||
|
Skip the RN-hosted UI. We already call `/v2/secure-payments` and receive a `securePaymentUrl`, but we also receive `requestIds` and `token` — that's everything we need to know what the merchant request is. Behind that token there is a contract on the destination chain that anyone can fulfill.
|
||||||
|
|
||||||
|
So the new flow becomes:
|
||||||
|
|
||||||
|
1. Backend calls RN `/v2/secure-payments` (same as today) and stores the `requestIds[0]` + destination wallet + amount + token on our `Payment` doc.
|
||||||
|
2. **We render our own checkout screen** that:
|
||||||
|
- Shows the buyer the wallet address to pay to (the destination resolved from the merchant reference / chain / token).
|
||||||
|
- Lets the buyer connect *any* wallet — Rabby, MetaMask, OKX, Phantom-bridged, WalletConnect.
|
||||||
|
- Builds the transfer transaction client-side (standard ERC-20 transfer) and asks the wallet to sign.
|
||||||
|
3. RN's webhook (`/v2/request/{id}`-style polling fallback) tells us when the payment lands.
|
||||||
|
|
||||||
|
### Why this is acceptable
|
||||||
|
|
||||||
|
- RN's value to us at that point is the *settlement bookkeeping*, not the UI. We use them as "did this address receive the expected amount before timeout?" — the wallet UX stays in our control.
|
||||||
|
- Buyer never sees a third-party brand mid-checkout, which is a UX win regardless of Rabby.
|
||||||
|
|
||||||
|
### Open
|
||||||
|
|
||||||
|
- Need to confirm RN actually settles a payment that arrives from a *transaction we built*, not from their hosted page. Their pricing/fees may be tied to going through their UI. **Test required** before committing to this path.
|
||||||
|
- Need a fallback for the buyer who insists on the RN hosted UI (some users will already have the link copied). Keep `securePaymentUrl` exposed as a "advanced / pay with RN" link.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. RN's multi-chain routing forces an expensive LiFi bridge
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
When we configure a destination route (e.g. BSC + USDC), RN's hosted UI still lets the buyer pick *any* chain where they hold funds (e.g. ARB). To honor that, RN routes the buyer's funds through **LiFi**, which charges bridging fees that **someone has to pay**, and it's not clearly disclosed who.
|
||||||
|
|
||||||
|
The visible costs:
|
||||||
|
- Buyer over-pays vs. nominal invoice amount (bad UX).
|
||||||
|
- Or we eat the spread (bad margin).
|
||||||
|
- Or seller gets less than they expected (worst — they'll dispute).
|
||||||
|
- Plus settlement latency goes from seconds to minutes-hours depending on the bridge.
|
||||||
|
|
||||||
|
### Mitigation (designed)
|
||||||
|
|
||||||
|
Take the chain choice away from RN's UI and bring it into ours, gated by what the *seller* will accept.
|
||||||
|
|
||||||
|
Two-step UX:
|
||||||
|
|
||||||
|
1. **At offer creation (seller side):** seller specifies which chain(s) they accept payouts on. We persist this as `acceptedChains: [bsc, arb, base, …]` on the offer / merchant configuration.
|
||||||
|
2. **At checkout (buyer side, before any RN call):** we show the buyer the seller's accepted chains. Buyer picks one. *Then* we call RN with that exact chain pinned as the destination. No LiFi bridge — same-chain transfer.
|
||||||
|
|
||||||
|
### Side benefit
|
||||||
|
|
||||||
|
This composes cleanly with #1 (own checkout screen): we already have to render the wallet picker, so adding a chain selector before the wallet step costs almost nothing.
|
||||||
|
|
||||||
|
### Open
|
||||||
|
|
||||||
|
- We need a per-seller config table for accepted chains. Today the env-level `REQUEST_NETWORK_MERCHANT_REFERENCE` hard-codes a single chain (`bsc`). Needs to become per-seller, per-offer.
|
||||||
|
- Does RN's API support creating a secure-payment that *rejects* off-chain payments rather than auto-bridging? Or do we have to enforce this purely on our side by never offering the cross-chain option to the buyer? **Confirm with RN docs/support.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Sanctioned-funds risk — single escrow wallet poisons the entire platform
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
Today the entire escrow stack receives funds into one (or a handful of) wallets — `REQUEST_NETWORK_MERCHANT_REFERENCE` resolves to a single destination address. If a buyer pays with funds tied to a sanctioned source / mixer / known-bad address:
|
||||||
|
|
||||||
|
- That destination wallet gets tagged non-compliant by Chainalysis / TRM / Elliptic.
|
||||||
|
- Downstream exchanges and OTC desks won't accept transfers from it.
|
||||||
|
- One bad buyer can effectively brick the entire platform's settlement layer.
|
||||||
|
|
||||||
|
This is a show-stopper for going live at scale. Same class of issue we already considered around SHKeeper.
|
||||||
|
|
||||||
|
### Mitigation (designed; needs RN feasibility check)
|
||||||
|
|
||||||
|
Per-`(buyer, merchant)`-pair ephemeral wallets. Each new escrow gets a freshly-generated address that only ever receives that one transaction. If those funds turn out to be dirty:
|
||||||
|
|
||||||
|
- Only that wallet is tainted.
|
||||||
|
- We never sweep it into our main treasury (or sweep only after the payment passes screening).
|
||||||
|
- Risk is **siloed to the individual escrow**, not platform-wide.
|
||||||
|
|
||||||
|
### What this requires (architectural work)
|
||||||
|
|
||||||
|
1. **Wallet abstraction layer** — service that on demand generates a fresh address (HD wallet derivation from a master seed kept in a hardware module / KMS) and returns it to the payment-intent flow.
|
||||||
|
2. **Address book / registry** — maps `(paymentId, chainId)` → derived address. Persists derivation path + sequence number so we can reproduce keys for sweeps later.
|
||||||
|
3. **Sweep job** — once a payment is confirmed AND has passed an on-chain screening check (Chainalysis API or similar), sweep the ephemeral wallet to the main treasury. If screening fails, the ephemeral wallet is quarantined and the payment refunded out of band.
|
||||||
|
4. **Key custody policy** — these are still our funds in custody briefly; need clear policy on who can sign sweeps, hot-key vs cold-key separation.
|
||||||
|
|
||||||
|
### Critical open question
|
||||||
|
|
||||||
|
**Does RN support creating a secure-payment with a destination wallet we specify per-request, rather than a static merchant reference?** If yes, this is straightforward — we generate a wallet, register it as the destination for one specific `/v2/secure-payments` call, done. If no (RN only allows pre-registered destinations), we have to either:
|
||||||
|
|
||||||
|
- Pre-register a large pool of addresses with RN and rotate through them, or
|
||||||
|
- Bypass RN's destination model and go full self-host (which is most of issue #4).
|
||||||
|
|
||||||
|
**Action: confirm with RN support whether per-request destinations are supported on the same API key.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. RN reduced to a notification service — viable, but not yet validated
|
||||||
|
|
||||||
|
### Problem statement
|
||||||
|
|
||||||
|
If we adopt #1 (own checkout UI), #2 (own chain selection), and #3 (own ephemeral wallets), RN's role in the flow collapses to:
|
||||||
|
|
||||||
|
> "Tell me when wallet X receives Y tokens (or doesn't, before timeout)."
|
||||||
|
|
||||||
|
Which is a *notification* primitive, not a payment platform. We'd be paying for a feature we're using maybe 5% of.
|
||||||
|
|
||||||
|
### Why this might still be worth it
|
||||||
|
|
||||||
|
- We get RN's chain watchers + reorg handling + webhook reliability for free.
|
||||||
|
- We don't have to run our own indexer on n chains.
|
||||||
|
- Their screening (if they do any) is one more compliance layer.
|
||||||
|
|
||||||
|
### Why this might NOT be worth it
|
||||||
|
|
||||||
|
- Pricing built around hosted-UI usage, not API-only. May not be cost-effective at API-only volumes.
|
||||||
|
- We're outsourcing the *one thing* RN is good at (settlement) and keeping the parts they don't help with (UX, wallet generation, compliance).
|
||||||
|
- Alternative: do the same with our own chain watcher (Alchemy webhooks / Tenderly / Goldsky) and skip RN entirely.
|
||||||
|
|
||||||
|
### What needs testing before we commit
|
||||||
|
|
||||||
|
1. **Webhook reliability at our volume.** What's RN's SLA for "address received funds → webhook delivered"? P50? P99?
|
||||||
|
2. **Custom destination support.** See open question in #3.
|
||||||
|
3. **Per-API-key rate limits.** If we end up calling `/v2/secure-payments` once per escrow, do we hit ceilings?
|
||||||
|
4. **Pricing for the notification-only flow** — is there a tier, or is it the same as the full-stack price?
|
||||||
|
5. **What happens when the payment arrives from a transaction WE built** (not theirs)? Does the webhook still fire? Is settlement still recognized? — this is the load-bearing test for the whole strategy.
|
||||||
|
|
||||||
|
Until #5 is confirmed, the rest is just paper architecture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cross-cutting next actions
|
||||||
|
|
||||||
|
| # | Action | Blocker / Owner |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | Test: payment via wallet-built transfer triggers RN webhook | Backend payments |
|
||||||
|
| 2 | Test: `/v2/secure-payments` accepts a per-request destination wallet | Backend payments |
|
||||||
|
| 3 | Confirm RN doesn't auto-bridge when buyer pays on the destination chain natively | Backend payments |
|
||||||
|
| 4 | Get RN's webhook P99 latency + delivery guarantees in writing | Product / RN account manager |
|
||||||
|
| 5 | Spec the wallet-abstraction layer (HD derivation + sweep job + key policy) | Backend, before going live |
|
||||||
|
| 6 | Spec the seller-side accepted-chains config | Backend + frontend |
|
||||||
|
|
||||||
|
Actions 1–4 are *information-gathering* and should run in parallel before any more architectural commitment to RN. Actions 5–6 are blocked on 1–3 confirming RN can actually support this shape.
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# Handover — Request Network Intent: Duplicate Key Bug
|
||||||
|
|
||||||
|
**Date:** 2026-05-27
|
||||||
|
**Endpoint:** `POST https://dev.amn.gg/api/payment/request-network/intents`
|
||||||
|
**Severity:** Blocks checkout retry flow on Request Network payments
|
||||||
|
**Status:** Reproducible in production (`dev.amn.gg`), root cause identified, three remediation paths proposed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Symptom
|
||||||
|
|
||||||
|
When a buyer attempts to create a Request Network payment intent from the checkout step 2 page, the backend returns:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"error": "E11000 duplicate key error collection: marketplace.payments index: uniq_pending_request_network_by_buyer_session dup key: { buyerId: ObjectId('68e3a21fbc79e4364c20a07e'), purchaseRequestId: \"template-checkout-1779856632092\", provider: \"request.network\", direction: \"in\" }"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The raw MongoDB error is being surfaced to the client, which is a secondary issue (information leak + ugly UX).
|
||||||
|
|
||||||
|
## 2. Environment (verified correct)
|
||||||
|
|
||||||
|
The infrastructure side is **not the cause**. All env vars and dashboard config are aligned:
|
||||||
|
|
||||||
|
- `REQUEST_NETWORK_API_KEY` — matches the dashboard "amn" Client ID, status **Active**.
|
||||||
|
- `REQUEST_NETWORK_MERCHANT_REFERENCE` — matches dashboard destination wallet (`0x05E2…573e`) on BNB Chain, USDC token slug.
|
||||||
|
- `REQUEST_NETWORK_ORIGIN=https://dev.amn.gg` — present in the Client ID's Allowed Domains (along with `https://amn.gg`).
|
||||||
|
- Webhook endpoint `https://dev.amn.gg/api/payment/request-network/webhook` is registered and **Active** in the dashboard.
|
||||||
|
- `REQUEST_NETWORK_ENABLED=true`, `PAYMENT_PROVIDER_MODE=live`.
|
||||||
|
|
||||||
|
The Request Network API was never reached on this failure — the error happens **inside our backend** before any outbound call.
|
||||||
|
|
||||||
|
## 3. Root cause
|
||||||
|
|
||||||
|
The `payments` collection has a unique (partial) index:
|
||||||
|
|
||||||
|
```
|
||||||
|
uniq_pending_request_network_by_buyer_session
|
||||||
|
keys: { buyerId: 1, purchaseRequestId: 1, provider: 1, direction: 1 }
|
||||||
|
```
|
||||||
|
|
||||||
|
The frontend submits a `purchaseRequestId` of the form `template-checkout-<timestamp>`. In the failing request:
|
||||||
|
|
||||||
|
```
|
||||||
|
purchaseRequestId: "template-checkout-1779856632092"
|
||||||
|
buyerId: 68e3a21fbc79e4364c20a07e
|
||||||
|
provider: request.network
|
||||||
|
direction: in
|
||||||
|
```
|
||||||
|
|
||||||
|
A previous attempt — almost certainly a retry from the same page session — **already inserted a Payment document with this exact key tuple and left it in a pending state**. The unique index correctly rejects the second insert.
|
||||||
|
|
||||||
|
This is a logic bug in how the intent endpoint and the frontend handle retries, not a database bug. The index is doing exactly what it should: preventing duplicate pending intents for the same checkout session.
|
||||||
|
|
||||||
|
### Why it triggers in practice
|
||||||
|
|
||||||
|
- `purchaseRequestId` is generated client-side from a timestamp and **persisted in component/page state**, so it does **not** rotate on retry.
|
||||||
|
- If the first POST creates the Payment doc but the client then errors (network blip, validation issue elsewhere, double-click), the second POST collides.
|
||||||
|
- The backend treats the endpoint as create-only rather than idempotent, so it tries `insertOne` every time.
|
||||||
|
|
||||||
|
## 4. Reproduction
|
||||||
|
|
||||||
|
1. Sign in as `buyer@marketplace.com`.
|
||||||
|
2. Open `https://dev.amn.gg/dashboard/shops/checkout/?step=2` for a template checkout.
|
||||||
|
3. Submit the intent successfully (or simulate a half-complete request that creates the Payment doc).
|
||||||
|
4. Submit again from the same page state without regenerating `purchaseRequestId`.
|
||||||
|
5. Observe the E11000 response.
|
||||||
|
|
||||||
|
Exact payload that reproduces is captured in the original ticket (`amount: 12`, `token: "USDT"`, `network: "bsc"`, `sellerId: "6918535be9301e0e4358d83e"`).
|
||||||
|
|
||||||
|
## 5. Solutions
|
||||||
|
|
||||||
|
Three layered fixes. **(c) is implemented as of 2026-05-27** in `backend/src/services/payment/requestNetwork/{requestNetworkPayInService,requestNetworkRoutes}.ts` (`nick/backend@bdbcc32`). Apply (a) once to clear the existing stuck doc. (b) is a frontend hygiene improvement worth keeping on the backlog but is no longer required to unblock checkouts.
|
||||||
|
|
||||||
|
> **Secondary fix shipped at the same time (`nick/backend@40750d3`, 2.6.20).** Once the idempotency check passed, every call was failing with `Request Network secure payment creation failed: HTTP 400` because the adapter was sending a flat payload to `/v2/secure-payments`, while the v2 endpoint requires `{ reference, requests:[{ destinationId, amount, metadata? }], redirectUrl?, callbackUrl? }`. The translation now happens inside `createSecurePaymentRequest` (the rich internal payload object is still passed to the response mapper for `paymentCurrency`/`network` context). Verified end-to-end with `backend/scripts/smoke/rn-payload-shape.mjs` against the real RN API: HTTP 201 with `securePaymentUrl` + `requestIds[]`.
|
||||||
|
|
||||||
|
### a) Hot unblock — clear the stale pending document
|
||||||
|
|
||||||
|
Run in the Mongo shell against the `marketplace` database:
|
||||||
|
|
||||||
|
```js
|
||||||
|
db.payments.deleteOne({
|
||||||
|
buyerId: ObjectId("68e3a21fbc79e4364c20a07e"),
|
||||||
|
purchaseRequestId: "template-checkout-1779856632092",
|
||||||
|
provider: "request.network",
|
||||||
|
direction: "in",
|
||||||
|
status: { $in: ["pending", "initiated", "awaiting_payment"] }
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Then retry the checkout. Use this only for the specific buyer/session being unblocked — do **not** broad-delete pending Payments.
|
||||||
|
|
||||||
|
### b) Frontend — rotate `purchaseRequestId` on every retry
|
||||||
|
|
||||||
|
Locate the checkout step 2 component that builds the `template-checkout-<timestamp>` id. Today this value is computed once and reused across retries. Change it so:
|
||||||
|
|
||||||
|
- A fresh id is generated every time the user lands on (or re-enters) step 2.
|
||||||
|
- A fresh id is generated when the user clicks "Pay" after a previous failure — i.e. tie generation to the click handler, not to mount, OR clear the cached value on any error response.
|
||||||
|
- Prefer a UUID/ULID over a timestamp to make the intent collision-proof even across rapid clicks.
|
||||||
|
|
||||||
|
This eliminates the collision from the client side and is the minimum fix.
|
||||||
|
|
||||||
|
### c) Backend — make `/intents` idempotent
|
||||||
|
|
||||||
|
The endpoint is semantically an *intent*: the same (buyer, purchaseRequest, provider, direction) tuple should always resolve to the same Payment document. Change the controller for `POST /api/payment/request-network/intents` to:
|
||||||
|
|
||||||
|
1. Look up an existing Payment matching `{ buyerId, purchaseRequestId, provider: "request.network", direction: "in" }` in any non-terminal status (`pending`, `initiated`, `awaiting_payment`).
|
||||||
|
2. If found, return that Payment (and its Request Network handoff data) with HTTP 200 — do **not** insert.
|
||||||
|
3. If not found, create the new Payment as today.
|
||||||
|
4. Wrap the insert in a try/catch on `E11000`; on collision, re-read and return the existing doc (handles the race between two concurrent requests).
|
||||||
|
|
||||||
|
This is the correct long-term shape and also defends against double-clicks, browser back/forward, and React strict-mode double-invocations.
|
||||||
|
|
||||||
|
Additionally:
|
||||||
|
|
||||||
|
- Stop returning raw Mongo error strings to the client. Map `E11000` on this collection to an HTTP 409 with a sanitized body like `{ success: false, code: "INTENT_ALREADY_EXISTS" }`.
|
||||||
|
- Log the raw error server-side only.
|
||||||
|
|
||||||
|
## 6. Out of scope (but worth noting)
|
||||||
|
|
||||||
|
- The webhook **Signing Secret** in the dashboard shows `Unavailable` for the active webhook. `REQUEST_NETWORK_WEBHOOK_SECRET` is set in env, but verify the value matches what the dashboard issued at webhook creation — if not, regenerate and update env. This will bite the next time a payment actually clears.
|
||||||
|
- The `amount: 12` is sent as `USDT` in the payload, but `REQUEST_NETWORK_PAYMENT_CURRENCY=USDC` and the merchant reference's token slug is the BSC USDC contract. Confirm whether the frontend should be sending `USDC` or whether the backend is supposed to normalize.
|
||||||
|
|
||||||
|
## 7. Suggested ownership
|
||||||
|
|
||||||
|
- **(a)** — Ops / whoever has Mongo access. One-shot.
|
||||||
|
- **(b)** — Frontend dev owning `dashboard/shops/checkout`.
|
||||||
|
- **(c)** — Backend dev owning `payment/request-network` controllers and the `Payment` model. This should land as a single PR with a regression test that fires two identical intent POSTs and asserts the second returns 200 with the same payment id.
|
||||||
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
This documentation workspace uses Taskmaster as the source of truth for agent work.
|
This documentation workspace uses Taskmaster as the source of truth for agent work.
|
||||||
|
|
||||||
|
## Repository Rules
|
||||||
|
|
||||||
|
- Repository-wide operating rules live in `../RTK.md`; follow them in addition to this file.
|
||||||
|
- For product or code changes that affect frontend or backend, keep `frontend` and `backend` package versions/build numbers bumped together and synchronized unless the user explicitly asks otherwise.
|
||||||
|
- Preserve Telegram Mini App auth retry behavior: `/api/auth/telegram` must accept repeated valid `initData` for the same launch session; replay rejection belongs only on one-time routes such as webhook/session creation.
|
||||||
|
- In the final response, mention version/build bumps and verification commands when they were part of the work.
|
||||||
|
|
||||||
## Taskmaster Workflow
|
## Taskmaster Workflow
|
||||||
|
|
||||||
- Before choosing implementation or documentation work, run `task-master next` from the repository root.
|
- Before choosing implementation or documentation work, run `task-master next` from the repository root.
|
||||||
|
|||||||
45
RTK.md
Normal file
45
RTK.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# RTK
|
||||||
|
|
||||||
|
Repository rules agents must follow for Amanat escrow work.
|
||||||
|
|
||||||
|
## Version and Build Numbers
|
||||||
|
|
||||||
|
- **Every build of `frontend/` or `backend/` must bump the patch component (`Z` in `X.Y.Z`) by one.** Container images on `git.manko.yoga` are tagged from `package.json` version — a build with an unchanged version overwrites the previous image and erases history. Patch bump on every build, no exceptions.
|
||||||
|
- Bump together so frontend and backend stay aligned (e.g. both go `2.6.18 → 2.6.19`).
|
||||||
|
- Bumping `Y` (minor) or `X` (major) is only for explicit milestone releases the user requests; never as a side-effect of an ordinary build.
|
||||||
|
- For any product or code change that affects `frontend/` or `backend/`, bump both versions together before final response in:
|
||||||
|
- `frontend/package.json`
|
||||||
|
- `frontend/package-lock.json`
|
||||||
|
- tracked frontend env files that set `NEXT_PUBLIC_APP_VERSION`
|
||||||
|
- `backend/package.json`
|
||||||
|
- `backend/package-lock.json`
|
||||||
|
- Backend runtime/version reporting should read from `backend/package.json`, not a hardcoded fallback.
|
||||||
|
- Keep frontend and backend on the same version/build number unless the user explicitly asks otherwise.
|
||||||
|
- Do not bump versions for docs-only changes unless the user asks for a release/build number.
|
||||||
|
- Mention the resulting frontend and backend version numbers in the final response.
|
||||||
|
|
||||||
|
## Pre-Deploy CLI Verification
|
||||||
|
|
||||||
|
- For any backend or frontend change, run the focused CLI smoke test for the touched area **before pushing a commit that would trigger a build**. The image tracker patch-bumps per build, so a failed build still consumes a version slot.
|
||||||
|
- Smoke-test scripts live under `backend/scripts/smoke/*.sh` (and the equivalent frontend dir). `scripts/test-*` is in `.gitignore`, so put committed smoke tests in `scripts/smoke/`. They must accept `BASE_URL` so the same script can target a local backend, dev, or production.
|
||||||
|
- Confirm the script passes against a local backend (or, where local isn't feasible, an explicitly named target) before pushing. After the deploy completes, re-run the same script against the deployed URL to confirm production behavior matches.
|
||||||
|
- If no smoke-test script exists for the touched area, create one as part of the change.
|
||||||
|
|
||||||
|
## CI Notification Safety
|
||||||
|
|
||||||
|
- Telegram CI notifications (`appleboy/drone-telegram` in `.woodpecker/*.yml`) must HTML-escape commit messages and strip git trailers (`Co-Authored-By:`, `Signed-off-by:`, `Reviewed-by:`, `Reported-by:`) before sending. Unescaped `<email@addr>` trailers cause "Bad Request: can't parse entities" 400 errors from the Telegram API.
|
||||||
|
- Use a `compose-notify` shell step that writes the rendered message into `.tgmsg`, then have the telegram plugin send `message_file: .tgmsg`. Do not interpolate `{{commit.message}}` directly into an `html`-formatted message body.
|
||||||
|
- Woodpecker eats `${VAR}` in command strings — always use `$VAR` (or `$$VAR` to escape) in pipeline command shells.
|
||||||
|
|
||||||
|
## Telegram Authentication
|
||||||
|
|
||||||
|
- `POST /api/auth/telegram` must allow Telegram Mini App retries with the same signed `initData`; Telegram may reuse launch data across reloads, retries, and duplicate client calls.
|
||||||
|
- Do not add one-time replay rejection to first-class Telegram login. Use signature verification, `auth_date` freshness, bot rejection, blocked-link checks, and rate limiting for this login path.
|
||||||
|
- Keep replay/deduplication checks scoped to routes where the payload is actually a one-time operation, such as webhook update handling or explicit Mini App session creation.
|
||||||
|
- Preserve or add regression tests whenever Telegram auth behavior changes.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- Run focused tests for the changed area and a typecheck/build when practical.
|
||||||
|
- If Redis, email, or other optional infrastructure is unavailable during tests, successful auth paths should fail open only where the production code already treats that dependency as non-critical, and the final response should mention any noisy but non-failing warnings.
|
||||||
|
- Before final response, report the important verification commands and whether they passed.
|
||||||
Reference in New Issue
Block a user