The whole dylib module axis is unimplemented and undocumented-as-deferred: modules/ does not exist, and three ADRs say subsystems are swappable
Источник:
dev/fera-research/docs/oc1/015-module-axis-unimplemented-undocumented.md(read-only; сгенерировано scripts/sync-docs.mjs)
The whole dylib module axis is unimplemented and undocumented-as-deferred: modules/ does not exist, and three ADRs say subsystems are swappable
Section titled “The whole dylib module axis is unimplemented and undocumented-as-deferred: modules/ does not exist, and three ADRs say subsystems are swappable”-
kind: risk
-
area: modules
-
evidence — repo reality (
~/dev/fera/fera, git loged4d66e pre-migration snapshot, 10 commits, all 2026-10):-
core/crates/=domain, media, notification, predictor, protocol, search, storage, trust, ui-contract, ui-elm, ui-engine-vello— eleven ordinary in-process workspace crates (1–6.rsfiles each). -
No
modules/directory.ls fera/modules→ empty. -
No
crate-type = cdylibanywhere (grep -rn "crate-type" --include=Cargo.toml→ 0). -
No
nylondependency anywhere in the workspace Cargo.toml files. -
Commit
575d16esays “workspace skeleton per TECHSPEC §0” — but TECHSPEC §0’s second half (TECHSPEC.md:30-39) is entirely/modules/*nylon-ring cdylibs. The commit implemented the first half only. -
ui-engine-vello(the Engine axis) is a core crate, not a dylib. -
What the docs say should be there:
TECHSPEC.md:30-39—/modules/:storage-sanakirja,search-milli,renderer-gpui,style-macos|win|linux,feature-macos|win|linux,native-macos|linux-gtk|winui,media-decoder-baseline,media-decoder-ffmpeg,calls-tgcalls— all “nylon-ring cdylib”.ADR.md:24-27— “Any heavy subsystem (storage, search, protocol, media decode, UI renderer, UI style, UI platform-feature layer, predictor) can be replaced without recompiling or restarting the whole application”.ADR.md:47-51— “First-party / performance-critical modules (storage, search, protocol, media decode, UI engine/style/feature, predictor) are native dylibs loaded vianylon-ring”.ADR.md:772-780(ADR-020) — per-struct granularity,layer: shared | types | impl, topological load order, module count “grows”;OQ.md:627-631— “hundreds of small dylibs, not a handful of big ones”.ADR.md:425-446(ADR-024) — distribution, channels, N-1 rollback, developer mode.ADR.md:854-855— “The host maintains compatibility surfaces for edition N-1 (adapters) — an accepted per-edition maintenance cost.”
-
Three subsystems are actively being built as in-process crates, which contradicts three separate ADRs:
Subsystem Docs say Repo says predictor ADR.md:24-27,:47-51— swappable native moduleTECHSPEC.md:25/core/crates/predictor/;TECHSPEC.md:214“Crate:predictor(part of core)”; repo: core crateprotocol ADR.md:24-25,:47-49— swappable native moduleTECHSPEC.md:20/core/crates/protocol/; repo: core crate; and ADR-026 says it is our own fork — a C-ABI dylib for a Rust-native typestate API is a contradiction in ambitionUI engine ADR.md:239-243,TECHSPEC.md:266— Engine axis, nylon-ring dylibrepo: core/crates/ui-engine-vello, in-process -
Nothing in the corpus records the deferral. No ADR, no OQ, no TECHSPEC phase note says “modules come later”. TECHSPEC §10 step 4 (
TECHSPEC.md:461-462) does put the nylon-ring boundary at step 4 — “prove hot-swap works” — but §0 lists the modules as present from day one, and steps 1–3 depend on crates that per ADR-001/002 should be dylibs.
-
-
severity: high
Суть / Substance
Section titled “Суть / Substance”The corpus’s organising idea — ADR-001’s micro-kernel with hot-swappable subsystems — has no implementation and no written deferral. That is not merely a status note; it has four concrete consequences:
-
The trait contracts are being designed against a boundary that does not exist. ADR-005 (batch-shaped APIs), ADR-014 (contract tests per trait), ADR-020 (
#[repr(C)]by-value, per-struct seams), ADR-021 (three version axes), ADR-022 (translators) all describe the dylib boundary. The real crates so far are plain Rust traits — which is the right first move (you cannot design an ABI before you know the shape of the calls), but it means the ABI-relevant decisions are being made implicitly by ordinary Rust API design and then must be reverse-fit into a C ABI later. The specific danger: any design choice that is free in-process and expensive across FFI (passingString/Vec, returning owned collections, deep trait hierarchies, generics) will get baked in and then block the ADR-020 boundary. My finding 010 (ABI:#[repr(C)]POD vs pinned rustc) is exactly this conversation happening in prose while the code moves the other way. -
The
predictorandprotocolcases are decisions, not omissions. TECHSPEC §0/§5/§1 put them in core; ADR-001/002 list them as swappable. One has to give. Forprotocolthe choice is forced by ADR-026: our own fork with a typestate API (TECHSPEC.md:65-81) cannot sensibly be a C-ABI dylib — the typestate is a compile-time device and would be erased at the boundary. Recommend explicitly: protocol is core, not a module (and amend ADR-001/002’s list). Forpredictor, TECHSPEC §5’s placement is defensible (it needs storage access and is small), so the cheaper fix is to drop it from ADR-001/002’s list. -
Every QA module gate is currently untestable.
QA.md:123-159(version mismatch → fallback, hash mismatch → warning, hot-reload with zero dropped calls, signature rejection, CPU-feature selection, topological load order) presuppose a loader and a manifest. None can run, so the module tier of QA is decorative — and per my finding 014, two of those gates are also wrong. -
The
nylon-ringdependency itself is unvetted in the corpus. The design leans entirely on it (ADR.md:19-21: explicit version + struct-size validation, graceful unload with draining, host-leased zero-copy output buffers) and on forking it (OQ.md:874-883, items (a)–(d)), yet there is no ADR for the dependency choice, no license/ maintenance assessment, and — per my findings 009/010 — the fork plan and the unload/ABI semantics are both unresolved.OPTIM-IDEAS-4:236advises not to fork before a Phase-0 baseline;OPTIM-IDEAS-2:48-50and-5:25say “since the plan is to fork”. The repo has not even depended on it yet, which is the one place where being undecided is cheap.
Варианты решения (tradeoffs)
Section titled “Варианты решения (tradeoffs)”- (A) Write the deferral down and split the ADRs. Keep core-crate boundaries for now; state in TECHSPEC §0/§10 that the module axis lands at step 4 and that steps 1–3 use in-process traits; amend ADR-001/002 to say which subsystems are actually module candidates.
- (B) Build the boundary first (extract one small module — e.g.
media-decoder-baseline, which is the most self-contained) to learn the ABI before the contracts harden. Highest information value per unit of work; delays features. - (C) Abandon hot-swapping for first-party subsystems, keeping modules
only for third-party plugins (WASM) where the sandbox story is the point.
Massive simplification: no manifest layering, no per-struct seams, no
N-1 rollback, no dev mode, no QA module tier. Contradicts ADR-001’s core
premise and the author’s stated reason for it (
ADR.md:11-14: “heavily, repeatedly re-optimized … a monolithic binary would make swapping individual subsystems prohibitively expensive”). - (D) Keep building in-process and hope the boundary is easy later.
Reject — that’s how the
#[repr(C)]/vtable/erasure questions end up decided by accident.
Предлагаемое решение
Section titled “Предлагаемое решение”(A) + (B), with (C)’s thinking applied where it is actually true.
- New ADR (032-ish) “What is a module, and when” that settles the
candidate list honestly:
- Real module candidates (self-contained, ABI-friendly, genuinely swappable): media decoders, the Engine axis, Native/Feature per-OS modules, search. These have narrow, data-shaped interfaces.
- Core, not modules:
protocol(typestate is compile-time; ADR-026’s fork is a Rust library),predictor(needs storage + is small),trust(it is the host side of the mechanism),ui-elm/ui-contract(the ABI-defining layer itself). Amend ADR-001:24-27 and ADR-002:47-51 to match, and note that thepredictor/protocolrows in those lists are the ones that created the contradiction.
- Record the deferral in TECHSPEC §0/§10 so the layout stops implying
the modules exist; make §0’s tree show the current state plus the step-4
target (the repo did this implicitly in commit
575d16e; the doc should say it). - Extract one real module early (
media-decoder-baselineis the best candidate: narrow trait, no state, already a stub crate) to discover the ABI’s actual constraints beforeStorageEngine/SearchEngineharden — that is finding 016’s subject. - Add a QA gate that is runnable today: “no public API in
ui-contract/protocolreturnsString/Vec/Box<dyn Trait>across a planned module boundary” — a lint-level check that keeps the door open for (B) without needing the loader. - Give
nylon-ringan ADR when it is first depended on: version, license, maintenance posture, what exactly it guarantees (struct-size validation, draining, leased buffers —ADR.md:19-21), and the fork gate fromOPTIM-IDEAS-4:236(baseline + profiling before forking). MarkOPTIM-IDEAS-2.md:48-50/-5.md:25as superseded on the fork-now question.