Merge pull request #133 from dan-claude-bot/feat/import-provenance
feat: box import records the trip, without rewriting who the box was
This commit is contained in:
commit
82c1888381
5 changed files with 437 additions and 1 deletions
26
CHANGELOG.md
26
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
|
||||
|
|
|
|||
33
README.md
33
README.md
|
|
@ -508,6 +508,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,
|
||||
|
|
|
|||
100
bin/box
100
bin/box
|
|
@ -476,6 +476,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
|
||||
|
|
@ -1569,6 +1580,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)"
|
||||
|
|
@ -1577,6 +1589,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}"; }
|
||||
|
|
@ -1587,6 +1605,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"
|
||||
|
|
@ -1785,6 +1822,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
|
||||
|
|
|
|||
|
|
@ -62,7 +62,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.
|
||||
|
|
|
|||
271
test/cli.sh
271
test/cli.sh
|
|
@ -1567,6 +1567,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 <target>' 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 "<key> <value>" 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 <logfile> <cfg-file|""> — the real box, shimmed
|
||||
local log="$1" cfg="$2"
|
||||
: > "$log"
|
||||
env FAKE_INCUS_LOG="$log" FAKE_CFG="$cfg" PATH="$ISHIM:$PATH" \
|
||||
"$BOX" import "$ARTIFACT" </dev/null >"$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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue