Validated 2026-08-02, against the v1.0 "Clean System Blueprint." This pass
adds the blueprint's explicit contracts (function signatures, numeric ID
validation, per-item retry limits) on top of the previously merged
reliability/security fixes. Everything below was independently re-checked
against this exact codebase — npm run check plus a 12-case functional
smoke test — not restated from a prior report.
npm run check— all files undersrc/passnode --check.- Functional smoke test (12 cases, mocked
fetch+ in-memory KV):/PANEL@TeamMarySyBotnormalizes and routes as/panel- Non-text message with no active flow is ignored
- Non-text message during an open support flow prompts
"Please send your response as text."(exact blueprint §11 wording) and does not create a ticket - Real text after that prompt creates exactly one ticket
5–6.
join:approvecallback_data is coerced toNumberfor bothchat_idanduser_id - Non-admins are rejected from
join:*callback actions with a message, and the Telegram API is never called - A failed
sendMessage()in the scheduler increments retry count rather than being deleted as sent 9–10. Malformed callback identifiers (non-numericchat_id/user_id) are rejected before reaching the Telegram API, with a graceful user-facing message
- §13 — numeric ID validation. Added
src/utils/validate.js(toSafeInteger), which throws on any callback-derived ID that isn'tNumber.isSafeInteger().callbacks.jscatches this locally and replies with a generic failure message instead of letting a malformedcallback_datapayload reachapproveChatJoinRequest/declineChatJoinRequest, or bubble up as an unhandled rejection. - §11 —
Support.isAwaitingText(chatId, env). Renamed from the previousisAwaitingInput(env, chatId)and flipped the argument order to match the blueprint exactly. Internal step name changed toawaiting_ticket_textto match §18's state shape. - §15 —
Content.publish(item, env). Flipped frompublish(env, item). This is a deliberate module-boundary contract —telegram.jsitself still takesenvfirst everywhere internally; only the two blueprint-specified public signatures (isAwaitingText,publish) takeenvlast. - §20 — per-item
max_retries.createScheduledItemnow storesmax_retries(default 3) on each scheduled item instead of relying on a single module-wide constant, matching the schema in §20 exactly.
telegram.jsthrows on Bot API failures instead of only logging.- Scheduler retries failed sends via
incrementRetry()rather than unconditionally callingmarkSent(). - Unknown scheduled item types throw and enter the retry/failed path.
- Callback authorization enforced for
menu:*(content/community) andjoin:*. community.jsnotifies the owner and all configured admins viaPromise.allSettled, so one unreachable admin doesn't mask successful notifications to the rest.schedules.jspaginatesKV.list()via cursor.- Commands normalize
@BotUsernamesuffix and case.
- Content create/list/edit/archive command and callback UX remains stubbed — the module boundary and
Content.publish()pipeline exist, but no guided Telegram conversation is built on top of it yet. - Support ticket list/resolve commands are not exposed through Telegram, although ticket creation and the underlying
resolveTicket()operation exist. - The blueprint's
processingschedule status (§20) is not implemented — items only move betweenpendingand thesched:failed:*prefix. This is fine at current scale (a single Worker instance processing one cron tick at a time won't race itself), but if scheduled volume grows enough to warrant overlapping cron runs, aprocessinglock would prevent double-sends.
A live Wrangler dry-run (npx wrangler deploy --dry-run) was not executed in
this sandbox (no network access to the npm/Cloudflare registries). Run before
deploying:
npm install
npm run check
npx wrangler deploy --dry-run --outdir dist