feat: box import records the trip, without rewriting who the box was
An imported box kept the artifact's mint stamp verbatim (#103) — 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. 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. The import is a third fact, orthogonal to the first two, so it takes its own keys and leaves every other one alone. Birth pair plus latest pair, the shape heavy-duty/rig#61 settled on for the same repeated-event question: imported/imported.by pinned once and never rewritten, imported.last/.last.by refreshed on every arrival, imported.count for the trips in between. Last-wins alone would erase the evidence of the earlier trips, which is the same mistake origin=import makes one level up. box info prints IMPORTED 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 and never claims another host: 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 breaking — and is not written by the import at all, so a legacy artifact still reads as MINTED (not recorded) rather than acquiring a shape it does not have. Closes #131. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f84e474549
commit
b2afea580e
5 changed files with 412 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
|
### 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**
|
- **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
|
(#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
|
three `user.*` keys, dropping the rest on the floor: the box version that
|
||||||
|
|
|
||||||
33
README.md
33
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`
|
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.
|
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
|
**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)`
|
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,
|
rather than erroring. `user.box.schema` names the stamp's _shape_ (an integer,
|
||||||
|
|
|
||||||
94
bin/box
94
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
|
differs, the same move migrate-host makes re-homing a legacy box
|
||||||
· a fresh machine identity (reset_identity, exactly like a clone), so its
|
· a fresh machine identity (reset_identity, exactly like a clone), so its
|
||||||
DHCP lease can never collide with the box it was exported from
|
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,
|
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
|
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.
|
# legacy 'user.claudebox' tag already says out loud at resolve_box.
|
||||||
box_provenance() {
|
box_provenance() {
|
||||||
local i="$1" schema created ver img fp m asked tpl u role rrepo rref origin from
|
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)"
|
schema="$(box_cfg "$i" user.box.schema)"
|
||||||
created="$(box_cfg "$i" user.box.created)"; ver="$(box_cfg "$i" user.box.version)"
|
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)"
|
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)"
|
role="$(box_cfg "$i" user.box.role)"
|
||||||
rrepo="$(box_cfg "$i" user.box.rig.repo)"; rref="$(box_cfg "$i" user.box.rig.ref)"
|
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)"
|
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
|
# 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.
|
# same mapping box_user() makes, for the same reason.
|
||||||
[ -n "$tpl" ] || [ "$(box_cfg "$i" user.claudebox)" != 1 ] || { tpl=claude; u="${u:-claude}"; }
|
[ -n "$tpl" ] || [ "$(box_cfg "$i" user.claudebox)" != 1 ] || { tpl=claude; u="${u:-claude}"; }
|
||||||
|
|
@ -1438,6 +1456,25 @@ box_provenance() {
|
||||||
else
|
else
|
||||||
printf '%-11s%s\n' MINTED "(not recorded — this box predates the mint stamp)"
|
printf '%-11s%s\n' MINTED "(not recorded — this box predates the mint stamp)"
|
||||||
fi
|
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
|
if [ -n "$tpl" ]; then
|
||||||
local paren=""
|
local paren=""
|
||||||
[ -z "$u" ] || paren="user $u"
|
[ -z "$u" ] || paren="user $u"
|
||||||
|
|
@ -1636,6 +1673,63 @@ cmd_import() {
|
||||||
tag="$(incus config get "$target" user.box 2>/dev/null || true)"
|
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 ] || tag="$(incus config get "$target" user.claudebox 2>/dev/null || true)"
|
||||||
[ "$tag" = 1 ] || incus config set "$target" user.box=1
|
[ "$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.
|
||||||
|
[ -n "$prev_n" ] && [ "$prev_n" -eq "$prev_n" ] 2>/dev/null || prev_n=0
|
||||||
|
n=$((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
|
# Placement: the artifact carries its profile list, but the isolation
|
||||||
# contract is THIS host's box-net profile. A box export already says
|
# 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
|
# box-net; anything else (a pre-0.4.0 artifact, a hand-rolled export) gets
|
||||||
|
|
|
||||||
|
|
@ -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
|
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
|
(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
|
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
|
rides along deliberately — and because scrubbing a disk image is a promise
|
||||||
tarball surgery cannot keep, export shouts that the file is a credential
|
tarball surgery cannot keep, export shouts that the file is a credential
|
||||||
instead of pretending to sanitize it.
|
instead of pretending to sanitize it.
|
||||||
|
|
|
||||||
252
test/cli.sh
252
test/cli.sh
|
|
@ -1391,6 +1391,258 @@ one_version_reader() {
|
||||||
check "the tree's VERSION has a single reader, box_version() (#103)" 0 "" \
|
check "the tree's VERSION has a single reader, box_version() (#103)" 0 "" \
|
||||||
one_version_reader
|
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 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"
|
||||||
|
{ grep -v '^user.box.origin ' "$IMPCFG"
|
||||||
|
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"
|
rm -rf "$MSHIM" "$MWORK"
|
||||||
|
|
||||||
# The rehearsal itself stays runnable: syntax-checked here, run on real hosts.
|
# The rehearsal itself stays runnable: syntax-checked here, run on real hosts.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue