Imported from outaTiME/ambito-dolar (
packages/backend/AGENTS.md). Install upstream withnpx skills add outaTiME/ambito-dolar --skill backend. Copyright stays with the author.
Backend
Rules for packages/backend, loaded on top of the root AGENTS.md when working here.
SST v4, Lambda handlers.
- API handlers go through
Shared.wrapHandler(...)and answer withShared.serviceResponse(...). packages/backend/src/routes/test.jsis an API endpoint, not a test. There is no test script here.- Public response shapes are contracts: the client, the website and both widget implementations
read them. A
/fetchchange reaches the ios widgets first, seepackages/client/docs/android-widgets.md. is_openinquotes.jsondrives how often the client asks for rates. Dropping it or changing its meaning fails silently, seedocs/product-policies.md. It must not go into/fetchwhilestoreFetchJsonObjectis gated onis_updated: a gated write leaves it stale on exactly the close and the open, the two runs that move it without new rates. Anything that writesquotes.jsonoutsideProcesshas to carry the flag over,packages/backend/src/routes/update-rates.jsreads the stored one for that.puppeteer-coreand@sparticuz/chromiummove together or not at all, both only used inpackages/backend/src/libs/chrome.js. A mismatch fails when the social lambda launches Chrome, which surfaces as a missing post and not as an error. The chromium README prescribes the check: match the Chrome major that puppeteer ships, from https://pptr.dev/supported-browsers, to the chromium package major.- One
timeoutinpackages/coreand no per call override. Every source was measured before it was set and only one is above a second, so a second number would be a guess. A success body read with.then(r => r.json())is outside that timer, and a timeout is never retried. - ky skips POST and PATCH, so those wrap in
promiseRetryinstead: every social target, the nested one insocial/instagram.jsthat waits out a media still processing, andgetBusinessDay. Retrying a publish can repeat it, and with lambda retries off ininfra/defaults.tsthat is the only way a post goes out twice. - Every lambda timeout in
infra/is twice its observed duration and says so. Move the pair together. - What survives a slow source is the
catch, not the timeout.getRatecatches andgetObjectRatescompacts the hole, so one slow source costs one rate.getBusinessDaythrows uncaught and costs the whole cycle. A lambda killed afterstoreRatesJsonObjectleaves S3 written and the push unsent, which is worse than dying before it. - Do not reorder
process.js.storeRatesJsonObject->updateRealtimeData->notifyis load bearing, a push landing before the board is written shows stale rates to the clients still on it. - Remove
updateInstantDatabefore 2027-08-31, the day InstantDB cloud shuts down. It carries no timeout of its own and runs beforenotify(), so a hung sdk eats the one minuteProcessbudget and stops push and socials for everyone.