Skip to content

ARCHITECTURE · 03

Concepts.

The pieces every other page in this section builds on are the three-part package, the tpkg trailer that glues it, the two press modes, the machine-wide runtime cache, and the two distinct moments at which a runtime is resolved.

Note

This section explains how to read the pages that follow.

Capabilities marked shipped are released and downloadable today, and every claim links its artifact.

Capabilities marked planned are locked designs that have not yet shipped.

The three-part package

Note

This capability is shipped.

Every tebako package is a composition of up to three independently distributable parts, a bootstrap loader, a language runtime, and one or more filesystem images, glued together by a tpkg trailer at the end of the file. The wire format is normative, and it has exactly one authoritative implementation: crates/tpkg in the product repository, #![forbid(unsafe_code)] Rust pinned byte-exact against the C99 reference header by golden vectors.

byte 0 → → EOF A · Bootstrap the Rust loader < 3 MB, CI size-gated the process entry point C · Payload slots slot 0: the app image slots 1–7: data, tools mounted at run time B · Runtime slot fat packages only format_id 4 (legacy) installed, never mounted Slot table n × 280 B records offset · size · format_id · mount point Ext blocks type 2: package manifest type 1: v2 signature optional Trailer header 166 B, always at EOF magic · flags · CRC read last, parsed first lean = A + C + trailer · fat = A + B + C + trailer · classic — refused by name: v2 has exactly two package shapes

Figure 1 — Package byte layout: the bootstrap, payload slots, the optional fat runtime slot, the slot table, extension blocks, and the 166-byte trailer header at end of file.

A — the bootstrap

The bootstrap is a small, static Rust binary, under 3 MB with the budget enforced in CI on every platform. It parses the trailer of its own executable, resolves the runtime into the shared cache (cache, then embedded fat slot, then verified download), and execs the runtime. It carries its own HTTP, TLS, and archive handling, and nothing shells out. The tebako-bootstrap crate is the shipping implementation; the C99 repo survives as the behavioral oracle against which its messages are byte-compared, and it builds nothing that a user can download. The bootstrap is published per platform triplet with every tebako release.

B — the language runtime

The runtime is two artifacts, published per (version × platform) by the runtime factory’s releases: the interpreter executable, and an env image, one .tfs holding the standard library, the gems, and everything the load paths expect. The set is downloaded once per machine, SHA-256 verified, and mounted, never extracted; see runtime as image. Lean packages resolve the runtime on first run, and fat packages carry the interpreter as a payload slot and install it offline.

C — the payload slices

The payload slices are bare .tfs filesystem images: the application tree, data, and native tools. They are created in-process (no mkdwarfs, no shell-outs) and are self-describing through a manifest inside the image at /tpkg/manifest.yaml. Each slice is embedded as a trailer-described slot and is mounted straight out of the package file at its declared mount point, with no extraction and no temporary copies.

The tpkg trailer

Note

The trailer format is shipped as format v1.

The trailer is the byte-level contract of the whole stack, and it has exactly one owner: crates/tpkg. Everything else, the bootstrap, tebako-pkg, and the runtime driver, consumes the crate, and nothing hand-writes a second copy of the layout. The reader checks magic and CRC and then walks the slot table backwards from EOF. All header integers are little-endian.

The header: 166 bytes, always at EOF

Offset Bytes Field Meaning

0

10

magic

The magic is "TEBAKOTFS\0"; the 4-byte TEBA prefix is what tells absent apart from corrupt.

10

4

version

A u32 that stays 1 forever; extensions live in flags, never in version bumps.

14

4

package_flags

A u32 with bit 0 LEAN, bit 1 SIGNED_V2, and bit 2 NO_INSTALL; the table below lists them.

18

4

slot_count

A u32 in the range 1–8 (TPKG_MAX_SLOTS).

22

8

slot_table_offset

A u64 holding the absolute file offset of slot record 0.

30

128

runtime_ref

A resolution hint, for example ruby@3.4.2;tebako=0.16.9;image; a fat package appends ;sha256=<64 hex>.

158

4

launcher_abi

A u32 naming the bootstrap→runtime wire version that the package requires (currently 1).

162

4

header_crc32

A CRC32 over header bytes [0, 162); it provides accident integrity, never authenticity.

The package_flags field: three bits, all pass-through

Bit Flag Meaning

bit 0

TPKG_FLAG_LEAN

The runtime is named by reference (runtime_ref) and resolved at run time; when the bit is unset, the package carries the runtime as a slot (fat).

bit 1

TPKG_FLAG_SIGNED_V2

The v2 chain-of-trust extension is carried between the slot table and the header. Readers of the v1 era pass the bit through and run the package, because trust enforcement is a reader capability, not a format barrier.

bit 2

TPKG_FLAG_NO_INSTALL

The publisher freezes the package: it runs standalone, but every install attempt is refused by name (exit 76). Absence of the flag means installable on request.

The version field stays 1 forever: new capability arrives through flags and extension blocks, so readers of the v1 era never break on v2 packages.

Each slot record: 280 bytes

Offset Bytes Field Meaning

0

8

offset

A u64 holding the absolute file offset of the image bytes.

8

8

size

A u64 holding the image length in bytes.

16

4

format_id

It says how to read the bytes: 0 auto, 1 dwarfs, 2 squashfs, 3 zip, 5 limnifs, and nothing more (see below).

20

4

flags

A u32 that is 0 today and is the future home of slot role bits.

24

256

mount_point

A UTF-8, NUL-padded path naming where the driver mounts the image.

format_id answers exactly one question

Id Format Meaning

0

auto

The format is detected from the image magic.

1

dwarfs

dwarfs-t, the FlatBuffers-metadata format; it is read-only at runtime, with a creation-time writer.

2

squashfs

SquashFS images, which are read as-is.

3

zip

Zip archives, which are read as-is.

4

runtime

A legacy role wart: it marks the fat runtime slot so loaders of the v1 era skip handing it over as a mount. It is kept for backward compatibility only, and new code never uses it.

5

limnifs

LimniFS, which is pure Rust and content-addressed; it is the default format for tfs mkimage and tebako press as of v0.2.0.

Note

This point is stated plainly: how do I read these bytes is the only thing a format id may say. Whether a slot is a runtime, and which payload carries the entrypoint, are roles that are declared by manifests and never encoded in the format axis. format_id 4 is the one historical violation, kept so that old loaders keep working; ids ≥ 6 are unallocated, and new semantics must never again be smuggled into the format field. That is the orthogonality law, and it is the reason a runtime image and an app image can share one container format.

Absent versus corrupt: named errors instead of partial behavior

Error Meaning

NoTrailer

No TEBA prefix appears in the last 166 bytes, so the trailer is absent rather than corrupt (a classic-era bundle, for which callers may fall back to offset auto-detection).

Magic

The prefix is present, but the full magic mismatches; the trailer is corrupt.

Crc

The magic is valid, but the header CRC32 is bad; the trailer is corrupt.

Version

The version field is not 1.

Slots

The slot_count lies outside the range 1–8.

Bounds

The slot table lies outside the file bounds.

Invalid

Structural validation failed, for example an overflowing offset+size, an unterminated fixed field, or a format_id above 5.

The TpkgError set is 1:1 with the C reference’s TPKG_ERR_* codes, and the strerror strings are byte-identical; the golden parity rule applies to the error surface too.

Extension blocks: where v2 lives

Note

This capability is shipped.

Between the slot table and the trailer header sits a chain of typed extension blocks, invisible to readers of the v1 era, which only ever read the header at EOF and the slot table at its recorded offset. Blocks walk forward from the end of the slot table and are self-delimiting by type and length; readers skip unknown types and carry them verbatim, while tebako-pkg validate rejects them with a named error.

The extension-block chain
[bootstrap][payload slots][slot table][ext blocks…][v2 signing ext?][header @EOF]

ext block: [u32be type][u32be length][payload]
  type 1 — v2 signing extension (when present, LAST before the header)
  type 2 — package manifest (YAML)

Type 1 — the chain-of-trust extension

The extension carries per-slot SHA-256 digests (8 × 32 B), the signer’s keyid (the low 64 bits of the OpenPGP fingerprint), and a detached OpenPGP signature (≤ 64 KiB). All of its numerics are big-endian, while the header stays v1-little-endian. The canonical signed bytes are slot table ‖ digests ‖ keyid ‖ header. The extension is flagged by TPKG_FLAG_SIGNED_V2, never by a version bump; see the chain of trust.

Type 2 — the package manifest

This extension is a YAML block that owns composition: package identity; entries[] mapping each command to its slot, entrypoint, and per-entry runtime_ref (which covers suites and multi-runtime packages, past the trailer’s 128-byte single-ref limit); package-level jail: and env:; and mounts[] per slot (exclusive by default, or union with declared precedence). The block references slots, and payload manifests stay inside the images, so no authority is duplicated.

Press modes: lean and fat

Note

This capability is shipped.

tebako press produces exactly two package shapes, and no third shape exists. Payload images default to LimniFS as of v0.2.0 (--format dwarfs remains a first-class opt-in); the shape question is only about where the runtime lives.

Lean: the default shape

The lean shape is the bootstrap plus payload slots plus the trailer, with TPKG_FLAG_LEAN set and runtime_ref filled. It is the smallest download, and the first run resolves the runtime into the shared cache.

Fat: the runtime carried inside

A fat package is a lean package that also carries the runtime interpreter as a payload slot (format_id 4), pinned by ;sha256= in the runtime_ref. The first run verifies the interpreter and installs it into the cache, so the interpreter needs no network. In the image era the env .tfs resolves alongside it: it is usually already cached from any prior tebako run, and otherwise one verified fetch resolves it (TEBAKO_OFFLINE=1 makes a miss the named exit 69 rather than a surprise).

Classic: refused by name

The classic shape is v1’s shape: the runtime and the app stitched into one monolithic binary, with no bootstrap, no trailer, and no shared cache. v2 retires it by design, and the CLI says so by name: tebako press --mode=classic exits with "the 'classic' press mode is a later tebako-rs milestone (use --mode=lean or --mode=fat)". The refusal is named, and there is never a silent fallback.

The shared runtime cache

Note

This capability is shipped.

Runtimes are installed once per machine, not once per package. The cache root is $TEBAKO_HOME (default ~/.tebako, %LOCALAPPDATA%\tebako on Windows), with entries of the form runtimes/<lang>-<lv>-<ver>-<triplet>/, for example runtimes/ruby-3.4.2-0.16.9-linux-musl-x86_64/. An entry holds immutable artifacts and their markers only: the interpreter (0755), the env .tfs (read-only, 0444), and the .sha256 / .origin markers. Installs are atomic (built in tmp/, then renamed) under a per-entry lock, so concurrent first runs are safe and a partial install is invisible. Every tebako package on the machine shares the same cache; the store page has the full layout.

Note

The marker is the trust anchor. A .sha256 sidecar means the artifact was verified at install; it is re-verified on re-fetch and never per run, so a cached runtime starts at full speed every time. No extracted layout/ tree lives in the cache anymore: when the press side needs the runtime’s files, it extracts the image in-process, per press, into the build prefix. TEBAKO_OFFLINE=1 makes resolution cache-or-named-error, TEBAKO_RUNTIME_MIRROR redirects downloads, and tebako cache list and cache prune manage the space.

Press-time versus run-time resolution

Two different actors can fetch a runtime, at two different moments, on two different machines. The two moments are kept deliberately distinct, because conflating them is a design error that is locked off.

Press-time resolution: the packager’s machine

tebako press resolves the runtime into the shared cache while packaging: bundler runs under the target runtime, native extensions compile against it, and a fat press embeds the interpreter it resolved. Resolution reads the runtime release’s manifest.json (with SHA256SUMS.txt as a fallback); the bootstrap itself comes from local binaries or from the product’s own release, verified the same way.

Run-time resolution: the user’s machine

The bootstrap resolves the runtime when the packaged binary is first run: a cache hit leads to exec; a fat slot leads to verification against ;sha256= and installation; otherwise the runtime is downloaded from the runtime release, sha256-verified, and installed atomically. Contract negotiation is fail-closed before any checksum is accepted: a runtime that speaks an unsupported handoff contract produces exit 75, naming both generations and the remedy, and never a half-installed cache.

The launcher ABI: handoff by reference

Note

The launcher ABI is shipped as version 1.

The bootstrap never mounts anything: it hands the payload slots to the runtime by reference and execs the runtime, and a successful handoff never returns. The wire follows:

The handoff wire
<runtime> --tebako-image <self|image-path>:<slot|->:<mount> … \
    --tebako-entry <argv0> <user args…>

There is one --tebako-image triple per payload slot to mount, and runtime-role slots are never handed over. Parsers split on the last two colons, so Windows drive letters in paths stay safe, and a malformed triple is a named exit 65. TEBAKO_RUNTIME_IMAGE names the env image, mounted whole at the runtime root (see runtime as image). Two version gates keep the ends honest: the trailer’s launcher_abi field guards the wire format (a mismatch is exit 66), and the runtime’s declared contract_version guards the semantics (fail-closed negotiation is exit 75).

Further reading

How a package works

The concepts on this page in motion: the boot sequence, the mount model, and every named exit code.

The runtime, as an image

Why part B is two artifacts, and how the env image and the payload slices co-mount in one process.

Where everything lives

~/.tebako in full: runtimes, payloads, shims, registries, keys, and the house rules.