The validated rstgcalls FFI scaffold lives only in /tmp — the repo contains zero Rust files
Источник:
dev/fera-research/docs/oc3/007-rstgcalls-work-lives-in-tmp.md(read-only; сгенерировано scripts/sync-docs.mjs)
The validated rstgcalls FFI scaffold lives only in /tmp — the repo contains zero Rust files
Section titled “The validated rstgcalls FFI scaffold lives only in /tmp — the repo contains zero Rust files”- kind: risk
- area: calls
- severity: high
Evidence
Section titled “Evidence”~/dev/fera/rstgcalls has no Cargo.toml and no .rs files. git ls-tree -r --name-only HEAD returns exactly three entries: .gitmodules, README.md, tgcalls. find ~/dev/fera/rstgcalls -name '*.rs' | wc -l → 0. cargo check --offline → error: could not find Cargo.toml. README.md:6 and :39 both say “Pre-code.” — accurate, and never updated.
The submodule tgcalls is pinned at d20de8e8 and carries no build system of its own — only tgcalls/Package.swift (SwiftPM). No CMakeLists/meson/Makefile anywhere in it.
The actual validated work is in throwaway spike directories in /tmp:
/tmp/bg-spike bindgen vs real tgcalls headers; src/gen.rs 544 829 B/tmp/bridge-spike bridge.hpp -> src_gen.rs (13 401 B)/tmp/cc-spike the `cc` C++ compile path/tmp/ws-spike 2-crate workspace smoke test/tmp/stub-spike the closest to real — full workspace, builds & tests green (223 MB)/tmp/stub-spike/crates/sys/Cargo.toml:1-13 is the validated crate layout:
[package] name="rstgcalls-sys" version.workspace=true edition.workspace=true build="build.rs"[features] ffi-stub=[] link-tgcalls=[][lints.rust] unsafe_code="allow"[build-dependencies] bindgen="0.72" cc="1.2"with /tmp/stub-spike/crates/safe/src/lib.rs holding the only real API surface: a CallBackend trait (:10-17), MockBackend (:19-27), TgcallsBackend (:33-51), and CallManager (:57).
Verified by running it:
$ cargo test --offline -> 2 passed (mock only)$ cargo test --offline --features ffi-stub -> 3 passedThe validated design decisions are real and worth keeping: hand-written C-ABI bridge header (rstgcalls_bridge.h) rather than raw bindgen over tgcalls C++ classes; opaque handle rstgcalls_instance*; int32_t return codes; bindgen with explicit --target plus -isystem paths parsed from clang++ -std=c++20 -E -x c++ -v -; crate-root deny(unsafe_code) + one scoped #[allow(unsafe_code)] on the gated backend module.
The design plan is untracked: ~/dev/fera/rstgcalls/docs/superpowers/plans/2026-10-17-rstgcalls-ffi-scaffold.md (926 lines, 10 tasks, all checkboxes unchecked), and TODO.md — which the plan references 8× — does not exist.
Nothing on the client side consumes it. ~/dev/fera/fera/Cargo.toml workspace members contain no rstgcalls. A recursive grep for rstgcalls|tgcalls|CallBackend|webrtc across the whole fera repo returns one hit: README.md:27, a link in an ecosystem listing.
Correction to prior notes
Section titled “Correction to prior notes”My own recorded note listed libyuv among the MISSING native libraries. That is wrong. Verified directly:
$ ls -la /usr/lib/libyuv.so-rwxr-xr-x 1 root root 677840 авг 18 21:15 /usr/lib/libyuv.so$ ldconfig -p | grep yuv libyuv.so (libc6,x86-64) => /usr/lib/libyuv.so$ pkg-config --exists libyuv -> NOlibyuv is present and linkable via -lyuv; it only lacks a .pc file (it is a GStreamer dependency, pulled in without pkg-config metadata).
Corrected native-dependency status on this machine:
| Library | Status | Evidence |
|---|---|---|
libwebrtc |
MISSING (genuinely) | no pkg-config; only libwebrtc-audio-processing-1 / libwebrtc-audio-coding-1 DSP libs and gstreamer-webrtc exist |
libtgvoip |
MISSING | absent everywhere |
libyuv |
PRESENT (/usr/lib/libyuv.so, 677 840 B, in ldconfig), no .pc |
corrected |
| openssl / libssl / libcrypto | 3.6.5 | present |
| opus | 1.6.1 | present |
| ffmpeg (libavcodec/format) | 63.1.102; libavutil 61.1.102 | present |
| clang / clang++ | 23.1.1 | binary present, no .pc |
| cmake | 4.4.4 | binary present, no .pc |
| ninja | 1.13.2 | present |
So the full-C++-link blocker list is libwebrtc and libtgvoip only — and libtgvoip is legacy-only per ADR-018’s scope.
The genuinely valuable output of the calls workstream — a proven offline path from real tgcalls headers through bindgen to a green test suite — exists only in /tmp. /tmp is not durable: it is conventionally cleared on reboot and is excluded from every backup. The repo that is supposed to hold it (rstgcalls) is empty and its design plan is untracked.
The risk is not hypothetical drift; it is total loss on a single reboot, with the recovery path being “re-derive the spike from the tgcalls headers” — which is exactly the multi-hour bindgen/clang-discovery work that the plan exists to prevent repeating.
There is also a spec-compliance defect already visible in the spike, which will become a real defect the moment the code is committed:
- ADR-014 is already violated.
ADR.md:566-570requires one contract suite per trait run against every implementation, andADR.md:579-581makes it “required infrastructure before any second implementation of a given trait is accepted”. There are already twoCallBackendimpls (MockBackend,TgcallsBackend) and notests/contract.rs/run_backend_contractanywhere. The precondition has been triggered; the suite does not exist.QA.md:29-30(cross-cutting gate CC5) says the same. - ADR-015 is violated.
CallManageris a plain struct overBox<dyn CallBackend>with no typestate;join/leavetake&mut self, sopush_audiois callable beforejoinand only a runtime check catches it (mock_push_before_join_errors). ADR-015 (ADR.md:593-608) requires invalid states to be unconstructible, and specifically namesCallManager<Idle|Active>plus aConnectionStateenum — neither exists. The spike even usesstarted: boolinMockBackend, which is the exact boolean-flag-combination anti-pattern the ADR names. - Return-code mapping is a single variant.
safe/src/error.rs:4declares one variant (BackendUnavailable);lib.rs:40collapses every non-zero code into it. The plan’s-1..-4table andUnknown(i32)do not exist.
Варианты решения (с tradeoffs)
Section titled “Варианты решения (с tradeoffs)”Option A — move the spike into the repo now, commit the plan, fix ADR-015/ADR-014 before adding more.
- Cost: the spike uses
unsafe_code="allow"at the sys-crate level and the safe crate currently emits 3missing_docswarnings — which would fail the Fera workspace’swarnings = "deny"(fera/Cargo.toml). Roughly a day of lint/cleanup. - Benefit: preserves the validated work permanently; converts a lost-every-reboot artifact into a real crate.
Option B — leave it in /tmp, re-derive later.
- Cost: the discovery work (bindgen
--targetworkaround, clang++-isystemparsing) has to be redone. - Benefit: zero cleanup time now.
- Risk:
/tmploss plus the plan (untracked, 926 lines) is equally exposed.
Option C — commit the spike as-is with #![allow(missing_docs)], and file ADR-014/ADR-015 compliance as follow-ups.
- Cost: knowingly ships a two-impl trait with no contract suite.
- Benefit: preserves the work with minimal friction.
- Risk:
ADR.md:579-581explicitly forbids this, andQA.md:29-30is a cross-cutting gate that applies to every PR.
Предлагаемое решение
Section titled “Предлагаемое решение”A. The validated spike is the single most expensive artifact in the Fera ecosystem so far and it is sitting in a directory the OS is entitled to delete.
- Copy
/tmp/stub-spikeinto~/dev/fera/rstgcalls/crates/{sys,safe}, commit it, and commit the plan doc (docs/superpowers/plans/2026-10-17-rstgcalls-ffi-scaffold.md). Create theTODO.mdthe plan references, so the blocker record has a committed home. Nothing about the spike needs to be finished to be committed; it needs to be durable. - Fix ADR-015 before adding any feature — it is cheap now (
CallManager<S>withIdle/Activeis a ~30-line change against a 100-line file) and expensive after acallssurface exists. AddConnectionState. This is the last cheap moment. - Add the ADR-014 contract suite in the same commit. With two impls already present the gate is live;
fn run_contract<S: CallBackend>(fresh: impl FnMut() -> S)mirrors the patternstorage/src/engine.rs:103already uses, so there is an in-repo precedent to copy rather than invent. - Expand
Errorto the plan’s-1..-4table +Unknown(i32)while the header is still 9 lines. - Record the corrected blocker list (libwebrtc + libtgvoip only; libyuv present without
.pc) inTODO.md, since the “missing libraries” list has already proven to be the wrong way round once. - Note that a header change is owed regardless: the plan mandates
int32_t rstgcalls_instance_stop(…, rstgcalls_final_state* out)while the current stub header returnsvoidand has noreceive_signalingat all — and there are no callbacks/events of any kind in the bridge (noextern "C"trampoline, no function-pointer registration). Signaling is a prerequisite for any real call, so it belongs on the critical path, not the backlog.
Cross-references
Section titled “Cross-references”QA.md:172-177already requiresrstgcallsto be “isolated behind its own module boundary; a build without the calls module compiles and runs the rest of the client” — theffi-stub/link-tgcallsfeature split incrates/sys/Cargo.toml:8is the right shape for that, and it already works.ADR-014/ADR-015violations here mirror the same two ADRs at the storage seam; seedocs/oc3/004-two-competing-store-seams.md.- Calls are deferred past the initial release per
RFC.md:27-28andRFC.md:245-248, so this is not a closed-alpha blocker — the loss risk is what makes it urgent.