feat(dataplane): typed Redis coordination plane with atomic Lua substrate, FSM migration, and Hydra hardening#20
Conversation
…nd async LLM refinement. Introduce RecordCardEmbedding and ClusterCard data structures for managing embeddings and clusters. Update existing IdeaAnalyzer to support description rewriting. Add new prompts for cluster refinement and representative selection.
…nalyzer structure.
… Update memory.yaml to include analyzer settings and fast analyzer configurations. Modify IdeaTracker to support both default and fast analyzer types, enhancing idea processing efficiency. Add new fabric components for analyzer and Redis configuration creation.
…loading and task description handling. Introduce new utility functions for loading configurations and task descriptions, and enhance the summary generation process. Update type checks for idea data structures in RecordBank. Add new components for statistics and summary processing.
…etons; drop exec-pool lru_cache resolvers.py Three globally-registered resolvers gone: eval (= builtins.eval, full RCE reachable via interpolation), merge and len (zero call sites in any config). get_object and ref registrations gain replace=True so a second register_resolvers() call from a test or notebook re-run no longer raises ValueError. oc.env is a built-in OmegaConf resolver; no explicit registration to harden. trackers/__init__.py Module-level _tb_default / _wb_default / _redis_default globals deleted. init_tb / init_wandb / init_redis now construct a fresh GenericLogger per call; the four get_* accessors deleted (zero external production callers). The composite init_tb_redis and init_wandb_redis factories no longer hand back a cached instance. Under multirun the second run gets its own writer pointed at its own config instead of silently reusing the first run's closed writer. python_executors/wrapper.py @functools.lru_cache(maxsize=1) removed from default_exec_runner_pool. The asyncio.Queue / Lock / subprocess transports bound to the first event loop no longer leak across asyncio.run boundaries — a second engine run gets a fresh pool. Docstring documents that lifecycle is the caller's responsibility. tests/conftest.py Replaced the _clear_exec_runner_pool autouse fixture (incompatible with the cache removal) with a session-scoped autouse fixture that calls register_resolvers() once per pytest session. Tests that load YAMLs via compose() without going through run.py no longer fail with Unsupported interpolation type ref. New tests (+16): - TestRegisterResolvers (4): dangerous resolvers absent, required resolvers present, double-registration idempotent, ref end-to-end. - test_trackers_singletons (8): fresh-per-call identity for the three init_* factories plus multirun-safety routing for the two composite factories. - test_exec_runner_pool (4): fresh-per-call identity, absence of lru_cache attributes, independent asyncio primitives across sequential asyncio.run invocations. Production callers in gigaevo/programs/stages/python_executors/ execution.py and three other sites still invoke run_exec_runner with pool=None — they now construct a single-use pool per call instead of sharing a process-wide pool. Subprocess startup is no longer amortized for those paths; follow-up will thread one pool through run_experiment per the spec.
…lts to null
gigaevo/llm/strict_chat_openai.py (new)
Factory strict_chat_openai(**kwargs) validates kwargs against
ChatOpenAI.model_fields plus the harvested Pydantic aliases
(model_name, openai_api_key, openai_api_base, openai_organization,
max_completion_tokens, timeout, stop_sequences) before construction.
Unknown kwargs raise StrictChatOpenAIError naming the offender;
pre-Hydra typos like tempetature: 0.5 now fail at YAML load instead
of silently shipping into model_kwargs and onward to the OpenAI API.
api_key=None raises with a message naming OPENAI_API_KEY env var so
the missing-env diagnostic is application-level instead of an
InterpolationResolutionError deep in OmegaConf.
config/llm/{single,openai,google,heterogeneous,heterogeneous_bandit,
openrouter_bandit,openrouter_ensemble,gemini25_pro,gemini31_pro,
gemini3_flash}.yaml
20 _target_ swaps: langchain_openai.ChatOpenAI →
gigaevo.llm.strict_chat_openai.strict_chat_openai. 18 api_key
interpolations gain the OmegaConf comma-default:
${oc.env:OPENAI_API_KEY,null}. The factory's None-guard surfaces a
clear typed error when the env is genuinely unset.
tests/llm/test_strict_chat_openai.py (new, 10 tests)
Known kwargs pass; known aliases pass (max_tokens, request_timeout,
base_url, api_key, etc.); single-typo kwarg raises naming the
offender; multi-typo kwarg raises naming both; None api_key raises
naming OPENAI_API_KEY; via Hydra instantiate, a typoed YAML field
raises InstantiationException whose __cause__ is the
StrictChatOpenAIError.
…cit _self_ to defaults
config/algorithm/*.yaml, config/constants/islands.yaml
IslandConfig.migration_rate was silently dropped at construction
(Pydantic extra='ignore'). Engine audit confirmed no consumer reads
per-island migration_rate; the global MapElitesMultiIsland.
migration_interval and max_migrants_per_island are authoritative.
Deleted the dead migration_rate: ${migration_rate} key at all 8
algorithm sites and the dead constant in constants/islands.yaml.
Schema pinned by two new tests in tests/evolution/test_island.py
so future regressions fail fast.
config/pipeline/*.yaml, config/algorithm/_base.yaml,
config/evolution/{default,steady_state}.yaml,
config/pipeline/auto.yaml
Plain interpolations ${redis_storage}, ${problem_context}, ${llm},
${metrics_context} unified to ${ref:...} across 14 YAMLs. Mixed
use of the plain form and the ref-mutating form caused C3 double-
instantiation of ProblemContext on every pipeline run; one half
saw add_auxiliary_metrics mutation, the other did not. Post-edit
rg over the four protected names returns zero plain references.
The pure-value interpolations (${primary_key}, ${higher_is_better},
${seed}, ${stage_timeout}, ${temperature}, etc.) stay as-is —
those don't traverse the mutating resolver.
config/experiment/*.yaml, config/algorithm/*.yaml,
config/constants/base.yaml, config/evolution/steady_state.yaml
Added explicit - _self_ to the defaults: block of 18 YAMLs (8
experiment + 8 algorithm + 2 misc). Hydra 1.3's implicit-last
semantics emits UserWarning; future Hydra would have flipped the
default. Last-position _self_ matches the runtime intent — file-
level overrides win over inherited defaults — and the migration-
bus / single-island-variant chains keep working unchanged.
…cycle leak fixes; gitignore artifacts wrapper.py Module-level contextvars.ContextVar[WorkerPool | None] carries an experiment-scoped pool through every run_exec_runner(pool=None) call site without touching stage signatures or factory closures. set_ambient_exec_runner_pool / reset_ambient_exec_runner_pool / get_ambient_exec_runner_pool form the bind/reset/lookup surface. run_exec_runner consults the ambient pool when no explicit pool= is supplied; explicit pool= argument still wins. Restores the subprocess-startup amortization that the lru_cache removal in the previous commit silently regressed. run.py run_experiment now builds one WorkerPool, binds it via the contextvar, and drains it in finally before resetting the token. Every existing pool=None call site (execution.py, runtime_metrics.py, optimization/utils.py, optimization/optuna/stage.py) now resolves to the bound pool instead of creating a fresh single-use pool per invocation. M3 fix: an inner try/finally wraps the dag_runner.start() + evolution_engine.start() + serve_until_signal sequence. The finally re-awaits both stop() methods so an exception between start and serve no longer leaks the two background asyncio tasks. Both stop() methods verified idempotent (engine.stop nulls out _task after cancel; runner.stop swaps _redis to None first). M4 fix: an outer try/finally covers the full body. Every component local defaults to None; the finally guards each component individually so a mid-flight instantiate failure no longer leaks the writer's daemon thread or the Redis ping connection. The ambient pool token is reset regardless. .gitignore Added wandb/, runs/, tb_logs/, tensorboard/ — test-run artifacts that were leaking into git status. 13 new tests in tests/stages/test_exec_runner_pool.py and tests/ entrypoint/test_run_experiment_lifecycle.py pin: - ambient-pool reuse across consecutive run_exec_runner calls - explicit pool= argument overrides ambient - fallback when no ambient is bound - source-grep guard against future unguarded pool=None call sites - start-then-exception still fires every stop() - instantiate-failure does not leak threads or the ambient pool token - stop()s remain idempotent after serve_until_signal already drove them Wider test sweep: 4760 passed. Out-of-lane (LOW, deferred): both engine.stop() and runner.stop() both call self.storage.close() on the same underlying storage — structurally safe due to RedisConnection idempotency, worth tightening to single-owner in Phase 1+. serve_until_signal accepts coroutines rather than callables, so an exception before iteration emits RuntimeWarning on the unawaited coros — Phase 1+ refactor.
Per dataplane doc §7.1 step 11. The two subprocess-IPC sites in gigaevo/programs/stages/python_executors/ are allowlisted at the per-file level (local trust boundary; the subprocess is gigaevo code running gigaevo code over a private pipe). gigaevo/programs/utils.py:18 carries an inline noqa with a docstring naming its Phase 3 migration target — it is the b64-encoded round- trip from programs/core_types.py:25 content_hash, the bug-class-FusionBrainLab#6 RCE-on-deserialise surface for any Redis blob. Future work removes this site by switching the content_hash codec to JSON+sha256.
engine_startup.py: add the two helpers mirroring wire_storage / wire_prompt_fetcher conflict-rejection contract. Idempotent on identical attach; raises on conflicting re-attach; returns False silently when invoked on non-target object types. run.py: replaces raw _dataplane = / _engine_root = attribute assignments with the two helper invocations. Discipline gap from the audit closed: every rebind site now goes through a named helper. 2 integration tests pin idempotent re-wire (silent no-op), conflicting re-wire (raises), and the non-target degrades-to-False path. __init__.py re-exports both helpers.
…elper The last live bypass of bug class FusionBrainLab#14 (gigaevo/evolution/bus/node.py:120 direct program.state = ProgramState.DONE on cross-engine migrant ingestion). The migrant is a freshly-rehydrated Python object with no prior in-run FSM history, so the in-run FSM table can't validate the transition — the migrant arrives in whatever state the originating engine last persisted (typically QUEUED), and the receiving engine must mark it DONE so its own evolution loop ignores it. state_manager.register_external_terminal_state(program, new_state) is the named cross-run bypass: rejects non-terminal targets, mutates the in-memory Program, no Redis I/O. Greppable so any future contributor adding cross-run state transfer has one place to read. bus/node.py:120 now calls the named helper; the bypass is no longer anonymous. 4 tests pin the helper's rejection of non-terminal targets and the node-level routing through it.
…tion_program_state_batch The single-program transition path migrated to dp.transition_program_state earlier; the batch methods at redis_program_storage.py:709-849 still used raw pipe.set with Python-side validate_transition only — concurrent writers could race on the same program in a batch. _batch_transition_via_dataplane builds BatchTransitionItem tuples and dispatches through dp.transition_program_state_batch. Per-item tokens derive from engine_root.split_program_token when wired; fall back to mint_root per item otherwise. The batch wrapper is per-item atomic (per dataplane doc §4.2 — whole-batch atomicity is not guaranteed); that matches the legacy semantics. batch_transition_by_ids loads programs via parallel get() in the dp branch (trades the legacy raw-JSON optimisation for FSM Lua atomicity). Legacy raw-pipeline path preserved under dataplane=None. 5 tests cover both branches, filter semantics, illegal pair rejection (returns Err), and the legacy fallback.
…y; wrap_lease for typed crash flow Two integrations on RedisInstanceLock.__init__. (1) Eliminates ~50 LOC of script_load + NoScriptError retry that duplicated LuaRegistry.evalsha. When dataplane is wired, _evalsha delegates to dp._lua.evalsha; the dp's reload-and-retry path is the single source of truth for the three lock Lua scripts. Legacy direct- aioredis path stays under dataplane=None for backwards-compat with callers that don't yet have a dp reference. (2) Wires wrap_lease (dataplane doc §3.13 + §5.2). After acquire(), the lease is wrapped via dp.wrap_lease and surfaces lock-loss as a typed CrashEvent. The renewer signals the lease's OneShotFlag on token-CAS failure or transport error; the new wrapped_lease property + observe_loss() coroutine return the CrashEvent (one-shot consumption). Callers can opt into typed control flow instead of polling lease.flag.is_set() out of band. 4 new tests pin: acquire routes via LuaRegistry, renewal-loss surfaces as CrashEvent, release clears the wrapper, dataplane=None returns None for wrapped_lease. 17 existing locking tests unchanged.
…iant Only compute_content_hash_hex has a consumer (coordinator.py uses it for the FSM idempotency token). The bytes variant had zero callers anywhere. Phase 2's emit_event.lua may want a wire-efficient 32-byte payload but that's a deliberate Phase 2 decision; today the bytes variant is speculative substrate. compute_content_hash_hex now computes sha256 directly via hexdigest() instead of routing through the deleted bytes function. Behaviour unchanged at every call site. ContentHash NewType + canonical-JSON encoder kept (vocabulary + in-use machinery).
mint_root + mint_split + mint_split_n cover every production caller (engine_startup, redis_program_storage, archive_storage, dataplane test suite). mint_combine had zero consumers — the Permission algebra's recombine operation is speculative substrate until a real consumer needs it. Three dedicated test classes deleted along with the function. The engine_startup comment that named mint_combine in a side note is updated to describe the actual subspace contract.
The doc-proposed Program.atomic_counter -> Monotonic[int] migration never landed; current production uses Lua-side INCR for atomicity. No in-process monotonic counter consumer ever materialised. The wrappers added runtime cost (an extra attribute + advance check) without a benefit. The lattices.py docstring is rewritten to describe what MonotoneLattice actually backs in production (epoch / generation / HLC counters) rather than the previous reference to the deleted Monotonic wrapper. 47 LOC of dedicated test class deleted; 63 LOC of Monotonic / MonotonicCounter implementation deleted; net -110 LOC.
_phase2_substrate.md enumerates the present-but-unused features that ARE kept deliberately because they are Phase 2 substrate per the design doc: lwwr_set/_get + Lua (LWW register with HLC tiebreak), HlcTimestamp (event timestamps), the lattice classes (BoolLattice powers Phase 2 admission), and 13 NewType IDs (events / streams / consumer groups / causation tracking — all zero runtime cost). For each: one sentence on why it ships now, one on its Phase 2 destination. Reachable from the package docstring at __init__.py. A future contributor pruning dead code sees this list before deleting them by mistake. Incidental: ruff I001 auto-fix on the unrelated gigaevo/utils/trackers/backends/wandb.py import block.
…nd archive try_replace_elite Restores the single-writer-per-subspace witness (doc §3.5) on the two surfaces that previously bypassed it: bandit (no token at all) and archive (mint_root per call instead of split-from-root). coordinator.py crdt_inc gains optional token: Token[CounterKey] | None. When supplied the token is consumed and tag-checked against the CounterKey; mismatch returns Err(DataPlaneError). When None the legacy path stands (backwards compat). llm/bandit.py SlidingWindowUCB1 / BanditModelRouter accept optional engine_root. _schedule_crdt_inc mints per-call counter tokens from engine_root.split_counter_token and threads them through _do_crdt_inc. Asymmetric engine_root without dataplane raises ValueError at construction. evolution/storage/archive_storage.py RedisArchiveStorage accepts engine_root. _add_elite_via_dataplane uses engine_root.split_cell_token when wired; mint_root fallback preserved for dataplane-less constructors. dataplane/engine_startup.py wire_archive_storage(archive, dataplane, engine_root) helper added matching the wire_storage/wire_bandit_router pattern (idempotent, raises on conflict). wire_bandit_router extended to accept engine_root. run.py wire_archive_storage invoked on every island; engine_root threaded into wire_bandit_router. 11 new tests pin token-discipline rejection (consumed twice, tag mismatch), wire-helper idempotency, backwards-compat with engine_root=None.
…lValue) Sourced[T, S] + the six aliases (LocalValue, CachedValue, ReplayedValue, GossipedValue, ExternalValue, SanitizedValue) were exported with zero production consumers — the provenance vocabulary was dead code that the doc §3.4 says should distinguish local-fresh-read vs cached-stale- read at every call site. dp.read_program now returns Result[LocalValue[Versioned[ProgramSnapshot]] | None, DataPlaneError]. The phantom-type wrapper makes the read's provenance explicit at the type level. Future replay paths would return ReplayedValue[Versioned[...]]; future cache reads would return CachedValue[Versioned[...]]; mypy then enforces that a function demanding LocalValue[Program] cannot accept a CachedValue[Program] by mistake (bug class FusionBrainLab#13 closed at the type system level). Production caller updated: gigaevo/runner/dag_runner.py _timeout_read_is_fresh unwraps the LocalValue via .value.value to reach the underlying Versioned. 5 tests updated to unwrap the LocalValue wrapper; 1 new test pins the phantom-wrapper shape returned by read_program. This is the foothold: a follow-up provenance audit can rg "LocalValue\|CachedValue\|ReplayedValue" gigaevo/ and find the first real consumer; subsequent reads (crdt_read, lwwr_get) can adopt the same pattern.
…p dead narrative
Net delta across 61 files: -1629 LOC (1069 added, 2698 deleted). No
behavioral changes, no test count delta — 4789 tests passing before
and after.
Three parallel cleanup lanes worked the branch diff against main:
Dataplane substrate (~-1500 LOC across coordinator, engine_startup,
permissions, models, codec, scripts, transitions, lattices, ids,
errors, crash, connection, the Lua scripts, and the co-located
tests). Stripped multi-paragraph rationales describing the design,
shrunk module docstrings, removed historical narrative ("previously",
"we now", "before this"), collapsed multi-line in-body comment
blocks to one-liners. The genuinely load-bearing WHY comments
stayed (the cjson directive fallback, the NOSCRIPT coalescing note,
the asyncio-only contract on OneShotFlag, etc.).
Migrated callers (~-570 LOC across redis_program_storage, locking,
state_manager, archive_storage, bandit, fetcher, stats, sync,
mutation_operator, dag_runner, selectors, strict_chat_openai). One
real code smell fixed in coevolution/stats.py: replaced
`for idx, (db, prefix) in enumerate(...)` with a stale dangling
`del db, prefix` that referenced no log line. Dual-path narration
("when wired, route through dp; when None, fall back...") collapsed
across the storage / archive / bandit dispatch sites. The
backwards-compat-shim apology comments tightened to one line each.
Tests + entry-point + configs (~-340 LOC across the test tree,
run.py, pyproject.toml, .gitignore). Test docstrings collapsed,
internal-slug "covers C3" / "T-A4" references stripped, narrative
about prior bypass behaviour replaced with what-the-test-asserts-now.
pyproject.toml banned-import policy block tightened; every TID251
per-file-ignore verified live (zero stale entries). .gitignore left
alone (no narrative to strip).
Code-smell pattern surfaced across 5 files (NOT fixed in this
sweep; flagged for a follow-up): the `if self._dataplane is not
None: return await self._<op>_via_dataplane(...)` dispatch shape
duplicates in redis_program_storage.atomic_state_transition,
fast_state_transition, batch_transition_state, batch_transition_by_ids,
and archive_storage.add_elite. A small dispatch_if_dataplane helper
or a coordinator-mixin would centralise the branch + assert. A
sibling pattern across prompts/fetcher.py, coevolution/stats.py,
coevolution/sync.py for lazy-init owned/borrowed DataPlane could
deduplicate via an OwnedDataPlane container.
Out-of-lane finding (not fixed): errors.all_error_types() omits
EliteInvalidError from its tuple but the test only checks subclassing,
not exhaustiveness. One-line fix; counted as behavior change; left
for a follow-up.
Net -32 LOC across 13 files. No behavioral changes, no test count
delta — 2841 focused tests + 4789 full sweep still pass.
Three parallel cleanup lanes worked the branch diff against main
with a focus on vacuous diffs (renames without semantic change,
reflowed messages with identical meaning, function extractions
that did not aid testing).
Dataplane substrate
test_smoke.py: test_method_stubs_raise_notimplemented renamed
to test_public_methods_callable (the old name asserted a stub-era
NotImplementedError that the body no longer checked; the body
comment explicitly contradicted the name). Module docstring
reflow archeology trimmed in test_no_silent_broad_except,
test_models, test_ids. Two back-references stripped from
test_transition_state ("WATCH/MULTI/EXEC path", "Compat fields
for non-coordinator readers"). ClaimState docstring no longer
names the migration-bus consumer (zero in-tree references).
make_actor_id soft-deprecation hint removed.
Migrated callers
Four vacuous renames reverted:
- mutation_operator.py: kwargs->positional flip on record_outcome
restored to kwargs (no readability benefit from positional).
- utils/trackers/__init__.py: four docstring rewordings ("Build"
-> "Initialize", "fans out writes" -> "writes to multiple
backends") rolled back.
- redis_program_storage.py: __all__ inline-to-three-line reformat
rolled back.
- locking.py: re-ordered attribute initialisations restored.
Dispatch-shape audit across the 5 dataplane-dispatch sites
confirmed uniform; no helper introduced.
Tests + run.py + configs
No edits needed. run.py / pyproject.toml / config YAMLs already
pass the vacuous-diff test post first cleanup pass — every
surviving comment describes present behaviour or load-bearing
WHY. TID251 per-file-ignores all verified live against current
import sites (zero stale entries).
Drop the dedicated _phase2_substrate.md and fold its enumeration of deliberately-kept-but-unused names directly into the package docstring at gigaevo/dataplane/__init__.py: lwwr_set / lwwr_get + Lua, HlcTimestamp, the lattice classes, and the 13 NewType identifiers covering events, streams, consumer groups, and causation tracking. The keep-list now lives where Python tooling and IDE introspection already surface it, removing the indirection through an out-of-band markdown file.
3. Каталог классов дефектов старой реализацииВ рамках настоящего PR идентифицировано четырнадцать структурных классов дефектов, каждый из которых наблюдался в исторической реализации в нескольких независимых точках. Ниже приводится перечень с указанием конкретных представителей и статуса закрытия в текущем PR. Класс №1: потеря обновлений при read-modify-writeИсторическое проявление. Несколько точек кода применяли паттерн «прочитать JSON-блоб из Redis, изменить в Python, записать обратно через Конкретные точки кода в версии до рефактора.
Закрытие в текущем PR. Каждая точка кода смигрирована на атомарную операцию в составе Класс №2: безграничные циклы повторов WATCH/MULTI/EXECИсторическое проявление. Цикл Конкретные точки кода. Закрытие. WATCH-циклы заменены атомарным Lua-скриптом, выполняющимся в одном раунде. Где Lua полностью покрывает семантику (одиночный CAS, инкремент эпохи, обновление обратного индекса), цикл повторов как таковой исключён. Где Lua покрывает основной путь, но резервный путь остаётся на WATCH ( Класс №3: продвижение курсора до захватаИсторическое проявление. Конкретные точки кода. Закрытие. Полное закрытие данного класса отнесено к фазе 2 общего плана программы изоляции. Транспорт шины сообщений сохранён в неизменённом виде на текущем этапе; миграция на Класс №4: отсутствие XACK и групп потребителейИсторическое проявление. Закрытие. Аналогично классу №3, отнесено к фазе 2. Субстрат Класс №5: дрейф схемы при перекатывающем обновленииИсторическое проявление. Pydantic-модели Закрытие. Полное закрытие отнесено к фазе 2 общего плана программы изоляции. Подпространство Класс №6: десериализация pickle как примитив RCEИсторическое проявление. Конкретные точки кода. Закрытие. Фаза 1: введена блокировка Класс №7: синхронный Redis внутри асинхронного путиИсторическое проявление. Закрытие. Словарь подсказок полностью смигрирован на асинхронный путь через Смежная работа. Класс родственных дефектов асинхронного сетевого слоя на длительных прогонах закрывается PR #17 «fix(infra): replace httpx with aiohttp + requests for long-running asyncio stability»: прямое использование Класс №8: модульные синглтоны, выживающие через
|
| Аудит | Зона | Находки | Серьёзность |
|---|---|---|---|
| a0903099 | DAG, state-машина, программы, core_types | 34 | 5 HIGH, 8 MEDIUM, 21 LOW |
| afcbae21 | Эволюция, селекторы, шина, архив | 15 | 6 HIGH, 5 MEDIUM, 4 LOW |
| a5961edd | Сериализация, кодеки | 13 | 5 HIGH, 4 MEDIUM, 4 LOW |
| ab074233 | Подсказки, коэволюция | 15 | 3 HIGH, 7 MEDIUM, 5 LOW |
| a032c213 | Трекеры, коллекторы | 20 | 9 HIGH, 6 MEDIUM, 5 LOW |
| a4956107 | Ideas-tracker, A-MEM | 14 | 6 HIGH, 5 MEDIUM, 3 LOW |
| a9f7bc32 | Hydra-конфигурационный слой | 3 + 1 бонус | 2 HIGH, 1 MEDIUM, 1 MEDIUM |
4.2. Полностью устранённые дефекты
Настоящий PR полностью устраняет следующие конкретные находки.
P1 (ab074233): gigaevo/prompts/fetcher.py:547-584. Гонка потери обновления на ключе prompt_stats:{prompt_id} через паттерн get → mutate → set. Устранение: переход на атомарные HINCRBY/HINCRBYFLOAT/LPUSH+LTRIM через DataPlane.crdt_inc, DataPlane.bounded_list_push, DataPlane.set_add. Коммит: ee87556, a0e9f8f.
P2 (ab074233): gigaevo/prompts/fetcher.py:470-494. Перекрёстное влияние между конкурентными мутациями через атрибут _current_pack. Устранение: перенос _current_pack из атрибута экземпляра в модульный ContextVar. Каждая конкурентная задача asyncio получает изолированную копию. Коммит: ee87556.
F9 (afcbae21): gigaevo/evolution/storage/archive_storage.py:184-219. Быстрый путь сравнения с кешированным current_prog приводил к отвержению объективно лучшего кандидата при устаревшем кеше. Устранение: кеш _elite_cache удалён полностью; обмен ячейки производится через атомарный archive_swap.lua с серверным сравнением скоров и tiebreak-битом. Коммит: 8a8f247, 983ed35.
Finding 2 (a9f7bc32): gigaevo/config/resolvers.py:43,47,48. Регистрация резолвера eval равной builtins.eval предоставляла полноценную поверхность произвольного исполнения кода через любую интерполяцию ${eval:...} в YAML или CLI-override. Резолверы merge и len имели нулевое количество точек вызова. Устранение: все три регистрации удалены. Любой ${eval:...} теперь возвращает ошибку OmegaConf на этапе резолюции. Коммит: b25c307.
Singleton #3 (a032c213): gigaevo/utils/trackers/__init__.py:8-10,17-21,34-38,56-62. Три модульных синглтона _tb_default, _wb_default, _redis_default выживали через Hydra instantiate, что приводило к возврату клиента, сконфигурированного для первого запуска эксперимента, во второй запуск в том же процессе. Устранение: глобальные переменные удалены; фабрики конструируют свежий экземпляр на каждый вызов. Регрессионный тест test_init_*_returns_fresh_per_call фиксирует инвариант. Коммит: b25c307.
4.3. Частично устранённые дефекты
Следующие находки частично закрываются текущим PR. Полное закрытие требует миграции конкретного потребителя на субстрат, который данный PR предоставляет.
#8 (a0903099): gigaevo/programs/core_types.py:24-26. content_hash использует cloudpickle.dumps(self.model_dump()) с усечением до 64 бит. Субстрат compute_content_hash_hex существует в gigaevo/dataplane/codec.py и применяет канонический JSON с сортировкой ключей + sha256 + 16 шестнадцатеричных символов. Миграция потребителя в core_types.py:24 отнесена к фазе 3 общего плана программы изоляции.
#9 (a0903099): gigaevo/programs/core_types.py:164-178. Pickle-десериализация произвольных строк. Линтер TID251 блокирует появление новых точек вызова; существующая точка кода помечена явно. Полное удаление отнесено к фазе 3.
#18 (a0903099): gigaevo/database/state_manager.py:127-128. Гонка вытеснения словаря блокировок. При маршрутизации переходов через dp.transition_program_state атомарность гарантируется серверной Lua, и внутрипроцессная блокировка более не несёт критическую нагрузку. При unwired-режиме (dataplane=None) гонка сохраняется как обратносовместимое поведение.
#20 (a0903099): gigaevo/database/state_manager.py:108-117. Дрейф состояния в памяти при сбое записи. Закрывается при wired-режиме через атомарную мутацию блоба и множеств статусов в одном EVALSHA. Зеркало в памяти через set_in_memory_state валидирует пару (current, target) против FSM-таблицы перед мутацией.
#25 (a0903099): gigaevo/programs/program.py:139-142. Потеря обновления atomic_counter при конкурентном merge. Закрывается для FSM-переходов через transition_state.lua (атомарный INCR). Для остальных путей записи (write_exclusive, update) рекомендуется завершить миграцию в фазе 2.
#34 (a0903099): gigaevo/programs/program_state.py:28-42. Переход DONE → QUEUED совместно с эвикцией lock создавал условия гонки между задачами-утечками от предыдущего запуска и текущим запуском. Закрывается через атомарную Lua-валидацию при wired-режиме.
F8 (afcbae21): archive_storage.py:222-226. Утечка обратного индекса при повторной попытке после WatchError. Закрывается для пути SumArchiveSelector через атомарный dp.try_replace_elite. Для ParetoFrontSelector сохраняется WATCH-цикл с верхней границей в 50 попыток и явным Err(DataPlaneError) по её исчерпании.
F13 (afcbae21): gigaevo/database/merge_strategies.py:25. atomic_counter инкрементируется на каждую запись, не на каждую логическую правку. Закрывается для FSM-переходов через атомарный Lua-инкремент; для прочих путей слияние через merge_programs сохраняет историческое поведение.
#2 (a5961edd): gigaevo/programs/utils.py:14. pickle_b64_deserialize без валидации длины и формата. Закрывается только в части блокирования появления новых точек вызова; существующая точка кода помечена на удаление в фазе 3.
#11 (a5961edd): gigaevo/programs/core_types.py:26. StageIO.content_hash использует cloudpickle.dumps(model_dump()). Аналогично #8: субстрат существует, потребитель ожидает миграции.
P8 (ab074233): gigaevo/prompts/coevolution/stats.py:96-101. Отсутствие тайм-аута сокета на асинхронном Redis-клиенте. Закрывается при маршрутизации через DataPlane.RedisConnection, который устанавливает socket_timeout_s=30.0 по умолчанию. Унаследованный путь ленивой инициализации в обратносовместимом режиме сохраняет историческое поведение.
P15 (ab074233): gigaevo/prompts/coevolution/stats.py:92-102. Асинхронные aioredis.Redis клиенты никогда не закрывались. Закрывается при wired-режиме через жизненный цикл DataPlane. Унаследованный путь сохраняет утечку.
#1 (a032c213): run.py:92. Порядок завершения: писатель → движок → исполнитель. Исправление M3/M4 в коммите 9aff248 ввело внутренний try/finally, гарантирующий выполнение engine.stop() и runner.stop() даже при исключении между запуском и serve_until_signal. Идемпотентность stop() проверена.
#13 (a032c213): composite.py:54-59. CompositeLogger.close() не учитывал совместное использование синглтонов. Закрывается через удаление синглтонов в фазе 0.
Finding 1 (a9f7bc32): gigaevo/config/resolvers.py:23-25. _ref_resolver мутирует исходный конфиг через присваивание parent[base] = instantiated_node. Закрывается частично через унификацию всех плейн-интерполяций ${X} в ${ref:X} в 14 YAML-файлах (закрывает следствие двойной инстанциации ProblemContext). Сам механизм мутации сохраняется до фазы 3 (полное удаление резолвера).
Finding 3 (a9f7bc32): regex резолвера ${ref:obj::attr}. Крайние случаи в полу-мутированном дереве при ошибке доступа к атрибуту закрываются по той же логике: триггерные пути устранены унификацией, сам механизм сохраняется до фазы 3.
4.4. Сводная оценка элиминации
| Категория | Количество |
|---|---|
| Полностью устранены | 5 |
Частично закрыты (закрытие зависит от wired-режима или ограничено блокированием новых точек вызова) |
14 |
| Не закрыты в данном PR (отнесены к фазам 2, 3 или к зонам вне субстрата DataPlane) | около 95 |
Стоит подчеркнуть, что значительная часть «незакрытых» дефектов относится к зонам, не входящим в круг ответственности DataPlane: внутренний параллелизм DAG, селекторы эволюции, бэкенды трекеров tensorboard/wandb, A-MEM ideas-tracker, транспорт шины сообщений (фаза 2). Для каждой зоны, входящей в круг DataPlane, проведено полное закрытие класса либо частичное закрытие с явным указанием отложенной части в каталоге keep-list раздела 15.
5. Архитектура решения5.1. Структура пакетаПодпространство 5.2. Публичная поверхность
|
12. Дисциплина линтера12.1.
|
| Требование RFC #18 | Компонент настоящего PR |
|---|---|
| SETNX TTL захват задачи (раздел 3.4, примитив «Claim») | scripts/instance_lock_acquire.lua + dp.acquire_instance_lock с типизированным LockHeld и Token[InstanceLockTag] |
| Продлеваемый пульс 3:1 (раздел 3.4, примитив «Heartbeat») | scripts/instance_lock_renew.lua + dp.renew_instance_lock + wrap_lease + CrashWatchedHandle с OneShotFlag для типизированной потери аренды |
| Освобождение по выполнению или по потере (раздел 3.4, примитив «Release») | scripts/instance_lock_release.lua + dp.release_instance_lock с идемпотентным контрактом и валидацией владения |
| Redis Streams XADD / XREADGROUP / XACK (раздел 3.4, примитив «Stream») | Словарь NewType StreamName, ConsumerGroup, ConsumerName, EventId, CausationId, CorrelationId плюс подпространство streams/ под Lua-скрипты |
Идемпотентная запись через Idempotency-Key (раздел 3.4, примитив «Idempotency») |
NewType IdempotencyToken + поле idempotency_token в transition_state.lua + compute_content_hash_hex для серверной дедупликации |
| Атомарный счётчик под распределённые метрики (раздел 2.3 RFC «уже распределённое») | scripts/counter_inc.lua + dp.crdt_inc + ActorIdentity(run_id, worker_id) + make_actor для актор-партиционирования |
| Ограниченная очередь под метрики скользящего окна | scripts/bounded_list_push.lua + dp.bounded_list_push + dp.bounded_list_range |
| LWW-регистр конфигов координационной плоскости | scripts/lwwr_set.lua + dp.lwwr_set / dp.lwwr_get с разрешением равенства HLC через HlcTimestamp |
| HLC-упорядочивание событий между несколькими драйверами (раздел 3.4) | HlcTimestamp с лексикографически сравнимой hex-упаковкой и монотонностью внутри одного процесса |
| Типизированная обработка отказов координационной плоскости (раздел 3.5) | Result[T, E] + полная иерархия DataPlaneError + CrashEvent + OneShotFlag + wrap_lease |
Контракт обработчика WorkerConfig (раздел 3.3) |
NewType NodeId плюс готовая семантика ActorIdentity под пары (run_id, worker_id) |
| Теговая маршрутизация задач (раздел 3.3, многоверсионные подписки) | Разметка ключей по подпространствам через KeyBuilder под структуру tasks:{tag} и worker:{tag}:{node}:{idx} |
| Контроль изоляции прикладных вертикалей (раздел 3.3) | Линейные токены Token[Tag] + EngineRoot с раздельными подтокенами program / cell / counter через mint_split_n |
| Версионирование схемы под перекатывающее обновление парка обработчиков (раздел 3.5) | Подпространство gigaevo/dataplane/upcasters/ (заглушка под фазу 2) + поле schema_version в дискриминированных моделях dp/models.py |
16.4. Снимаемые ограничения
RFC #18, раздел 2.2, перечисляет четыре кодифицированных ограничения, препятствующих переходу к распределённой топологии: запрет двух драйверов на одном префиксе через RedisInstanceLock; неявная привязка CardStore к локальной файловой системе; жёсткая зависимость от loky-пула; отсутствие словаря идентификаторов обработчика.
Настоящий PR снимает первое ограничение в архитектурном смысле: блокировка экземпляра смигрирована через LuaRegistry.evalsha, дисциплина владения формализована через Token[InstanceLockTag], потеря аренды типизирована через CrashEvent. Расширение блокировки на лидерскую аренду (lease:gam-rebuilder, lease:scheduler) или на эксклюзивный захват GPU (gpu-lock:{node}:{idx}) производится добавлением фабричной функции поверх существующих Lua-скриптов без модификации субстрата. Второе ограничение остаётся за пределами настоящего PR (ArtifactStore относится к плоскости данных, координационная плоскость к ней не примыкает); третье и четвёртое ограничения адресуются параллельным PR #14 и опираются на словарь NewType настоящего PR для типизации параметров обработчика.
16.5. Уже распределённые подсистемы
RFC #18, раздел 2.3, фиксирует подсистемы, уже работающие распределённо через Redis: блоб состояния для программ, архив под обмен ячейками, реестр многоруких бандитов, программный счётчик. Каждая из четырёх подсистем смигрирована на DataPlane в рамках настоящего PR:
- Блоб состояния для программ: переход через
dp.transition_program_stateс серверной валидацией FSM-таблицыPROGRAM_STATE_TRANSITIONSвtransition_state.lua. Распределённая корректность достигается атомарностью EVALSHA: одновременныеtransition_program_stateот двух драйверов либо упорядочиваются Redis, либо одна из попыток получает типизированныйTransitionError.illegalбез побочного эффекта. - Архив под обмен ячейками: переход через
dp.archive_cell_swapс серверной арбитрацией ровно одного победителя на ячейку черезarchive_swap.lua. Разрешение равенства лексикографическим сравнением идентификатора кандидата исключает класс «later writer wins» при равных значениях score. - Реестр многоруких бандитов: запись через
dp.crdt_incс актор-партиционированием.ActorIdentity(run_id, worker_id)обеспечивает каноническую упорядоченность операций приращения без потерь обновления при междрайверной агрегации. - Программный счётчик: атомарный INCR внутри
transition_state.luaвстроен в ту же транзакцию, что и переход FSM. Расхождение между статусом программы и инкрементом счётчика становится непредставимым.
Распределённая корректность всех четырёх подсистем зафиксирована тестами gigaevo/dataplane/tests/ под стратегии Hypothesis, моделирующие параллельные команды от двух и более драйверов.
16.6. Режим --embedded
RFC #18, раздел 3.2, определяет режим --embedded, в котором драйвер и обработчик исполняются внутри одного процесса при сохранении полной типизации обмена через Redis Streams. Данный режим опирается на свойство координационной плоскости: семантика обмена обязана быть инвариантной относительно физического размещения участников. Настоящий PR поставляет требуемую инвариантность через Result[T, E] (одна и та же типизация ответа независимо от транспорта), Token[Tag] (одна и та же дисциплина владения), Versioned[T] (одно и то же контракт-обещание свежести). Переключение между распределённой и встроенной топологиями выполняется фабрикой обработчика без модификации прикладного кода.
16.7. Сводная оценка
Концептуально настоящий PR закрывает структурный пробел между текущей реализацией (раздел 1 RFC «Status quo») и целевой реализацией (раздел 3 RFC). Этапы 3, 4 и 5 RFC опираются на следующие свойства, гарантируемые настоящим PR:
- Каждая операция координационной плоскости возвращает
Result[T, E]с исчерпывающим набором ошибочных вариантов. - Каждая аренда (захват, продление, освобождение) типизирована через линейный токен с дисциплиной однократного потребления.
- Каждое чтение, требующее свежести, обернуто в
Versioned[T]с явным контрактомFreshness. - Каждая операция приращения метрики проходит через атомарный счётчик с актор-партиционированием.
- Каждая запись в каноническом виде имеет вычисляемый хеш содержимого, обеспечивающий серверную дедупликацию.
Совокупность перечисленных свойств обеспечивает, что фаза 3 RFC (RedisStreamWorkerBackend) реализуется как прямое сложение типизированного событийного потока поверх уже существующего субстрата без переизобретения дисциплины координации.
6b94be1 to
bbae675
Compare
|
Heads-up on test-side integration with #10 ( vs #10: the
vs #14: Verified locally by integrating all three PRs — full suite (5862 tests) passes after the test-side adjustments. Parallel notes on #10 and #14. |
DataPlane
1. Аннотация
Настоящий пакет изменений вводит в репозиторий
gigaevo-coreподсистемуgigaevo.dataplane: единственный типизированный асинхронный координатор для всех взаимодействий с Redis. Координатором закрываются десять из четырнадцати структурных классов дефектов, идентифицированных и адресуемых настоящим PR, а сам подход кодифицирует архитектурную дисциплину, при которой бо́льшая часть исторически наблюдавшихся ошибочных состояний становится непредставимой по построению (unrepresentable by construction).В рамках PR реализуется:
Полноценный субстрат координатора: пул соединений с обязательным
decode_responses=True, реестр Lua-скриптовLuaRegistryс однократнымNOSCRIPT-восстановлением, каталог из восьми атомарных Lua-скриптов, типизированная иерархия исключений, словарь идентификаторов наNewType, перечислительные FSM-таблицы, типизированный возвращаемый типResult[T, E], словарь свежестиFreshness, фантомные типы провенансаSourced[T, S], линейные токеныToken[Tag]с подсистемой выпуска, обёртки потерянного владенияwrap_lease/CrashWatchedHandle, гигиенический индикаторOneShotFlag, носители времениHlcTimestamp.Полные миграции пяти прикладных вертикалей: хранилище программ, координатор переходов состояний, архив элит, очередь миграционной шины (частично; полное закрытие отложено в фазу 2 настоящей программы изоляции), словарь подсказок (
prompts/fetcher,prompts/coevolution/{stats,sync}), реестр многоруких бандитов (llm/bandit), распределённая блокировкаRedisInstanceLock.Упрочнение пути, проходящего через Hydra и OmegaConf (фаза 0): удалены резолверы произвольного исполнения кода, устранены долгоживущие модульные синглтоны, исправлен дрейф
lru_cacheповерх объектов с привязкой к событийному циклу, формализованы вспомогательные функцииwire_*для каждой точки повторного связывания при сборке объектов.Дисциплинарные ограждения: блокировка прямых импортов
redis/redis.asyncio/redis.exceptionsвне субстрата DataPlane черезruff TID251с явным белым списком унаследованных файлов; блокировка прямых вызововpickle.loads/cloudpickle.loadsчерез тот же механизм с белым списком точек кода подпроцессного IPC; AST-инспектор, отвергающий неаннотированные широкиеexceptвнутри субстрата.Дополнительные слои тестирования: исчерпывающее покрытие FSM (полное произведение
ProgramState × ProgramState), фаззинг конкурентных писателей (одновременная гонка наcrdt_inc,transition_program_state,try_replace_elite,acquire_instance_lock), property-based проверки идемпотентности черезcontent_hash, регрессионные тесты для каждого исправленного класса дефектов.PR подготавливает архитектуру для последующей фазы 2 (событийный поток с группами потребителей, валидация версии схемы, межпроцессная репликация состояний). Субстрат фазы 2 размещён в коде и каталогизирован в разделе 15 настоящего документа.
Концептуальным потребителем заложенной архитектурной базы выступает RFC «Распределённое исполнение в gigaevo-core» (issue #18). RFC описывает переход от модели «один драйвер, один хост» к парку независимых обработчиков, координируемых через Redis по принципу «единственная плоскость управления». Этапы 3, 4 и 5 RFC опираются на примитивы координации, типизированные исключения, идемпотентность и агрегацию с актор-партиционированием, поставляемые настоящим PR. Полная таблица соответствий «требование RFC ↔ компонент DataPlane PR» приводится в разделе 16 настоящего документа.
2. Контекст и мотивация
2.1. Историческая ситуация в репозитории
До настоящего PR взаимодействие с Redis в
gigaevo-coreраспределялось по более чем двадцати разнородным точкам входа: ручные конвейерыWATCH/MULTI/EXEC, прямые вызовыINCR,HSET,HGETALL,XADD,XREAD,SETNX, выполнение Lua-скриптов через прямойEVAL, плюс набор приватных синхронных клиентов внутри асинхронных подсистем. Каждая точка вызова повторяла локальную копию инвариантов: какие именно поля инкрементируются, какой именно набор ключей участвует вWATCH, в каком порядке производится проверка инвариантов, какой именно тип возвращаемого значения интерпретируется как ошибка.Подобная фрагментация порождала структурно повторяющиеся классы дефектов. Каждое исправление носило локальный характер и не предотвращало воспроизведение того же дефекта в соседнем модуле спустя месяц.
2.2. Цель PR
Целью настоящего PR является ввод единственной точки концентрации семантики координации, через которую обязаны проходить все операции записи, чтение которых требует свежести, и все операции с условной атомарностью. Через типизацию
Result[T, E], линейные токеныToken[Tag], дискриминированные FSM-таблицы и фантомные типы провенанса достигается состояние, при котором значительная часть исторически встречавшихся ошибочных состояний оказывается непредставимой на уровне типов.Помимо лечения существующих дефектов, ставится задача создания дисциплинарного субстрата для последующих фаз: событийный поток (фаза 2), источник событий с переключением источника правды (фаза 3), распределённое слияние через HLC + VectorClock (фаза 4 общего плана программы изоляции). Дополнительно архитектурная база поставляется в распоряжение распределённой топологии, описанной в RFC #18 «Распределённое исполнение в gigaevo-core»: целевая нагрузка (оптимизация CUDA-ядер через эволюционный поиск в смежном проекте
kernel-evoс эксклюзивным GPU-захватом и гетерогенным парком обработчиков) предъявляет требования, удовлетворяемые ровно теми примитивами, которые вводятся настоящим PR.2.3. Принципы
Реализация подчиняется следующим архитектурным принципам.
Принцип 1: монопольный субстрат. Все обращения к Redis обязаны проходить через
gigaevo.dataplane.DataPlane. Ограничение поддерживается линтеромruff TID251с исчерпывающим белым списком унаследованных файлов; каждая запись в белом списке сопровождается коммитом, удаляющим её по мере миграции соответствующего файла.Принцип 2: атомарность на стороне сервера. Любая операция записи, требующая условной семантики (read-modify-write, expected-from CAS, content-hash дедупликация), выполняется в единственном
EVALSHAчерез зарегистрированный заранее Lua-скрипт. Повторные попытки на стороне приложения исключаются как класс, повторные попытки WATCH-цикла исключаются как класс.Принцип 3: типизация по построению. Каждая ID-сущность оформляется через
NewType, каждое лицензионное состояние гарантируется через линейный токенToken[Tag], каждое чтение, требующее свежести, возвращается под фантомным конструктором провенанса (LocalValue[T],CachedValue[T], и т.п.), каждый возврат предоставляется в дискриминированной формеResult[T, E]. Средство проверки типовmypyв строгом режиме обнаруживает рассогласования до момента выполнения.Принцип 4: один контур для каждого ресурса. Каждый подпространственный ресурс (программа в FSM, ячейка архива, счётчик CRDT, ключ блокировки) обладает собственным корневым токеном, выпускаемым через
mint_rootна этапе запуска движка. Дочерние токены доставляются вниз по графу вызовов посредствомEngineRoot.split_*. Двух одновременных писателей одного подпространства невозможно сконструировать без явного видимого нарушения дисциплины линейного потока.Принцип 5: упорядоченная разрядность жизненного цикла. Запуск и останов координатора идемпотентны, защищены ленивой инициализацией
asyncio.Lock, обрабатываютasyncio.CancelledErrorотдельно отException, разделяют чистку пула и финальное освобождение токенов черезContextVar.await dp.shutdown()корректен даже после частичного сбояawait dp.startup().Принцип 6: типизированная обработка сбоев. Внутренние исключения никогда не пересекают границу координатора. Любой результат возвращается через
Result[T, E]; обработка сбоев производится черезmatch. Внутри субстрата широкиеexcept Exceptionдопускаются исключительно на границе конвертации вResult, и каждое такое место помечается через# noqa: BLE001под контролем AST-инспектораtest_no_silent_broad_except.2.4. Гранулярность изменений и стратегия поставки
Изменения поставляются в виде последовательности приблизительно пятидесяти коммитов, каждый из которых проходит полный прогон тестов (~4 789 тестов). Сборка PR проводилась поэтапно:
feat(dataplane): typed coordinator package foundation) с публичной поверхностью без тел методов.wrap_leaseдля типизированного контроля потерянной блокировки.EngineRootи потокового разделения токенов в хранилище.Freshnessнаcrdt_read, добавление обёрткиLocalValueповерхread_program.run.py: точка входа послеinstantiateприсоединяет координатор к хранилищу, бандиту, словарю подсказок,DagRunnerи движку эволюции.eval, дезактивация модульных синглтонов трекеров, устранениеlru_cacheна пуле подпроцессного исполнения, формализация обёрткиstrict_chat_openaiповерхChatOpenAIс явным контролем неизвестных kwargs, унификация${ref:...}против${X}в 14 YAML-файлах, добавление явного_self_в 18defaults-блоках.except, регрессии для каждого исправленного класса.17. Метрики PR
17.1. Количественные
17.2. Качественные
17.3. Покрытие
gigaevo/dataplane/**/*.pyRedisProgramStorage.transition_program_stateRedisProgramStorage.batch_transition_*RedisArchiveStorage.add_eliteRedisInstanceLockGigaEvoArchivePromptFetcherBanditModelRouterMainRunSyncHook18. Связанные параллельные PR
Настоящий PR координируется с серией параллельных открытых pull request, каждый из которых закрывает локализованный класс проблем в смежной зоне. Перечень с координатами и областью применимости:
gigaevo/llm/bandit.pycompute_bandit_reward; те жеisfinite()-проверки введены императивно вcounter_inc.luaиarchive_swap.luaevolution/engine/acceptor.pyarchive_swap.luaevolution/mutation/mutation_operator.py_with_langfusemust not mutate caller's configllm/models.pyutils/text_sanitize.pyи тринадцать точек интеграцииfetcher.py,stats.py,bandit.pyинтеграционным коммитомpyproject.toml,pytest.inixdist_groupдоступен под Redis-fixture-семействаllm/bandit.pyllm/call_outcome.pytry/exceptladder в bandit и роутере единым словарём 12 вариантовLLMCallOutcome; импортыBanditActionиclassify_call_resultвведены вbandit.pyинтеграционным коммитомprograms/stages/python_executors/wrapper.pylru_cache) более радикальным средством; TOCTOU-symlink-swap (RCE-вектор), фильтрация секретов окружения (AWS_*,OPENAI_API_KEYи др.), bounded spill черезmmap. Настоящий PR принимает имплементацию из #14 целиком в интеграционном коммитеoptimization,validation,dag_runner__cause__, структурные атрибутыSyntaxError,logger.exception(...)pipeline_builder,StageRegistryValueError)infra/aiohttp_factory.py,infra/requests_factory.py,infra/_net.pyhttpcore. ДисциплинаRedisConnectionнастоящего PR родственна по формеasyncio.get_event_loop+ skip-guardtest_manifesttest_manifest.pymain; смежно с разделом 13.7 настоящего PRПосле интеграционного коммита (см. историю ветки) настоящий PR содержит зеркало всего набора изменений nightly и автоматически объединяем с любой из перечисленных параллельных веток.
19. Заключение
Настоящий пакет изменений завершает первую фазу долгосрочной программы изоляции взаимодействий с Redis в единственный типизированный субстрат. Десять из четырнадцати структурных классов дефектов, идентифицированных и адресуемых настоящим PR, закрываются либо существенно нейтрализуются. Три класса (события, группы потребителей, версионность схемы) отнесены к фазе 2 с подготовленным субстратом. Один класс (десериализация pickle как RCE) частично закрывается через линтер с явным указанием оставшейся миграционной цели.
Дисциплинарные ограждения (линтер
TID251, AST-инспектор широкихexcept, валидаторы враждебных входов, экспериментная двухпроходная зачистка набора изменений) обеспечивают невозможность регрессии: любая новая попытка ввода прямогоredis-импорта вне DataPlane или новой точки вызоваpickle.loadsобнаруживается на этапе CI; любой новый широкийexceptв субстрате без явного маркера приводит к падению AST-инспектора.Полная типобезопасность через
Result[T, E]+Token[Tag]+Versioned[T]+Freshness+Sourced[T, S]+CrashEventобеспечивает состояние, при котором значительная часть исторически наблюдавшихся ошибочных состояний оказывается непредставимой по построению. mypy в строгом режиме обнаруживает рассогласования до момента запуска.Архитектурная база подготовлена для последующих фаз: подпространство под
upcasters/, словарь NewType для событий и потоков, решёточный каталог для распределённых путей допуска, фантомный конструктор провенанса для разделения кешированных, повторных и локальных чтений. Будущие миграции (RedisStreamTransportна группы потребителей, версия схемы на каждой Pydantic-модели, источник событий с переключением источника правды) опираются на субстрат, уже размещённый в коде и каталогизированный в разделе 15 настоящего документа. Концептуальным целевым потребителем выступает RFC «Распределённое исполнение в gigaevo-core» (issue #18): этапы 3, 4 и 5 RFC реализуются как прямое сложение типизированного событийного потока поверх настоящего субстрата без переизобретения дисциплины координационной плоскости, как подробно изложено в разделе 16.Совокупно представленное вмешательство кодифицирует дисциплину «ill-formed state is unrepresentable by construction» применительно к Redis-взаимодействиям. Эта дисциплина переносима на последующие подсистемы (транспорт шины сообщений, источник событий, распределённое слияние) с минимальной добавочной стоимостью, поскольку базовый словарь типов, линтерных правил и тестовых паттернов уже введён в репозиторий и принят к исполнению.
Полный проектный документ (классы дефектов, аудит, архитектура, миграции, тестирование, связь с RFC #18) поставляется в виде серии комментариев ниже.