feat: a minted box records how it was minted, and box info reads it back #129
3 changed files with 63 additions and 2 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -43,6 +43,17 @@ which records not just what changed but what each drill run proved.
|
||||||
records **one hop** — a clone of a clone names its parent and forgets its
|
records **one hop** — a clone of a clone names its parent and forgets its
|
||||||
grandparent, because the alternative is an unbounded chain in a config
|
grandparent, because the alternative is an unbounded chain in a config
|
||||||
value and the parent is the box an operator can go look at.
|
value and the parent is the box an operator can go look at.
|
||||||
|
One key sits in **neither** column and is therefore *cleared*:
|
||||||
|
`user.box.mode.asked`. It records whether a container was asked for or
|
||||||
|
fallen back into, which makes it a mint-*event* fact — and the asker was
|
||||||
|
the **source's** operator. A clone refuses `--vm`/`--container` outright,
|
||||||
|
so nobody was asked anything about this instance, and there is no true
|
||||||
|
value to re-stamp it with; inheriting it made `box info` print
|
||||||
|
`MODE vm (asked: auto)` on a clone, describing a demand never made of it.
|
||||||
|
The clear lands with the re-stamp, before the start, and the read side
|
||||||
|
needs no special case: the `MODE` line is gated on `asked`, so absence
|
||||||
|
renders as silence while `TYPE` still reports VM or CT off the preserved
|
||||||
|
instance type.
|
||||||
- **`box info` grew a provenance block** (#103) — it printed
|
- **`box info` grew a provenance block** (#103) — it printed
|
||||||
`NAME / STATE / TYPE / IPV4`, exposures and snapshots, and surfaced *none*
|
`NAME / STATE / TYPE / IPV4`, exposures and snapshots, and surfaced *none*
|
||||||
of the `user.box.*` keys, including the two that already existed. A stamp
|
of the `user.box.*` keys, including the two that already existed. A stamp
|
||||||
|
|
|
||||||
16
bin/box
16
bin/box
|
|
@ -1079,6 +1079,17 @@ cmd_new() {
|
||||||
# that rig role — reading them off the source is the whole point of a clone.
|
# that rig role — reading them off the source is the whole point of a clone.
|
||||||
# ('incus copy' preserves the instance type too, so mode stays true as well.)
|
# ('incus copy' preserves the instance type too, so mode stays true as well.)
|
||||||
#
|
#
|
||||||
|
# 'mode.asked' is the one key that sits in NEITHER column, and so it is
|
||||||
|
# CLEARED rather than re-stamped or inherited. It is a mint-event fact —
|
||||||
|
# only the mint knew whether a container was asked for or fallen back into
|
||||||
|
# for want of /dev/kvm — and the asker was the SOURCE's operator. A clone
|
||||||
|
# refuses --vm/--container outright (nobody was asked anything here), so an
|
||||||
|
# inherited 'asked' makes 'box info' print a demand that was never made of
|
||||||
|
# this instance. There is no true value to re-stamp it with: the honest
|
||||||
|
# answer is absence, and absence is already how the whole block renders
|
||||||
|
# what it does not know — the MODE line simply does not print, while TYPE
|
||||||
|
# above still says VM or CT off the preserved instance type.
|
||||||
|
#
|
||||||
# origin.from records ONE hop. A clone of a clone names its parent and
|
# origin.from records ONE hop. A clone of a clone names its parent and
|
||||||
# forgets its grandparent: the alternative is an unbounded chain in a config
|
# forgets its grandparent: the alternative is an unbounded chain in a config
|
||||||
# value, and the parent is the box an operator can actually go look at.
|
# value, and the parent is the box an operator can actually go look at.
|
||||||
|
|
@ -1088,6 +1099,11 @@ cmd_new() {
|
||||||
user.box.created="$(mint_time)" \
|
user.box.created="$(mint_time)" \
|
||||||
user.box.origin=clone \
|
user.box.origin=clone \
|
||||||
user.box.origin.from="$srcref"
|
user.box.origin.from="$srcref"
|
||||||
|
# Cleared, not set-to-empty: an empty value is still a key, and a reader
|
||||||
|
# that greps the config would find it. Tolerated failure because the source
|
||||||
|
# may predate the stamp and never have carried the key at all — a clone
|
||||||
|
# must not die over a key that was already absent.
|
||||||
|
incus config unset "$instance" user.box.mode.asked >/dev/null 2>&1 || true
|
||||||
incus start "$instance"
|
incus start "$instance"
|
||||||
wait_agent "$instance"
|
wait_agent "$instance"
|
||||||
reset_identity "$instance"
|
reset_identity "$instance"
|
||||||
|
|
|
||||||
38
test/cli.sh
38
test/cli.sh
|
|
@ -1256,6 +1256,17 @@ check "clone: does NOT re-stamp the image — the disk really came from it (#103
|
||||||
restamp_has "$CLONELOG" 'user\.box\.image'
|
restamp_has "$CLONELOG" 'user\.box\.image'
|
||||||
check "clone: does NOT re-stamp the role — the source's rig converged it (#103)" 1 "" \
|
check "clone: does NOT re-stamp the role — the source's rig converged it (#103)" 1 "" \
|
||||||
restamp_has "$CLONELOG" 'user\.box\.role'
|
restamp_has "$CLONELOG" 'user\.box\.role'
|
||||||
|
# The third column, and the one review found: 'mode.asked' is neither lineage
|
||||||
|
# nor re-stampable. It is a mint-event fact whose asker was the SOURCE's
|
||||||
|
# operator, and a clone refuses --vm/--container, so nobody was asked anything
|
||||||
|
# here. It is CLEARED — there is no true value to give it.
|
||||||
|
check "clone: clears the inherited mode.asked — nobody asked THIS box (#103)" 0 "" \
|
||||||
|
grep -qF 'config unset w2 user.box.mode.asked' "$CLONELOG"
|
||||||
|
check "clone: ...and does not re-stamp it with a fabricated answer (#103)" 1 "" \
|
||||||
|
restamp_has "$CLONELOG" 'user\.box\.mode\.asked'
|
||||||
|
# Cleared, never set-to-empty: an empty value is still a key on the instance.
|
||||||
|
check "clone: clears it rather than setting it empty (#103)" 1 "" \
|
||||||
|
grep -qE 'config set .*user\.box\.mode\.asked=($|[[:space:]])' "$CLONELOG"
|
||||||
# Order: the re-stamp lands on the copied instance BEFORE it is started, so a
|
# Order: the re-stamp lands on the copied instance BEFORE it is started, so a
|
||||||
# clone is never observable wearing its source's provenance. Fail-closed — an
|
# clone is never observable wearing its source's provenance. Fail-closed — an
|
||||||
# absent line makes the arithmetic fail, not pass.
|
# absent line makes the arithmetic fail, not pass.
|
||||||
|
|
@ -1267,6 +1278,17 @@ restamp_precedes_start() {
|
||||||
}
|
}
|
||||||
check "clone: the re-stamp precedes the start (#103)" 0 "" \
|
check "clone: the re-stamp precedes the start (#103)" 0 "" \
|
||||||
restamp_precedes_start "$CLONELOG"
|
restamp_precedes_start "$CLONELOG"
|
||||||
|
# The clear rides the same rule for the same reason: a clone must never be
|
||||||
|
# observable — not for one moment, not to 'box info' — wearing an 'asked' its
|
||||||
|
# operator never gave. Fail-closed the same way.
|
||||||
|
clear_precedes_start() {
|
||||||
|
local unset_ln start
|
||||||
|
unset_ln="$(grep -n 'config unset .* user.box.mode.asked' "$1" | head -1 | cut -d: -f1)"
|
||||||
|
start="$(grep -n '^incus start ' "$1" | head -1 | cut -d: -f1)"
|
||||||
|
[ -n "$unset_ln" ] && [ -n "$start" ] && [ "$unset_ln" -lt "$start" ]
|
||||||
|
}
|
||||||
|
check "clone: the mode.asked clear precedes the start too (#103)" 0 "" \
|
||||||
|
clear_precedes_start "$CLONELOG"
|
||||||
|
|
||||||
# --- the read half: 'box info' surfaces it ---------------------------------
|
# --- the read half: 'box info' surfaces it ---------------------------------
|
||||||
# A stamp nothing can read is not done. cmd_info printed NAME/STATE/TYPE/IPV4
|
# A stamp nothing can read is not done. cmd_info printed NAME/STATE/TYPE/IPV4
|
||||||
|
|
@ -1304,13 +1326,25 @@ check "info: surfaces the origin (#103)" 0 "ORIGIN mint" infobox "$STAMPED"
|
||||||
# Still the box it always was: the new block is additive, above the snapshots.
|
# Still the box it always was: the new block is additive, above the snapshots.
|
||||||
check "info: still prints the state block it always did" 0 "IPV4 10.1.2.3" infobox "$STAMPED"
|
check "info: still prints the state block it always did" 0 "IPV4 10.1.2.3" infobox "$STAMPED"
|
||||||
|
|
||||||
# A clone reads back as a clone, naming its source.
|
# A clone reads back as a clone, naming its source. Modelled on what the
|
||||||
|
# --from branch actually leaves behind: origin re-stamped, mode.asked cleared.
|
||||||
CLONECFG="$MWORK/clone.cfg"
|
CLONECFG="$MWORK/clone.cfg"
|
||||||
{ grep -v '^user.box.origin ' "$STAMPED"
|
{ grep -v -e '^user.box.origin ' -e '^user.box.mode.asked ' "$STAMPED"
|
||||||
echo 'user.box.origin clone'
|
echo 'user.box.origin clone'
|
||||||
echo 'user.box.origin.from work/authed'; } > "$CLONECFG"
|
echo 'user.box.origin.from work/authed'; } > "$CLONECFG"
|
||||||
check "info: a clone says so, and names the box it came from (#103)" \
|
check "info: a clone says so, and names the box it came from (#103)" \
|
||||||
0 "ORIGIN clone of work/authed" infobox "$CLONECFG"
|
0 "ORIGIN clone of work/authed" infobox "$CLONECFG"
|
||||||
|
# ...and stays silent about a demand nobody made of it. The MODE line is gated
|
||||||
|
# on 'asked' precisely so absence renders as silence rather than as a guess;
|
||||||
|
# TYPE above still reports VM off the instance type, so nothing is lost.
|
||||||
|
info_has_mode() { infobox "$1" | grep -q '^MODE'; }
|
||||||
|
check "info: a clone prints no MODE line — nobody asked IT anything (#103)" 1 "" \
|
||||||
|
info_has_mode "$CLONECFG"
|
||||||
|
check "info: ...while TYPE still reports what it actually is (#103)" \
|
||||||
|
0 "TYPE VM" infobox "$CLONECFG"
|
||||||
|
# The mint keeps its MODE line — there, the operator really was asked.
|
||||||
|
check "info: a MINT still surfaces what was asked for (#103)" \
|
||||||
|
0 "MODE vm (asked: auto)" infobox "$STAMPED"
|
||||||
|
|
||||||
# --- legacy boxes: the promise that they keep working under every verb ------
|
# --- legacy boxes: the promise that they keep working under every verb ------
|
||||||
# A box carrying the boundary tag and NOTHING else — every box minted before
|
# A box carrying the boundary tag and NOTHING else — every box minted before
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue