Skip to content

ARCHITECTURE · 06

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, and anything undeclared is refused by name rather than assumed or silently served.

Note

The schema registry (9 grammars), the era refusals, contract negotiation (exit 75), the layout check (78), and the trailer era gate (77) are shipped.

The S1–S61 scenario catalog completes as the e2e suite grows, and that growth is planned.

Two kinds of YAML, never conflated

Schemas are the grammars themselves: versioned, living in one registry (docs/spec/schemas/ in the product repository), and each carrying its name, MAJOR version, and evolution metadata. Declarations are the per-artifact instances that conform to a schema: in-image manifests, the runtime release card, the registry file, the package L2 block, and 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. Per the SSOT invariant, a cross-repo contract value has exactly one authoritative owner, and every other consumer either flows it (a generated file, a manifest, an exported symbol) or asserts parity in CI. A second hand-written copy of any contract value, in any language and any repository, is a bug on arrival.

The edges that gate a run

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

EDGE DECLARATION REFUSAL

C1 · source tarball → runtime factory

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

An absent or corrupt input is refused with exit 132 at configure time.

C2 · runtime factory → loader (the release card)

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

The refusal comes pre-download as an era-1 refusal, and a contract too new is exit 75.

C3 · runtime exe ↔ env image

in-image /lib/tebako/layout.yaml

The driver checks post-mount and pre-init: a missing layout is era-1, and a mismatch is exit 78.

C6 · loader → package trailer

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

An era mismatch is exit 77 in either direction, and an unknown critical block is refused.

C8 · loader → registry

tpkg-registry.yaml schema_version

A missing version is an era-1 refusal, and a newer MAJOR is an upgrade refusal.

C10 · payload → runtime

per-entry runtime_requirement + abi facets in the L1 manifest

An unsatisfiable requirement is a named error naming the entrypoint, never a segfault.

C13 · everything local → store

~/.tebako/layout-version + config schema

A newer layout is an upgrade refusal, and an older one gets the named migration, never a silent mix.

C17 · jail declaration → enforcement

tpkg::jail authored shape

A malformed declaration is exit 73, and an unknown directive is a named error.

The schema evolution law

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

# RULE

1

Two numbers carry one meaning: schema_version is MAJOR and breaks, and schema_minor adds.

2

Readers ignore unknown fields within their MAJOR, and they must not invent semantics for them.

3

An unknown MAJOR is a named refusal naming the artifact, its version, the consumer, the consumer’s maximum spoken version, and the upgrade remedy.

4

A missing schema_version means era 1, which is a named refusal and never a silent default.

5

Type changes are MAJOR, and there is no silent coercion, ever.

6

A deprecation window covers a rename or retype inside one MAJOR: writers dual-emit for two MINORs, readers prefer the new field and warn on the old, and then the old field drops.

7

An entry marked critical: true is refused by a reader that does not understand it; non-critical skips are for decoration, never semantics.

8

Lists stay lists: entries carry identity fields, and keyed maps never hold multiplicity data.

9

YAML is the format for everything authored, JSON Schema documents the shape, and 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, and 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 is the following: any change to env/argv/handoff semantics increments the contract by exactly one, and additive payload-side changes that 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; it is a clean, explained refusal.

Locked non-goals

The model locks several non-goals. Old-path readers are not adapted: era 1 is refused. Load-bearing fields are never skipped silently. Readers perform no type coercion, and the handoff has no second grammar; this model is the declaration layer around it. There are no per-run trust checks, because verification happens at fetch and install, never per run. The accountability mechanism completes the set: every rule answers to the scenario catalog (S1–S61), which covers 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.