Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/nightly-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,26 @@ jobs:
fetch-depth: 0
token: ${{ secrets.RELEASE_TOKEN }}

# Refuse to cut a stable tag while dig-constants is stale or duplicated.
#
# dig-constants is the ecosystem's one source for ports, paths and shared values, and both of
# its failure modes are SILENT — neither is a build error:
# • stale — a changed (not renamed) value means this node disagrees with every component
# that is current, and nothing says so.
# • duplicated — cargo cannot unify semver-incompatible 0.x minors, so several copies link
# into one binary, each serving a different subsystem. On 2026-08-03 this repo
# shipped FOUR (0.1.0/0.4.0/0.5.1/0.8.0) against a published 0.9.0.
#
# Placed BEFORE version resolution deliberately: the point is that no tag exists to deploy, not
# that a bad build is caught later. Releasing is the moment the drift escapes the repo.
#
# The script fails closed if crates.io cannot be read — a gate that passes on a network error
# is bypassable by causing one.
- name: Require dig-constants to be single and current
if: steps.token.outputs.present == 'true'
shell: bash
run: ./scripts/check-dig-constants-current.sh

- name: Resolve version + skip if already tagged
if: steps.token.outputs.present == 'true'
id: ver
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ edition = "2021"
# the ROOT manifest (`[workspace.package].version`), so it MUST be set here for a
# release to fire (§3.6). The library crates (dig-node-core/dig-runtime/dig-wallet)
# keep their own independent versions — only the released binary tracks the workspace version.
version = "0.93.8"
version = "0.93.9"

# Release hardening, matching digstore: keep integer-overflow checks ON in release.
# The node parses untrusted serialized input and does offset/length arithmetic over
Expand Down
89 changes: 89 additions & 0 deletions scripts/check-dig-constants-current.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
# check-dig-constants-current.sh — refuse a release unless dig-constants is SINGLE and LATEST.
#
# WHY THIS EXISTS
#
# dig-constants is the ecosystem's one source for ports, paths, schemes and shared values. Its whole
# purpose is that two components cannot disagree about a canonical value. Two ways that guarantee
# fails silently, and neither shows up as a build error:
#
# 1. STALE — the node compiles against an old copy while the value has since changed. A renamed
# constant fails loudly; a CHANGED one does not. The node simply uses the wrong port/path and
# disagrees with every component that is current.
#
# 2. DUPLICATED — cargo cannot unify semver-incompatible 0.x minors, so a graph can carry several
# dig-constants versions AT ONCE, each linked into a different subsystem of the same binary.
# On 2026-08-03 dig-node's lock held FOUR (0.1.0 via dig-clvm, 0.4.0 via dig-gossip +
# dig-node-core, 0.5.1 via dig-nat + digstore-chain, 0.8.0 via dig-download) while 0.9.0 was
# published. That is the exact drift the crate exists to prevent, happening inside one process.
#
# Duplication is the more dangerous of the two and the one a version-bump review never catches,
# because every individual manifest looks reasonable.
#
# WHAT IT CHECKS — the LOCK, not the manifest range. `dig-constants = "0.4"` is "satisfied" by a lock
# at 0.4.0 forever; only the lock says what actually compiles in.
#
# FAILS CLOSED. A release gate that passes when it cannot reach crates.io is bypassable by inducing a
# network error. If the index cannot be read, this refuses and says so.
#
# Usage: scripts/check-dig-constants-current.sh [path/to/Cargo.lock]
# Exit: 0 = single version, and it is the latest published; 1 = anything else.

set -uo pipefail

LOCK="${1:-Cargo.lock}"
CRATE="dig-constants"
UA="dig-node-ci/1.0 (https://github.com/DIG-Network/dig-node; release gate)"

[ -f "$LOCK" ] || { echo "::error::$LOCK not found"; exit 1; }

# Every version of the crate present in the resolved graph.
mapfile -t FOUND < <(awk -v c="$CRATE" '
/^\[\[package\]\]/ { name=""; ver="" }
/^name = / { gsub(/^name = "|"$/,""); name=$0 }
/^version = / { gsub(/^version = "|"$/,""); ver=$0; if (name==c) print ver }
' "$LOCK" | sort -u)

if [ "${#FOUND[@]}" -eq 0 ]; then
echo "::error::$CRATE does not appear in $LOCK at all. If dig-node genuinely no longer depends on it, delete this gate deliberately rather than letting it pass silently."
exit 1
fi

# The published tip, from the sparse index. A bare curl 403s here — the descriptive User-Agent is
# mandatory, not decoration.
name_len=${#CRATE}
if [ "$name_len" -le 2 ]; then path="$name_len/$CRATE"
elif [ "$name_len" -eq 3 ]; then path="3/${CRATE:0:1}/$CRATE"
else path="${CRATE:0:2}/${CRATE:2:2}/$CRATE"
fi

body="$(curl -sS --max-time 30 -A "$UA" "https://index.crates.io/$path" 2>/dev/null)" || body=""
LATEST="$(printf '%s' "$body" | grep -v '"yanked":true' | sed -n 's/.*"vers":"\([^"]*\)".*/\1/p' | tail -1)"

if [ -z "$LATEST" ]; then
echo "::error::could not read the crates.io sparse index for $CRATE. Refusing rather than assuming current — a gate that passes on a network error is not a gate."
exit 1
fi

echo "published tip : $LATEST"
echo "in this lock : ${FOUND[*]}"

rc=0

if [ "${#FOUND[@]}" -gt 1 ]; then
echo "::error::$CRATE resolves to ${#FOUND[@]} DIFFERENT versions in one binary: ${FOUND[*]}"
echo "::error::cargo cannot unify semver-incompatible 0.x minors, so each is linked into a different subsystem and they can disagree about a value that is supposed to be canonical by construction. Find the consumers with: cargo tree -i $CRATE"
rc=1
fi

for v in "${FOUND[@]}"; do
if [ "$v" != "$LATEST" ]; then
echo "::error::$CRATE $v is behind the published $LATEST. Bump the consumer that pins it — a 0.x minor gap is semver-BREAKING, so the caret range will never resolve forward on its own."
rc=1
fi
done

if [ "$rc" -eq 0 ]; then
echo "OK: exactly one $CRATE ($LATEST), matching the published tip."
fi
exit "$rc"
Loading