Furth FortuneM9 reference

Mission foundation / System overview

MerDB

Typed data with declared access paths

MerDB stores typed records and requires applications to declare how they will read them. It rejects broad, unplanned scans and exposes ordered change streams for other services.

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

Three admitted read forms, and no fourth

read form · the verbs · the mechanism · what has no verb

READ FORM THE VERBS THAT SPELL IT WHAT THE ENGINE ACTUALLY DOES 1 · POINTby id, or by an exact key db.entity.get · fetch · getMany · count db.blob.open · db.file.stat · db.stream.tile md_slot resolves id to slot by open addressing, O(1) count reads md_table_live, a maintained number, never rows 2 · PREFIX or RANGEover the key-ordered tree db.blob.list · db.file.list · db.stream.level db.entity.range · rowsRange on an ordered index merfs_ksparse_range walks one lexicographic keyspace a page is a keyset cursor, never an offset 3 · DECLARED INDEXa name the schema declared db.entity.search · searchMulti · related rowsExact · idsPrefix · countSearch · deleteExact md_search and md_scan read the posting store only a name nothing declared returns the sentinel -1 NOT A READ FORM · THERE IS NO VERB TO WRITE IT WITH a WHERE predicate no read verb takes a predicate argument at all; the schema IS the filter a join of two entities there is no join verb; a declared relation is the one navigation between rows a full predicate scan no scan verb exists, and the internal natural-order page is unreachable from db.* an undeclared index md_search returns -1 and the bridge raises a typed BadRequest, fail-closed THE DESIGN RULE Design the access paths first, then the tables. If a read you need is not a key, a prefix, or a declared-index hit, the schema is wrong, not the query. MerDB does not grow a generic predicate scanner to make that read possible.
Plate 02

The surface you call names the contract

mode axis · lifetime axis · byte surfaces · path identity

MODE IS THE NAMESPACE, NEVER INFERRED TABLE STATE BYTES, PATHS AND TILES OVER ONE SUBSTRATE PERM · data/ TEMP · temp/<inst> STRICT OPEN db.entity.*refuses undeclared fields db.temp.entity.*the same verbs, leased db.dyn.*keeps every field verbatim db.temp.dyn.*the same verbs, leased db.blob.*key to opaque bytes db.file.*_file_meta row plus a body db.stream.*coordinate-keyed tiles one chunk storeone byte cursor, one page tree ENTITY IDENTITY IS A CANONICAL PATH: root + / + slug data/<slug>the only root an app may declare slug = lowercase(name)so identity is case-insensitive the storage key everywhererows, catalog rows, posting dirs PLATFORM-ONLY ROOTS system · metadata · _catalog · apps/… · _system · temp md_root_privileged refuses an app declaration; only platform scope may name one one subtree per app merdb_persist_open_app opens <store>/apps/<uid>/ THE BARE SURFACE IS GONE, AND A STORE IS ALSO A VALUE db.<verb> raises BadRequest the bridge names entity, dyn, blob or file instead db.perm · db.tempOf(id) · db.store(kind) a handle carries the same record verbs, with the lifetime captured
Plate 03

Four declared kinds collapse onto one keyspace

DDL verb · posting home · key shape · substrate · declare-time refusals

DECLARED BY THE SCHEMA POSTING HOME KEY SHAPE SUBSTRATE defineIndex(t, name, col)ordered, numeric range defineSearch(t, n, c, mode)prefix | token | exact exact also backs every FK defineRelation(p, r, c, fk)declares the navigation AND creates the child FK index defineUnique(t, col)an exclusive value claim _postidx_o/<pathkey>_<name> _postidx/<pathkey>_<name> search and FK share it _postidx_u/<pathkey>_<col> MULTI key = term || 0x00 || pk value empty exact = [T||00, T||01) prefix = [P, incr(P)) UNIQUE key = term, no pk suffix value = pk, 8 bytes BE a second distinct id gives MDKI_CONFLICT at once MdKIndex over mdksparse a hot window cold runs version-LWW tombstones tiered compaction REFUSED AT DECLARE TIME · AN INDEX THAT WOULD LIE IS NEVER BUILT not indexable bytes, nested merbuf, composite and measure carry no total order no i64 order key text, stream, decimal, i128 and u128 all map to key 0: use defineSearch no comparable key a stream column cannot spell a unique or exact key: every row collides a conflicting redefine the refusal names BOTH sides; the same spec is idempotent success
Plate 04

The low-cardinality discriminator trap

the trap · two correct answers · what the engine can and cannot see

THE SQL-ISM: ONE TABLE PLUS A STATUS INDEX TWO CORRECT ANSWERS WHAT THE ENGINE CAN SEE entity: event id · at · userId · status defineSearch("byStatus", "status") status is pending or done two terms over every row the posting list degenerates one term holds a huge fraction of the table, so the read walks nearly all of it, and every write still pays to maintain the posting A · DISTINCT ENTITIES audit_event and denial_event are separate, directly readable streams. Each read is a key, a prefix or a declared-index hit, and the discriminator is gone entirely. B · THE CHANGE FEED db.entity.subscribe plus poll drains a work queue in commit order, and a pending-prefix key does the same job. No status column is ever read. IT MEASURES NO CARDINALITY MerDB refuses a non-indexable type, an undeclared column, and an order key that would collapse. It counts no distinct values, so nothing in the engine refuses a status index. SO THE SCHEMA CARRIES IT A low-cardinality column stays a declared, UNINDEXED value on the row. The reader carries it back with the row that a real access path found. THE LEGITIMATE INDEX IS HIGH-CARDINALITY An owner principal, a target id, a foreign key, a tag token: one posting per value, so a lookup returns a small set. A control-plane table of a handful of rows needs no index at all. A full db.entity.range walk plus an in-code filter is the sanctioned small-table path, and it adds no posting store for every write to maintain.
Plate 05

Write policy: five deltas, one commit point

the delta vocabulary · save as a merge · the refusal cascade · exactly once

ONE WRITE VOCABULARY · FIVE MbDelta OP CODES op 0 · setsave, updateProps op 1 · incrementthe accumulator path op 2 · arrayAppendone element op 3 · arrayRemoveevery equal element op 4 · CasSetguarded by expected db.entity.save IS AN UPSERT-MERGE, DRIVEN BY THE RUNTIME DIRTY BITS a typed recordfrom draft() or get(), edited Dict.dirty marks a columnonly a user assignment marks one op-0 delta per markan untouched column is omitted mergeinto the row an existing id calls update_op a NotFound falls through to an insert at that same id an id of 0 mints a new row the engine default-fills every column the caller omitted THE REFUSAL CASCADE INSIDE ONE UPDATE · NOTHING PARTIAL REACHES THE STORE Undeclaredstrict mode sawan unknown column BadTypenever coerced tothe declared type RejectedByPolicythe column isdeclared immutable Conflict · CASan expected valueno longer matches Overflowan i64 accumulatorwould wrap Conflict · uniqueanother row alreadyclaims the value THE ONE COMMIT POINT md_store_decoded writes the mutated row, then md_feed_publish assigns the commit LSN and appends the change event. An allocation or encode failure at that boundary restores the pre-image index terms, so a row and its postings never split. EXACTLY ONCE · THE BOUNDED (clientId, seq) RING every write may carry an opId the pair (clientId, seq); the pair (0, 0) bypasses dedup entirely a per-client FIFO ring MD_DEDUP_WINDOW entries, added once, oldest evicted at the head a replay returns the first result the original minted id comes back, and an increment never double-counts
Plate 06

The change feed: one cursor, three places to read from

publish · subscribe and poll · ring, durable tail, resync · retention and restart

PUBLISH · ON EVERY COMMITTED WRITE md_feed_publishinsert · update · delete version = ++db->lsnthe engine commit LSN the RAM ringMD_FEED_CAP slots, the oldest evicted first the _sys_feed durable tailone persisted row per event, pk = the feed seq SUBSCRIBE AND POLL · THE CURSOR IS THE ONLY SUBSCRIBER STATE subscribe(scope, fromCursor) scope filters by entity path and returns a subscriber id poll(sid, maxEvents) advances the cursor past EVERY scanned event, filtered or not PAGE SHAPE { results, more, next } a keyset cursor, never an offset WHERE THE NEXT EVENT COMES FROM above evicted_through served straight from the RAM ring with no durable read at all below it, above the floor replayed LOSSLESSLY from _sys_feed, one durable point read per event below the durable floor a LOUD typed Resync; the cursor moves to head, never a silent gap RETENTION AND RESTART poll-driven tail GC deletes only at or below the lowest active cursor, and always keeps MD_FEED_DURABLE_KEEP recent events restart reseeds the cursor space next_seq = max(the durable tail, the persisted high-water), so a lost tail can never re-mint a seq a client already saw
Plate 07

Schema authority: the declaration is the durable truth

the catalog · the evolution cascade · read-time reconcile · what does not exist

THE DECLARATION IS DURABLE: _catalog/* defineProperty ON AN EXISTING COLUMN WHAT A READ RECONCILES _catalog/tablesslug · name · root · tier · pin _catalog/columnsone row per declared property _catalog/indexesone role per slug, kind, name _catalog/col_dropsthe durable read filter _catalog/configcaps · mint high-water · feed seq same type and array shape?no gives BadRequest, nothing moves nullable being tightened?yes gives BadRequest; widening passes does defaultValue match?no gives BadType, checked before a write evolve in placethe new default and the widening land default-fill on read a row written before a column existed reads that column at its declared default, so no backfill pass is ever needed drop filter on read destroyPropertyAndData tears down every index on the column, rewrites the rows, and registers the name in col_drops THREE THINGS THE SCHEMA MODEL DELIBERATELY DOES NOT HAVE no rename verb model a rename as destroy plus re-declare, so both halves are explicit and the app owns them no table UID storage is slug-keyed, so a re-declared name gives a fresh empty entity, with no tombstone no silent truncation an identifier wider than its stored field is refused, because a cut identity would alias
Plate 08

Deployment, durability, and what an open trusts

profile to adapter · the fsync order · the crash window · the clean-shutdown marker

ONE DECLARED PROFILE PICKS THE BOTTOM KEY-TO-BLOB PRIMITIVE MD_DEPLOY_EPHEMERALtest harnesses and scratch in-memoryno adapter is bound at all MD_DEPLOY_LOCALoffline or native client file-per-record<store>/<table-uid>/<id>.mb MD_DEPLOY_DURABLEcloud, Fathom, the data plane the sparse tiera hot window plus cold runs MD_DEPLOY_WEBthe JS host injects the store merdb_open_with_storeOPFS or IndexedDB MD_DEPLOY_BENTHOSa block device, no filesystem block adapterone extent and one append lane AN UNDECLARED PROFILE OPENS NOTHING merdb_open prints the refusal and leaves the engine unopened: declared policy, no defaults, fail-closed. THE ENGINE ABOVE THE SEAM IS ONE Indexes, relations, search, mint, the feed, the dedup ring and the MerBuf codec compile native AND to WASM, so query semantics are identical on every target by construction. merdb_persist_fsync SYNCS IN A FIXED ORDER, SO A RECOVERED STORE NEVER POINTS AT DATA THAT DID NOT LAND 1 · derived indexes every _postidx table, which a rebuild could refresh 2 · external values NOT rebuildable from rows, so as durable as the rows 3 · the feed high-water monotonic, so a lost tail cannot re-mint a seen seq 4 · catalog and rows the commit point lands LAST, with the row stores THE CRASH WINDOW, AND WHAT AN OPEN TRUSTS DURABLE BOUNDS THE WINDOW 250 ms since the last fsync 1000 app writes 4 MB of written bytes whichever bound trips first clean_shutdown present written last and fsync'd at close, so the durable indexes are trusted and the open stays lazy clean_shutdown absent and the store is not genesis, so the open forces an eager reload and rebuilds the derived indexes from rows

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.

Declared access paths

The application states how data will be found before it is used. The database can prepare for those needs and refuse unsafe access patterns.

Specific example

A readiness dashboard can fetch records by unit, asset, and time window without scanning unrelated mission data.

Controlled updates

Changes follow clear rules and reach a single commit point. Other services can follow the change stream without silent gaps.

Specific example

A logistics service can react when a part status changes and keep an audit trail of what it observed.

Schema as authority

The data shape is declared and reviewed. Stored information is checked against that shape instead of relying on informal field names.

Specific example

A coalition data exchange can reject a malformed readiness record before it affects planning.

Uses

Example uses

Pilot questions

What the team must decide

The questions the system must answer quickly

The data changes that require an audit trail

The behavior required during partial network loss

Request a technical briefing