Repository navigation
feat(branding): channel-only hop gate, link/@ replace maps, prompt library, branding failure trail - #342
Merged
Conversation
…y, failure trail
Branding hop (A -> C -> B)
- new require_channel gate (branding_require_channel): messages written by
normal users fail the gate and are delivered directly with their own
"Forwarded from <user>" header; channel-originated content (a native
channel post in A, or a forward whose origin is a channel) takes the
branded hop. Composes with branding_require_forward.
- Python core (branding.should_hop + entity bridge + registry) and the Go
worker port stay decision-identical: 5 new cross-engine parity fixtures
(reference_branding.json regenerated; the Go harness carries chat_type
and the origin type).
- the job payload now carries chat_type so the worker re-decides the gate
at claim time from the same inputs Python used.
- smart menu gains the "Channel-only hop" toggle (rsmrc:) beside the
forward-gate and force-forward toggles; dashboard RuleModal gains the
checkbox + hint; the registry exposes the key to the settings surfaces.
Replace engine ("replace any link found" / "replace any @id")
- link_replace_enabled upgrades a task with no stricter link policy to
REWRITE_LINKS; link_rewrite_map gains the "*" catch-all (every link to
one target) and URL-prefix keys (one deep link) alongside domain keys.
- new mention_rewrite_map: @old -> @new pairs (case-insensitive) with the
same "*" catch-all, applied in the final replacement pass after the AI
stage so a rewrite cannot resurrect a replaced handle. Go port
(cleaning/mentions.go) is index-based where RE2 lacks look-around, with
the same decisions.
- dedicated bot panel (smart menu -> "Link & @replace maps", rmap:) with
one-pair-per-line editors; dashboard RuleModal section (checkbox + two
textareas); registry rows render on the mobile settings sheet.
Prompt library: enable/disable + the missing bot manager
- prompt_templates gains an enabled column (guarded ALTER on SQLite,
ADD COLUMN IF NOT EXISTS on Postgres, schema.sql updated, backup
export/restore carry it); PromptService.set_enabled switches a template
without touching its versions.
- delivery semantics: a disabled template stays in the library (preview
and the tester keep working) but the pipeline falls back to the task's
AI config own prompt ([PIPELINE:AI_REWRITE:PROMPT_OFF]); the Go prompt
store resolves a disabled template as not found.
- the bot gets the global prompt manager it never had (AI -> Prompts):
list, create, rename, edit system prompt / user template / description,
test against a real provider, delete with confirmation, enable/disable;
system templates are view/test only. The per-rule prompt picker badges
disabled templates and links to the manager. The mobile AI screen gains
the same library (CRUD + test) over the shared /api/prompts contract.
Failure trail
- branding failures now carry their own event codes into the Rust log
store: BRANDING_HOP_FAILED / BRANDING_HOP_EMPTY (Python audit events),
BRANDING_STAGE1_FAILED / BRANDING_STAGE2_FAILED (worker logger attrs
the fanout forwards), and DELIVERY_BLOCKED is reported as
BRANDING_STAGE2_FAILED when the job died inside the hop.
i18n (10 locales, 1:1 parity on every surface)
- bot catalog +79 keys x10 (prompt manager, replace maps, smart-menu
gate, registry labels/help for the four new settings, localized reason
sentence); catalog re-sorted into the batch writer's canonical order.
- web locales +12 keys x10; settingLabels.generated.ts regenerated;
mobile catalogs +32 keys x10 (prompt section + setting labels).
Tests
- core: test_replace_maps.py (24) and test_prompt_enabled.py pin the
mention/link semantics, the channel gate, registry exposure and the
prompt enable contract; branding group pin updated.
- worker: mentions_test.go, links_star_test.go (table-driven), parity
fixtures extended; full worker + api suites pass unchanged.
Docs: docs/branding-and-replace.md (flows + composition + failure trail),
feature matrix rows X13-X16, coverage doc, README changelog.
Codex Review Summary: ❌ FailedThis 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. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…very defects The documented pyrogram surface (docs.pyrogram.org, verified against the v2.0.106 tag source) is now committed as docs/pyrogram-methods-2.0.106.json — 163 high-level methods with exact signatures, 104 types, 14 enums, handlers, the generated error classes and the raw TL schema counts. scripts/audit_pyrogram_surface.py holds the repository to it: every client-method call site's kwargs and every pyrogram.errors/enums/types import must resolve 1:1. Today: 125 call sites, 130 symbol imports — PASS. Two real defects fell out of the audit; both failed real deliveries and are regression-pinned in telegram_runtime/tests/test_documented_surface.py: 1. get_album_message_ids called get_messages(chat_id, limit=40) — the pinned get_messages has no `limit` parameter (a Bot-API concept), so the resolver raised on every album and worker album deliveries degraded to single copies. It now uses the documented one-to-one method get_media_group(chat_id, message_id). 2. message_thread_id does not exist on the pinned forward_messages / copy_message / copy_media_group / send_message / send_* signatures (a Bot-API parameter official pyrogram's MTProto layer never grew), so a task pointed at a forum topic failed EVERY delivery with TypeError — on both the live dispatcher/queue path and the Go Worker's runtime adapter. New core/infrastructure/telegram/pyrogram_compat.py probes the installed client's signature once and threads the kwarg only when the method accepts it: on the pin a topic task delivers to the chat's General topic; on a fork that grew the parameter, topic targeting works unchanged. The adapter's five delivery methods and the durable queue / dispatcher thread_kwargs construction are wired through it. docs/pyrogram-api-map.md documents the full one-to-one map, including the Go Worker's gateway RPC -> runtime handler -> pyrogram call resolution table and the failure taxonomy each hop reports.
…ip review 0-100 Full ownership-and-resolution review of the Go worker (claim lane, send fence, tenant scoping, outcome lifecycle, reconciler, gRPC client, store security). Verification: go vet, full go test ./... -count=1, -race on internal/queue, internal/engine/..., internal/ops and test/ownership — all green; findings and evidence in docs/worker-ownership-review.md. Two crash-class defects found and fixed: 1. No recover() anywhere in the worker: one panicking payload crashed the process, the restart re-claimed the SAME job and panicked again — a crash loop stalling the whole queue. Engine.Process now contains the panic (CRITICAL log + stack + JOB_PANIC_CONTAINED event code), hands the job to the retry budget (bounded, dead-letters at exhaustion) and deliberately leaves the fence untouched: a post-send panic keeps the fence's terminal SENT record, an unrecorded one ages out to UNKNOWN like every other ambiguity. The ops collector gains a dedicated "panicked" outcome counter (never folded into Telegram "failed"), exposed through /healthz. Regression-pinned by panic_containment_test.go: the poisoned job retries with one spent attempt and a healthy job right after it still delivers SENT. 2. backoffFor multiplied 2^attempts in one step: 2^63 seconds already overflows an int64 nanosecond duration, so a high-attempt job wrapped to a near-zero backoff and retried in a tight loop instead of waiting out the 5-minute cap (the table test caught backoffFor(99) == 1s). Replaced with an overflow-free doubling loop that returns the cap as soon as it is reached; TestBackoffForIsBoundedExponential pins the table including the overflow classes. docs/worker-ownership-review.md records the model as reviewed (two-phase claim lane, pre-read fence with never-stolen CLAIMED/SENDING rows, evidence-gated reconciler, tenant-scoped filter/AI-config/prompt reads, parameterized SQL, AES-256-GCM provider keys, redaction choke points) and the deliberate non-changes with their reasons.
…es in the dispatch harness Two CI failures, two root causes: 1. python-tests: the bot dispatch harness swaps filters.regex for its fake in every REGISTERING_MODULES entry — the new replace_maps_bot_ui and prompt_bot_ui mixins were missing from that list, so their real pyrogram filters (which expose .p, not .matches) read as "matches everything" and rmap: swallowed every press. Both modules registered. 2. flutter: the l10n catalogs were rewritten by sync_mobile_catalog.py, whose serializer turned the files' literal \n escapes into real newlines inside single-quoted strings — invalid Dart, one syntax error per catalog. All ten files restored from master and the 32 new keys re-inserted in place (quote-escaped, no rewriter run): the diff is now exactly 32 added lines per file.
- mobile: import core/models.dart in prompt_templates_section.dart. ErrorKind is declared there; the missing import made flutter analyze fail with undefined_identifier at line 64. - bot: spell the prompt-manager test verb as CB_PRM_TEST. The literal verb == "pt" is a callback verb (prompt test), not a language tag, but it matched the language-tag audit's Portuguese pattern and failed test_no_language_tag_is_compared_in_code. Same wire format, one spelling, no false positive.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does
Upgrades the branding/replace/AI suite end to end — one shared decision per behavior, implemented across the Python engine, the Go worker and all three clients (Telegram bot, dashboard, mobile).
1. Branding hop (A → C → B) with the channel-only gate
branding_require_channelgate: a message written by a normal user fails the gate and is delivered directly with its own "Forwarded from <user>" header; channel content (a native channel post in A, or a forward whose origin is a channel) takes the branded hop. Composes withbranding_require_forward.MessagePayload.chat_typerides the job payload so the Go worker re-decides the gate at claim time from the same inputs Python used.worker/internal/parity/reference_branding.json(regenerated from the live Python decision); the Go harness now carrieschat_typeand the origintype.rsmrc:), dashboard RuleModal checkbox + hint, mobile settings sheet (registry row, localized), REST settings groupbranding.2. Replace engine — "replace any link found" / "replace any @id"
link_replace_enabled+link_rewrite_map:*rewrites every link to one target, a domain key rewrites one domain, a URL-prefix key rewrites the deep links it starts with (entity-aware, both engines).mention_rewrite_map:@old → @new(case-insensitive) or* → @mainfor every @id — applied in the final replacement pass after the AI stage, so a rewrite cannot resurrect a replaced handle. Go port avoids RE2's missing look-around with index-based boundary checks that make the same decisions.rmap:panel, one-pair-per-line editors,-clears), dashboard RuleModal section (checkbox + two textareas), mobile registry rows, REST settings.3. AI rewrite composition + prompt library
prompt_templates.enabledcolumn (guarded ALTERs on SQLite/Postgres, backup export/restore carry it). A disabled template stays in the library (preview/test keep working) but deliveries fall back to the task's AI-config prompt ([PIPELINE:AI_REWRITE:PROMPT_OFF]; the Go store resolves it as not found).AI → 🧠 Prompts): list, create, rename, edit system prompt / user template / description, test against a real provider, delete with confirmation, enable/disable; system templates view/test only; the per-rule prompt picker badges disabled templates. Mobile gains the same library (CRUD + test) over/api/prompts.4. Failure trail into the Rust log store
BRANDING_HOP_FAILED/BRANDING_HOP_EMPTY(Python audit events),BRANDING_STAGE1_FAILED/BRANDING_STAGE2_FAILED(worker logger attrs forwarded by the fanout), andDELIVERY_BLOCKEDis reported asBRANDING_STAGE2_FAILEDwhen the job died inside the hop — one Log Center filter per failure class.5. i18n — 10 locales, 1:1 parity everywhere
Bot catalog +79 keys ×10 (incl. registry labels/help for the four new settings and a localized sentence for the new reason code), catalog re-sorted into the batch writer's canonical order; web locales +12 ×10 + regenerated
settingLabels.generated.ts; mobile catalogs +32 ×10.audit_cross_language,i18n_audit, the telegram literal guard and both UI-copy gates pass.Tests
core/tests/test_replace_maps.py(24),core/tests/test_prompt_enabled.py,worker/internal/domain/cleaning/mentions_test.go,links_star_test.go(table-driven), 5 new branding parity fixtures.prompt_test.go-compatible store.worker+apiGo suites pass; the Python suite shows zero new failures vsmaster(the only locally-unrunnable tests are the async ones — no pytest-asyncio in this environment; logic verified directly withasyncio.run).Docs
docs/branding-and-replace.md(the flows, the composition, the failure trail, the worker configurability), feature matrix rows X13–X16, coverage doc, README changelog.