Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

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, renderer Engine, 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 — StorageEngine contract suite vs sanakirja. (defined)
    • QA.md:83-84 — MediaDecoder contract 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:23 and :145-147 name the crate (“SearchEngine trait + milli-fork impl”) and the engine, but no signature exists. Verified: grep for trait SearchEngine across 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-27 makes the UI renderer hot-swappable; ADR.md:239-243 defines it as an axis; ADR.md:971-972 pins the substrate (Vello + Parley + winit + AccessKit) “with the project’s own HoF layer on top (ADR-008)”; ADR.md:1013-1016 decides single-pass hit-testing “in the same frame pass” with interaction state “returning inline in the HoF view code”; QA.md:105-107 requires proving an Engine swap does not touch ui-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 winit EventLoop and 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 means QA.md:102-104’s “pixel-diff scoped per Style module” is scoped to something that does not exist as a module (ADR.md:294-295 corrects it to “per-Style-file-set” — RFC and QA were not updated; see my finding 014).
    • StorageEngine as specified is also too small for its own ADR: TECHSPEC.md:136-143 has no delete, compact, transaction, migrate, put_read_state, get_predictor_events, or account-scoping — yet ADR-010’s outbox, ADR-011’s ReadState, 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

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 touching ui-elm or 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-pass view() -> RenderTree that 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; SearchEngine returning (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.

(A) + (B) in sequence: declare gates inapplicable until the traits exist, then draft them before any module is extracted.

  1. Immediate (cheap, prevents false confidence): mark QA.md:64, :83, :105-107 as 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.
  2. Engine trait — 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, per ADR.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).
  3. SearchEngine trait — include: account-namespaced hits; the RegexSearchPlan typestate 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 or Encrypted-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.
  4. Grow StorageEngine to 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).
  5. 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.