diff --git a/docs/now/2026-10-03-the-sweep-gets-one-step-per-seller.md b/docs/now/2026-10-03-the-sweep-gets-one-step-per-seller.md new file mode 100644 index 000000000..53c09edc5 --- /dev/null +++ b/docs/now/2026-10-03-the-sweep-gets-one-step-per-seller.md @@ -0,0 +1,28 @@ +# NOW -- The sweep gets one step per seller (2026-10-03) + +## What was read + +- The self-hosted Inngest dashboard, Status=Failed, last 7 days: three runs, all `CRM: proactive seller sweep`, all on 2026-09-16, durations 15 s, 2m02s and exactly 5m00s. +- All three: "Your server returned HTTP 502 before the SDK responded. No step output was produced before the request failed." +- The function was one `step.run('sweep')`: one HTTP request from the Inngest server to the bot carrying every connected seller in turn, each allowed up to INGEST_TIMEOUT_MS = 170 s on the render. + +## What this spec says + +- `specs/functions/crm-proactive-sweep.t27` is new; the function had no spec since it was born on 2026-09-12. +- STEPS = `resolve-sellers`, `sweep-${owner}`: one request per seller, a finished seller memoized inside the run, the trace names the seller a 502 lands on. +- RETRIES stays 0: a retry re-runs the interrupted step and may push a second card to a seller. +- DOMAIN `crm` is new to the corpus; README's DOMAIN vocabulary gains it. + +## Not verified + +- Which seller each 502 landed on: a single step left no per-seller trace. +- Whether the 15 s and 2m02s failures were a redeploy under the request or an app crash; Railway logs were not read. +- The exact Railway edge request budget; 5m00s is the observed cut. + +## Where the code goes + +- 999-multibots-telegraf, branch `sweep-per-seller-steps`: `crmProactiveSweep.ts`, `functions.manifest.json`, test `crmProactive.sweepSteps.test.ts` citing this spec, lesson `docs/inngest/lessons/2026-09-17-one-http-request-per-seller.md`. + +## Re-dated + +- Re-dated on 2026-10-03 for re-review: this entry was first written on 2026-09-17, and its content is otherwise unchanged. diff --git a/specs/functions/README.md b/specs/functions/README.md index 024b35eaf..c669defde 100644 --- a/specs/functions/README.md +++ b/specs/functions/README.md @@ -45,7 +45,7 @@ and as a function card here. The site joins the two by `REPO` + `LEGACY_ID` = th | `NAME` | `str` | display name | | `REPO` | `str` | `999-multibots-telegraf` | | `SERVICE` | `str` | `file:line` of `inngest.createFunction(` in that repo | -| `DOMAIN` | `str` | `neuro` \| `reels` \| `training` \| `morph` \| `render` \| `payment` \| `broadcast` \| `instagram` \| `content` \| `monitoring` \| `analytics` \| `webhook` \| `welcome` | +| `DOMAIN` | `str` | `neuro` \| `reels` \| `training` \| `morph` \| `render` \| `payment` \| `broadcast` \| `instagram` \| `content` \| `monitoring` \| `analytics` \| `webhook` \| `welcome` \| `crm` | | `TRIGGER` | `str` | `event` \| `cron` | | `EVENT` | `str` | canonical event name; `""` for a cron function | | `LEGACY_EVENTS` | `[N]str` | event names the code still listens to (multi-trigger); `[0]str = []` for a cron function | diff --git a/specs/functions/crm-proactive-sweep.t27 b/specs/functions/crm-proactive-sweep.t27 new file mode 100644 index 000000000..c56bcdc00 --- /dev/null +++ b/specs/functions/crm-proactive-sweep.t27 @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/functions/crm-proactive-sweep.t27 -- function crm-proactive-sweep (Inngest, 999-multibots-telegraf) +; Source of truth for the function card on t27.ai (#/functions?function=crm-proactive-sweep). +; The registration was read from 999-multibots-telegraf:src/inngest_app/functions/crm/crmProactiveSweep.ts#L101 (main @7d48227). +; ID is the canonical id; this function was born spec-first on 2026-09-12 and has no legacy id. +; STEPS lists step.run names in source order; a ${...} in a name is a template the code +; expands per item. Values here are read from the source, not inferred from a name; what +; could not be read says so in NOTE. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module fn_crm_proactive_sweep; + +pub const KIND : str = "function"; +pub const ID : str = "crm-proactive-sweep"; +pub const LEGACY_ID : str = ""; +pub const NAME : str = "CRM: proactive seller sweep"; +pub const REPO : str = "999-multibots-telegraf"; +; file:line of inngest.createFunction( in the repo named in REPO +pub const SERVICE : str = "src/inngest_app/functions/crm/crmProactiveSweep.ts:101"; +pub const DOMAIN : str = "crm"; +; "event" or "cron"; an event function has EVENT (+ LEGACY_EVENTS), a cron function has CRON +pub const TRIGGER : str = "cron"; +pub const EVENT : str = ""; +pub const LEGACY_EVENTS : [0]str = []; +pub const CRON : str = "*/30 * * * *"; +pub const TZ : str = "UTC"; +pub const SUMMARY_EN : str = "Every 30 minutes resolves the connected sellers (owner first) and runs one proactive CRM tick per seller, each in its own step, so a seller's stalled clients get a card in the seller's Telegram; one run at a time, no retries."; +; step.run names in source order; sweep-${owner} runs once per resolved seller, sequentially +pub const STEPS : [2]str = ["resolve-sellers", "sweep-${owner}"]; +; retries declared on the function; when the code declares none, the Inngest JS SDK v3 default (4) is written and NOTE says so +pub const RETRIES : u8 = 0; +; "admin-telegram" (onFailure handler messages the admin chat), "log" (no handler) or "refund+notify" +pub const ON_FAILURE : str = "admin-telegram"; +; what a run touches outside the function, from a fixed vocabulary (see README) +pub const SIDE_EFFECTS : [3]str = ["messages-user", "paid-api", "db-write"]; +; the first step that stops a bad payload; "none" when the first step already acts, "unknown" when not probed +pub const GUARD : str = "none"; +; JSON payload of the 2026-09-09 safe probe and what it reached ("" / "skipped" when none was sent; "not-deployed" when the function is not on the probed build) +pub const SAFE_PROBE : str = "{}"; +pub const PROBE_RESULT : str = "not-deployed"; +pub const CONTROL : str = "spec+code"; +pub const NOTE : str = "Born 2026-09-12 when the proactive sweep left an in-process setInterval (CRM_SWEEP_DRIVER=timer restores it, CRM_PROACTIVE_MINUTES=0 disables both). Concurrency 1 and retries 0 because a tick may push a card to a seller; a retry or an overlap could push a second one. Safe mode (e2e_test=true) skips the run entirely; no carrier registered returns did=paused, not an error. 2026-09-17: three FAILED runs on 2026-09-16 (durations 15 s, 2m02s, 5m00s) all read 'HTTP 502 before the SDK responded' on the self-hosted Inngest dashboard -- until then the whole tick was one step.run(sweep), i.e. one HTTP request carrying every seller in turn, each allowed up to INGEST_TIMEOUT_MS=170 s; the Railway edge cut the request. Now resolve-sellers is a step and each seller is a sweep- step: one request per seller, finished sellers memoized, the trace names the seller a 502 landed on. Not verified: which seller each 502 hit, and whether the two short failures were a redeploy under the request. Test: 999-multibots-telegraf src/__tests__/services/crmProactive.sweepSteps.test.ts.";