YUME Internet freedom by FixCraft
On this page

Contributing to YUME

YUME is experimental security and networking software. Read the documentation map and the relevant protocol or API contract before editing a subsystem. Keep changes focused and verify the behavior they affect.

Project direction

YUME develops its own embeddable stealth transport and wire protocols. Xray, VLESS, and REALITY are comparison subjects, not implementation sources or compatibility targets. Keep the independent implementation and declared dependency boundary described in Why YUME.

Review changes in the order security, stealth, then usable speed. Stealth is part of protecting users, since a server that can be recognized can be found and blocked. Never weaken authentication, confidentiality or resource bounds for stealth or speed, and never weaken stealth for speed. A comparison must preserve authentication, channel binding, ratchet limits, authorization, and resource bounds; weakening those to improve a score changes the product being measured. Record an intentional behavior change and its evidence rather than calling it a behavior-preserving cleanup.

YUME is a reusable transport. Applications own their account, device, enrollment and business rules outside YUME. Expose a missing general transport capability through a cohesive interface with tests; do not add consumer-specific branches, names, policy or build dependencies to the engine. An application should integrate through configuration and supported interfaces without patching YUME’s source. This is a design requirement; the experimental ABI does not yet provide every required capability.

The source graph is in SOURCE_MAP.md, and the contracts and rationale are in ARCHITECTURE.md. YUME builds native YTP/1. Transport v2, the protocol before it, has been removed, and commit 9070b0a is the last that contains it. Read a planned feature’s old implementation there before rebuilding it natively.

Decision and review discipline

Before editing, state the requested outcome and check its premise against live source, callers and tests. Separate observed behavior, product requirements, proposals and test results. Agent notes and previous plans cannot establish owner approval. Explain an unfamiliar term by its operation and consequences before asking the owner to decide about it. Make routine engineering decisions within the authorized scope.

Challenge an incorrect premise or a poor solution whether it came from the owner, another contributor, an agent, or your own draft. Give concrete evidence, explain the consequence in plain language, and recommend a sound alternative. An informed owner decision may change direction; record its actual trade-off at the relevant contract. Do not invent agreement or treat uncertainty as proof.

Source and executable tests establish what happens, not what ought to happen. When an implementation violates an intended security or lifecycle requirement, fix the defect or explicitly resolve the design conflict. Do not weaken the requirement, expected result, or test fixture merely to make the code pass. Review the final diff against the original purpose as well as the tests.

For a disputed feature or interface, identify its behavior, actual callers, intended requirement and validation path. Then choose direct reuse, a tested port, temporary retention for a named development need, or removal. Past effort alone justifies neither retaining an implementation nor rewriting it.

A feature starts from a recorded owner requirement and its contract page. State the interface first, or in the same change as the code: the protocol, configuration, ABI, control socket, command line or module page it changes. Tests reject a configuration key, command-line option, control request or error code, ABI export or wire label that its page does not state.

Distinguish dead-code removal from deliberate retirement. Prove unreachability through callers, guards, the signed baseline and relevant integration tests before calling a path dead. scripts/check_reachability.py finds functions no shipped program reaches, and config/reachability.json lists the code kept on purpose, each entry with its reason. Reachable code can be retired by an authorized change that updates controlled callers and preserves required behavior. Record rejected removals and disproved claims once at their topic owner.

Keep ownership and dependency direction explicit. Split code at a demonstrated trust, lifetime, dependency or testing boundary. Moving a large class across files does not separate its state ownership. Give one contract one owner; independent copies of its validation or policy are defects. Distinct trust or state types may intentionally have identical fields. A smaller file, a familiar competitor feature, or a passing build alone is not an architectural benefit.

Build

Native Linux applications require libsystemd development headers and libraries (libsystemd-dev on Debian/Ubuntu). The managed TUN adapter uses rtnetlink for addresses/routes and systemd-resolved’s D-Bus API for per-link DNS. DNS-free TUN configurations do not require a running resolved service. The C ABI and engine do not link the Linux network manager.

The module libraries (YUME_BUILD_BASEFWX_MODULES=ON) build the pinned BaseFWX checkout and need liboqs with ML-KEM-1024. yume, yumed and the C ABI never link them.

The desktop GUI (YUME_BUILD_GUI=ON) needs Qt 6.4 or later: on Debian and Ubuntu qt6-base-dev and qt6-declarative-dev, and at run time the QtQuick Controls, Dialogs, Layouts, Shapes, Templates and Window QML modules. It links no YUME library, and nothing else links Qt. Keep it to Qt 6.4 interfaces, which CI’s Ubuntu 24.04 image provides.

The normal developer build is:

./ezbuild.sh --tests

Tests are off by default; omit --tests only when you do not intend to run ctest. A test build also fetches the Node.js release the cover tools pin, checked against its SHA-256 by scripts/ensure-node.sh, and their tests fail with the reason on any other Node.

This prepares YUME’s checksum-pinned, default-off patched OpenSSL build. Build and --clean share a lock keyed by the canonical build-directory path, so relative, absolute and symlink aliases cannot run them concurrently. The Linux helper requires flock, realpath and a writable shared lock directory (YUME_LOCK_ROOT, defaulting to the user’s YUME cache). A build directory must not be the source checkout or one of its ancestors. The native application does not use BaseFWX. Only the module libraries build on its pinned checkout. A normal full CMake configuration rejects stock OpenSSL because the default openssl-chrome151 backend requires the additive capability.

Do not delete or replace an existing basefwx/ developer checkout. For module qualification, use an isolated checkout at the exact pin. ezbuild.sh leaves BaseFWX untouched.

Test

Run focused tests for the files changed, followed by the proportional suite:

ctest --test-dir build --output-on-failure -R '<focused-regex>'
ctest --test-dir build --output-on-failure
git diff --check

Use isolated build directories for the experimental shared ABI, foundation-only, module, sanitizer or static configurations. Do not reuse evidence from an older source hash as if it qualified the current candidate. Long remote matrices should run detached and write a final machine-readable summary; polling them repeatedly is not useful evidence.

YUME_BUILD_FUZZERS=ON requires Clang and selects YUME_SANITIZE=address+undefined, disables LTO, and adds fuzz coverage to source-built libraries as well as the harnesses. Use a separate build directory for TSan. Verify instrumentation in the parser objects and compile commands before treating a fuzz run as parser coverage; harness-only flags are insufficient.

Prepare a TSan build with YUME_OPENSSL_SANITIZE=thread set before calling yume_openssl_ensure. It builds the pinned, patched OpenSSL with ThreadSanitizer into its own static-only cache prefix, so TSan sees OpenSSL’s own synchronization and checks YUME’s use of it. CI’s thread-sanitize job does this.

Writing code

Write for the next reader. YUME is maintained by people and agents who often start cold, and its code is read far more often than it is written. A reader should see what a change does, which object owns each resource, and why, without knowing its history. Prefer plain control flow and names that state the domain and the unit. Comment where something surprising happens, such as an ownership transfer, a lifetime that outlives a callback, or a deliberate exception to a rule.

Stay consistent with the surrounding code. Within a file or a closely related set of interfaces, reuse its names, types and patterns, and never add a second way to do what the code already does. Moving code to a newer pattern is a change of its own. Clever constructs, such as template metaprogramming, macros, implicit conversions and default lambda captures in code that outlives its scope, need a demonstrated benefit and a comment.

Put each concern where it is owned. New code goes into the layer and module that owns its responsibility, as the source map and cmake/YumeLayering.cmake define. Separate parsing, policy, state changes, I/O and presentation when they change for different reasons, and give modules narrow, typed interfaces. These rules apply to every change, however small.

Errors and exceptions

YUME uses C++ exceptions. Google’s style guide bans them because its existing code is not exception-safe. YUME’s code must be, and these rules keep its control flow readable:

  • Expected outcomes are values. When the code that detects a failure, or its caller, decides what happens next, as for malformed or hostile peer input, invalid configuration, a refused peer, a timeout, a closed stream, a missing file or an exhausted limit, return it as engine::Status or engine::Result, a codec status or a std::error_code. The protocol codecs (src/ytp, src/circuit) and the session engine (src/engine) handle all peer input this way. Never ignore a returned status. New functions that return one are [[nodiscard]].
  • Exceptions abandon an operation. Throw only when no nearby caller can continue: allocation failure, a broken invariant, an error from a throwing library, or a failure that must unwind one command, request or message to the boundary that owns it. The relay module keeps its established pattern of throwing its own error type for a malformed record and converting it at its API. Do not introduce that pattern in other modules.
  • Every function is exception-safe. RAII releases what it acquired. A function that changes lasting state builds the new state first and publishes it in a step that cannot fail. Adding a throw means checking every caller between it and the boundary that catches it.
  • Boundaries contain them. The C ABI, destructors and C destroy functions, noexcept functions, thread entry points, event-loop completion handlers and callbacks into user or module code catch everything and convert it into their typed status, refusal or exit code. Use error-code overloads on cancellation and close paths that cannot report failure. Catch by reference, and record or translate the failure in every catch (...).
  • No hidden recovery. Never use an exception for ordinary control flow, swallow one, or continue with state the failure may have half-changed. ABI, transport and lifecycle failures reach their callers as typed statuses, and nothing parses human error text to recover one.

C++, API, and naming

Use .clang-format for changed lines and .clang-tidy for focused analysis. Avoid reformatting unrelated code. For example:

git-clang-format --diff
clang-tidy -p <build-dir> src/path/to/file.cpp
  • For new or meaningfully touched YUME code, use PascalCase for types, enums, and enum values; snake_case for functions and methods; kPascalCase for constants; ALL_CAPS for macros; and a trailing underscore for members. Preserve constructors, external overrides/APIs, C ABI names, CLI/JSON/wire fields, and coherent untouched interfaces. Do not use this rule to justify a mass rename or full-tree formatting pass.
  • Prefer clear subsystem names over generic Manager/Runtime names at public boundaries; within an existing namespace, follow the established type name.
  • Use std::size_t for counts, fixed-width integers for wire fields, and std::chrono for durations and time points.
  • Validate externally supplied JSON roots and field types before dispatch.
  • The product version, YTP/1, config schema 1, the ABI, the evidence profile and the relay channel protocol are different identifiers. Preserve existing versioned cryptographic domains and wire labels, and add new domains for intentional protocol migrations.

The following predeployment rule applies while YUME has no deployed users. Before the first deployment to real users, replace this exception with an explicit policy naming the supported interfaces and their migration obligations. Do not infer deployment status from an old instruction, memory or version label.

Do not add aliases, fallback dialects, silent defaults or no-op options for hypothetical callers. Update controlled readers, writers and tests together. A temporary compatibility path must name its verified consumer and retirement condition; a source-level caller is no release or ABI freeze. New features and planned work still need deliberate design decisions. An old name or an uncalled function alone does not prove that a feature is obsolete.

Experimental replacement ABI changes

The role-neutral ABI v1 surface is an experimental build-tree candidate. It is not the stable installed boundary and may break before its endpoint, stream, packet, and clean-prefix gates pass. Every change still updates and tests all of:

  1. include/yume/yume.h declarations and behavioral comments;
  2. src/abi/yume.map export control;
  3. candidate debian/libyume1.symbols metadata without emitting an ABI package;
  4. the exception-contained implementation;
  5. strict C/C++ ABI tests, buffer sizing, lifecycle, and error paths;
  6. docs/ABI.md.

Record ownership and behavioral breaks explicitly even while compatibility is unfrozen.

Documentation and review

Update comments and operator-facing docs in the same patch as behavior. Put normative wire requirements under docs/protocol/; current support boundaries in docs/IMPLEMENTATION_STATUS.md; and release notes under docs/release/. Search CLI help, man pages, release material, and manually maintained website pages for duplicate claims.

Documentation, manuals, the changelog, CLI help and figures are generated from docs/src/en_US/ and docs/diagrams/. Edit the source a generated file’s banner names, never the generated file, and run the sync, check and catalog commands in the source guide, which owns the format, figures, CLI help and locales. Review hand-authored site landing-page claims with each behavior change.

Keep public docs focused on setup, behavior, design, and supported interfaces. Keep task queues, checkpoints, run summaries, and agent handoffs in ignored private TEMP_*.md notes with explicit deletion conditions. Lasting private guides contain rules, decisions, and technical rationale, with raw evidence in private artifact storage. Public automation guidance contains only repository facts that apply to a fresh clone.

The website/ tree publishes the static project site. Edit the canonical docs/src/ source, never the generated docs/*.md or website/docs/*.md, and keep website claims within the implementation, threat, stealth, packaging, and release documents. Preserve accurate no-release and no-JavaScript defaults; enable artifact links only after the release API returns each exact expected asset.

Keep the site usable with a keyboard, narrow viewport, zoomed text, reduced motion, failed JavaScript, and unavailable GitHub metadata. Do not add secrets, captures, private paths, analytics, telemetry, remote fonts, or third-party scripts. Validate the CI and Pages documentation transformations, Jekyll build, internal links, generated routes, browser behavior, and accessibility affected by the change. A website build qualifies only the static publication surface, not runtime or security behavior.

Before handoff, review the complete diff, confirm no secrets or generated machine state are present, record tests actually run (not intended), and list every meaningful unrun gate. Before a commit, stage it and run scripts/check_repository_hygiene.py --staged, which checks whitespace in the staged files, new ones included. CI checks every committed file the same way, and each commit pushed to a branch for its message: a subject of at most 72 columns, a body wrapped at 72, a Validation: paragraph, no attribution trailers, and a signature.