Furth FortuneM9 reference

Assurance fabric / System overview

Persistence

From typed value to durable storage

The persistence path encodes typed rows, stores large objects by key, records pending writes, and exposes ordered committed changes.

Inside the system

Architecture plates

Each plate preserves the internal layout, paths, boundaries, and highlighted decisions. Use the short label first, then follow the lines through the system.

Plate 01

One write, five owners, and exactly one encode

Trident · MerBuf · MerDB · MerFS · device

OWNER STEP THE OTHER CONSUMER OF MerFS Tridenttrident_rt.c db.entity.save(rec) the type comes from the record's own MbSchema Tridentthe dirty bits only the dirty columns dict_idx_dirty picks one MbDelta per edited column MerBufmerbuf_col.h mbcol_encode_planned canonical column bytes plus the schemaHash fingerprint MerDBmerdb_engine.c merdb_save · merdb_update_op md_next_id mints the id, md_store_key builds the table key MerFSmerfs_store.h a MerFsStore adapter, 10 hooks file, segment, sparse or block: nothing below here knows a schema devicethe medium bytes on the device a file tree, a volume, a raw extent, or OPFS inside a browser device.storage.* 5 verbs: upsert get len delete list a MerFsBlobStore bound on the runtime TRIDENT_RT_CAP_DEVICE_STORE gates it dev_scope_key prefixes every key no MerDB engine on this leg at all THE PROCESS THAT HOLDS BOTH fathom_host_fathom_dataplane.c opens the engine with merdb_open and the MD_DEPLOY_DURABLE profile. MerDB is a library, not a service. One process holds the host, Trident, the engine, and the store. The same MerBuf bytes serve every tier. No tier re-encodes them.
Plate 02

The format decision: two typed answers, and one narrow door for JSON

defined entity · dynamic MerBuf · the interop boundary

THE QUESTION a value to persist or to exchange between two MerTek parts Ask: is the shape declared? THE DESTINATION a defined entity the schema is known db.entity.defineEntity + defineProperty MD_STORE_STRICT refuses an undeclared, unindexed column and logs the refusal a dynamic MerBuf the shape is open or per instance merbuf.new + merbuf.encode / decode db.dyn is MD_STORE_OPEN and keeps every saved column verbatim interop.json.* ONLY a non-MerTek system reads it 8 C kernels in JSON_FN_TABLE: parse · parseLoose · parseOpts · parseToNative stringify · stringifyOpts · pretty · toNative WHY EACH ONE Typed columns, declared indexes, a fingerprint over the canonical column set, and a sparse partial save. Still typed, still binary, still decodes without a text parse. Use it for open or per-instance data, not to avoid declaring a schema. THE DEFECT interop.json.stringify called to save internal data. JSON is a text parse, and it is untyped. Take one of the two shapes above.
Plate 03

A row, a blob, a tile, and the twenty bytes that join them

three surfaces · one substrate · the streamref handle

WHAT YOU CALL WHERE IT LANDS THE BOUNDARY IN BYTES db.entity.save(rec) typed columns, one row the MerDB row store keyed (table, id) by md_store_key db.blob.upsert(key, bytes) 5 verbs: upsert open len delete list MdBlobTable, key-ordered merfs_bt_open, on the same substrate db.stream.upsertTile(...) 7 verbs; the args are stream, coord, bytes db_stream_key composes it stream/resLevel/ry/rx/track, 10-digit coords MerDB calls merfs_bt_open with an inline_max of 256. A value of 256 bytes or fewer stays INLINE in the index leaf. A larger value spills to a chunk, and the leaf keeps only the chunk reference. So one substrate carries a scalar row, an index posting, and a multi-gigabyte tile. THE HANDLE THAT JOINS A ROW TO ITS BYTES db.stream.ref(streamId, codecId, extent0, extent1) builds one MbStreamRef a stream column MB_STREAM_BYTES is 20; the row holds the handle, not the bytes REFUSED a stream id over 400 characters: the tile-key suffix would truncate THE WORKED INSTANCE: THE MEDIA STORE system/media/media_asset key, assetRef (stream), status system/media/media_level FK assetUid, one row per level db.stream object media/<key> tiles at coord {resLevel: level}
Plate 04

One dirty bit, three custodians, and the edit a failover must not lose

set the bit · merge the delta · clear on the acknowledgement

1 · TRIDENT SETS THE BIT dict_mark_dirty_key sets one byte in Dict.dirty A user write marks the column: o.f = v and u[k] = v, in the tree-walker and in the VM. A mint or a decode uses plain dict_set, so a fresh draft or get starts clean. 2 · MerDB MERGES THE DELTA merdb_update_op takes an MbDelta list op 0 sets a scalar column. op 2 appends one array element. op 3 removes every equal one. The engine leaves an omitted column untouched, so two disjoint writes both survive. 3 · FATHOM CLEARS ON THE ACK fo_begin_attempt(o, now) stamps the attempt watermark fo_clear_acked clears ONLY a column whose changed_at is older than that watermark. WHAT THIS BUYS An edit made during the attempt stays dirty and rides the next send. TWO FENCES ON THE SAME WRITE fo_op_apply_once(s, op_id) applies one op id once, so a replay never double-counts. sa_admit fences the epoch A promotion bumps the epoch. The fence then denies the old master. The wire op carries the op id, the changed-at stamp, the epoch, and a 48-byte field name slot.
Plate 05

The change feed: one cursor, two sources, and no silent gap

publish · the ring and the durable tail · poll and resume

THE WRITE SIDE a committed write insert, update or delete md_feed_publish stamps the next commit LSN as the version MdFeed, the RAM ring a 4096-event cap, then the oldest evicts md_feed_durable_append one _sys_feed row per event, pk = seq THE READ SIDE merdb_subscribe(db, scope, fromCursor) returns a subscription id merdb_poll(db, sid, ...) 1 · the durable tail, when the cursor sits behind the ring 2 · then the RAM ring itself neither serves it: loud resync THE CURSOR IS THE RESUME TOKEN IT RESUMES A subscriber behind the RAM ring replays from _sys_feed instead of a full resync. OR IT RESYNCS, LOUDLY A cursor ahead of the head, or below the reclaimed floor, sets resync and restarts from head. THE TAIL STAYS BOUNDED md_feed_gc deletes only at or below the lowest active cursor, and always keeps the last 4096 events. THE TRIDENT SURFACE db.entity.subscribe · poll · unsubscribe · feedHead · feedRetention db.dyn carries the same five verbs over the open surface
Plate 06

Durability, the fsync order, and a shard map that is declared data

the crash window · the sync order · path ownership

THE CRASH WINDOW, ON THREE AXES 250 ms merdb_tick, the time cadence 1000 writes MD_DURABLE_FSYNC_OP_EVERY 4 MB MD_DURABLE_FSYNC_BYTES_EVERY THE ACCEPTED LOSS A host crash inside the window drops the acked writes that the last fsync did not cover. merdb_persist_fsync SYNCS IN THIS ORDER 1 · derived indexes md_post_fsync_all 2 · externalized values md_blob_fsync_all 3 · the feed seq high-water md_flush_feed_seq 4 · the catalog, then the rows md_flush_tables, then the adapter WHY THIS ORDER A dependent artifact reaches disk before the commit point names it. THE SHARD MAP IS DECLARED DATA shard_map.pathOwnership a MerTekStak MerDB row, one entry per owned path prefix CrOwnerMap · cr_resolve a prefix binds to a node SET at any depth; prefixes may nest; no node is an exclusive owner cr_merge_lww the present record with the highest version wins THE GRANULE IS THE RECORD The merge drops every column of the record that loses.
Plate 07

What is still open, drawn as open

what ships · what the design of record leaves open

WHAT SHIPS WHAT IS NOT SETTLED array deltas: append and remove db.entity.arrayAppend is delta op 2. db.entity.arrayRemove is delta op 3. Each applies to the row the engine holds. [decided, not built] per-element order keys A fractional index would give an insert at any position in O(1), and reduce the delta to one add of {element, orderKey}. a read on the node that took the write MerDB runs one owner per node and holds no store locks, so two writes on one node serialize instead of racing. [open] cache consistency for application data Eventual by default. The open work is to name any data that needs a stronger guarantee, and to declare it rather than assume it. an explicit baseline in orm.tri ormChanges(baseline, working) diffs two records. ormSave writes it with db.entity.updateProps. ormSaveCas writes it with db.entity.updateIf. [open] ORM depth Carry the baseline with the entity, and wire orm.tri to the acknowledge-clear lifecycle in fathom_failover_outbox.h.

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.

One encoding decision

A value is encoded once through the approved data model. Storage services do not invent new meanings for the same record.

Specific example

A mission report keeps the same shape whether it is held in memory, saved locally, or moved to a service.

Rows and large objects

Small records and large media can be stored in suitable forms while remaining linked by stable identity.

Specific example

A sensor event can point to a large image without placing the whole image inside the event record.

Durable change tracking

The platform records pending work and ordered changes so recovery does not silently drop an accepted edit.

Specific example

A disconnected node can preserve a maintenance update and continue its change stream after recovery.

Uses

Example uses

Pilot questions

What the team must decide

Which accepted changes may never be lost

How large media relates to mission records

What recovery behavior must be demonstrated

Request a technical briefing