Furth FortuneEngineering reference
TECHNICAL DOCUMENTATION

2028 · MerTek systems

On this page

Core system / System overview

MerFS

The device filesystem with no ambient root

MerFS is the filesystem a BenthOS device runs, its own engine apart from the MerDB storage that hosted deployments use. An application reaches files only through the granted scope the device handed it. A new file version takes its name only after its complete body and metadata pass their checks.

Worked example

A pump controller that never boots on half a schedule

A water utility runs 300 pump-station controllers on BenthOS. Each night the operations center sends every station a new pump schedule of about 180 KB. The stations sit on rural power lines and lose power several times a week, sometimes in the middle of a transfer.

  1. At first boot the device’s authority service creates a pump-app scope and grants it to the pump application. The grant carries a 32 MiB quota, a 256-file limit and a 2-second volatility ceiling. The application holds that handle and nothing above it.

    merfs_status grant_pump_app(merfs_system *system,
        const merfs_scope *pump_scope,
        const uint8_t grant_id[MERFS_IDENTITY_BYTES], merfs_scope **pump_app)
    {
        merfs_grant_options grant;
        merfs_publication_options durable_now;
        merfs_grant_options_default(&grant);
        memcpy(grant.identity, grant_id, MERFS_IDENTITY_BYTES);
        grant.max_logical_bytes = UINT64_C(32) << 20;  /* 32 MiB of files */
        grant.max_files = 256u;
        grant.max_delay_ns = UINT64_C(2000000000);     /* at most 2 s volatile */
        grant.max_open_files = 16u;
        grant.max_builders = 2u;
        merfs_publication_options_default(&durable_now);
        durable_now.max_delay_ns = 0;
        return merfs_system_grant_scope(system, pump_scope, &grant, &durable_now,
            pump_app);
    }
  2. Each night the application first opens the schedule it is about to replace. The open returns an observation token naming the exact version it saw, and the handle reads that version and no other.

    merfs_status read_schedule(merfs_scope *pump_app, uint8_t *out,
        size_t capacity, merfs_observation *seen)
    {
        merfs_file *file = NULL;
        uint64_t offset = 0;
        size_t got = 0;
        merfs_status status = merfs_file_open(pump_app, "schedule", 8u, seen,
            &file);
        if (status != MERFS_OK) return status;
        if (merfs_file_body_bytes(file) > capacity) {
            merfs_file_close(&file);
            return MERFS_LIMIT;
        }
        while (status == MERFS_OK && offset < merfs_file_body_bytes(file)) {
            status = merfs_file_read_body(file, offset, out + offset,
                capacity - (size_t)offset, &got);
            offset += got;
        }
        merfs_file_close(&file);
        return status;
    }
  3. The application stages the new schedule in 16 KB chunks under the same name, then commits with the token. The complete body must validate as one Rune document before a single checked root publication gives it the name. With a zero volatility window, the commit returns only after that root is durable.

    merfs_status replace_schedule(merfs_scope *pump_app,
        const merfs_observation *seen, const chest *schedule,
        const chest_source *metadata, merfs_completion *done)
    {
        merfs_builder *builder = NULL;
        merfs_publication_options durable_now;
        const uint8_t *bytes = (const uint8_t *)chest_bytes(schedule);
        size_t size = chest_size(schedule), sent = 0;
        merfs_status status = merfs_builder_begin(pump_app, "schedule", 8u,
            MERFS_PUBLISH_REPLACE, seen, metadata, &builder);
        if (status != MERFS_OK) return status;
        while (sent < size) {
            size_t step = size - sent < 16384u ? size - sent : 16384u;
            status = merfs_builder_append(builder, bytes + sent, step);
            if (status != MERFS_OK) {
                merfs_builder_abort(&builder);
                return status;
            }
            sent += step;
        }
        merfs_publication_options_default(&durable_now);
        durable_now.max_delay_ns = 0;  /* return only after the new root is durable */
        return merfs_builder_commit(&builder, &durable_now, done);
    }
  4. Station 117 loses power 72 KB into the transfer. On reboot MerFS mounts the newest complete durable root. The staged chunks were never reachable from it, so the old schedule answers under schedule and the partial one has no name at all.

  5. One night a technician’s console and the operations center both publish a schedule. The later commit carries a token for a version that is no longer the newest. MerFS returns a conflict instead of overwriting, and the application rereads before it decides.

    merfs_status update_schedule(merfs_scope *pump_app, const chest *schedule,
        const chest_source *metadata, uint8_t *scratch, size_t capacity)
    {
        merfs_observation seen;
        merfs_completion done;
        merfs_status status = read_schedule(pump_app, scratch, capacity, &seen);
        if (status != MERFS_OK) return status;
        status = replace_schedule(pump_app, &seen, schedule, metadata, &done);
        if (status == MERFS_CONFLICT) {
            /* a newer schedule was published after our read: reread, decide again */
            status = read_schedule(pump_app, scratch, capacity, &seen);
        }
        return status;
    }
What happened

Station 117 ran the previous night’s schedule until the next transfer replaced it normally. No controller in the fleet can start its pumps from a partial file, because a partial file never has a name. No application can reach the boot manifest by spelling a longer path.

System diagram

One key across storage backends

The application record stores a stable object key. Storage policy can move the bytes without changing the record.

Object key sha256:8a2… / 14 MB image
Edge cache encrypted local file
Object store durable remote copy
Memory store test fixture
MerFS contract put • get • exists • remove

Inside the system

How the parts work together

Follow the data, decisions, and access boundaries. Each diagram explains a concept used by applications.

Map 01

Granted scope and checked publication

An application starts from the scope it was granted. A staged version takes its name only after its complete body and metadata validate.

  1. Granted scope explicit authority
  2. Named candidate private staged body
  3. Validation complete body + metadata
  4. Published version visible only after checks
Map 02

Hosted and device storage are separate

Hosted MerDB persists through its own host-storage provider, and MerMount projects selected Store rows as read-only files. MerFS serves BenthOS devices and depends on neither.

  1. Hosted MerDB host-storage provider
  2. MerMount optional read projection
  3. MerFS device file system
  4. BenthOS device operating system

Key parts

What the system does

These are the main boundaries, inputs, outputs, and failure rules. The examples show a specific use of each part.

Granted scope, no ambient root

Every file call starts from a scope handle the device’s authority service granted. A name is a single component: no .., no slashes, no drive letters and no working directory, so a longer spelling never reaches more.

For example

The pump application can open schedule in pump-app. Asking for ../boot refuses as a malformed name before any lookup happens.

A version appears only when complete

A writer stages the body privately and then commits. The body and the metadata must each validate as one complete Rune document before a single checked root publication gives the new version its name. Readers that opened the old version keep reading it.

For example

A 180 KB schedule cut off at 72 KB stays a private candidate. After reboot it is gone and the old schedule still answers.

Separate from hosted storage

MerFS keeps a device’s files. Hosted MerDB persists through its own host-storage provider, and MerMount shows selected Store rows as read-only native files. Neither of them uses MerFS, and MerFS uses neither.

For example

The utility’s office servers run MerDB on Linux with no MerFS volume, while every pump controller boots from one.

Code

MerFS in code

Each example is short and complete enough to read on its own. Hover a dotted word for its definition.

C

Create a file only if the name is free

The observed absence is the condition. If another writer creates run-log first, this commit conflicts instead of replacing it. A 0.5 s volatility window lets the log become visible before it is durable.

merfs_status create_run_log(merfs_scope *pump_app, const chest *body,
    const chest_source *metadata, merfs_completion *done)
{
    merfs_observation absent;
    merfs_builder *builder = NULL;
    merfs_publication_options within_window;
    merfs_status status = merfs_scope_observe(pump_app, "run-log", 7u, &absent);
    if (status != MERFS_NOT_FOUND) return status;  /* the name must be free */
    status = merfs_builder_begin(pump_app, "run-log", 7u,
        MERFS_PUBLISH_CREATE, &absent, metadata, &builder);
    if (status == MERFS_OK)
        status = merfs_builder_append(builder, chest_bytes(body), chest_size(body));
    if (status != MERFS_OK) {
        merfs_builder_abort(&builder);
        return status;
    }
    merfs_publication_options_default(&within_window);
    within_window.max_delay_ns = UINT64_C(500000000);  /* durable within 0.5 s */
    return merfs_builder_commit(&builder, &within_window, done);
}
C

Names are components, never paths

Lookup is byte-exact and case-sensitive. A name that tries to climb out, carries a path separator, or holds invalid UTF-8 refuses with MERFS_ARGUMENT.

int names_stay_inside(merfs_scope *pump_app)
{
    merfs_observation seen;
    /* one component per name: each of these refuses MERFS_ARGUMENT */
    return merfs_scope_observe(pump_app, "..", 2u, &seen) == MERFS_ARGUMENT &&
        merfs_scope_observe(pump_app, "../boot", 7u, &seen) ==
            MERFS_ARGUMENT &&
        merfs_scope_observe(pump_app, "logs\\2028", 9u, &seen) ==
            MERFS_ARGUMENT;
}
C

List a scope from one pinned generation

An enumerator pins the structure it opened on and walks names in order. A file created mid-listing shows up in the next listing, never halfway through this one.

void list_scope(merfs_scope *pump_app)
{
    merfs_enumerator *names = NULL;
    char name[128];
    size_t length = 0;
    merfs_entry_kind kind;
    merfs_observation seen;
    if (merfs_enumerator_open(pump_app, NULL, 0, &names) != MERFS_OK)
        return;
    while (merfs_enumerator_next(names, name, sizeof name, &length, &kind,
            &seen) == MERFS_OK)
        printf("%s %.*s\n", kind == MERFS_ENTRY_SCOPE ? "scope" : "file",
            (int)length, name);
    merfs_enumerator_close(&names);
}
C

Retire a file without breaking its readers

Retire takes the name out of later lookups and needs the token for the version being retired. Handles already open keep reading until they close, and the space comes back later through reclamation.

merfs_status retire_previous(merfs_scope *pump_app,
    const merfs_observation *seen, merfs_completion *done)
{
    merfs_publication_options durable_now;
    merfs_publication_options_default(&durable_now);
    durable_now.max_delay_ns = 0;
    return merfs_retire(pump_app, "schedule-previous", 17u, seen,
        &durable_now, done);
}
C

Durable now, or durable within a window

Visibility and durability are separate facts. Inside the window a publication may be visible before it is durable, and a crash then returns the last durable root, never a torn file. The device can always see the gap between the two generations.

merfs_publication_options durable_now, within_window;
merfs_publication_options_default(&durable_now);
durable_now.max_delay_ns = 0;                      /* commit waits for a durable root */
merfs_publication_options_default(&within_window);
within_window.max_delay_ns = UINT64_C(500000000);  /* visible now, durable within 0.5 s */

/* nonzero once every visible publication is also durable */
int caught_up = merfs_volume_visible_generation(volume) ==
    merfs_volume_durable_generation(volume);

Uses

Example uses

  • Schedules and configuration on unattended devices
  • Per-application file storage on BenthOS devices
  • Logs and media kept on device flash

Review questions

What the team must decide

Which scopes each application needs, and what quotas each one carries

Which files must be durable at commit and which can use a volatility window

Which data belongs in files and which in the device’s record store

Contact us
FURTH FORTUNE / ENGINEERING REFERENCEBack to top ↑