[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)
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.
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 |
|
The magic is |
10 |
4 |
|
A u32 that stays 1 forever; extensions live in flags, never in version bumps. |
14 |
4 |
|
A u32 with bit 0 LEAN, bit 1 SIGNED_V2, and bit 2 NO_INSTALL; the table below lists them. |
18 |
4 |
|
A u32 in the range 1–8 (TPKG_MAX_SLOTS). |
22 |
8 |
|
A u64 holding the absolute file offset of slot record 0. |
30 |
128 |
|
A resolution hint, for example |
158 |
4 |
|
A u32 naming the bootstrap→runtime wire version that the package requires (currently 1). |
162 |
4 |
|
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 |
|
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 |
|
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 |
|
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 |
|
A u64 holding the absolute file offset of the image bytes. |
8 |
8 |
|
A u64 holding the image length in bytes. |
16 |
4 |
|
It says how to read the bytes: 0 auto, 1 dwarfs, 2 squashfs, 3 zip, 5 limnifs, and nothing more (see below). |
20 |
4 |
|
A u32 that is 0 today and is the future home of slot role bits. |
24 |
256 |
|
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 |
|
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. |
Absent versus corrupt: named errors instead of partial behavior
| Error | Meaning |
|---|---|
|
No |
|
The prefix is present, but the full magic mismatches; the trailer is corrupt. |
|
The magic is valid, but the header CRC32 is bad; the trailer is corrupt. |
|
The version field is not 1. |
|
The slot_count lies outside the range 1–8. |
|
The slot table lies outside the file bounds. |
|
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.
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 |
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:
<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
-
~/.tebakoin full: runtimes, payloads, shims, registries, keys, and the house rules.