Furth FortuneEngineering reference
TECHNICAL DOCUMENTATION

2027 · MerTek technology

On this page

Platform / System overview

MTK

One application source, run on every device

MTK is the platform MerTek applications run on: Trident source and configuration at the center, a C runtime underneath. Trident holds the behavior, MerDB and Rune hold the data, Thalassa and Nautilus handle scenes and assets, and Keel does the math. Fathom, the host side, connects the program to files, networks, devices and other nodes.

Worked example

A new tag-out rule, from one edit to two devices

The Port of Halverson runs 14 ship-to-shore cranes. Maintenance adds a rule: a crane leaking hydraulic fluid faster than 2 L/h is tagged out before its next lift. Crews read crane status on tablets, and the berth planning system asks a yard service over HTTP. Both must give the same answer from the moment the rule ships.

  1. The rule is one Trident edit. The engineer adds a TaggedOut member and one branch to the cranes module. Each limit is a measure that carries its unit, so 2L/h compares only with another flow rate.

    enum CraneStatus { Ready = 1, Watch = 2, TaggedOut = 3 }
    
    const measure watchLeak = 0.5L/h:volumetricFlow
    const measure tagOutLeak = 2L/h:volumetricFlow
    
    CraneStatus statusFor(measure leak) {
        if (leak > tagOutLeak) {
            return CraneStatus.TaggedOut
        }
        if (leak > watchLeak) {
            return CraneStatus.Watch
        }
        return CraneStatus.Ready
    }
  2. The compiler checks every screen at once. The tablet's badge function still branches on only Ready and Watch, so the build stops at that when and names the missing member. A status no screen handles never reaches a crew.

    $ tri --test craneinspect
    craneinspect/a/cranes/badge.tri:2:12: refused [S.when] an enum `when` names every live member of its enum, and no arm names `TaggedOut`
      2 |     return when (status) {
        |            ^^^^^^^^^^^^^^^
    tri: falseStart: compile
    tri: 1 refusal in craneinspect
    tri: test: a/cranes/_test/status.tri: refused
  3. Named cases pin the rule. A source test sits in an _test/ folder beside the module it checks, and the comment at its top lists typed cases. tri --test runs main once per case against the same compiled program, and the test never ships.

    #{
      u16 schemaVersion = 1
      chest[] cases = [
        {
          text name = "dry crane is ready"
          chest arguments = { measure leak = 0L/h:volumetricFlow, text expected = "Ready" }
        },
        {
          text name = "slow seep is watched"
          chest arguments = { measure leak = 1.2L/h:volumetricFlow, text expected = "Watch" }
        },
        {
          text name = "fast leak is tagged out"
          chest arguments = { measure leak = 2.4L/h:volumetricFlow, text expected = "TaggedOut" }
        }
      ]
    }
    bool main(measure leak, text expected) {
        cranes.rules.CraneStatus status = cranes.rules.statusFor(leak)
        assert (status.name() == expected, "crane status for leak rate")
        return true
    }
  4. The launch checks the program's reach against its grant. The compiler lists every library the program calls. The yard service's grant revokes outbound HTTP and process launch. A later edit that tries to call out stops at launch with a named refusal.

    # _grant/grant.draft
    # The yard service answers requests and calls out to nothing.
    u16 schemaVersion = 1
    text[] revoke = ["t.net.http", "t.proc"]
  5. The same source runs on both devices. The crew tablet runs it under tri with a window and shows the color badgeFor returns. The yard service runs it under tri --headless and answers the berth planner through a declared endpoint, whose parameters the runtime checks against their types before the handler runs.

    listener({ port = 8080 })
    
    on request({
        method = "GET"
        pattern = "/cranes/{id}/status"
        parameters = [
            { name = "id", source = "path", typeSpelling = "u32" },
            { name = "leak", source = "query", typeSpelling = "measure" }
        ]
    }, const p) {
        u32 crane = p.id
        measure leak = p.leak
        cranes.rules.CraneStatus status = cranes.rules.statusFor(leak)
        chest body = { f64 crane = f64(crane), text status = status.name() }
        return t.net.http.serve.json(200, [], body)
    }
What happened

The rule reached the tablets and the yard service as one source generation. The compiler stopped the build at the badge that did not handle TaggedOut, three named cases pinned the 0.5 and 2 L/h limits, and the grant kept outbound HTTP closed. When the berth planner sent a leak rate in millimetres, the service answered with an error instead of a wrong status.

System diagram

One application contract across the MTK fabric

Application behavior sits at the center. The shared systems surround it, and every device runs the same types, data and rules.

Application contract .tri source + configuration
Trident behavior • types • tasks
Data MerDB • Rune
Render + assets Thalassa • Nautilus
Compute + host Keel • Fathom
Deployment edge
WatchTabletDesktopHeadsetService

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

Where a new capability belongs

Sort by reuse and ownership before work begins.

Reuse / ownership
Shared mechanism
Packaged experience
Reusable
Core runtime mechanism
Wheelhouse shared authoring
application-specific
Application application behavior
Product delivered workflow
Map 02

Three task routes through the reference

Each route crosses a different set of systems and checks.

Capability
  1. 01System boundaries
  2. 02Trident
  3. 03Evidence
Render
  1. 01Nautilus
  2. 02Thalassa
  3. 03Debug views
Data
  1. 01MerTekTypes
  2. 02Rune
  3. 03MerDB

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.

Source and configuration at the center

An application is one folder: Trident source under a/, identity and settings documents under _app/, stored collections under _db/, and the host's lend under _grant/. Those files are the whole application source, and every tool edits them through the same checks.

For example

The crane rule lives in a/cranes/rules.tri, the berth name in _app/config/yard.draft, and the network access in _grant/grant.draft.

Shared systems do the heavy work

MerDB stores records, Rune encodes them, Thalassa renders, Nautilus imports assets, Keel computes and the host reaches the operating system. Each takes its parameters from Trident and holds no application rule of its own.

For example

The 2 L/h limit is a Trident constant. Storing a reading and drawing the red badge happen in MerDB and Thalassa, which hold no crane-specific code.

One source, many hosts

A host composes the runtime for its device: tri with a window on a tablet or desktop, tri --headless for a service, tricli for a one-call check. The program, its types and its grant rules are the same under each.

For example

The crew tablet and the yard service run one source generation. Thalassa sizes the badge for each screen, from a watch face to a headset, and the service returns the same status as JSON.

Code

MTK in code

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

Trident

Every screen handles every status

The badge function names all three members, so it compiles. Its sibling module is reached by a relative path, rules, with no import list.

text badgeFor(rules.CraneStatus status) {
    return when (status) {
        is rules.CraneStatus.Ready { use "green" }
        is rules.CraneStatus.Watch { use "amber" }
        is rules.CraneStatus.TaggedOut { use "red: do not lift" }
    }
}
Chest

Site facts live in a typed document

A settings document under _app/config/ holds facts that differ per deployment. Every line states its type, so a typo in a value is refused when the deployment compiles. Safety limits stay in reviewed source.

# _app/config/yard.draft
# Site facts for this deployment. Safety limits live in the source.
text berth = "Berth 4"
u32 cranes = 14
Trident

The service reads its settings once

A session's main runs once and returns the state every later call reads. It checks the settings document with expect, so a missing or mistyped field stops the launch with a named fault.

chest main(text[] argv) {
    chest? found = t.app.document("config.yard")
    if (found == null) {
        fail "no yard settings document"
    }
    chest yard = found
    expect { text berth, u32 cranes } = yard else {
        fail "yard settings refused: berth or cranes is missing"
    }
    chest state = { text berth = berth, u32 cranes = cranes }
    return state
}
Shell

Two hosts, one folder

The tablet and the service run the same folder; only the host invocation differs. The service answers with the status the shared rule computed.

$ tri craneinspect               # crew tablet: a windowed session
$ tri --headless craneinspect    # yard service: no window

$ curl "http://127.0.0.1:8080/cranes/7/status?leak=2.4L%2Fh%3AvolumetricFlow"
{"crane":7,"status":"TaggedOut"}
$ curl "http://127.0.0.1:8080/cranes/3/status?leak=0.2L%2Fh%3AvolumetricFlow"
{"crane":3,"status":"Ready"}
Shell

A wrong unit is a fault, not a status

The planner sent millimetres. The rule's comparison refused to compare a length with a flow rate, and the request got a 500. The service's diagnostic names the rule, the line and the value.

$ curl -i "http://127.0.0.1:8080/cranes/3/status?leak=7.5mm"
HTTP/1.1 500 Response
content-type: text/plain

handlerFault

# the service's diagnostic stream
a.cranes.rules:7:14: fault [O.rel-m] a comparison reads one dimension, and its operands hold two: 7.5mm and 2<(1/3600000)*m3/s>:volumetricFlow
  note: a.main:13:61: arguments of `a.cranes.rules.statusFor`: 7.5mm
  at a.cranes.rules.statusFor (a.cranes.rules:7:14)
  at GET /cranes/{id}/status (a.main:13:61)
Shell

Test cases run one by one

Each case has its own name and its own fresh runtime. When an expectation is wrong, the failure names the case, the assertion and both values.

$ tri --test --list craneinspect
a/cranes/_test/status.tri dry crane is ready
a/cranes/_test/status.tri fast leak is tagged out
a/cranes/_test/status.tri slow seep is watched

$ tri --test craneinspect
tri: test: a/cranes/_test/status.tri slow seep is watched: failed
a/cranes/_test/status.tri:20:5: fault [contract] assert 'crane status for leak rate' failed in main: status.name() == expected with status.name() = "Watch", expected = "Ready"
  at main (a/cranes/_test/status.tri:20:5)

Uses

Example uses

  • Field and depot applications that span tablets and services
  • Rules that must behave the same on every device
  • Replacing per-device codebases with one source folder

Review questions

What the team must decide

Which rules and records several devices must share

Which devices need a window and which run headless

Which libraries each deployment should lend

Contact us
FURTH FORTUNE / ENGINEERING REFERENCEBack to top ↑