fix(cron): delivery uses owning profile's secret scope + adapters under multiplex - #83197
fix(cron): delivery uses owning profile's secret scope + adapters under multiplex#83197mjshorty wants to merge 3 commits into
Conversation
|
I rebased this patch locally onto current I found two blocking edge cases that the current tests do not exercise:
Please pass this kwarg only to
For any resolved non-default profile, please return its non-empty map or I would also add scope-reset tests for These are merge blockers for a multiplex deployment because one causes a deterministic provider startup |
…ousResearch#80921) Deterministic, LLM-free conformance cells against the real SessionDB with real SIGKILL mid-write, per the tracking issue's spot-probe method: - cell 1: acknowledged-append durability + recovery determinism (adapted from the issue's 29.5K probe, scaled kill window, identical assertions) - cell 2: consume-once under 8-process concurrent claim_handoff - cell 3 (new): compression-rotation atomicity — never a compression-ended parent without a continuation (NousResearch#80337 contract; NousResearch#80487 recovery context) - cells 4-5: documented stubs interlocked with NousResearch#82956-NousResearch#82959 and NousResearch#83197/NousResearch#83557 Journal-mode matrix (resolver default / DELETE / WAL-with-skip-gate) per cell; every wait deadline-bounded; writers asserted alive at kill time.
One xfail per open member of the class mapped on NousResearch#82936: profile-scoped state resolved from ambient process state at use time instead of bound to the owning profile/session at creation. Test-only; fixes nothing; flips to XPASS as per-site fixes land. Members: NousResearch#82936 (multiplex terminal env), NousResearch#81952 (corrupt config silent fallback), NousResearch#83346 (ambient session-key profile), NousResearch#80318 (profile scope hides root MoA presets), NousResearch#83197/NousResearch#83557 (cron delivery scope reset before delivery).
411557c to
4e15112
Compare
|
rebased onto current main |
|
rebased onto current main (follow-up: thread profile_adapters through _run_one_job_body so per-profile delivery works with the newer scheduler refactor) |
…er multiplex BUG 1 root cause: run_one_job() reset the profile secret scope in the inner finally BEFORE _deliver_result ran, so at delivery time current_secret_scope() was None. load_gateway_config -> _getenv reads TELEGRAM_BOT_TOKEN through the scope and only falls back to os.environ when NO scope is installed — so a multiplexed job's owning profile .env was ignored at delivery, producing the wrong/empty token (wrong bot/chat, dropped thread). Additionally, the gateway started the cron ticker with only the SHARED default-profile adapters dict; _deliver_result / DeliveryRouter never consulted Gateway._profile_adapters[<job profile>], so even with the right token a secondary-profile cron job couldn't reach its live adapter. PART 1: keep the profile secret scope installed THROUGH delivery. - Remove reset_secret_scope from the inner run_job finally. - Reset it in the delivery block's outer finally (after save + delivery), so run AND delivery both run inside the owning profile's scope. - The run_job except path still resets the scope before propagating (that path never reaches the delivery finally). PART 2: route cron delivery through the job profile's live adapters. - New _deliver_adapters_for_job() resolves Gateway._profile_adapters[profile] (mirroring authz_mixin) with fallback to the shared adapters dict for the default profile / non-multiplex path. - Thread profile_adapters through run_one_job -> _deliver_result. - Thread profile_adapters through CronScheduler.start / fire_due / InProcessCronScheduler.start / _start_multiplex -> tick -> run_one_job. - gateway/run.py passes runner._profile_adapters into cron_start_kwargs. Tests: run_one_job keeps secret scope through delivery; _deliver_adapters_for_job selects profile adapters vs shared fallback; run_one_job threads the map through. Cron suite 519 passed; gateway multiplex/cron-fire 25 passed.
…iplex Addresses ghosty-11's review on NousResearch#83197 (merge blockers for multiplex): 1. gateway/run.py no longer injects ``profile_adapters`` into the generic cron provider start kwargs. The kwarg is now passed ONLY to InProcessCronScheduler (the sole consumer), via a testable ``_build_cron_start_kwargs`` helper. External providers (Chronos) keep their ``start(stop_event, *, adapters, loop, interval)`` contract — a multiplex deployment with an external provider previously crashed with a deterministic TypeError. 2. _deliver_adapters_for_job no longer falls back to the default profile's shared adapter map for a non-default profile. A missing, empty, or unresolvable per-profile registry now resolves to {} (never the shared map), so delivery cannot cross profile identity boundaries. The standalone delivery path still works off the active profile's scoped credential. 3. ChronosCronScheduler.fire_claimed accepts and forwards profile_adapters (provider interface growth is additive). 4. Regression tests: external-provider kwarg isolation, in-process profile_adapters wiring, missing/empty/None/resolver-error profile map handling, standalone fallback with {}, and scope-reset guarantees when save_job_output raises or delivery raises a BaseException.
12676ea to
29ed094
Compare
|
Thanks for the detailed review — both blocking points are addressed in the updated branch (rebased onto current 1. Provider kwarg break (external providers).
2. Missing/empty per-profile map falls back to the default profile's adapter.
Also:
Test results on the rebased branch (current main base): The single failure ( |
Summary
Fixes cron delivery using the wrong bot/chat under multiplex (BUG 1): a multiplexed secondary-profile cron job delivered with the owning profile's secret scope torn down and the shared default-profile adapter map — so the wrong/empty
TELEGRAM_BOT_TOKENwas used and the original thread was dropped ("Thread not found").Two root causes, two parts:
Part 1 — profile secret scope dropped before delivery
run_one_job()installed the job profile's secret scope, butreset_secret_scope(_scope_token)ran in the run-jobfinallybefore_deliver_result. At delivery timecurrent_secret_scope()wasNone, soload_gateway_config() → _getenvonly fell back toos.environ(no profile scope) and the owning profile's.envwas ignored.Fix: keep the scope installed through delivery — remove the inner
finallyreset, reset it in the outer delivery-blockfinally(after save + delivery). The run-jobexceptpath still resets before propagating.Part 2 — delivery only used the shared default adapter map
The gateway started the cron ticker with
cron_start_kwargs = {"adapters": runner.adapters, ...}(the shared default-profile dict)._deliver_result/DeliveryRouternever consultedGateway._profile_adapters[<job profile>], so even with the right token a secondary-profile job couldn't reach its live adapter.Fix: thread the gateway's per-profile adapter map through the cron scheduler:
_deliver_adapters_for_job()resolvesGateway._profile_adapters[profile](mirroringgateway/authz_mixin.py), falling back to the sharedadaptersdict for the default profile / non-multiplex path.profile_adaptersthroughrun_one_job → _deliver_result.profile_adaptersthroughCronScheduler.start / fire_due / InProcessCronScheduler.start / _start_multiplex → tick → run_one_job.gateway/run.pypassesrunner._profile_adaptersintocron_start_kwargs.Tests
test_run_one_job_keeps_secret_scope_through_delivery— asserts the profile secret scope is still installed (and the profile secret resolves) at_deliver_resulttime. Fails on old code, passes with the fix.test_run_one_job_delivery_uses_profile_adapters/..._falls_back_to_shared_adapters_for_default— assert_deliver_adapters_for_jobselects the profile map vs the shared fallback.test_run_one_job_threads_profile_adapters_to_delivery— asserts the gateway's per-profile map reaches_deliver_result.Verification
python -m pytest tests/cron/ -q→ 519 passed, 1 skippedpython -m pytest tests/gateway/test_multiplex_adapter_registry.py tests/gateway/test_cron_fire_webhook.py tests/gateway/test_multiplex_profile_authz.py -q→ 25 passedmain; the 4 discord flakes pass in isolation — none introduced by this change.python -c "import cron.scheduler, gateway.run"→ clean