diff --git a/shared/build-lib.sh b/shared/build-lib.sh index 8f13165..1440950 100755 --- a/shared/build-lib.sh +++ b/shared/build-lib.sh @@ -131,21 +131,22 @@ _validate_semver() { case "$_v3" in '' | *[!0-9]*) _bad ;; esac } -# Walk backwards from HEAD until a non-auto-commit is found, return its -# short SHA. Auto-commits are recognised by their canonical commit- -# message prefixes: +# Walk backwards from HEAD until a non-auto-commit is found, return the +# resolved git ref (e.g. "HEAD~2"). Auto-commits are recognised by their +# canonical commit-message prefixes: # # - "chore(embedded): cut v-beta" (beta auto-commit, build:993) # - "release: v lockstep" (stable auto-commit, build:986) # -# Used by compute_build_label to embed a stable source-SHA into beta -# labels without triggering the embedded-commit recursion: HEAD shifts -# when embedded bytes change, but the source SHA returned here stays -# the same as long as the underlying source hasn't moved. +# Used by the build-label helpers to derive a stable identifier (short +# SHA, three-word slug) from the underlying source state. The source +# ref is invariant across the auto-commit step (HEAD shifts when +# embedded bytes change), so a re-run on the same source state produces +# the same identifiers and no spurious commit. # -# Falls back to plain `HEAD` short SHA when the walk doesn't find a -# non-auto-commit in the first 32 commits (defensive cap). -_source_commit_short_sha() { +# Defensive cap: stops walking after 32 commits and returns whatever +# ref was reached. +_source_commit_ref() { _i=0 _ref="HEAD" while [ "$_i" -lt 32 ]; do @@ -159,7 +160,40 @@ _source_commit_short_sha() { esac break done - git -C "$root_dir" rev-parse --short=7 "$_ref" 2>/dev/null || echo "unknown" + echo "$_ref" +} + +# Three-word slug derived deterministically from the source commit's +# full SHA. Same source state → same slug; survives embedded auto- +# commits by deferring to _source_commit_ref. +# +# Three 4-hex-char chunks of the SHA are taken modulo the wordlist +# length (shared/build-words.txt). 283 words × 3 = 22M combinations, +# enough that a tester eyeballing two beta builds can tell at a glance +# whether they're running the same source. +# +# Format: "word-word-word" (lowercase, hyphen-separated). +_source_commit_slug() { + _ref=$(_source_commit_ref) + _full_sha=$(git -C "$root_dir" rev-parse "$_ref" 2>/dev/null || echo "0000000000000000") + _words_file="$root_dir/shared/build-words.txt" + if [ ! -f "$_words_file" ]; then + # Fall back to plain short SHA if the wordlist is missing + # (e.g. running from a checkout that predates this feature). + git -C "$root_dir" rev-parse --short=7 "$_ref" 2>/dev/null || echo "unknown" + return + fi + _wc=$(awk 'END{print NR}' "$_words_file") + _h1=$(echo "$_full_sha" | cut -c 1-4) + _h2=$(echo "$_full_sha" | cut -c 5-8) + _h3=$(echo "$_full_sha" | cut -c 9-12) + _n1=$(( (0x$_h1 % _wc) + 1 )) + _n2=$(( (0x$_h2 % _wc) + 1 )) + _n3=$(( (0x$_h3 % _wc) + 1 )) + _w1=$(awk -v n="$_n1" 'NR==n{print; exit}' "$_words_file") + _w2=$(awk -v n="$_n2" 'NR==n{print; exit}' "$_words_file") + _w3=$(awk -v n="$_n3" 'NR==n{print; exit}' "$_words_file") + echo "${_w1}-${_w2}-${_w3}" } # Compute build label and channel. Reads positional args: @@ -218,17 +252,19 @@ compute_build_label() { alpha | beta) channel="$_arg" _date=$(date -u +"%Y-%m-%d") - # Resolve the *source* SHA — the most recent commit that is - # not itself an embedded-files auto-commit. This stays stable - # across the build's auto-commit step (build:971-995) which - # advances HEAD by one when embedded bytes change. Using - # HEAD directly here would create an infinite loop: each cut - # would update the embedded label with the new chore-commit - # SHA, which would in turn require another chore commit. - # The source SHA is invariant across embedded auto-commits - # so the second cut on the same source state is a no-op. - _src_sha=$(_source_commit_short_sha) - build_label="v${_next_stable}-${channel} · ${_date} · g${_src_sha}" + # Three-word slug derived from the *source* SHA — see + # _source_commit_slug. Two builds carrying the same slug + # were cut from the same source state, so a tester can + # glance at the on-page label and confirm "yes, this is + # the cobalt-otter-meadow build I was emailed about" without + # having to look up a SHA. The slug is invariant across + # the embedded auto-commit step (build:971-995) so a re-cut + # on unchanged source produces the same slug, no spurious + # commit. Full source SHA is available via the binary's + # `--version` output and the chart appVersion for any case + # where exact provenance matters. + _slug=$(_source_commit_slug) + build_label="v${_next_stable}-${channel} · ${_date} · ${_slug}" _emit_build_label_sidecar "$_tool" return 0 ;; diff --git a/shared/build-words.txt b/shared/build-words.txt new file mode 100644 index 0000000..45b3fa6 --- /dev/null +++ b/shared/build-words.txt @@ -0,0 +1,283 @@ +acorn +agate +alder +almond +amber +anchor +angle +antler +apple +apricot +arbor +arch +arrow +ashen +aspen +atlas +attic +auburn +aurora +azure +badge +bagel +ballad +balm +banner +barn +basil +basin +basket +beacon +beam +bear +beech +beetle +berry +biscuit +blanket +blossom +boat +bonnet +book +boulder +brass +bread +breeze +brick +bridge +brook +broom +bubble +bucket +buckle +button +cabin +cable +cactus +cake +candle +canvas +canyon +carpet +cart +cedar +chair +chalk +chamber +chapel +charm +cherry +chime +cinder +citrus +clay +cliff +cloud +clover +cluster +coal +coast +cobble +cocoa +coin +comet +compass +copper +cottage +cove +cradle +crane +crater +creek +crescent +cricket +crystal +daisy +dawn +deer +denim +diamond +ditch +dock +dolphin +dome +drift +drum +dune +dusk +eagle +ember +emerald +fable +falcon +feather +fence +fern +fiddle +finch +flag +flame +flask +flax +fleece +flint +flute +foam +forest +fork +fort +fountain +fox +garden +garnet +gear +ginger +glacier +glade +glass +globe +gold +granite +grass +grove +hammer +harbor +harp +harvest +hazel +hearth +heron +hill +hinge +hive +holly +hook +horizon +horn +hut +indigo +iris +iron +island +ivory +ivy +jade +jasper +juniper +kayak +kelp +kettle +key +kiln +kite +lake +lamp +lantern +lark +laurel +leaf +ledge +lemon +lens +lilac +lily +linen +locust +log +loom +lotus +maple +marble +marigold +marsh +mast +meadow +melon +mesa +meteor +mint +mirror +mist +mitten +moon +moor +moss +mug +nectar +nest +niche +nickel +nimbus +oak +oasis +ocean +onyx +opal +orbit +orchard +otter +owl +oyster +paddle +palm +panel +parrot +patio +pearl +pebble +pelican +pepper +perch +pier +pillow +pine +pipe +plank +plaza +pocket +pond +poplar +port +prairie +prism +puddle +quarry +quartz +quill +quilt +rabbit +raft +rain +ranch +raven +reed +reef +ribbon +ridge +river +robin +rock +rope +saddle +sage +sail +salt +sand +sapphire +sash +satin +scarf +sea +seal +seed +shawl +shield +shore +shrub +silver +silo +slope +smoke +snail +snow +spire