Skip to content

ARCHITECTURE · 26

The contract model.

Every interface in the ecosystem has exactly one owner, one declarative contract, and one verification point. Consumers verify fail-closed before execution; a mismatch is a named error that names both sides; anything undeclared is refused by name — never assumed, never silently served.

Note

Status: shipped — the schema registry (9 grammars), era refusals, contract negotiation (exit 75), the layout check (78), the trailer era gate (77).

Status: planned — the S1–S61 scenario catalog completes as the e2e suite grows.

Two kinds of YAML — never conflated.

Schemas are the grammars themselves — versioned, living in ONE registry (docs/spec/schemas/ in the product repo), each carrying its name, MAJOR version, and evolution metadata. Declarations are the per-artifact instances conforming to a schema — in-image manifests, the runtime release card, the registry file, the package L2 block, the store config. Every producer validates its declarations against the schema it owns in its own CI; every consumer validates before use and refuses invalid input with a named error. And per the SSOT invariant: a cross-repo contract value has exactly ONE authoritative owner — every other consumer FLOWS it (generated file, manifest, exported symbol) or ASSERTS parity in CI. A second hand-written copy of any contract value, in any language, any repo, is a bug on arrival.

The edges that gate a run.

Twenty contract edges map the ecosystem (C1–C20). The load-bearing ones, with their refusal shapes:

EDGE DECLARATION REFUSAL

C1 · source tarball → runtime factory

tebako-mount-root + versions.yml schema + SHA256SUMS

absent/corrupt → exit 132 at configure

C2 · runtime factory → loader (the release card)

runtime-manifest.yaml: era, contract_version, mount_root, abi, image_layout, digests

pre-download — era-1 refusal; contract too new → exit 75

C3 · runtime exe ↔ env image

in-image /lib/tebako/layout.yaml

driver post-mount, pre-init: missing → era-1; mismatch → exit 78

C6 · loader → package trailer

header + L2 package-manifest block (era, pressed_by, reader_era)

era mismatch → exit 77 either direction; unknown critical block → refusal

C8 · loader → registry

tpkg-registry.yaml schema_version

missing version → era-1 refusal; newer MAJOR → upgrade refusal

C10 · payload → runtime

per-entry runtime_requirement + abi facets in the L1 manifest

unsatisfiable → named error naming the entrypoint — never a segfault

C13 · everything local → store

~/.tebako/layout-version + config schema

newer → upgrade refusal; older → the named migration, never a silent mix

C17 · jail declaration → enforcement

tpkg::jail authored shape

malformed → exit 73; unknown directive → named error

The schema evolution law.

Every schema in the ecosystem obeys these rules. No exceptions, no local dialects.

# RULE

1

Two numbers, one meaning — schema_version is MAJOR (breaks), schema_minor adds

2

Readers ignore unknown fields within their MAJOR — and must not invent semantics for them

3

Unknown MAJOR is a named refusal: artifact, its version, consumer, its max spoken, "upgrade"

4

Missing schema_version = era 1 — a named refusal, never a silent default

5

Type changes are MAJOR — no silent coercion, ever

6

Deprecation window — rename/retype inside one MAJOR: writers dual-emit for two MINORs, readers prefer new + warn on old, then drop

7

critical: true — a reader that doesn’t understand a critical entry REFUSES; non-critical skips are for decoration, never semantics

8

Lists stay lists — entries carry identity fields; no keyed maps for multiplicity data

9

YAML for everything authored; JSON Schema documents the shape; CI validates producers

       MINOR +1                    MINOR +2                    MAJOR +1
┌──────────────┐          ┌──────────────┐          ┌──────────────┐
│ field added  │  rename? │ old+new both │  after   │ old dropped  │
│ (additive,   │ ───────▶ │ written; old │ 2 MINORs │ entirely;    │
│ readers skip)│          │ deprecated   │ ───────▶ │ readers of   │
└──────────────┘          └──────────────┘          │ old MAJOR    │
                                                    │ are refused  │
                                                    └──────────────┘

Contract negotiation: the handoff’s semantics version.

The launcher ABI is the wire format; the contract version is its semantics version, declared by the runtime release manifest and enforced by the loader — so a future handoff change never silently mis-executes under an old bootstrap.

contract 1  the original handoff: --tebako-image/--tebako-entry argv,
TEBAKO_RUNTIME_IMAGE, trailer/ABI gating
contract 2  the widened grammar: image-path triples, bare-file slot tokens,
env-image-first multi-mount, direct entry execution
negotiation (fail-closed, BEFORE any checksum acceptance):
declared == SUPPORTED  → proceed
absent/unparseable     → a pre-contract release = contract 1 by definition
any other value        → exit 75 naming both generations and the remedy —
nothing installed, the cache untouched

The bump rule: any change to env/argv/handoff semantics increments the contract by exactly one; additive payload-side changes old runtimes may ignore do NOT bump it. The runtime driver carries the same value compiled in, and the factory’s CI fails any release where manifest and driver disagree. A negotiation failure is never a crash and never a silent accept — a clean, explained refusal.

Locked non-goals.

No old-path readers — era 1 is refused, not adapted. No silent skipping of load-bearing fields. No type coercion in readers, no second grammar for the handoff — this model is the declaration layer around it. No per-run trust checks — verification happens at fetch and install, never per run. And the accountability mechanism: every rule answers to the scenario catalog (S1–S61) — bootstrap×package, loader×runtime, driver×env image, driver×payloads, shim×payloads, payload×payload, press×inputs, store, registry, factory, trust, and the evolution cases — each scenario an e2e id with its expected named behavior.