Skip to content

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

OQ — Open Questions

Источник: dev/fera/project-design/context/OQ.md (read-only; сгенерировано scripts/sync-docs.mjs)

Companion docs: ADR.md, RFC.md, TECHSPEC.md, QA.md.

Questions explicitly deferred during architecture design, each with enough context to be picked back up later without re-deriving it. Status: Open until resolved; resolution should be recorded back into ADR.md (new ADR or addendum) and this entry marked Resolved → see ADR-XXX.


OQ-001: Engine vs Style — do they need to be separate dylibs?

Section titled “OQ-001: Engine vs Style — do they need to be separate dylibs?”

Context (ADR-009): UI is split into Engine/Style/Feature axes. It’s possible Style could be pure data/config (a theme file) loaded by one Engine, rather than requiring its own hot-swappable dylib with the full nylon-ring machinery.

What would resolve it: Prototype both (Style-as-dylib vs Style-as-data-loaded-by-Engine) for one platform, compare implementation complexity and actual runtime flexibility gained. If no real-world use case needs Style to be more than data (e.g. no Style module ever needs its own compiled logic beyond tokens), prefer the simpler data-file approach.

Resolved (2026-09-30) → ADR-029. Style is declarative data (tokens) loaded by the Engine. If style needs special logic, the logic is described in the data but implemented in dylibs, selected by OS where needed — which is the Feature axis’s job. Final axis split: three native dylib axes (Engine / Feature / Native) + Style as a data layer; theme distribution rides the Lattice themes/ subtree mechanics.


OQ-002: Single-pass vs two-pass hit-testing in the HoF rendering layer

Section titled “OQ-002: Single-pass vs two-pass hit-testing in the HoF rendering layer”

Context (TECHSPEC §6): Immediate-mode UIs (e.g. egui) typically build visual tree and hit-test tree in one pass, returning interaction state (.clicked()) inline. A two-pass design (build tree, then a separate hit-test pass) is more conventional for retained-mode but adds latency between input and response.

What would resolve it: Prototype a representative interactive component (e.g. a message with hover actions) both ways; decide based on actual input-latency measurement and code ergonomics, not just conceptual preference.

Resolved (2026-09-30) → ADR-030. Single-pass. After ADR-028/023 our own hit-testing covers only lists/messages/scroll; overlays and native widgets hit-test in the OS. No prototype needed — the two-pass weaknesses (latency) apply, its strengths (complex z-order) don’t.


OQ-002: Single-pass vs two-pass hit-testing in the HoF rendering layer

Section titled “OQ-002: Single-pass vs two-pass hit-testing in the HoF rendering layer”

Context (TECHSPEC §6): Immediate-mode UIs (e.g. egui) typically build visual tree and hit-test tree in one pass, returning interaction state (.clicked()) inline. A two-pass design (build tree, then a separate hit-test pass) is more conventional for retained-mode but adds latency between input and response.

What would resolve it: Prototype a representative interactive component (e.g. a message with hover actions) both ways; decide based on actual input-latency measurement and code ergonomics, not just conceptual preference.


OQ-003: Sticker frame cache — raw vs. per-frame-compressed storage

Section titled “OQ-003: Sticker frame cache — raw vs. per-frame-compressed storage”

Context (TECHSPEC §4): mmap’d pre-decoded sticker frames could be stored raw (best mmap benefit, largest disk footprint) or compressed per-frame (smaller disk footprint, but decompression cost on every frame access partially defeats the point of mmap).

What would resolve it: Benchmark both against the fixed benchmark scene (see OQ-006) measuring: disk usage, cold-cache first-display latency, warm-cache steady-state CPU usage with 50 concurrent animated stickers.


OQ-004: tlottie vs velato as the default Lottie backend

Section titled “OQ-004: tlottie vs velato as the default Lottie backend”

Context (ADR/TECHSPEC §4): tlottie (CPU/SIMD) is the safe baseline; velato (Vello/GPU) is untested for this workload and its Lottie feature coverage relative to Telegram’s actual sticker corpus is unverified.

What would resolve it: Run the full sticker test corpus (a representative sample of real Telegram stickers, not synthetic ones) through velato and record any rendering discrepancies or unsupported features vs tlottie. Only after coverage is confirmed adequate should the benchmark comparison (§7 of RFC) even be the deciding factor — a faster-but-visually-wrong renderer is not a real candidate.

Resolved (2026-09-30) → sequencing decision. tlottie is the default, implemented first. velato testing is deferred to near v2: if coverage proves sufficient by then, it becomes a default candidate; if not, it stays an experimental backend with an explicit opt-in. The coverage-first rule above (visually-wrong ≠ candidate) stands for that future evaluation.


OQ-005: Metadata vs content classification for encryption tiering

Section titled “OQ-005: Metadata vs content classification for encryption tiering”

Context (ADR-016): Some fields are ambiguous — e.g. does a chat list’s last-message preview text count as lightweight “metadata” (needs fast search across hundreds of chats) or heavy “content” (message text, subject to the strong AEAD tier)?

What would resolve it: An explicit, field-by-field audit of everything persisted (chat list entries, message bodies, media captions, search index terms, activity log) with a tier assignment recorded per field — not left to be decided ad hoc per PR as fields get added.

Resolved (2026-10-01) → ADR-031. Full field audit recorded (metadata: IDs/timestamps/read-state/outbox/chat-list/indexes/event-log; content: text/entities/media/profiles/search-terms; preview = content; secret-chat instance = all content under its own key). Searchable encryption via deterministic HMAC tags adopted for secret chats, opt-in “paranoid mode” candidate for the main index; frequency-analysis limitation accepted; author flags it as a new attack vector to design against deliberately.


OQ-006: Fixed benchmark scene — exact parameters

Section titled “OQ-006: Fixed benchmark scene — exact parameters”

Status: RESOLVED (2026-10-02, author approved the draft as-is) — parameters below are FROZEN as fera-scene-v0.

Context (TECHSPEC §7, QA.md): Referenced repeatedly (Engine benchmarks, sticker cache benchmarks, regex-search performance threshold) but never pinned to concrete numbers beyond a strawman (“5000 messages, 50 concurrently-animating stickers”).

Proposed parameters (v0 draft, for the author to mark up):

Parameter Proposed Rationale
Scene name/version fera-scene-v0 referenced by exact name from every benchmark
Messages per bench chat 5,000 the original strawman, kept
Total messages in scene 100,000 20 chats × 5k — exercises range scans + search corpus
Message type mix 70% text / 20% media (photo, 1–3 MiB) / 8% sticker / 2% other (voice, file) mirrors typical chat usage
Chat list size 500 chats (30 with unread) the “search across hundreds of chats” metadata case
Concurrent animated stickers 50 the strawman; matches RESEARCH-OQ-003 stress math (1.5 GB/s decompressed)
Sticker dimensions 512×512 @ 30fps, 3 s RESEARCH-OQ-003 arithmetic basis
Scroll behavior programmatic: top→bottom→random-jump ×20 scroll-driven read patterns, cache hit/miss mix
Corpus for regex search the 100k messages above same scene, two-phase vs naive threshold measurement
Accounts 1 main + 1 secondary (notifications merge) NotificationActor across accounts
Synthesis synthetic, seeded (deterministic RNG) reproducible; real anonymized sample can become v1 later
Outbox pre-fill 50 pending messages offline write-path benchmarking

Freeze (approved 2026-10-02): checked into /tools/bench-scene/ as fera-scene-v0 (generator with pinned seed), referenced by exact name+version from every benchmark, never re-described per use site. Bumping the scene = new version suffix, old results explicitly invalidated.


OQ-007: Web-of-trust for plugin/module signing

Section titled “OQ-007: Web-of-trust for plugin/module signing”

Context (ADR-003): Explicitly deferred during architecture discussion as “interesting, revisit later” rather than designed now. The self-hosted TUF-like manifest (root key + user-managed trusted-key list) is the v1 model; a decentralized web-of-trust (users signing other users’ keys directly, without relying on the root manifest or GitHub) was floated as a future direction.

What would resolve it: A dedicated design session once the v1 trust model has real usage/friction data to design against — e.g. once it’s clear whether users actually want to trust third-party modules from people they know personally, vs. only ever using officially-signed modules or a small number of well-known community ones.

Reviewed (2026-10-01): stays Open, decision sharpened:

  • v1 ships a zero-cost bridge: the trusted-key store already records a signed-by: [key_ids] field (empty for now) — when/if WoT is enabled later, the data model is already there.
  • Refined resolve criterion: at least N real users have manually added a third-party key — manual key-add usage is the signal that direct person-to-person trust is exercised at all; without it, WoT solves a problem nobody has. UX risk acknowledged (PGP’s 30-year lesson: trust chains are hard for users to reason about).

Context: Not discussed at all during the architecture sessions that produced these documents. Telegram’s secret chats have distinct properties (device-local E2E encryption, no cloud sync, self-destruct) that don’t obviously map onto the offline-first “local storage is source of truth” model (ADR-010) — a secret chat arguably shouldn’t be as durably persisted/synced as regular chats.

What would resolve it: A dedicated design pass: does the client support secret chats at all in v1? If yes, how does their storage/lifecycle model diverge from ADR-010/ADR-016, and does that require a third encryption tier or a fundamentally different (non-sanakirja, or same engine but different retention policy) storage path?

Resolved (2026-09-30) → ADR-025. Full support in v1; separate sanakirja instance with TTL policy; MTProto E2E as-is (DH + emoji fingerprint); per-device chats, no key migration; same passcode with a dedicated Argon2id context. Passkey-as-encryption-factor for secret chats: v1.5, see ADR-027.


OQ-009: Predictor event log storage — shared with message storage or separate?

Section titled “OQ-009: Predictor event log storage — shared with message storage or separate?”

Status: RESOLVED (2026-10-02) → see resolution note at the end.

Context (TECHSPEC §5): ChatActivityEvent logging needs a storage backend; unclear whether it shares the main sanakirja-backed StorageEngine instance (simpler, one engine to maintain) or uses a separate lightweight store (avoids mixing high-frequency low-value activity logs with high-value message data in the same backup/encryption/compaction policy).

What would resolve it: Decide based on whether activity-log write frequency/volume meaningfully affects StorageEngine performance characteristics for message reads — measure once Tier 1 predictor logging is actually implemented against the fixed benchmark scene (OQ-006).

Resolved (2026-10-02) → PREDICT.md §9 + OQ-021 layout. The raw behavior log is irreplaceable → lives in data/accounts/<id>/predictor/ (data tier, content-encrypted per ADR-031); model state is rebuildable from the log → cache/predictor/. Storage engine: the log shares the main sanakirja instance’s domain (per-account data), model snapshots use rkyv under cache/. The “same engine vs separate store” question dissolved — the split follows the data/cache boundary, not the engine boundary. Performance watch-item (log write frequency) remains a Phase-0 benchmark checkpoint, not a blocker.


OQ-010: Pixel-diff acceptable threshold for visual regression

Section titled “OQ-010: Pixel-diff acceptable threshold for visual regression”

Status: procedure recorded (2026-10-02); the threshold itself is set empirically when the UI exists — by design, not by omission.

Context (QA.md, UI section): “documented acceptable diff threshold (TBD)” — no concrete number chosen. Too strict a threshold makes the test suite flaky across font-rendering/subpixel differences between CI environments and dev machines; too loose defeats the purpose of a 1:1 visual fidelity goal.

What would resolve it: Empirically run the pixel-diff harness across a couple of different machines/CI runners with a known-identical build and observe natural variance before picking a threshold — don’t guess a number first and hope it holds.

Procedure recorded (2026-10-02), threshold deferred as designed:

  1. Build the same commit on ≥2 dev machines + 2 CI runners (Windows first — our primary dev platform; then macOS, Linux).
  2. Capture the same reference screenshots per Style module (same seeded scene data — fera-scene-v0 where applicable).
  3. Run the pixel-diff harness machine-vs-machine on identical builds; record the natural variance distribution (per-pixel delta %, and perceptual-hash delta as a second metric).
  4. Threshold = p99 of observed natural variance × safety margin (~1.5×), floor at a minimum sanity value. Record the number per OS per Style module in QA.md when set.
  5. If variance between “identical” builds exceeds a usable threshold, the fix is normalizing the capture pipeline (same GPU/driver class or CPU-rendered reference frames via vello_cpu), not loosening the threshold. Antialiasing/text subpixel differences are expected to dominate — compare with identical font cache settings, not defaults.

OQ-011: Two-phase regex search performance threshold

Section titled “OQ-011: Two-phase regex search performance threshold”

Context (QA.md, Storage & Search section): The corpus-size threshold above which two-phase (FTS-narrowed) PCRE2 search must outperform a naive full-scan baseline is undefined.

What would resolve it: Measure both approaches across a range of corpus sizes on the fixed benchmark scene (OQ-006) and pick the threshold where the two-phase approach’s fixed overhead (literal extraction, FTS query) is actually amortized — don’t assume “always faster” without data.

Resolved (2026-09-30) → decision sketch, calibration pending. The threshold is a decision matrix, not one number: (corpus size) × (narrowing quality). Structure (to become an ADR once implemented):

  • No literals extractable from the pattern → always naive.
  • Candidate count > K (hard cap, % of corpus) → naive (supertoxic literal case).
  • Corpus threshold: configurable, default derived from benchmarks on the fixed scene — frozen as a default, not as a constant; the heuristic goal is a general formula “when to take which route” for mid-tier hardware, not the strongest or weakest, with reasonable power draw.
  • The route decision is computed at query-planning time and modeled as a typestate plan (RegexSearchPlan::Naive | TwoPhase{...}) — no runtime if-branches in the executor; the plan is unit-testable without executing the search. Calibration happens when the benchmark scene (OQ-006) is frozen; until then the defaults are provisional.

OQ-012: Official Engine/Style/Feature “triads” — exact scope of guaranteed support

Section titled “OQ-012: Official Engine/Style/Feature “triads” — exact scope of guaranteed support”

Context (ADR-009): “Official triads per OS are the primarily tested/ supported combinations; arbitrary user-mixed combinations are best-effort” — but the exact list of officially supported triads, and what “best-effort” concretely means for support/bug-report triage on mixed combinations, isn’t defined.

What would resolve it: A support-policy decision, likely made once there’s at least one real cross-platform user base to write the policy for rather than speculatively now.

Terminology update (2026-10-02, author confirmed): “triad” is obsolete — Style became a data layer (ADR-029) and Native joined as a fourth axis (ADR-023). The official per-OS combination is a quad: Engine + Native + Feature + Style-data. The support-policy question itself stays open: exact list of officially supported quads and what “best-effort” concretely means for user-mixed combinations — to be decided once there’s a real cross-platform user base.


OQ-013: Plugin container format — Lattice (.lttc)

Section titled “OQ-013: Plugin container format — Lattice (.lttc)”

Context (ADR-022): Plugin packaging must keep rights/manifest data where a translator cannot substitute them. Ideas floated: a custom container (e.g. .fera-plugin — zip or tar.zst with a fixed layout: manifest.json, plugin.wasm, assets; signature covers the manifest, the manifest carries hashes of all files). .dmg rejected (macOS-only disk image, wrong tool for the job).

Name (decided 2026-09-30): Lattice — Lattice Container / Lattice Package Format; short LTT Container; extension .lttc (pronounced L-T-T-C /ˌɛl tiː tiː siː/). Semantics: lattice = решётка, the Merkle DAG is literally one. Recorded in NAMING-UX.md.

Status: the author is designing this herself; direction and the decisions below are hers. Recorded so future sessions don’t re-litigate them. Final format spec is still pending — the container project is a separate future workstream.

Scope decision (2026-09-30):

  • Lattice is a universal plugin-and-manifest container — any host may adopt it, with or without native code, under its own trust rules. Native code is a first-class format citizen (code/native/ in the layout). Only the Fera host opts out of native plugin code (ADR-002: Fera loads third-party code as WASM only) — a Fera loading policy, not a format restriction. Fera’s own native modules stay plain signed dylibs per ADR-021.
  • It will be its own project/repo — created for Fera but not Fera-internal (standalone identity like re:call).
  • Built from scratch — no squashfs (or any existing FS) underneath; the format is fully ours.

Current design (author’s sketch, being explored): a single-file image, content-addressed (CAR-style) with a Merkle DAG:

<container> → Superblock → FS metadata → Merkle DAG → CAR blocks
(manifest / wasm / assets)

Author’s refinements:

  • Custom extension (explicitly not .plugin — TBD by her); magic bytes live in the superblock anyway.
  • A plugin is multiple wasm files, not one — component-style composition; partial updates then touch only rebuilt components.
  • Configs/assets are addressed by directory paths inside the container’s internal FS — the container is a small virtual FS.
  • Files split small (better cross-version/cross-plugin dedup at the cost of more Merkle nodes / block-table entries).
  • Fit with WASI: per-directory preopens map naturally onto capability grants (a plugin sees only the subtrees it was granted).
  • Compression: deflate, per-block (author’s pick over zstd — accept the worse ratio for ubiquity/simplicity; per-block placement is what keeps random access alive).
  • Manifest is a special entity, not a regular block:
    • Stored uncompressed — readable/greppable without a decompressor.
    • Laid out separately in the container’s virtual tree, not as an ordinary content-addressed file.
    • The header carries two direct pointers (manifest start + end) so a loader reads the manifest without touching the block table or walking the Merkle DAG — “parse as fast as possible”, everything else (block table, DAG) resolves later/lazily.
  • Stated goal: “набить как можно больше фич, которые пригодятся в будущем” — the format should carry headroom features from day one rather than grow them ad hoc.
  • Day-one tooling (decided 2026-09-30):
    • Rust build-script support targeting wasmtime out of the box.
    • CLI: bundle creation + verification.
    • Config duality: RON inside the bundle, TOML read from outside — the packed copy is RON (Rust-native, round-trips the type system); the human-edited view is TOML. Deliberately two formats, one per audience.
  • Headroom feature set — approved as a whole (2026-09-30), each still to be detailed when the format spec is written: format version + feature flags in the superblock (old loaders must refuse politely); multiple roots/subtrees per container (plugin / translations / themes — translations update without repacking wasm); uncompressed license/provenance (SBOM) section; delta-friendly block layout (OSTree/casync-style ordering for minimal v1→v2 patches); optional per-subtree signatures (different keys for code vs translations); 4 KiB block alignment (mmap-friendly); optional detached signature mode (re-sign without re-hashing content).

Properties this buys: sign the Merkle root once → the whole container is transitively covered (the “signature covers the whole entry” hardening of ADR-021 comes for free, and a translator physically cannot substitute anything); lazy per-block verification on read (no whole-file hashing at load); dedup via a shared content-addressed block store; incremental updates = fetch only changed blocks (OSTree-pull-style).

Open points the author is weighing (recorded from discussion, decisions are hers):

  • Random access: plain CARv1 is a linear block dump (no index); the format needs a block table (CID → offset/len/compressed) embedded in the superblock/FS-metadata region.
  • Hash choice: sha256 (consistent with ADR-021 manifest, SHA-NI) vs blake3 (native Merkle tree hashing, faster, but a second algorithm in the trust stack).
  • Block sizing: fixed (SquashFS-style 128 KiB / IPFS-style 1 MiB) vs content-defined chunking (FastCDC, restic/borg-style) — the latter dedups far better across versions.
  • Signature placement: in the container superblock (self-contained) vs in the ADR-021 distribution manifest referencing the root CID vs both.

What would resolve it: the standalone container project — its spec and reference implementation. Until that exists, this OQ stays open.

Progress (2026-09-30): spec draft exists — context/LATTICE.md (v0.1). All four OPENs decided by the author (BLAKE3 default hash, FastCDC chunking, dual signatures superblock+ADR-021, little-endian). Second round same day: superblock 4 KiB with header_size field, 4 signature slots, binary Merkle trees + pointer table (24-byte entries, 2–64 KiB), fine-grained canonical FS layout with manifest as a directory of per-concern RON files, manifest-in-root via virtual super-node (§5.3), reserved tail 3684 bytes zero-filled, magic b"LTTC_BUNDLE >^<\0" (16 bytes). Lattice re-scoped as a universal plugin-and-manifest container — code/native/ is first-class in the format; only the Fera host forbids native plugin code (ADR-002 policy). Remaining: signature-disagreement policy (§10.1, TBD), then the standalone repo + reference implementation.


OQ-014: Engine substrate — GPUI family vs Vello + Parley

Section titled “OQ-014: Engine substrate — GPUI family vs Vello + Parley”

Context (ADR-008/009): The architecture assumed GPUI. Re-assessment: upstream GPUI is explicitly pre-1.0 with frequent breaking changes, has near-zero documentation (the decisive objection — the author does not fear unstable APIs; the requirement is stable behavior within a pinned version plus usable docs), and a fragmented fork ecosystem (gpui-ce, bezel-gpui, gpui-kit from Longbridge). Xilem (alpha, perma-experimental since 2022) and Vello (split into vello_gpu / classic / vello_cpu at ~beta) were surveyed. The working lean became Vello + Parley + winit + AccessKit with our own HoF layer on top: best documentation among candidates, pinnable versions, vello_cpu as a CPU fallback — at the price of writing hit-testing, scroll, virtualization, and text input ourselves. GPUI Kit remains the strongest GPUI-family fallback candidate.

What would resolve it: Decide before Engine-module implementation begins (TECHSPEC §10, step 3+). If Vello+Parley: prototype the input/IME/selection path first — identified as the heaviest part. The Engine trait boundary (ADR-009) keeps this a contained decision either way.

Resolved (2026-09-30) → hybrid: Vello + Parley renderer with native text-input slots. The heaviest part of a from-scratch renderer (IME, spell-check, selection, emoji picker) is delegated to the platform via the Native axis (ADR-023) instead of being written in-house; only lists/scroll/message rendering stay on our own engine. Cocoa scroll quirks (rubber-band et al.) come along with the native surface on macOS — a welcome side effect, not a bug. Prototype order unchanged: input/IME path first, now via the native slot contract.


Context (ADR-022): Level 2 behavioral translators exist for asynchronicity translation (the named example). The full set of behavioral capabilities — which call-shape changes are translatable at all — is deliberately not enumerated.

What would resolve it: Enumerate lazily when a first real plugin needs a specific behavioral translation; keep the set minimal, and treat each addition as a new user-facing opt-in capability.

Resolved (2026-10-01) → minimal starter set + reference examples.

  • The spec ships a minimal starter set of L2 capabilities, beginning with async-translation (the named example) as the reference implementation proving the L2 architecture works end-to-end. Further capabilities stay lazily enumerated as real plugins need them.
  • Example plugins per type: for each plugin type, a reference example is written — in Rust, plus a total of four versions in other languages (to demonstrate portability of the plugin surface, “чисто как оно”). These are spec/tooling deliverables, not shipped features.

OQ-025: GitHub organization & repository topology

Section titled “OQ-025: GitHub organization & repository topology”

Status: recorded (2026-10-02) — org creation deferred until the author creates it; repo creation by agent via gh follows immediately after.

Context: The project’s code will be split across multiple repos in a GitHub organization, not one monorepo. The author explicitly rejected a dumb monorepo; the model is the Rust-compiler-style repo tree — a root repo that recursively pulls the others (cargo git-dependencies for crates, git submodules for non-cargo content).

Decisions recorded (2026-10-02, author’s choices):

  • Org name: thefera, display name “The FERA Project”. Created by the author herself (requires plan confirmation in browser — cannot be done via API/CLI).
  • Repo list (phase 1 — create when org exists):
    • fera — launcher + core module + trust manifest (the root repo)
    • fload — nylon-ring fork (module runtime; OQ-024 fork intent)
    • mikkaj — milli fork (search, retarget heed→sanakirja, ADR-012)
    • grmrs — grammers fork (protocol, ADR-026)
    • lattice — LTT container (standalone project, OQ-013)
    • rstgcalls — tgcalls bindings (ADR-018)
    • project-design — private; the context/ design docs
    • site — public website
  • Phase 2 (subsystem repos, after anchors settle): split by subsystem — render / audio / video / anim / plugins / registry / … Each subsystem repo is itself a workspace of many small cdylibs (ADR-020 struct-per-dll: common.dll, X-types.dll, X.dll — those are build artifacts inside a subsystem repo, not repos).
  • How the tree pulls: cargo git-deps recursively (Rust-compiler model) + submodules for non-cargo content (theme KDL files, bench scene fixtures).
  • README per repo is to be written at creation time (agent task), each noting: what it is, status (pre-code), originating ADR/OQ, where progress is tracked.

What would resolve it: the author creating the thefera org; then repos are created and populated in one pass.


Context: Fera will eventually need a NixOS/nixpkgs story (flake, module, or both). Nothing decided; parked so it doesn’t get lost.

What would resolve it: A dedicated packaging pass once there’s an actual buildable binary to distribute — revisit after v1.


Context: Telegram’s web proxy (a new proxy type, distinct from MTProto proxies) is explicitly deferred.

Status: not before v1.5 at the earliest, and at the latest together with Mini Apps or in v2.1 — do not design before.


Context: Telegram Mini Apps (bot web apps) support.

Status: deferred to after v2 — do not design before.


OQ-019: Feature map — Telegram Desktop source survey + top-5 clients per platform

Section titled “OQ-019: Feature map — Telegram Desktop source survey + top-5 clients per platform”

Context: Before locking v1 scope, a full feature survey is wanted: a set of agents that read the entire Telegram Desktop source and produce a feature map of everything available per platform, plus the same survey for the top 5 alternative clients per platform.

Status: planned, not started — explicitly a “later” task; launch the survey agents when v1 scoping begins.

What would resolve it: Running the survey; the resulting feature map then feeds the v1 scope decision.


OQ-020: Developer mode — exact semantics

Section titled “OQ-020: Developer mode — exact semantics”

Context (ADR-024): local/unsigned native modules run only in an explicit developer mode. Open: what exactly it gates (per-module allowlist vs blanket toggle), whether it’s per-session or persistent, and how visibly it’s surfaced in the UI beyond “persistent indicator”.

What would resolve it: a small UX decision pass on the developer- mode surface; not blocking v1 design, but must be settled before the first public build that accepts third-party native modules.


OQ-021: «А как располагаться блядь» — runtime on-disk layout

Section titled “OQ-021: «А как располагаться блядь» — runtime on-disk layout”

Status: RESOLVED (2026-10-02) — canonical tree below is final.

Context: Before implementation starts, decide the on-disk layout of everything the runtime needs: which .so/.dll files exist (launcher, core module, Engine/Feature/Native axis modules, translators), where WASM plugins and their Lattice block stores live, where caches go (message LRU, sticker frame cache per OQ-003/RESEARCH-OQ-003, KDL→rkyv theme cache .fera-cache/ per ADR-030), where downloads land (module updates, N-1 rollback copies per ADR-024), user data vs app data vs cache separation, and platform conventions (XDG on Linux, %APPDATA%/%LOCALAPPDATA% on Windows, ~/Library/… on macOS) versus a self-contained install directory.

Priority: take this up before the remaining open OQs (author’s call, 2026-09-30).

Progress notes (2026-09-30):

  • Module tree follows the ADR-020 three-tier granularity: hundreds of small dylibs, not a handful of big ones. Working tree: modules/ layer0(common) / layer1(X-types) / layer2(X) / subsystems(engine/feature/native/storage/search/media) / core.dll (+ .n-1) / trust-manifest.json. Naming: common.dll, X-types.dll, X.dll — the for-X-types prefix is dead (ugly). Load order is topological over the manifest’s depends_on.
  • Link-time pointer resolution, no per-call dynamic dispatch: modules export tables of pointers (static EXPORT: &VTable), the host resolves and patches the importer’s slots at link time — one indirect call (call [rax]), like a virtual function without vtable walking. Flat types stay #[repr(C)] by-value (ADR-020).
  • Relink mechanism: ERB (epoch-based reclamation). Author’s pick. Hot-swap = new pointer table in a new epoch; old epoch drains (in-flight calls finish) before old code is freed. Deferred to a dedicated design pass (“отложим на секунду”) — recorded here so it isn’t lost.

What would resolve it: a concrete layout tree (per-OS where they differ), approved by the author, recorded in TECHSPEC as the canonical filesystem contract.

Resolved (2026-10-02): canonical layout above is FINAL — approved by the author with the file-level examples. The full tree (with example files: renderer-vello-1.5.0.dll.part in temp/, per-module quadruplets in modules_data/, bafy.9x2k.blk blocks, cid-map.ttso indexes, gc/mark-2026-10.ron, package.lttc per installed plugin, account/messages/main.skj, cache/themes/dark.rkv, etc.) is the contract. Transcription into TECHSPEC as the filesystem contract follows (per-OS path prefixes: %LOCALAPPDATA%\Fera / XDG / ~/Library/Application Support/Fera + bundle for signed code on macOS).

Turso note: .ttso file extension is tentative (Lattice-style short naming) — finalize when Turso actually ships in the build.

Canonical layout v1 (2026-10-01, author’s tree):

Fera/
├── bin/ fera.exe, updater.exe
├── modules/
│ ├── modules.toml minimal top-level (profile, enable/disable)
│ ├── modules_data/ per-module quadruplet: config.toml (human) +
│ │ config.ttso, manifest.ron (copy from Lattice) +
│ │ manifest.ttso (Turso index) — all signed
│ ├── temp/
│ ├── core/ core.dll (+ .n-1)
│ ├── common/ types, memory, io, primitives
│ ├── ui/ types, message, chat, render, compositor, text,
│ │ style, native, feature, platform
│ ├── protocol/ types, transport, mtproto, session, auth,
│ │ serialization
│ ├── storage/ types, engine, filesystem, serialization,
│ │ search, cache
│ ├── media/ types, fetch, decode, encode, thumbnail, cache
│ ├── trust/ types, manifest, keys, verification, provenance
│ ├── platform/ windows, filesystem, process, notifications,
│ │ integration
│ └── trust-manifest.ron signed root manifest
├── plugins/
│ ├── store/ blocks/, indexes/, gc/ (Lattice pool w/ GC)
│ ├── installed/<plugin-id>/ package.lttc, state/
│ └── registry/ index/, trust/
├── data/
│ ├── global/
│ ├── accounts/<account-id>/ profile/, messages/, media/, state/,
│ │ predictor/, secrets/
│ ├── runtime/ locks/, sockets/, state/
│ └── migrations/
├── cache/ messages/, media/, stickers/, thumbnails/,
│ shaders/, compiled/, themes/, predictor/,
│ _index.ttso (Turso cache-metadata index)
└── tmp/ downloads/, updates/, extraction/, atomic/

Resolved details (2026-10-01, author’s answers):

  • modules.toml — minimal top-level config: settings, profile, enable/disable. modules_data/ (separate directory) holds the exact per-module config.
  • Config format split (author’s correction, 2026-10-01): the module-folder config is TOML (human-edited — the Lattice TOML-outside/RON-inside duality applied to the runtime tree); inside the module’s Lattice bundle it is RON (machine, Rust-typed). Per-module quadruplet in modules_data/<module>/: manifest.ron (copy from Lattice, canonical) + manifest.ttso + config.toml + config.ttso — all signed.
  • The SQL copies are Turso (not raw SQL files): RON = source of truth, Turso databases = derived signed queryable indexes (“which modules require shared-types > X” without parsing RON), rebuilt from RON, mismatch = warning. This promotes Turso from FUTURE-CHECKS §2 to a live stack member. Extension: Turso file extension tentatively .ttso (Lattice-style short naming).
  • TURSO GETS A THIRD ROLE: cache metadata indexes. Sticker/media cache metadata (content-hash → file, LRU stats, TTL) is table-query-shaped — sanakirja KV holds blobs, Turso answers “which stickers are older than N and unopened for a week”. So Turso in the project: manifest indexes, config copies, cache metadata.
  • serialization.dll in protocol/ and storage/ — different contexts, separate modules (MTProto vs rkyv).
  • cache/compiled/ — rkv-compiled themes + precompiled modules.

OQ-022: Plugin fuel budgets & shutdown/module-swap TTL

Section titled “OQ-022: Plugin fuel budgets & shutdown/module-swap TTL”

Context (2026-10-01, author’s decisions recorded for the design pass):

  • Plugin fuel: the host core ultimately decides the granted budget; plugins may request how much they want (“хочу столько”), the host then decides — the request is a hint, not a contract. The user sees (and adjusts) granted amounts in the UI. Mechanics stay standard wasmtime fuel metering.
  • Host-function TTL: 1 minute, then kill — and the TTL timer starts only after the epoch has ended. Ordering on module swap: epoch flip (ERB, see OQ-021 notes) → relink → TTL starts → kill stragglers at 60 s. TTL is a safety net after draining, not a replacement for it.

What would resolve it: the runtime design pass that specifies the fuel-declaration surface (WIT type? manifest field?), the UI, and the TTL timer implementation in the nylon-ring-equivalent host runtime.

Runtime split (2026-09-30, author’s decision): plugin runtimes are counted as three, behind one shared runtime trait:

  • Wasmtime — baseline + main support.
  • WASI — experimental; chosen purely as a GPU/power-consumption optimization play (less aggressive JIT, etc.).
  • Other — anything else, via the trait.

OQ-023: Predictor (ADR-019) — development & formalization of the heuristic/model analysis

Section titled “OQ-023: Predictor (ADR-019) — development & formalization of the heuristic/model analysis”

Context (2026-10-01, author’s clarification): the “heuristics + slightly more complex but still cheap model-based analysis” idea applies only to the predictor — it is the development and formalization path for ADR-019’s tier ladder (Tier 1 naive statistics → Tier 2 cheap model → Tier 3 online learning). Not a project-wide practice; other subsystems are out of scope.

What would resolve it: the ADR-019 sequencing constraint stands (Tier 2/3 only after real Tier 1 telemetry exists); this OQ tracks the formalization work itself — feature definitions, model family choice, validation criteria — to be designed once Tier 1 data accumulates.

Progress (2026-10-01): design doc exists — context/PREDICT.md (v0.1). All eight of its open questions resolved by the author: next-open as primary hit definition; A/B (system vs behavior) model split accepted; raw log → data/ (irreplaceable), model state → cache/ (rebuildable); instrumentation order frame-governor → prefetch → sticker-retention; FTRL-as-expert-inside-Hedge as the final composition (stacking only as a replay-harness experiment); log retention 30 days then aggregates, with view/reset/disable user controls; Hedge context = time bucket + power state.


OQ-024: Total power-efficiency optimization pass + module-runtime fork

Section titled “OQ-024: Total power-efficiency optimization pass + module-runtime fork”

Status: design settled (2026-10-02) — final formulation below.

Context (2026-10-01, author’s directive):

  • Goal: squeeze the entire application’s consumption as low as possible (“выдрочить”) — plus a dedicated power-saving mode that reduces consumption further. This is a first-class optimization objective, not an afterthought.

  • The second plugin backend (WASI, OQ-022 runtime split) exists partly for this — less aggressive JIT etc. feeds the power goal.

  • Plugin linking goes through a reclamation mechanism. The split (author’s direction, informed by discussion): Hazard Pointers for the big/primary module loads (few, predictable reclamation — the important swaps), QSBR/EBR for the many small modules (hundreds of struct-per-dll modules, rare swaps, near-zero reader cost on the hot path). Final validation in the OQ-024 design pass with profiling.

  • The library we planned to use for the module runtime gets forked:

    1. optimize it, 2. rewrite its internals to our approach.
  • Given the very large dylib count (ADR-020 struct-per-dll), the in-memory module-storage model is itself an optimization target — plus an optimized EBR table (the pointer/epoch tables must be tuned for hundreds of modules, not a handful).

  • Actor model stays, complemented (author’s direction, 2026-10-01): ractor-on-tokio work-stealing remains the backbone; blocking work (sanakirja disk IO, media decode) goes to dedicated OS threads via spawn_blocking; actor pools (e.g. ChatActor pool) are size-configurable so the app runs excellently on 1–2 threads (battery/power mode) and scales up on desktop. Effects/tasks stay short — what work-stealing wants. Not a modification of the actor model; an addition to it.

  • Memory-floor optimizations, built on understanding how the runtime actually works (2026-10-01, author’s additions):

    • Thread parking + freezing background work. When the app is idle and in the background, unload every thread except the control thread and the network thread — park the rest. Thread parks are cheap; a backgrounded app should hold close to zero active compute.
    • Minimum-memory as an explicit design objective across the whole project — “агрессивные оптимизации в целом”, especially in the power-saving mode (which tiers of caches shrink, which actors park, what gets frozen vs evicted).

Progress (2026-10-02):

  • Actor-model compromise confirmed: ractor-on-tokio + spawn_blocking + configurable pools (no second ad-hoc threading model, no actor-model rewrite).
  • SMR synthesis confirmed by the author: actor-owned state = plain ownership (no SMR); inter-module pointer tables = QSBR with quiescent points in actor loops; big-module unloads = HP; guards never held across .await.
  • nylon-ring fork — full intent (author, clarified): fork to (a) optimize pointer storage, (b) remove virtual tables in favor of the link-time pointer tables, (c) tune it close to epochs (QSBR-style), and (d) pin the Rust version so modules can be compiled without #[repr(C)] — a fixed compiler version makes the Rust ABI itself stable enough across our own modules. Fork stays gated behind Phase 0 (baseline metrics per OPTIM-IDEAS-4).
  • Alternative ASYNC runtimes (author’s actual question): survey tokio alternatives (smol/async-std, glommio/io_uring-based, compio, embassy-style) for the power/1-2-thread goals — added as an OQ-024 sub-question, evaluated in Phase 0. Plugin runtimes already have three backends (OQ-022) — separate axis, already decided.

FINAL FORMULATION (2026-10-02, all contradictions resolved):

OQ-024: Total resource optimization and energy-saving runtime.

Minimize CPU, wake-ups, RSS/PSS, virtual memory, thread count, allocation rate and energy as first-class goals, while holding latency/throughput SLOs. A dedicated power-saving mode is a first-class mode, not “slightly fewer threads”: thread parking + freezing background work (in idle/background: park everything except control + network threads), cache shrink/compress-to-disk, timer coalescing, plugin unloading, minimal runtime (current_thread or explicitly limited workers), reconciliation on wake.

Threading: ractor-on-tokio stays the backbone (no actor-model rewrite, no second ad-hoc threading model). spawn_blocking with semaphore/limits for short blocking ops; named OS threads with stop protocols for long-lived blocking workers (disk IO, decode). Configurable actor-pool sizes (N=1–2 power mode, N=cores desktop). No unbounded mailboxes.

Reclamation (final synthesis): actor-owned state = plain ownership (no SMR). Inter-module pointer tables = QSBR with quiescent points placed in the actor loops (our known safe points, not arbitrary Tokio tasks). Big-module unloads = Hazard Pointers (few objects, precise control). No guard ever held across .await. Timeout + HP fallback if a grace period stalls.

nylon-ring fork (full intent, gated behind Phase 0): (a) optimize pointer storage (compact, cache-friendly, flat arrays — no sparse maps); (b) remove virtual tables in favor of link-time pointer tables (single indirect call, no vtable walk); (c) tune reclamation close to epochs (QSBR); (d) pin the Rust compiler version so our own modules compile without #[repr(C)] — a fixed rustc version makes the Rust ABI stable between our modules. Manifest consequence: module metadata must carry the required rustc version; the host checks it at load. Fork only after Phase 0 baseline + profiling (OPTIM-IDEAS-4 §6 phase plan).

Alternative async runtimes: survey tokio alternatives (smol/async-std, glommio/io_uring, compio) for power/1–2-thread goals during Phase 0; tokio stays default unless a measured win. Plugin runtimes already have three backends behind a trait (OQ-022) — a separate, already-decided axis.

Optimization cadence: every change lands only with a reproducible benchmark (CPU/RAM/energy/latency; idle/burst/load/ hot-swap; 1/10/100/500 plugin scenarios) on target hardware — pre-registered-falsifiers style. Materials: OPTIM-IDEAS 1–5.


OQ-027: Telemetry policy at ОБТ/release

Section titled “OQ-027: Telemetry policy at ОБТ/release”

Context (2026-10-02, author’s decisions forming): ЗАТ (closed alpha) ships an in-app bug-report button + a self-hosted Sentry (own infrastructure, not SaaS). Mid-discussion the author added: telemetry will be a separate DLL module — explicitly disable/removable, riding the module system (ADR-001/020), so “off” is a real architectural state, not a settings flag.

Still open:

  • Default state at ОБТ/release: off-by-default opt-in vs on-by-default opt-out (module present but disabled).
  • Sentry’s payload minimization (what a crash report contains — full stack + environment vs a stripped core) given the project’s offline-first/privacy posture.
  • Whether Sentry stays the mechanism at scale or gets replaced by an in-house collector riding the same module slot.

What would resolve it: a small privacy-policy design pass before ОБТ opens; the module-slot decision is already recorded (NAMING-UX §1.5).


OQ-026: Second site — teaser page (“we will be there soon”)

Section titled “OQ-026: Second site — teaser page (“we will be there soon”)”

Status: parked (2026-10-02) — second site to be made later.

Context (author’s words, 2026-10-02): a second, earlier site is planned — a teaser page, “we ‘will be there soon’” style. Purpose: presence before launch.

The main site repo (OQ-025) is the full presentation site. Its content requirements (author’s list, recorded verbatim in intent):

  • Beautifully present the project: what Fera is
  • Highlight the qualities (ergonomics, speed, lightweightness — NAMING-UX §2.5 tagline territory)
  • Show benchmarks (publish results; fera-scene-v0 results once they exist)
  • An email field (collect interested users)
  • A link to our Telegram (community channel)
  • Photos/screenshots of the client

What would resolve it: building the teaser when the author says start; building site when there is something to show (screenshots, benchmark numbers).