diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b9b995..d8a11e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,32 @@ which records not just what changed but what each drill run proved. ### Added +- **`box import` records that the trip happened — without rewriting who the + box was** (#131) — an imported box kept the artifact's mint stamp verbatim + (#103), which is correct: the mint time, box version, image and origin + belong to the originating host and should survive the trip. But nothing + recorded the *import*, so an imported box was indistinguishable from one + minted here at the artifact's mint time. It now stamps `user.box.imported` + / `user.box.imported.by` (the **first** trip, pinned once and never + rewritten), `user.box.imported.last` / `user.box.imported.last.by` (the + **latest**, refreshed on every arrival) and `user.box.imported.count` — a + birth pair plus a latest pair, the shape heavy-duty/rig#61 settled on for + the same repeated-event question, because last-wins alone would erase the + evidence of every earlier trip. This is deliberately **not** + `origin=import`: `origin` answers how the instance came into *being* — mint + or clone — and overwriting it would make an exported clone come back + claiming to be an import, with nothing left saying it was ever a clone and + an `origin.from` naming a lineage no key explains any more. The import is a + *third* fact, orthogonal to the first two, so it takes its own keys and + every key the artifact carried is left exactly as it arrived. `box info` + prints an `IMPORTED` line directly under `MINTED`, because that adjacency is + what stops the artifact's mint time being misread as this host's; it states + only the ordering (`the mint above predates it`) and never claims another + host, since box has no record of which host minted a box and a re-import + onto the *same* host is the documented upgrade flow. `user.box.schema` does + not move — adding a key is not a breaking change — and is not written by the + import at all, so a legacy artifact with no stamp still reads as + `MINTED (not recorded)` rather than acquiring a shape it does not have. - **A minted box records how it was minted, and `box info` reads it back** (#103) — `cmd_new` knew a great deal at the moment it launched and wrote three `user.*` keys, dropping the rest on the floor: the box version that diff --git a/README.md b/README.md index d0c1afb..39a7c7c 100644 --- a/README.md +++ b/README.md @@ -464,6 +464,39 @@ box version that cloned it) and leaves the lineage keys alone, because the clone's disk genuinely did come from that image, template and role. `origin.from` records one hop: a clone of a clone names its parent, not its grandparent. +**An import records the trip, and rewrites nothing** +([#131](https://github.com/heavy-duty/box/issues/131)). Everything `incus +import` restores is the _artifact's_ truth, so an imported box keeps its mint +stamp verbatim — the mint time, the box version, the image and the origin +belong to the originating host and survive the trip on purpose. What `box +import` adds is the one fact the artifact cannot carry: that the trip happened. + +``` +MINTED 2026-06-01T10:00:00Z by box 0.7.0 +IMPORTED 2026-07-20T09:14:03Z by box 0.8.1 (the mint above predates it) +ORIGIN clone of work/authed +``` + +It is **not** `origin=import`, and the difference is the whole point. `origin` +answers how the instance came into _being_ — mint or clone — and overwriting it +would destroy that: the clone above would come back claiming to be an import, +with nothing left saying it was ever a clone and an `origin.from` naming a +lineage no key explains. The import is a _third_ fact, orthogonal to the first +two, so it takes its own keys and leaves every other one alone. + +The `IMPORTED` line sits directly under `MINTED` because that adjacency is what +stops the mint time being misread as this host's. Note what it does not claim: +box has no record of _which_ host minted the box, and a box can be exported and +re-imported onto the same host (that is the upgrade flow above), so the line +states only the ordering — the one thing box actually knows. + +**A box can make the trip more than once**, and both ends are kept: the first +import is pinned forever, the latest is refreshed on every arrival, and a count +says how many. Last-wins alone would erase the evidence of the earlier trips, +which is the same mistake `origin=import` makes one level up. (The shape +follows [heavy-duty/rig#61](https://github.com/heavy-duty/rig/issues/61)'s +manifest: a birth pair plus a latest pair.) + **Boxes minted before this stamp existed keep working**, under this verb and every other — they render as a box with blanks and say `MINTED (not recorded)` rather than erroring. `user.box.schema` names the stamp's _shape_ (an integer, diff --git a/bin/box b/bin/box index f36ca3f..bb61369 100755 --- a/bin/box +++ b/bin/box @@ -438,6 +438,17 @@ snapshots). What box then re-stamps is THIS host's truth: differs, the same move migrate-host makes re-homing a legacy box · a fresh machine identity (reset_identity, exactly like a clone), so its DHCP lease can never collide with the box it was exported from + · the import EVENT — when the box landed here, and by which box version + +The mint stamp is NOT rewritten. The artifact's mint time, box version, image, +template and origin (mint or clone) are the originating host's truth and +survive the trip intact — 'box info' shows them unchanged, with an IMPORTED +line under them saying the mint predates the arrival. In particular the import +does not overwrite 'origin': a clone that was exported and imported still +reads as a clone, and still names the box it was cloned from. + +A box that makes the trip more than once keeps both ends: the first import is +pinned forever, the latest is refreshed each time, and a count says how many. Auth state rides along by design — the artifact carries the box's whole disk, logins included. That is the point (log in once, keep the file), and the same @@ -1420,6 +1431,7 @@ box_cfg() { incus config get "$1" "$2" 2>/dev/null || true; } # legacy 'user.claudebox' tag already says out loud at resolve_box. box_provenance() { local i="$1" schema created ver img fp m asked tpl u role rrepo rref origin from + local imp impby impl implby impn schema="$(box_cfg "$i" user.box.schema)" created="$(box_cfg "$i" user.box.created)"; ver="$(box_cfg "$i" user.box.version)" img="$(box_cfg "$i" user.box.image)"; fp="$(box_cfg "$i" user.box.image.fingerprint)" @@ -1428,6 +1440,12 @@ box_provenance() { role="$(box_cfg "$i" user.box.role)" rrepo="$(box_cfg "$i" user.box.rig.repo)"; rref="$(box_cfg "$i" user.box.rig.ref)" origin="$(box_cfg "$i" user.box.origin)"; from="$(box_cfg "$i" user.box.origin.from)" + # The import event (#131) — a THIRD fact, read from its own keys. 'origin' + # above still says mint or clone, which is what it has always meant. + imp="$(box_cfg "$i" user.box.imported)"; impby="$(box_cfg "$i" user.box.imported.by)" + impl="$(box_cfg "$i" user.box.imported.last)" + implby="$(box_cfg "$i" user.box.imported.last.by)" + impn="$(box_cfg "$i" user.box.imported.count)" # A pre-rename box has no metadata at all but is always a Claude box — the # same mapping box_user() makes, for the same reason. [ -n "$tpl" ] || [ "$(box_cfg "$i" user.claudebox)" != 1 ] || { tpl=claude; u="${u:-claude}"; } @@ -1438,6 +1456,25 @@ box_provenance() { else printf '%-11s%s\n' MINTED "(not recorded — this box predates the mint stamp)" fi + # Directly under MINTED, because adjacency is what defeats the misreading: + # the mint time above is the ARTIFACT's, carried across the trip on purpose, + # and a reader who meets it alone will take it for this host's. + # + # The qualifier states the one thing box actually knows — the ORDERING. It + # does not say "on another host": a box can be exported and re-imported onto + # the SAME host (that is the installer's own upgrade advice, #66), and + # nothing on the box records which host minted it. Inventing a location here + # would be precisely the fabricated fact the stamp exists to avoid. + if [ -n "$impl" ] || [ -n "$imp" ]; then + printf '%-11s%s\n' IMPORTED \ + "${impl:-$imp} by box ${implby:-${impby:-unknown}} (the mint above predates it)" + # The birth of the import history, shown only when it is a DIFFERENT trip + # from the latest — on a single import both pairs hold the same values and + # a second line would be noise. Continuation indent, no second label. + if [ -n "$imp" ] && [ "$imp" != "${impl:-$imp}" ]; then + printf '%-11s%s\n' '' "import ${impn:-?} — the first was $imp by box ${impby:-unknown}" + fi + fi if [ -n "$tpl" ]; then local paren="" [ -z "$u" ] || paren="user $u" @@ -1636,6 +1673,69 @@ cmd_import() { tag="$(incus config get "$target" user.box 2>/dev/null || true)" [ "$tag" = 1 ] || tag="$(incus config get "$target" user.claudebox 2>/dev/null || true)" [ "$tag" = 1 ] || incus config set "$target" user.box=1 + # The import EVENT (#131). Everything above either restores the ARTIFACT's + # truth or re-establishes THIS host's boundary; none of it records that the + # trip happened. The mint stamp (#103) rides inside the artifact verbatim — + # which is correct, mint facts belong to the originating host — and that is + # exactly what creates the gap: an imported box is otherwise indistinguishable + # from one minted here at the artifact's mint time. + # + # Deliberately NOT 'origin=import'. 'origin' answers how this instance came + # into BEING — mint or clone — and overwriting it destroys that answer: a + # clone that was exported and imported would come back claiming to be an + # import, with nothing left saying it was ever a clone and an 'origin.from' + # naming a lineage no key explains any more. The import is a THIRD fact, + # orthogonal to the first two, so it takes its OWN keys, and every key the + # artifact carried — origin, origin.from, created, version, template, user, + # image, role, rig.* — is left exactly as it arrived. + # + # Birth pair + latest pair, the shape heavy-duty/rig#61 settled on for the + # same repeated-event question. It transfers: a box can make the trip more + # than once, and these keys ride inside the artifact on every subsequent one. + # · imported / imported.by FIRST-wins. Pinned once, never rewritten. + # · imported.last / .last.by LAST-wins. When it landed HERE. + # · imported.count how many trips — first and last alone + # cannot tell 2 apart from 5, and nothing + # else on the box can reconstruct it. + # Last-wins alone would repeat, one level down, the very mistake this key + # exists to avoid: the second import would erase the evidence of the first. + # First-wins alone cannot answer "when did this land on THIS host", which is + # the question an operator actually asks. + local first_at prev_n n now + first_at="$(incus config get "$target" user.box.imported 2>/dev/null || true)" + prev_n="$(incus config get "$target" user.box.imported.count 2>/dev/null || true)" + # A count that is absent (the first trip) or not an integer (a hand-edited + # config, a foreign user.box.imported.count) must never fail an import that + # has already happened — arithmetic on it under 'set -e' would. Same side the + # schema check in box_provenance() falls on: degrade, never die. + # Two holes, and they have to close together. `[ 08 -eq 08 ]` PASSES — test + # parses decimal — and then `$((08 + 1))` aborts with "value too great for + # base", because arithmetic reads a leading zero as octal. That abort lands + # after the physical 'incus import' and before the stamp, the placement fix + # and the start: precisely the window this guard exists to protect. So the + # digits-only case closes sign and garbage, and 10# forces base ten. + case "$prev_n" in ''|*[!0-9]*) prev_n=0 ;; esac + n=$((10#$prev_n + 1)); now="$(mint_time)" + local istamp=( + user.box.imported.last="$now" + user.box.imported.last.by="$(box_version)" + user.box.imported.count="$n" + ) + # The birth of this box's import history: pinned on the first trip and + # carried by the artifact forever after, so a box on its third host still + # names the trip that started it. + [ -n "$first_at" ] || istamp+=(user.box.imported="$now" user.box.imported.by="$(box_version)") + # No 'user.box.schema' is written here, on either path. "Absent schema means + # pre-stamp" is load-bearing for the MINTED line, and stamping schema=1 onto + # a legacy artifact that carries no mint stamp would claim a shape it does + # not have. These keys need no schema to be read — every reader tolerates + # absence by construction. Nor does the schema BUMP: #103's contract is that + # it moves on removal or repurposing, never on addition, and this only adds. + # + # Before the start, like the clone re-stamp and for the same reason: an + # imported box is never observable — not for a second, not by a concurrent + # 'box info' — without the record of how it got here. + incus config set "$target" "${istamp[@]}" # Placement: the artifact carries its profile list, but the isolation # contract is THIS host's box-net profile. A box export already says # box-net; anything else (a pre-0.4.0 artifact, a hand-rolled export) gets diff --git a/docs/box-design.md b/docs/box-design.md index c4b0cce..de293fe 100644 --- a/docs/box-design.md +++ b/docs/box-design.md @@ -50,7 +50,13 @@ default, that survives `rm`, a host teardown, an upgrade, a move. The split of truths is the design: everything `incus import` restores is the artifact's (disk, config, snapshots); everything box re-stamps on import is the current host's (the `user.box=1` boundary tag, the `box-net` placement, a fresh -machine identity via the same `reset_identity` a clone gets). Auth state +machine identity via the same `reset_identity` a clone gets, and the record +that the trip happened). That last one is #131, and it is deliberately *not* +`origin=import`: `origin` says how the instance came into **being** — mint or +clone — and the import is a third, orthogonal fact. Overwriting `origin` would +make an exported clone come back claiming to be an import, with its +`origin.from` lineage left unreadable, so the import gets its own keys and the +artifact's mint stamp survives the trip untouched. Auth state rides along deliberately — and because scrubbing a disk image is a promise tarball surgery cannot keep, export shouts that the file is a credential instead of pretending to sanitize it. diff --git a/test/cli.sh b/test/cli.sh index e02201c..3223018 100644 --- a/test/cli.sh +++ b/test/cli.sh @@ -1391,6 +1391,277 @@ one_version_reader() { check "the tree's VERSION has a single reader, box_version() (#103)" 0 "" \ one_version_reader +# --------------------------------------------------------------------------- +# The import event (#131) — DRIVEN, on both halves, like the mint stamp above. +# +# An imported box keeps the artifact's mint stamp verbatim: the mint time, the +# box version, the image and the origin belong to the originating host and +# survive the trip on purpose (#129). What was missing is any record that the +# trip HAPPENED — and the obvious fix, 'origin=import', is the wrong one: it +# would overwrite whether the thing was a mint or a clone before it was +# exported, and leave 'origin.from' naming a lineage nothing explains. So the +# import takes its own keys, and the assertions that matter most here are the +# ABSENCE ones: every key the artifact carried must come out the far side +# untouched. +# +# The write half needs its own shim, because cmd_import reads 'incus config +# show ' as the name-collision guard and must see the name FREE — the +# opposite answer from the one the mint shim gives. +ISHIM="$(mktemp -d)"; IWORK="$(mktemp -d)" +cat > "$ISHIM/incus" <<'SHIM' +#!/usr/bin/env bash +# Fake incus for the import drive. FAKE_CFG answers 'config get' with a file +# of " " lines — it stands in for the config that rode inside the +# artifact and that 'incus import' has just restored. +printf 'incus %s\n' "$*" | tr '\n' ' ' >> "$FAKE_INCUS_LOG" +printf '\n' >> "$FAKE_INCUS_LOG" +case "$*" in + # Nothing exists under that name: the collision guard passes. The same call + # enumerates volatile hwaddrs later, where an empty answer is also correct. + "config show "*) exit 1 ;; + "config get "*) + [ -n "${FAKE_CFG:-}" ] || exit 0 + key="$*"; key="${key##* }" + awk -v k="$key" '$1 == k { $1 = ""; sub(/^ /, ""); print }' "$FAKE_CFG" ;; + *"--columns P") printf '"box-net"\n' ;; # already on the contract, no re-home +esac +exit 0 +SHIM +chmod +x "$ISHIM/incus" + +# A real tarball, because cmd_import reads the instance name out of the +# artifact with tar before incus is ever called — a stub cannot fake that. +ARTIFACT="$IWORK/work-20260718T120000Z.tar.gz" +mkdir -p "$IWORK/backup" && printf 'name: work\n' > "$IWORK/backup/index.yaml" +tar -czf "$ARTIFACT" -C "$IWORK" backup/index.yaml + +importbox() { # importbox — the real box, shimmed + local log="$1" cfg="$2" + : > "$log" + env FAKE_INCUS_LOG="$log" FAKE_CFG="$cfg" PATH="$ISHIM:$PATH" \ + "$BOX" import "$ARTIFACT" "$log.out" 2>&1 + local rc=$? + cat "$log.out" + return "$rc" +} +# One matcher per surface, so an absence assertion is a plain non-zero exit. +# 'config set' is the ONLY call that can write a key, so grepping it is what +# separates "box stamped this" from "the artifact carried it". +import_set() { grep -F 'config set' "$1" | grep -qE "$2"; } + +# --- the first trip --------------------------------------------------------- +# The artifact of a box that was MINTED elsewhere and never imported before. +MINTED_ART="$IWORK/minted.cfg" +cat > "$MINTED_ART" <<'CFG' +user.box 1 +user.box.schema 1 +user.box.created 2026-06-01T10:00:00Z +user.box.version 0.7.0 +user.box.template claude +user.box.user claude +user.box.origin mint +CFG +ILOG="$IWORK/import.log" +check "import: a shimmed 'box import' runs to completion (#131)" 0 "imported work" \ + importbox "$ILOG" "$MINTED_ART" +check "import: records WHEN the box landed here, UTC ISO 8601 (#131)" 0 "" \ + import_set "$ILOG" 'user\.box\.imported\.last=[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z' +check "import: records WHICH box version performed the import (#131)" 0 "" \ + import_set "$ILOG" "user\\.box\\.imported\\.last\\.by=$(cat "$ROOT/VERSION")" +check "import: pins the FIRST trip as its own key (birth pair, rig#61) (#131)" 0 "" \ + import_set "$ILOG" 'user\.box\.imported=[0-9]{4}-' +check "import: ...with the box version that made it (#131)" 0 "" \ + import_set "$ILOG" "user\\.box\\.imported\\.by=$(cat "$ROOT/VERSION")" +check "import: counts the trip — the first one is 1 (#131)" 0 "" \ + import_set "$ILOG" 'user\.box\.imported\.count=1' + +# --- the absence assertions: this is the whole point of the issue ----------- +# 'origin=import' is the road not taken. 'origin' says how the instance came +# into BEING — mint or clone — and the import is a third, orthogonal fact. +check "import: does NOT overwrite origin — an import is not a coming-into-being (#131)" 1 "" \ + import_set "$ILOG" 'user\.box\.origin=' +check "import: does NOT restamp the mint time — it is the origin host's (#131)" 1 "" \ + import_set "$ILOG" 'user\.box\.created=' +check "import: does NOT restamp the box version that MINTED it (#131)" 1 "" \ + import_set "$ILOG" 'user\.box\.version=' +check "import: does NOT restamp the template — lineage rides in the artifact (#131)" 1 "" \ + import_set "$ILOG" 'user\.box\.template=' +check "import: does NOT restamp the image the artifact was built on (#131)" 1 "" \ + import_set "$ILOG" 'user\.box\.image' +check "import: does NOT restamp the rig that converged it (#131)" 1 "" \ + import_set "$ILOG" 'user\.box\.rig\.' +# Adding a key is not a breaking change (#103's schema contract), so the schema +# does not move — and is not written here at all: stamping schema=1 onto a +# legacy artifact would claim a mint stamp shape it does not have. +check "import: does NOT touch user.box.schema — adding a key is not breaking (#131)" 1 "" \ + import_set "$ILOG" 'user\.box\.schema' +# Before the start, like the clone re-stamp: an imported box is never +# observable without the record of how it got here. Fail-closed — a missing +# line makes the arithmetic fail, not pass. +import_stamp_precedes_start() { + local set start + set="$(grep -n 'config set .* user.box.imported.last=' "$1" | head -1 | cut -d: -f1)" + start="$(grep -n '^incus start ' "$1" | head -1 | cut -d: -f1)" + [ -n "$set" ] && [ -n "$start" ] && [ "$set" -lt "$start" ] +} +check "import: the import stamp precedes the start (#131)" 0 "" \ + import_stamp_precedes_start "$ILOG" + +# --- a CLONE that was exported and re-imported ------------------------------ +# The case that makes 'origin=import' indefensible: it would come back +# claiming to be an import, with nothing left saying it was ever a clone and +# an origin.from pointing at a lineage no key explains. +CLONE_ART="$IWORK/clone-artifact.cfg" +{ grep -v '^user.box.origin ' "$MINTED_ART" + echo 'user.box.origin clone' + echo 'user.box.origin.from work/authed'; } > "$CLONE_ART" +CLOG="$IWORK/clone-import.log" +check "import: an exported CLONE imports cleanly (#131)" 0 "imported work" \ + importbox "$CLOG" "$CLONE_ART" +check "import: ...and is still a clone afterwards — origin untouched (#131)" 1 "" \ + import_set "$CLOG" 'user\.box\.origin=' +check "import: ...and still names the box it was cloned from (#131)" 1 "" \ + import_set "$CLOG" 'user\.box\.origin\.from' +check "import: ...while still recording that the trip happened (#131)" 0 "" \ + import_set "$CLOG" 'user\.box\.imported\.last=' + +# --- the second trip: first-wins, last-wins, and a count -------------------- +# The artifact of a box that has already been imported twice. Last-wins alone +# would erase the evidence of the first trip, which is the same mistake +# 'origin=import' makes one level up. +TWICE="$IWORK/twice.cfg" +{ cat "$MINTED_ART" + echo 'user.box.imported 2026-06-15T08:00:00Z' + echo 'user.box.imported.by 0.7.0' + echo 'user.box.imported.last 2026-07-01T09:00:00Z' + echo 'user.box.imported.last.by 0.8.0' + echo 'user.box.imported.count 2'; } > "$TWICE" +RLOG="$IWORK/reimport.log" +check "re-import: an already-imported artifact imports again (#131)" 0 "imported work" \ + importbox "$RLOG" "$TWICE" +check "re-import: the FIRST trip is pinned, never rewritten (#131)" 1 "" \ + import_set "$RLOG" 'user\.box\.imported=[0-9]' +check "re-import: ...nor is the version that made it (#131)" 1 "" \ + import_set "$RLOG" 'user\.box\.imported\.by=' +check "re-import: the LATEST trip IS refreshed (#131)" 0 "" \ + import_set "$RLOG" 'user\.box\.imported\.last=[0-9]{4}-' +check "re-import: ...by this box version (#131)" 0 "" \ + import_set "$RLOG" "user\\.box\\.imported\\.last\\.by=$(cat "$ROOT/VERSION")" +check "re-import: the count advances 2 → 3 (#131)" 0 "" \ + import_set "$RLOG" 'user\.box\.imported\.count=3' +# A count that is not an integer (hand-edited config, a foreign key) must not +# fail an import that has already happened — arithmetic under 'set -e' would. +BADN="$IWORK/badcount.cfg" +{ cat "$MINTED_ART"; echo 'user.box.imported.count not-a-number'; } > "$BADN" +BLOG="$IWORK/badcount.log" +check "re-import: a non-integer count does not fail the import (#131)" 0 "imported work" \ + importbox "$BLOG" "$BADN" +check "re-import: ...it restarts the count rather than inventing a total (#131)" 0 "" \ + import_set "$BLOG" 'user\.box\.imported\.count=1' +# A leading zero is the hole the non-integer fixture CANNOT catch: '08' passes +# an -eq guard (test parses decimal) and then dies in arithmetic, which reads +# it as octal. That abort would land after the physical 'incus import' and +# before the stamp, the placement fix and the start — the exact window the +# degrade-never-die contract exists to protect. A zero-padded count is not +# exotic either: it is what any external tool that formats numbers writes. +ZEROPAD="$IWORK/zeropad.cfg" +{ cat "$MINTED_ART"; echo 'user.box.imported.count 08'; } > "$ZEROPAD" +ZLOG="$IWORK/zeropad.log" +check "re-import: a zero-padded count does not fail the import (#131)" 0 "imported work" \ + importbox "$ZLOG" "$ZEROPAD" +# Counted as decimal 8, not degraded to 0 and not read as octal: '08' is a +# real previous total, so the honest next value is 9. +check "re-import: ...and counts it as decimal, so 08 advances to 9 (#131)" 0 "" \ + import_set "$ZLOG" 'user\.box\.imported\.count=9' + +# --- a legacy artifact with no stamp at all --------------------------------- +# A pre-stamp box export, or a hand-rolled 'incus export' of an unmanaged VM. +# It must import cleanly, get the boundary tag, get the import record — and NOT +# acquire a fabricated mint, which is what 'not recorded' exists to say. +LLOG="$IWORK/legacy-import.log" +check "import: a legacy artifact with NO stamp imports cleanly (#131)" 0 "imported work" \ + importbox "$LLOG" /dev/null +check "import: ...it still gets the boundary tag (importing is minting) (#131)" 0 "" \ + grep -qF 'config set work user.box=1' "$LLOG" +check "import: ...and the import record (#131)" 0 "" \ + import_set "$LLOG" 'user\.box\.imported\.last=' +check "import: ...but NO invented mint time (#131)" 1 "" \ + import_set "$LLOG" 'user\.box\.created=' +check "import: ...and no invented mint version either (#131)" 1 "" \ + import_set "$LLOG" 'user\.box\.version=' + +# --- the read half: 'box info' must not let the mint be misread ------------- +# The whole hazard: MINTED carries a time that is deliberately NOT this host's, +# and a reader who meets it alone will take it for one. The IMPORTED line sits +# directly under it and states the only thing box actually knows — the +# ORDERING. It does not claim another host: a box can be exported and +# re-imported onto the SAME host (#66's upgrade advice), and nothing records +# which host minted it. +IMPCFG="$MWORK/imported.cfg" +{ cat "$STAMPED" + echo 'user.box.imported 2026-07-20T09:14:03Z' + echo 'user.box.imported.by 0.8.1' + echo 'user.box.imported.last 2026-07-20T09:14:03Z' + echo 'user.box.imported.last.by 0.8.1' + echo 'user.box.imported.count 1'; } > "$IMPCFG" +check "info: surfaces when the box arrived, and by which box (#131)" \ + 0 "IMPORTED 2026-07-20T09:14:03Z by box 0.8.1" infobox "$IMPCFG" +check "info: ...and says the mint above is NOT this arrival (#131)" \ + 0 "the mint above predates it" infobox "$IMPCFG" +check "info: ...while the artifact's mint time still reads unchanged (#131)" \ + 0 "MINTED 2026-07-19T14:22:07Z by box 0.8.0" infobox "$IMPCFG" +# It must not invent a location for the mint — box has no record of one. +check "info: ...and never claims the mint happened on another host (#131)" 1 "" \ + info_has "$IMPCFG" 'another host|elsewhere|remote host' +# A single trip prints ONE line: both pairs hold the same values and a +# 'first was...' continuation would be noise. +check "info: a single import prints no redundant 'first was' line (#131)" 1 "" \ + info_has "$IMPCFG" 'the first was' + +# A box that made the trip more than once shows both ends and the count. +IMPCFG2="$MWORK/imported-twice.cfg" +{ cat "$STAMPED" + echo 'user.box.imported 2026-06-15T08:00:00Z' + echo 'user.box.imported.by 0.7.0' + echo 'user.box.imported.last 2026-07-20T09:14:03Z' + echo 'user.box.imported.last.by 0.8.1' + echo 'user.box.imported.count 3'; } > "$IMPCFG2" +check "info: a repeat traveller shows the latest trip (#131)" \ + 0 "IMPORTED 2026-07-20T09:14:03Z by box 0.8.1" infobox "$IMPCFG2" +check "info: ...and the first one, with the count (#131)" \ + 0 "import 3 — the first was 2026-06-15T08:00:00Z by box 0.7.0" infobox "$IMPCFG2" + +# An imported CLONE reads as a clone that also travelled — the two facts sit +# side by side, neither having eaten the other. +IMPCLONE="$MWORK/imported-clone.cfg" +# mode.asked is dropped, not merely unasserted: since #129 the clone path +# clears it (nobody asked THIS box anything), so a fixture built from the mint +# shape that kept the key would describe a box the clone path cannot produce. +# No assertion here reads it — which is exactly why it would rot unnoticed. +{ grep -v '^user.box.origin ' "$IMPCFG" | grep -v '^user.box.mode.asked ' + echo 'user.box.origin clone' + echo 'user.box.origin.from work/authed'; } > "$IMPCLONE" +check "info: an imported clone is still a clone (#131)" \ + 0 "ORIGIN clone of work/authed" infobox "$IMPCLONE" +check "info: ...and still says it was imported (#131)" 0 "IMPORTED" infobox "$IMPCLONE" + +# A box that was never imported says nothing at all — no empty IMPORTED line, +# the same rule every other key in the block follows. +check "info: a never-imported box prints no IMPORTED line (#131)" 1 "" \ + info_has "$STAMPED" '^IMPORTED' +# And a legacy box with no stamp whatsoever still reads as 'not recorded'. +check "info: a stampless box still says the mint was not recorded (#131)" \ + 0 "predates the mint stamp" infobox "$LEGACY" +check "info: ...and prints no IMPORTED line for a key it does not have (#131)" 1 "" \ + info_has "$LEGACY" '^IMPORTED' + +check "help import: names the import record it writes (#131)" 0 "import EVENT" \ + "$BOX" help import +check "help import: says the mint stamp is NOT rewritten (#131)" 0 "does not overwrite" \ + "$BOX" help import + +rm -rf "$ISHIM" "$IWORK" + rm -rf "$MSHIM" "$MWORK" # The rehearsal itself stays runnable: syntax-checked here, run on real hosts.