Import on the isolated side
The destination instance is the same application, in the isolated zone, pointed at the transported store. It treats the medium as untrusted until proven otherwise, and the order is the guarantee rather than a sequence of steps: nothing is pushed, served or written before the whole medium has been re-verified (FR-054).
What makes an instance a destination
Section titled “What makes an instance a destination”One setting: zone: — the identity of the zone this instance serves, the
metadata.name of the Retriever that describes it.
mode: mirrorzone: isolated-productionstorage: root: /mnt/usb/tobby-storestate: root: /var/lib/tobby/statedestination: registry: registry.zone.example.comA source-side instance reads its zone from the Retriever it resolves and
sets nothing here. A destination has no Retriever — its content arrives on a
medium — and without zone: it cannot tell whether a medium is addressed to
it at all. tobby media verify, tobby media import and the four
/api/v1/media endpoints refuse to run without it, naming the setting
(TBY-CFG-001).
Verifying needs nothing else. Importing additionally needs
destination.registry: verification reads, the push writes, and the push
has to have somewhere to go.
The Media screen
Section titled “The Media screen”/media is the guided counterpart of the verification pipeline. It opens
with the medium’s inventory summary — the zone it is addressed to, when the
delivery was resolved, which recipes it carries, how many files and bytes —
and then a numbered sequence of three steps.
1 — Verify. Re-reads and re-hashes every covered file, then checks each
recipe’s signature against this instance’s trust roots. On a full disk
this is minutes of I/O, so it runs in the background with live progress and
the page polls: you can close the tab and come back. Asking for a second
verification while one is walking the medium is refused
(TBY-MED-031, HTTP 409) — two
walks over the same disk halve each other and answer nothing new.
2 — Report. Three stages named separately — manifest completeness and
checksums, ingredient digests, recipe signatures — and one verdict per
delivery: pushable, partial or blocked. A blocked delivery names the
file that failed, which is the difference between “re-copy the disk” and
“call the source zone”. The raw report is the same JSON document
GET /api/v1/media/verification serves.
3 — Push. The control does not exist until a verdict has cleared at least one delivery. Not greyed out: absent from the document. What is pushed then goes through the same controls a passthrough promotion goes through — the registry allow-list and the recipe signatures, re-checked over the exact bytes about to leave — only what the zone registry is missing moves, and the signed recipes land in the zone’s own cookbook with their signatures.
Reading the screen is a viewer action. Verifying and importing are
operator actions. Waiving one of the two refusals below is an
administrator’s, and the waiver checkboxes are rendered only for one.
What blocks what
Section titled “What blocks what”The unit of a block is the recipe. A recipe whose signature verifies and whose every reachable file matches its pinned digest is pushable; a recipe failing either is blocked whole, with no override, and named in the report with the file that decided it. Its neighbours on the same medium are unaffected.
That is deliberate. A delivery that verified in part is not a delivery — a recipe is one signature over the exact bytes of one set — but withholding what failed is not the same as discarding what did not, and a medium carrying several deliveries still delivers the intact ones. The alternative, blocking a whole physical trip over one corrupted byte, is what pushes operators toward overrides, and the override that would matter here is precisely the one this product refuses to offer.
Per-recipe refusals, none of them waivable by anyone:
| Code | Condition |
|---|---|
TBY-MED-010 |
a file the recipe reaches is not on the medium |
TBY-MED-011 |
a covered file’s size differs from its inventory entry |
TBY-MED-012 |
a covered file’s content does not hash to its inventory entry |
TBY-MED-013 |
a file the recipe reaches that the inventory does not list |
TBY-MED-014 |
a reachable manifest or index that cannot be parsed |
TBY-MED-015 |
a blob whose bytes do not hash to the digest its own path claims |
The last one is what keeps the unsigned inventory from being load-bearing: an attacker who corrupts a blob and rewrites the inventory to agree defeats the inventory and is still caught by the content address.
Four refusals stay medium-wide
Section titled “Four refusals stay medium-wide”Per-recipe salvage is meaningless for these, so they block everything:
| Condition | Code | Waiver |
|---|---|---|
| Manifest absent, unreadable, or in a format this build does not read | TBY-MED-001, TBY-MED-002, TBY-MED-003, TBY-MED-004 |
none |
The recipe graph (meta/recipes.json) does not match its inventory entry |
TBY-MED-005 |
none |
| The medium is addressed to another zone | TBY-MED-006 |
administrator, audited |
| The medium is older than the last one imported for this zone | TBY-MED-007 |
administrator, audited |
The first two leave nothing to reason about — without the inventory there is no completeness question to ask, and the graph is the reachability set, so an altered one makes every per-recipe verdict meaningless.
The last two are addressed to someone else, or to an earlier moment. They
are anti-accident guards, not security controls: the manifest is
unsigned, so a hostile party can forge either field. That is exactly why
they are the only two an administrator may waive — --allow-zone-mismatch
and --allow-stale on the command line, checkboxes on the screen,
allowZoneMismatch and allowStale on the API — and why both the attempt
and the applied waiver are written to the
audit log with the actor and the origin.
No role waives an integrity or a signature verdict. There is no flag, no confirmation dialog and no configuration key for it, for anyone, administrators included.
Findings that block nothing
Section titled “Findings that block nothing”Three conditions are reported and never pushed, without blocking anything:
a file under manifest coverage the inventory does not list
(TBY-MED-020), an inventoried file
no verified recipe reaches
(TBY-MED-021), and a covered
bookkeeping file other than the recipe graph that does not match its
inventory entry (TBY-MED-022).
There is no side door for loose artifacts: content the medium carries that no verified recipe reaches is reported by name and stays where it is.
An unverified medium serves nothing
Section titled “An unverified medium serves nothing”“Verification precedes any push, any serving, and any local write” has
three verbs. A destination instance holding a transported medium withholds
/v2/ and /files/ until a verification it performed has cleared the
medium.
- Both surfaces answer
403withTBY-MED-030— orTBY-MED-032once a verification has run and the medium did not come out whole — in the shape each surface’s clients understand: the OCI error envelope fordockerandhelm, plain text foraptanddnf. Never a404, never a silent503. The refusal names the medium and the screen that opens the gate. - The instance stays live and ready.
/healthzand/readyzboth answer200, and/readyzstates in its body which surfaces are closed and where to open them. A503would take the instance out of rotation and remove the very screen that fixes the condition. - The gate opens on a whole medium and on nothing else. A
partialverdict does not open it. The push decision is per recipe because a recipe is a delivery; serving is not that decision, because/v2/hands out blobs and a blob a blocked recipe reaches is exactly the byte range that failed. Push the intact recipes into the zone registry, which then serves them from content that arrived by the checked path. - There is no opt-out, and no verdict survives a restart. A cached verdict says these bytes were right once; the question the gate asks is whether they are right now. Re-hashing a disk on restart is minutes.
- No role bypasses it. A closed gate is not an authorization refusal: the role matrix decides who may ask, the gate decides whether this instance is willing to hand anything out yet.
A source-side mirror instance is unaffected. Its store carries a manifest
too — it wrote one — but the medium is its own output rather than something
that changed hands, and it has no zone: configured, which is precisely how
the requirement tells the two sides apart. A passthrough instance is
unaffected for the same reason: its store is a transit cache, not a
delivery.
From the command line
Section titled “From the command line”Both commands re-verify first, and both refuse to run without a zone.
tobby media verify --storage-root /mnt/usb/tobby-store --zone isolated-productiontobby media verify --storage-root /mnt/usb/tobby-store --zone isolated-production --output json | jq .verdicttobby media import --config /etc/tobby/config.yamltobby media verify reports and writes nothing at all — not even the
medium’s own operation log. tobby media import does the whole journey and
journals what it did onto the medium, under _tobby/logs/ and therefore
outside the manifest’s coverage: that file is the return audit channel of
the transfer.
| Command | 0 |
1 |
3 |
4 |
|---|---|---|---|---|
tobby media verify |
every delivery is pushable | — | refused by policy (zone identity, freshness) | a verification failure |
tobby media import |
imported | a push failed | refused by policy | a verification failure |
These commands run in their own process, against the store directory
directly. They do not open a running instance’s surfaces: the serving
gate is opened by a verification that instance performed, from its screen
or through POST /api/v1/media/verify. The refusal message says so, because
the mistake is an easy one to make.
After the import
Section titled “After the import”A completed import advances the per-zone freshness record — which is what makes re-importing last month’s medium a refusal rather than a silent rollback — and the zone registry now serves the transferred content. Connecting the zone’s clusters and hosts to it works identically in both modes: see connect your clients.
Media that come back for a second cycle, and the housekeeping that keeps them a sane size, are on managing media over time.