ADR-014 mandates contract-test suites for two traits that have no signature anywhere in the corpus
Источник:
dev/fera-research/docs/oc1/016-two-mandated-contract-tests-have-no-trait.md(read-only; сгенерировано scripts/sync-docs.mjs)
ADR-014 mandates contract-test suites for two traits that have no signature anywhere in the corpus
Section titled “ADR-014 mandates contract-test suites for two traits that have no signature anywhere in the corpus”- kind: problem
- area: modules / storage / ui
- evidence:
ADR.md:566-569(ADR-014) — “Contract tests: one test suite per trait (StorageEngine,SearchEngine,MediaDecoder, rendererEngine, etc.), run against every implementation (first-party swaps and, where feasible, third-party ones) to guarantee trait-semantic compliance independent of hot-swapping.”QA.md:64—StorageEnginecontract suite vs sanakirja. (defined)QA.md:83-84—MediaDecodercontract suite. (defined)TECHSPEC.md:435-437— the same four: “StorageEngine,SearchEngine,MediaDecoder,Engine”.- Only two of the four are ever defined:
StorageEngine—TECHSPEC.md:136-143(4 methods).MediaDecoder—TECHSPEC.md:178-181(1 method).SearchEngine— no trait definition anywhere.TECHSPEC.md:23and:145-147name the crate (“SearchEngine trait + milli-fork impl”) and the engine, but no signature exists. Verified: grep fortrait SearchEngineacross the whole corpus → 0 hits.- Renderer
Engine— no trait definition anywhere. Not in TECHSPEC §6 (which describes axes in prose), not in ADR-009/028/030. Verified:grep -rn "trait Engine"→ 0 hits.
- The Engine axis is the single most load-bearing boundary in the
project:
ADR.md:24-27makes the UI renderer hot-swappable;ADR.md:239-243defines it as an axis;ADR.md:971-972pins the substrate (Vello + Parley + winit + AccessKit) “with the project’s own HoF layer on top (ADR-008)”;ADR.md:1013-1016decides single-pass hit-testing “in the same frame pass” with interaction state “returning inline in the HoF view code”;QA.md:105-107requires proving an Engine swap does not touchui-elm/domain. - What “swap the Engine” means is undefined. The substrate owns the
window, the wgpu device, the present path, the text layout (Parley),
the hit-test tree and (via the Native axis slots) partly the input
stack (
ADR.md:974-979). A swappable Engine that owns a winitEventLoopand a window is not swappable at runtime — swapping it means tearing down the window. - The Style axis has no trait either, because it is now data
(
ADR.md:272-273), which meansQA.md:102-104’s “pixel-diff scoped per Style module” is scoped to something that does not exist as a module (ADR.md:294-295corrects it to “per-Style-file-set” — RFC and QA were not updated; see my finding 014). StorageEngineas specified is also too small for its own ADR:TECHSPEC.md:136-143has nodelete,compact,transaction,migrate,put_read_state,get_predictor_events, or account-scoping — yet ADR-010’s outbox, ADR-011’sReadState, ADR-019’s predictor log, ADR-021’s “on-disk data format versions independently” and ADR-025’s TTL deletion all require them (my findings 007, 016).
- severity: mid
Суть / Substance
Section titled “Суть / Substance”ADR-014 makes contract tests “required infrastructure before any second
implementation of a given trait is accepted” (ADR.md:579-581), and
the project’s whole modularity story rests on those suites being the safety
net. But two of the four named traits — including the Engine, the most
consequential boundary — have never been written down. A contract test
suite cannot be written for a trait that does not exist, so:
- ADR-014’s central promise is currently unenforceable for the Engine
axis, i.e. exactly where “swappable” is most valuable (the author chose
Vello partly because alternatives may later appear —
ADR.md:242-243,ADR.md:994-996). QA.md:105-107(“swapping the Engine module does not require touchingui-elmor domain code”) is untestable: there is no Engine interface to swap, and no statement of what crosses it.- The Engine boundary’s content is the unresolved question, not a
missing signature. ADR-030’s single-pass decision means the interface
must expose paint + hit-test + interaction state in one pass
(
ADR.md:1013-1016), which is a very different trait shape from the two-passview() -> RenderTreethat TECHSPEC §6 still describes (TECHSPEC.md:260-263, pointing at OQ.md — now resolved to single-pass). So the trait cannot be drafted until that is reconciled — which is a sequencing fact the corpus has not noticed.
The same class of gap applies to SearchEngine: the two-phase search
decision (ADR.md:369-372), the typestate plan
RegexSearchPlan::Naive | TwoPhase{…} (OQ.md:299-302) and the
per-account requirement (my finding 007) all constrain its shape, and none
of it is reflected in a signature.
Варианты решения (tradeoffs)
Section titled “Варианты решения (tradeoffs)”- (A) Draft the two missing traits now, as explicit interfaces in
TECHSPEC §3/§6 with the decisions that constrain them (single-pass
Engine returning interaction state;
SearchEnginereturning(AccountId, ChatId, MessageId)-namespaced hits and the naive/two-phase plan). Then the contract suites become writable. - (B) Defer the traits to the phase that implements the modules (step
4 per
TECHSPEC.md:461-462), and mark the ADR-014 gates as not-yet-applicable rather than silently unrunnable. Cheapest, keeps the gate text honest, but leaves the ABI-relevant design implicit — the risk named in my finding 015. - (C) Shrink the Engine axis claim — declare the Engine swappable only in the sense of “different pinned Vello versions / different engines at build time”, not at runtime, and drop the hot-swap requirement for it. Honest about what is achievable with a window-owning substrate, but gives up one of the four axes’ raison d’être.
Предлагаемое решение
Section titled “Предлагаемое решение”(A) + (B) in sequence: declare gates inapplicable until the traits exist, then draft them before any module is extracted.
- Immediate (cheap, prevents false confidence): mark
QA.md:64,:83,:105-107as blocked on trait definition, and add the trait definitions as explicit TECHSPEC items in §3 (SearchEngine) and §6 (Engine). Note in ADR-014 that two of its four suites have no subject yet. Enginetrait — decide its shape from ADR-030, not from TECHSPEC §6. Single-pass means roughly:begin_frame(input) -> frame graph,draw(...)with hit-testing and interaction state returned inline. The decisions that must be written down: who owns the winit window and the wgpu device (currently the Engine, perADR.md:971— which is what makes runtime swap impossible and needs to be stated or fixed); whether the trait is&mut self-per-frame or command-buffered; how interaction state escapes into the Elm loop (Msg), given ADR-008’s “local mechanics stay local” rule has no home since GPUI is gone (my finding 001); and whether Parley text shaping is inside the trait (it must be, or style metrics leak across the boundary).SearchEnginetrait — include: account-namespaced hits; theRegexSearchPlantypestate as the executor boundary (per OQ-011); index rebuild/versioning hooks, since the index format changes independently of everything else; and the tier boundary — whether the trait returns plaintext orEncrypted-style tagged terms is currently undetermined by ADR-031 (my finding 004), so the trait’s signature literally cannot be written until that decision lands.- Grow
StorageEngineto match its ADRs — add account scoping,ReadState/outbox accessors, predictor-event-log access, TTL deletion (ADR-025), and an explicit migration/version hook (ADR-021’s “on-disk data format versions independently of all three” needs an entry point; see my finding 016). - Long term, the cheap guard: a CI check that every trait named in
QA.md’s contract-gate list has a corresponding definition in TECHSPEC or the code, and a contract-suite crate per trait.