diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2f5233b3..67dc75bf 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: Report a problem with digstore +about: Report a problem with dig-store title: "[bug] " labels: bug --- @@ -17,7 +17,7 @@ A clear description of the bug. What you expected instead. **Environment** -- `digstore --version`: +- `dig-store --version`: - OS / arch: - Install method (installer / from source): diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index e34b4da6..95be94a4 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ blank_issues_enabled: false contact_links: - name: πŸ”’ Report a security vulnerability (private) - url: https://github.com/DIG-Network/digstore/security/policy + url: https://github.com/DIG-Network/dig-store/security/policy about: Please report security issues privately β€” see SECURITY.md. Do NOT open a public issue. A URN is a credential; never paste real URNs, store ids, or salts. - name: πŸ’¬ Questions & discussion - url: https://github.com/DIG-Network/digstore/discussions + url: https://github.com/DIG-Network/dig-store/discussions about: For usage questions and ideas, start a discussion instead of an issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 5eb05e6f..2125b90d 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,6 +1,6 @@ --- name: Feature request -about: Suggest an idea for digstore +about: Suggest an idea for dig-store title: "[feat] " labels: enhancement --- diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 2232b35d..122c4d70 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -1,4 +1,4 @@ -# The ONE place digstore's cross-OS binary build lives. It is a REUSABLE workflow +# The ONE place dig-store's cross-OS binary build lives. It is a REUSABLE workflow # (`on: workflow_call` only β€” it never fires on its own): both release paths call it, so the # OS/arch matrix + the guest-wasm build prereq are defined exactly once (DRY, CLAUDE.md Β§2.5): # @@ -6,15 +6,26 @@ # * nightly-release.yml β€” the NIGHTLY pre-release channel. # # The caller passes the version string stamped into each artifact's filename and, optionally, the -# git ref to build. The job builds the raw `digstore` CLI binary AND its first-class `digs` alias -# (issue #434) for every supported OS/arch, in TWO asset shapes (two installers consume them with -# different naming conventions, each verified by that installer's own repo): +# git ref to build. The job builds the primary `dig-store` CLI binary AND its first-class `digs` +# alias (issue #434) for every supported OS/arch. +# +# RENAME (rename epic #703): the primary binary was renamed `digstore` -> `dig-store`. For ONE +# transition cycle every asset is DUAL-PUBLISHED under BOTH the new `dig-store-*` stem AND the +# legacy `digstore-*` stem, so the two installers (apt.dig.net + dig-installer) stay green until +# they cut over to `dig-store-*`. The Linux tarball ALSO ships a `digstore` -> `dig-store` compat +# symlink at its root so existing scripts that call `digstore` directly keep working (the `digs` +# alias already shields interactive users). Both are transitional and drop in a later release. +# +# Assets produced, in TWO shapes (two installers consume them with different naming conventions, +# each verified by that installer's own repo): # # 1. Bare per-OS binaries for the universal installer (dig-installer's RawBinary matcher): -# digstore--[.exe] + digs--[.exe] -# 2. Linux .tar.gz tarballs for the APT repository (apt.dig.net's build-deb.sh ASSET_TEMPLATE -# `digstore-{ver}-{arch}-unknown-linux-gnu.tar.gz`) β€” each archive holds BOTH bare binaries at -# its root, unpacked to /usr/bin/digstore + /usr/bin/digs. +# dig-store--[.exe] + digstore--[.exe] (legacy) + +# digs--[.exe] +# 2. Linux .tar.gz tarballs for the APT repository (apt.dig.net's build-deb.sh ASSET_TEMPLATE) β€” +# published under BOTH `dig-store---unknown-linux-gnu.tar.gz` AND the legacy +# `digstore---unknown-linux-gnu.tar.gz`. Each archive holds `dig-store` + `digs` at +# its root, PLUS a `digstore` -> `dig-store` compat symlink, unpacked to /usr/bin. # # BUILD PREREQ (CLAUDE.md Β§3.5 / BINDING contract D6): `digstore-cli`'s build.rs embeds the real # guest wasm, so `digstore-guest` MUST be built for `wasm32-unknown-unknown` BEFORE the CLI, or the @@ -44,40 +55,40 @@ env: jobs: build: - name: Build digstore CLI (${{ matrix.out_name || matrix.target }}) + name: Build dig-store CLI (${{ matrix.out_name || matrix.target }}) strategy: fail-fast: false matrix: include: - os: windows-latest target: x86_64-pc-windows-msvc - bin: digstore.exe + bin: dig-store.exe # Bare binary for the universal installer (Windows RawBinary = .exe). out_name: windows-x64.exe - os: ubuntu-latest target: x86_64-unknown-linux-gnu - bin: digstore + bin: dig-store # Bare binary for the universal installer (Linux RawBinary = no ext)… out_name: linux-x64 - # …PLUS a .tar.gz for apt, named to apt.dig.net's ASSET_TEMPLATE. - tarball: digstore-{ver}-x86_64-unknown-linux-gnu.tar.gz + # …PLUS a .tar.gz for apt, named to apt.dig.net's ASSET_TEMPLATE arch triple. + tarball_arch: x86_64-unknown-linux-gnu - os: ubuntu-24.04-arm # native arm64 Linux runner target: aarch64-unknown-linux-gnu - bin: digstore + bin: dig-store # apt builds an arm64 .deb from this; the universal installer does not # resolve a linux-arm64 RawBinary today, so only the tarball is needed. - tarball: digstore-{ver}-aarch64-unknown-linux-gnu.tar.gz + tarball_arch: aarch64-unknown-linux-gnu # Both macOS arches build on the fast Apple-silicon macos-14 runner; the Intel binary is # CROSS-COMPILED there (host arm64 β†’ target x86_64-apple-darwin). The one native C dep in # the macOS graph is vendored OpenSSL (openssl-src builds it via `cc`), which the Apple # toolchain compiles for x86_64 from the arm host β€” no system openssl to cross-link. - os: macos-14 # Apple silicon (arm64) target: aarch64-apple-darwin - bin: digstore + bin: dig-store out_name: macos-arm64 - os: macos-14 # Apple silicon β€” cross-compiles the Intel binary target: x86_64-apple-darwin - bin: digstore + bin: dig-store out_name: macos-x64 runs-on: ${{ matrix.os }} steps: @@ -109,10 +120,10 @@ jobs: - name: Build guest wasm run: cargo build -p digstore-guest --target wasm32-unknown-unknown --release --locked - - name: Build digstore + digs CLI (release) + - name: Build dig-store + digs CLI (release) # Build BOTH bins β€” `digs` is the first-class alias (issue #434), shipped alongside - # `digstore` in every asset below. - run: cargo build -p digstore-cli --release --locked --target ${{ matrix.target }} --bin digstore --bin digs + # `dig-store` in every asset below. + run: cargo build -p digstore-cli --release --locked --target ${{ matrix.target }} --bin dig-store --bin digs # The caller's `version` is stamped into filenames verbatim: a plain `1.2.3` for a stable # tag, or the synthesized `1.2.3-nightly.YYYYMMDD.` for a nightly. @@ -125,31 +136,39 @@ jobs: run: | set -euo pipefail mkdir -p dist - SRC="target/${{ matrix.target }}/release/${{ matrix.bin }}" + REL="target/${{ matrix.target }}/release" + SRC="$REL/${{ matrix.bin }}" test -f "$SRC" || { echo "binary not produced: $SRC"; exit 1; } - # The `digs` alias binary sits beside `digstore` (bin name differs only by the - # `digstore`->`digs` substring, incl. the `.exe` suffix on Windows). - DIGS_BIN="${{ matrix.bin }}"; DIGS_BIN="${DIGS_BIN/digstore/digs}" - DIGS_SRC="target/${{ matrix.target }}/release/${DIGS_BIN}" + # The `digs` alias sits beside `dig-store`. Its filename is derived INDEPENDENTLY (not by + # a `dig-store`->`digs` substring β€” after the #703 rename there is no such substring to + # replace); the only variation is the Windows `.exe` suffix. + EXE="" + case "${{ matrix.bin }}" in *.exe) EXE=".exe";; esac + DIGS_SRC="$REL/digs${EXE}" test -f "$DIGS_SRC" || { echo "digs binary not produced: $DIGS_SRC"; exit 1; } # (1) Bare per-OS binaries for the universal installer, when this target declares an - # out_name. BOTH digstore and its first-class alias digs (issue #434). + # out_name. The primary `dig-store`, the transitional legacy `digstore` (same bytes, + # dual-published for one cycle), and the first-class `digs` alias (issue #434). if [ -n "${{ matrix.out_name }}" ]; then - cp "$SRC" "dist/digstore-${VERSION}-${{ matrix.out_name }}" + cp "$SRC" "dist/dig-store-${VERSION}-${{ matrix.out_name }}" + cp "$SRC" "dist/digstore-${VERSION}-${{ matrix.out_name }}" cp "$DIGS_SRC" "dist/digs-${VERSION}-${{ matrix.out_name }}" fi - # (2) .tar.gz (binaries at archive root, named `digstore` + `digs`) for apt, when this - # target declares a tarball template. - if [ -n "${{ matrix.tarball }}" ]; then - TARBALL="${{ matrix.tarball }}" - TARBALL="${TARBALL/\{ver\}/$VERSION}" + # (2) .tar.gz for apt, when this target declares a tarball arch. The archive root holds + # `dig-store` + `digs`, PLUS a `digstore` -> `dig-store` compat symlink (transitional, + # rename epic #703). Published under BOTH the new `dig-store-*` stem AND the legacy + # `digstore-*` stem so apt.dig.net stays green until it cuts over. + if [ -n "${{ matrix.tarball_arch }}" ]; then + ARCH="${{ matrix.tarball_arch }}" STAGE="$(mktemp -d)" - cp "$SRC" "$STAGE/digstore" + cp "$SRC" "$STAGE/dig-store" cp "$DIGS_SRC" "$STAGE/digs" - chmod 0755 "$STAGE/digstore" "$STAGE/digs" - tar -czf "dist/${TARBALL}" -C "$STAGE" digstore digs + chmod 0755 "$STAGE/dig-store" "$STAGE/digs" + ln -s dig-store "$STAGE/digstore" # transitional compat symlink + tar -czf "dist/dig-store-${VERSION}-${ARCH}.tar.gz" -C "$STAGE" dig-store digs digstore + tar -czf "dist/digstore-${VERSION}-${ARCH}.tar.gz" -C "$STAGE" dig-store digs digstore rm -rf "$STAGE" fi ls -la dist @@ -157,6 +176,6 @@ jobs: - name: Upload assets as workflow artifact uses: actions/upload-artifact@v4 with: - name: digstore-assets-${{ matrix.target }} + name: dig-store-assets-${{ matrix.target }} path: dist/* if-no-files-found: error diff --git a/.github/workflows/nightly-release.yml b/.github/workflows/nightly-release.yml index 770e58d0..92031aa6 100644 --- a/.github/workflows/nightly-release.yml +++ b/.github/workflows/nightly-release.yml @@ -32,7 +32,7 @@ # 60-day auto-disable (GitHub platform behavior, not this workflow's): GitHub disables a # `schedule:` trigger after 60 days with NO repo activity on a public repo, with no auto-re-enable # β€” and since #590 removed the push-to-main trigger, this cron is the ONLY automatic release -# path. If nightlies silently stop, check `gh api repos/DIG-Network/digstore/actions/workflows/ +# path. If nightlies silently stop, check `gh api repos/DIG-Network/dig-store/actions/workflows/ # nightly-release.yml --jq .state` and re-enable with `gh workflow enable nightly-release.yml` # (see runbooks/release.md + SPEC.md Β§12.1). # ───────────────────────────────────────────────────────────────────────────────────────────── diff --git a/.github/workflows/publish-binary.yml b/.github/workflows/publish-binary.yml index 8ed568ac..e868319a 100644 --- a/.github/workflows/publish-binary.yml +++ b/.github/workflows/publish-binary.yml @@ -76,14 +76,16 @@ jobs: - name: Contract gate (producer <-> verifier) run: cargo test -p digstore-host --test dighost_serve - - name: Build static-musl digstore - run: cargo build --release --target x86_64-unknown-linux-musl -p digstore-cli --bin digstore + - name: Build static-musl dig-store + run: cargo build --release --target x86_64-unknown-linux-musl -p digstore-cli --bin dig-store - name: Stage + hash id: stage shell: bash run: | - BIN=target/x86_64-unknown-linux-musl/release/digstore + # The binary was renamed `digstore` -> `dig-store` (#703); the dighub S3 layout + # (`digstore//digstore`) is the compile-worker contract and stays UNCHANGED. + BIN=target/x86_64-unknown-linux-musl/release/dig-store test -f "$BIN" || { echo "binary not produced"; exit 1; } file "$BIN" VER="g$(git rev-parse --short HEAD)" @@ -106,7 +108,7 @@ jobs: shell: bash run: | KEY="digstore/${VER}/digstore" - aws s3 cp "target/x86_64-unknown-linux-musl/release/digstore" "s3://${BUCKET}/${KEY}" + aws s3 cp "target/x86_64-unknown-linux-musl/release/dig-store" "s3://${BUCKET}/${KEY}" printf '{"version":"%s","key":"%s","sha256":"%s"}\n' "$VER" "$KEY" "$SHA" > latest.json cat latest.json aws s3 cp latest.json "s3://${BUCKET}/digstore/latest.json" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d8d8f1cc..77612958 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,6 @@ # STABLE binary release. On a `vX.Y.Z` tag (cut by the nightly-release orchestrator's stable job # β€” nightly-release.yml β€” either from the midnight cron detecting a version bump or from a manual -# `workflow_dispatch`), this workflow builds the raw `digstore` CLI + its `digs` alias for every +# `workflow_dispatch`), this workflow builds the raw `dig-store` CLI + its `digs` alias for every # OS/arch, in the two asset shapes both installers consume (bare per-OS binaries + apt tarballs, via # the reusable build workflow), and publishes them to a STABLE GitHub Release. It ALSO uploads the # Linux x86_64 binary to the dighub S3 artifact bucket for the hub compile-worker. The changelog is @@ -87,7 +87,7 @@ jobs: mkdir -p dist-all find dist -type f -exec cp {} dist-all/ \; ls -la dist-all - gh release create "${GITHUB_REF_NAME}" --repo "$GH_REPO" --title "digstore ${GITHUB_REF_NAME}" --generate-notes || echo "release already exists" + gh release create "${GITHUB_REF_NAME}" --repo "$GH_REPO" --title "dig-store ${GITHUB_REF_NAME}" --generate-notes || echo "release already exists" gh release upload "${GITHUB_REF_NAME}" --repo "$GH_REPO" dist-all/* --clobber # --------------------------------------------------------------------------- @@ -133,7 +133,7 @@ jobs: if: steps.gate.outputs.enabled == 'true' run: cargo build -p digstore-guest --target wasm32-unknown-unknown --release --locked - - name: Build digstore CLI (Linux x86_64, release) + - name: Build dig-store CLI (Linux x86_64, release) if: steps.gate.outputs.enabled == 'true' run: cargo build -p digstore-cli --release --locked @@ -151,7 +151,10 @@ jobs: run: | set -euo pipefail VERSION="${GITHUB_REF_NAME#v}" - BIN=target/release/digstore + # The binary was renamed `digstore` -> `dig-store` (#703). The dighub S3 layout + # (`digstore//digstore` + `digstore/latest.json`) is the hub compile-worker's + # contract and is UNCHANGED here β€” only the local build artifact name changed. + BIN=target/release/dig-store test -f "$BIN" || { echo "missing $BIN"; exit 1; } SHA=$(sha256sum "$BIN" | cut -d' ' -f1) KEY="digstore/${VERSION}/digstore" diff --git a/Cargo.lock b/Cargo.lock index 9cde13bc..b2ad331c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2289,7 +2289,7 @@ dependencies = [ [[package]] name = "digstore-chain" -version = "0.13.5" +version = "0.14.0" dependencies = [ "aes-gcm", "anyhow", @@ -2327,7 +2327,7 @@ dependencies = [ [[package]] name = "digstore-chunker" -version = "0.13.5" +version = "0.14.0" dependencies = [ "digstore-core", "hex", @@ -2337,7 +2337,7 @@ dependencies = [ [[package]] name = "digstore-cli" -version = "0.13.5" +version = "0.14.0" dependencies = [ "anstream 0.6.21", "anstyle", @@ -2403,7 +2403,7 @@ dependencies = [ [[package]] name = "digstore-core" -version = "0.13.5" +version = "0.14.0" dependencies = [ "aes-gcm-siv", "hex", @@ -2500,7 +2500,7 @@ dependencies = [ [[package]] name = "digstore-remote" -version = "0.13.5" +version = "0.14.0" dependencies = [ "async-trait", "axum", diff --git a/Cargo.toml b/Cargo.toml index 9afe0170..ace89a9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ exclude = ["crates/digstore-prover/guest", "crates/dig-client-wasm"] [workspace.package] edition = "2021" -version = "0.13.5" +version = "0.14.0" license = "GPL-2.0-only" [workspace.dependencies] diff --git a/README.md b/README.md index 58894515..3a91339b 100644 --- a/README.md +++ b/README.md @@ -1,538 +1,538 @@ -

digstore

- -

- A Git-shaped, encrypted, content-addressable store that compiles to a single self-defending WebAssembly module. -

- -

- CI - Release - License: GPL-2.0 - Platforms - Rust 1.94.1 -

- ---- - -`digstore` gives you Git-style commands β€” `init`, `add`, `commit`, `log`, `clone`, -`push`, `pull` β€” for a store that is **encrypted at rest** and compiles into a -**single `.wasm` file**. That one file is both your data and the server that gates -access to it. A host that stores or relays it sees only ciphertext addressed by -hashes; it cannot read what it carries. - -You address content with a URN, and the URN *is* the key: it both locates and -decrypts. Hand someone a URN and they can read that resource; without it they -can't, and there's no separate password or access list to manage. - -Unlike Git, digstore is built for **build output**, not repo source β€” you point a -store at a directory like `dist/` and it captures what's there. - -> New here? The full design is in the whitepaper: -> [`docs/whitepaper/digstore-whitepaper.pdf`](docs/whitepaper/digstore-whitepaper.pdf). - ---- - -## Install - -### Universal installer (recommended) - -The DIG installer downloads the right `digstore` binary for your OS and adds it -to your `PATH`. It lives in its own repo, -[**DIG-Network/dig-installer**](https://github.com/DIG-Network/dig-installer) -(the GUI desktop installer β€” the single-file `DigStore-Setup-*` β€” lives there -too, and it can optionally also install the `dig-node` local node). - -```sh -# macOS / Linux -curl -fsSL https://raw.githubusercontent.com/DIG-Network/dig-installer/main/install.sh | sh -``` - -```powershell -# Windows (PowerShell) -irm https://raw.githubusercontent.com/DIG-Network/dig-installer/main/install.ps1 | iex -``` - -Then open a **new** terminal and check it works: - -```sh -digstore --version -``` - -Already installed? Keep it current with the built-in self-updater: - -```sh -digstore update # download + install the latest release, in place -digstore update --check # just report whether a newer release exists -``` - -### Raw binary (macOS / Linux) - -Prefer the installer above. If you grab the raw per-OS binary from the -[Releases](https://github.com/DIG-Network/digstore/releases) page instead, pick -the file for your CPU β€” **`digstore--macos-arm64`** (Apple Silicon: M1/M2/M3) -or **`digstore--macos-x64`** (Intel Mac); **`digstore--linux-x64`** (or -the `aarch64-unknown-linux-gnu.tar.gz` for ARM Linux). A macOS/Linux binary has -**no file extension β€” that's normal**, not a broken download. - -Downloaded binaries lose their exec bit, and macOS quarantines an unsigned -download (Gatekeeper). Two commands fix both β€” **no `sudo` needed**: - -```sh -chmod +x digstore--macos-arm64 # make it executable -xattr -d com.apple.quarantine digstore--macos-arm64 # macOS only: clear Gatekeeper - -# then put it on your PATH under the name `digstore` (Apple Silicon: no sudo): -mv digstore--macos-arm64 /opt/homebrew/bin/digstore # Intel mac / Linux: /usr/local/bin -digstore --version -``` - -If you hit **`permission denied`**, it's the exec bit or quarantine above β€” not -privileges; `sudo` is the wrong fix. - -### Build from source (any platform) - -You need [Rust](https://rustup.rs) (pinned to 1.94.1 via `rust-toolchain.toml`). -The CLI embeds a WebAssembly guest, so build that first: - -```sh -rustup target add wasm32-unknown-unknown -cargo build -p digstore-guest --target wasm32-unknown-unknown --release -cargo build -p digstore-cli --release -``` - -The binary is at `target/release/digstore` (`digstore.exe` on Windows). Copy it -somewhere on your `PATH`. - ---- - -## Quick start - -Start **free** β€” scaffold a store from a template and preview it over the real -`chia://` read path locally, with **no wallet, no chain, and nothing spent**. Only -`init`/`deploy` touch mainnet (and cost `$DIG`). - -```sh -digstore new static-site my-store # scaffold a working store β€” FREE, no wallet/chain -cd my-store -digstore dev # live local preview over the real chia:// read path β€” FREE -``` - -When it's ready, publish it on Chia. This is the canonical publish flow β€” run it -top-to-bottom from inside your project (it spends the dynamic per-capsule `$DIG` -price + a small XCH fee, fetched live so you always pay the same as DIGHub): - -```sh -digstore init # create the on-chain store (mints on Chia; store id = launcher id) -digstore add -A # stage every file under the store root -digstore add --discovery # publish the public /.well-known discovery manifest -digstore commit -m "v3" # anchor a new capsule on-chain (dynamic per-capsule $DIG price + XCH fee) -digstore push origin # push the deployment to DIGHub (rpc.dig.net) -``` - -Then inspect and read it back: - -```sh -digstore log # list published capsules (each root hash = one capsule) -digstore urn index.html # preview the URN a file will have β€” no guessing - -# read a resource back (store id + root come from `digstore log --json`): -digstore cat urn:dig:chia::/index.html -``` - -> Prefer JS? `npm create dig-app` scaffolds the same `static-site` template (and -> more) from Node β€” the same free, no-spend starting point. - -Commands discover the `.dig/` workspace by walking up from wherever you are (like -Git). `add`/`urn` operate on the store's **content root** (the current directory -by default; commonly a build dir β€” see below), and resource keys are always -relative to that root, so URNs are stable no matter which subdirectory you run -from. - ---- - -## Multiple stores per workspace - -A single `.dig/` workspace can hold many stores, each with its own content, -keys, and history (a store accrues a series of **capsules** as you commit). - -```sh -digstore init site --dir dist # a store named "site" that captures ./dist -digstore init docs --dir build/docs -digstore stores # list stores; * marks the active one + capacity -digstore use site # switch the active store - -digstore --store site add -A # stage everything under dist/ into "site" -digstore staged # what's staged + size + remaining headroom -digstore unstage # clear staging -digstore commit -m "v1" # seal a deployment; writes a local urns.json index -``` - -- **Store selection:** `--store ` > the active store (`use`) > the single - store if there's only one. (`--project` is a hidden, deprecated alias of `--store`.) -- **Content root:** each store captures a build directory (default: the current - dir; set with `--dir` at `init` or `digstore dir `). `-C/--cwd ` - overrides it for one command. -- **Per-store cap:** each store is capped at **128 MB** of staged content, - enforced at `add` (and defensively at `commit`); remaining capacity is shown by - `add`, `status`, `staged`, and `digstore stores`. -- **URN manifest:** `commit` writes a local `urns.json` / `urns.txt` β€” the - publisher's index of every shareable URN for that deployment. - ---- - -## How content is addressed: URNs - -Every resource is named by a URN. The URN alone locates **and** decrypts it: - -``` -urn:dig::[:][/] -``` - -| Part | Meaning | -|---|---| -| `` | Chain identifier, e.g. `chia` | -| `` | Your 64-hex store id (required) | -| `` | Optional: pin a specific deployment root; omit for the current one | -| `` | Optional: which resource (content-root-relative path) | - -`digstore urn [PATHS]` previews the exact URN (and retrieval key) a file *will* -have against the active store β€” so you can check before you commit instead of -guessing. - ---- - -## Public vs private stores - -```sh -digstore init # public: anyone with the URN can read -digstore init --private # private: URN locates, but reading also needs a secret salt -``` - -- **Public** β€” the URN is sufficient to decrypt. -- **Private** β€” decryption also requires a secret salt the publisher holds and - shares out-of-band. Pass it with `--salt ` on `cat`/`checkout`. - ---- - -## Sharing over a remote - -A remote is an HTTPS endpoint that hosts and serves your `.wasm` module. - -```sh -# publisher -digstore remote add origin https://example.com/stores/ -digstore push origin - -# consumer (fresh directory) -digstore clone https://example.com/stores/ -digstore cat urn:dig:chia::/readme -digstore pull origin # later: fetch the publisher's newer deployment -``` - -`clone`/`pull` **verify** what they download before installing it: the module must -match the store id you asked for, and the served root must carry the publisher's -signature. A malicious or broken server cannot feed you fabricated content β€” the -command fails instead. Remotes must be `https://` (plain `http://` is allowed only -for `localhost`). - ---- - -## Deploy from GitHub Actions (CI) - -Auto-publish your built site/dapp to your existing store on every push β€” a new -capsule, git-push-to-deploy. The store must already exist (you ran -`digstore init` once); CI only **advances** it (it never mints). - -One-time setup, on the machine that created the store: - -```sh -digstore log --json # copy the store_id -digstore deploy-key export # copy the 64-hex publisher deploy key -``` - -Add two repository **secrets** β€” `DIG_MNEMONIC` (your funded deploy wallet) and -`DIG_DEPLOY_KEY` (the key above) β€” commit a `dig.toml` (see [`examples/dig.toml`](examples/dig.toml)), -then add the workflow ([`examples/github-actions-deploy.yml`](examples/github-actions-deploy.yml)): - -```yaml -- name: Deploy to DIG - uses: DIG-Network/digstore@v0.5.29 # pin to a release tag - with: - mnemonic: ${{ secrets.DIG_MNEMONIC }} - deploy-key: ${{ secrets.DIG_DEPLOY_KEY }} - output-dir: dist -``` - -> **⚠ Security:** v1 ships the funded wallet mnemonic into CI as a secret β€” it can -> spend ALL of that wallet's DIG/XCH. Use a **dedicated, low-balance deploy -> wallet** funded with only enough `$DIG` for your expected deploys (each deploy -> costs a uniform per-capsule price in `$DIG` + a small XCH fee). For the on-chain -> root advance you can instead use a -> **revocable writer deploy token** (see [Writer deploy tokens](#writer-deploy-tokens--advance-the-root-without-the-owner-seed) -> below) so the owner key never enters CI; the funded wallet is still needed to pay -> the DIG + XCH fee. - -`digstore deploy` reconstructs the store locally from the deploy key + the -on-chain root, stages your `output-dir`, advances the root, and pushes the new -capsule β€” all non-interactively. See `digstore deploy --help`. - -### Preview a build without spending (free) - -`digstore deploy --preview` builds a **free preview capsule** β€” it runs the real -compile β†’ verify β†’ decrypt read path on your `output-dir`, writes a local `.dig` -artifact, and prints its content address (`storeId:rootHash` + `dig://` URN). -**No chain, no wallet, no deploy key, nothing spent** β€” the preview store id is a -fresh ephemeral id, so a preview never touches (or impersonates) your real store. -Use it to verify a build, or to serve a shareable preview from CI: - -```sh -digstore deploy --preview # β†’ /../.dig-preview/.dig -digstore deploy --preview --preview-out p.dig # explicit artifact path -``` - -### Writer deploy tokens β€” advance the root without the owner seed - -The CI flow above ships the funded wallet into CI. To advance a store's root from -CI **without exposing the owner key**, use a **writer deploy token**: a revocable -delegate the owner pre-authorizes (the hub Teams "Deployer" flow / on-chain -`updateStoreOwnership`). A writer can change **only the metadata root** β€” it can -never change ownership or melt the store, and the owner revokes it at any time. - -```sh -digstore commit -m "deploy" --writer-key $DIGSTORE_WRITER_KEY # writer-signed root advance -digstore deploy --writer-key $DIGSTORE_WRITER_KEY # same, in the CI deploy flow -``` - -Prefer the `DIGSTORE_WRITER_KEY` env var so the key isn't visible in the process -table. The wallet seed still pays the per-capsule `$DIG` price + XCH fee; the writer -key only authorizes the on-chain root advance. (This is distinct from the Β§21 -publisher `--deploy-key`/`DIGSTORE_DEPLOY_KEY` above, which lets DIGHUb accept the -capsule.) - ---- - -## On-chain anchoring (Chia mainnet) - -Every store is **anchored on Chia mainnet**. `digstore init` mints an empty store -singleton on-chain, and the singleton's **launcher id becomes the store id**. -Every `digstore commit` then pushes the new deployment's root to that singleton -with an on-chain update and **blocks until the update confirms** before finalizing -the deployment locally. - -> **This spends real XCH and DIG.** Anchoring is mandatory β€” there is no offline mode. -> `init` and `commit` will not proceed without an unlocked wallet seed and enough -> funds, and they block on mainnet confirmation. All broadcast and chain reads go -> through [coinset.org](https://coinset.org) over HTTPS (no peer node or TLS cert -> to run). -> -> **`$DIG` fee (per capsule):** every `init` and every `commit`/`deploy` pays a -> `$DIG` fee to the DIG treasury β€” embedded atomically in the same spend bundle as -> the mint/update (memo = store id). The price is **dynamic and USD-pegged** -> (β‰ˆ $1/capsule/year of hosting Γ· the live DIG price), **uniform per capsule**; the -> hub computes the live amount in the browser. The CLI stays **deterministic** β€” it -> never fetches a price itself: it takes the amount as input and falls back to a -> protocol default if unset. Set it explicitly with `--dig-amount ` (e.g. -> `--dig-amount 87.5`), the `DIGSTORE_DIG_AMOUNT` env var, or `dig-amount` in -> `dig.toml` (precedence: flag > env > dig.toml > default). Before submitting, each -> command prints the cost and your current balance; if the wallet is short on XCH -> **or** `$DIG` the command blocks and tells you what's missing. Need `$DIG`? Get it -> on TibetSwap, dexie.space, or xch.9mm.pro. Use `digstore balance` to check your -> spendable XCH (mojos) and DIG at any time. - -### 1. Set up a wallet seed - -digstore keeps an encrypted BIP-39 seed in `~/.dig/seed.enc`. - -```sh -digstore seed generate # create a new mnemonic (shown once β€” back it up) -# or -digstore seed import # import an existing mnemonic -digstore seed status # is a seed present / unlocked? -digstore lock # clear the cached-unlock session -``` - -The seed is encrypted with a passphrase (Argon2id + AES-256-GCM). After unlock it -is cached for a configurable TTL; `DIGSTORE_PASSPHRASE` supplies the passphrase -non-interactively (for CI/scripts). Global settings live in `~/.dig/config.toml` -(`coinset_url`, `unlock_ttl`, `fee`). - -### 2. Fund the wallet - -Minting and updates cost both XCH (the transaction fee) and DIG (the DIG token, a -Chia CAT). The wallet derived from your seed needs **both**. Run `digstore balance` -to see your current spendable XCH (mojos), DIG (3-decimal display), and the wallet -receive address. If either is short, `init`/`commit` block, disclose the exact cost -up front, and print the **receive address** to fund: - -``` -insufficient funds: need mojos, have ; fund xch1… -``` - -Both XCH and DIG are received at the same `xch1…` receive address (DIG as a CAT). -Send funds there, wait for them to confirm, then retry. - -**Where to get `$DIG`:** [TibetSwap](https://v2.tibetswap.io/), -[dexie.space](https://dexie.space), or [xch.9mm.pro](https://xch.9mm.pro) β€” the DIG -CAT is `a406d3a9…832f81`. Get XCH from any Chia exchange or wallet. - -### 3. Init mints, commit anchors - -```sh -digstore init # mints the store singleton; store id = launcher id - # blocks until the mint confirms on mainnet - -digstore add readme.txt --key readme -digstore commit -m "first deployment" - # pushes the new root on-chain; blocks until - # confirmed, then finalizes the deployment locally -``` - -Both commands take `--wait-timeout ` (default `300`) for how long to wait on -confirmation. On a confirm-timeout the store is kept **pending** (and the local -deployment is *not* finalized) β€” it is resumable, not lost. - -### 4. Resume / inspect an anchor - -```sh -digstore anchor # resume a pending anchor: confirm the chain coin - # and flip the store to confirmed (idempotent) -digstore anchor status # read-only: show the store's anchor state -digstore anchor status --json # machine-readable state -``` - -Per-store anchor state (network, store id / launcher, coin id, status, last root, -last tx id, confirmed height) is recorded in the store's `anchor.toml`. - -The compiled `.dig` module also embeds the on-chain pointer (network, launcher/store id, -current coin id, confirmed height, and a coinset endpoint hint) directly in its data -section. `digstore anchor status` surfaces this alongside the local `anchor.toml` state -(use `--json` for machine-readable output); `digstore anchor inspect ` dumps -the pointer from any module file without a local workspace. The embedded coinset URL is -a hint only β€” local config and flags always take precedence. - -> Note: `clone`/`pull` verify the publisher's signature over the served head **and** -> verify that the served root equals the store's current on-chain singleton root β€” -> read from the chain via the launcher id embedded in the module. They **fail closed** -> on a mismatch or an unreachable chain, making the chain the authority for the current -> root. (A module with no embedded on-chain pointer falls back to the head-signature -> gate.) See [`SECURITY.md`](SECURITY.md). - ---- - -## Command reference - -| Command | What it does | -|---|---| -| `digstore init [name] [--dir ] [--private] [--wait-timeout ]` | Create a store (default name `default`); mints its singleton on mainnet (store id = launcher id); `--dir` sets its content root | -| `digstore stores` | List stores with active marker, root, content root, capacity (the `projects` alias is hidden/deprecated) | -| `digstore use ` | Set the active store | -| `digstore dir []` | Show or set the active store's content root | -| `digstore add [-A] [--key ]` | Stage files (`-A` = the whole content root) | -| `digstore staged` / `digstore unstage` | List the staging area / clear it | -| `digstore commit [-m ] [--wait-timeout ] [--writer-key ]` | Seal a new deployment, anchor its root on mainnet (blocks until confirmed), compile the module, write the URN manifest. `--writer-key` advances the root with a revocable **writer deploy token** instead of the owner seed (the deprecated `--deploy-key` alias still works) | -| `digstore status` | Show staged/modified/untracked + capacity | -| `digstore log [--limit N]` / `digstore diff ` | List / compare deployments | -| `digstore urn [PATHS…] [--root ]` | Preview the URN(s) files will have | -| `digstore cat [--salt ] [--verify-proof]` | Read a resource by URN | -| `digstore checkout --out [--salt ]` | Write a whole deployment to a directory | -| `digstore remote add\|list\|remove …` | Manage remotes | -| `digstore clone ` / `push [remote]` / `pull [remote]` | Sync with a remote (verified) | -| `digstore deploy [--store-id ] [--output-dir ] [--build-command ] [-m ] [--writer-key ]` | CI auto-deploy: advance an EXISTING store from a fresh checkout (reads `dig.toml`); never mints. `--writer-key` advances the root with a revocable writer deploy token (owner seed stays out of CI) | -| `digstore deploy --preview [--preview-out ]` | Build a **free** preview capsule via the real read path (writes a local `.dig` artifact + content address); no chain, no wallet, nothing spent | -| `digstore deploy-key export [--out ]` | Export the store's publisher deploy key (for a CI secret) | -| `digstore anchor [--wait-timeout ]` | Resume a pending on-chain anchor (confirm the coin, flip to confirmed) | -| `digstore anchor status [--json]` | Show the active store's anchor state + embedded module chain pointer (read-only) | -| `digstore anchor inspect [--json]` | Dump the on-chain pointer embedded in any module file (read-only, no workspace needed) | -| `digstore balance [--json]` | Show spendable XCH (mojos) and DIG (3-decimal) + the wallet receive address (read-only) | -| `digstore seed generate\|import\|status` / `digstore lock` | Manage the encrypted wallet seed used for anchoring | - -Global flags: `--store ` (target a specific store), `-C/--cwd ` -(operating directory for this command), `--dig-dir ` (workspace location), -`--json` (machine-readable), `--quiet`, `--verbose`, `--color `. - -### Agent-friendly surface (scripting / CI) - -digstore is built to be driven by scripts and agents as well as humans: - -- **`--json`** β€” every command emits ONE structured object to stdout (human prose - goes to stderr). On **failure**, `--json` emits a structured error envelope to - stdout instead of prose: `{"ok":false,"error":{"code","exit_code","message","hint"}}` - β€” branch on `error.code` (a stable UPPER_SNAKE string) or the exit code, never on - the message text. -- **`digstore --help-json`** β€” the whole invocation contract as one JSON object: - the command tree, the global flags, each arg's `choices`/`default`/`value_name`, - and the exit-code table below. One call yields everything needed to invoke the CLI. -- **`digstore completion `** β€” shell completions. - -#### Exit codes - -Every failure maps to a distinct, stable exit code (and the matching `code` in the -`--json` envelope), so a script can tell failure classes apart: - -| Code | `code` | Meaning | -|---:|---|---| -| 0 | `OK` | success | -| 1 | `ERROR` | an unclassified error | -| 2 | `INVALID_ARGUMENT` | a bad/missing argument or flag value | -| 3 | `NO_STORE` | no digstore workspace/store found here | -| 4 | `NOT_FOUND` | the requested resource/root/key was not found | -| 5 | `VERIFICATION_FAILED` | content failed verification (tamper, wrong salt/key) | -| 6 | `NETWORK` | a network/remote error | -| 7 | `NON_FAST_FORWARD` | the remote root advanced; pull before pushing | -| 8 | `UNAUTHORIZED` | missing/invalid credentials or signing key | -| 9 | `NO_SEED` | no wallet seed is set up | -| 10 | `BAD_PASSPHRASE` | wrong seed passphrase | -| 11 | `INVALID_MNEMONIC` | the BIP-39 mnemonic is invalid | -| 12 | `INSUFFICIENT_FUNDS` | not enough XCH or DIG to complete the spend | -| 13 | `CHAIN` | a Chia chain / coinset.org error | -| 14 | `CONFIRM_TIMEOUT` | on-chain confirmation timed out (resumable) | -| 15 | `MINT_FAILED` | the on-chain mint failed | -| 16 | `UPDATE_FAILED` | the on-chain root update failed | -| 17 | `TOO_LARGE` | the operation is too large for one transaction; split it into smaller batches | -| 18 | `NEEDS_CONSOLIDATION` | the wallet is spendable but too fragmented; consolidate coins (or pass `--consolidate`) first | - -`digstore --help-json` emits this same table under `exit_codes` (generated from the -source, so it never drifts). - -### Wallet seed - -`digstore seed generate|import|status` and `digstore lock` manage the encrypted -BIP-39 wallet seed used for on-chain anchoring β€” see -[On-chain anchoring](#on-chain-anchoring-chia-mainnet) above for details. - ---- - -## What this gives you - -- **Encrypted at rest.** Content is encrypted with a key derived from its URN. - There is no key stored anywhere to recover β€” lose the URN, lose the read. -- **Provider-blind hosting.** Whoever hosts your store holds only ciphertext keyed - by hashes; they can't scan it or read requests. -- **Verified downloads.** `clone`/`pull` reject content that isn't the genuine, - publisher-signed store. -- **Uniform, self-contained.** A store is a single `.wasm`, padded to a uniform - size so its bytes reveal nothing about how much content it holds. Copy it to - back it up; run it to serve it. - ---- - -## Security - -Security posture, the hardening applied, and known residual risks are documented -in [`SECURITY.md`](SECURITY.md). Please report vulnerabilities privately to the -maintainer rather than opening a public issue. - -## Help & community - -- **Docs:** [docs.dig.net](https://docs.dig.net) -- **Discord:** [discord.gg/v78aygUZt](https://discord.gg/v78aygUZt) β€” questions, help, and project chat. - -## Contributing - -Build, test, and contribution guidelines are in -[`CONTRIBUTING.md`](CONTRIBUTING.md). - -## License - -Licensed under the [GNU General Public License v2.0](LICENSE) β€” the same license -as Git. +

dig-store

+ +

+ A Git-shaped, encrypted, content-addressable store that compiles to a single self-defending WebAssembly module. +

+ +
+ +--- + +`dig-store` gives you Git-style commands β€” `init`, `add`, `commit`, `log`, `clone`, +`push`, `pull` β€” for a store that is **encrypted at rest** and compiles into a +**single `.wasm` file**. That one file is both your data and the server that gates +access to it. A host that stores or relays it sees only ciphertext addressed by +hashes; it cannot read what it carries. + +You address content with a URN, and the URN *is* the key: it both locates and +decrypts. Hand someone a URN and they can read that resource; without it they +can't, and there's no separate password or access list to manage. + +Unlike Git, dig-store is built for **build output**, not repo source β€” you point a +store at a directory like `dist/` and it captures what's there. + +> New here? The full design is in the whitepaper: +> [`docs/whitepaper/digstore-whitepaper.pdf`](docs/whitepaper/digstore-whitepaper.pdf). + +--- + +## Install + +### Universal installer (recommended) + +The DIG installer downloads the right `dig-store` binary for your OS and adds it +to your `PATH`. It lives in its own repo, +[**DIG-Network/dig-installer**](https://github.com/DIG-Network/dig-installer) +(the GUI desktop installer β€” the single-file `DigStore-Setup-*` β€” lives there +too, and it can optionally also install the `dig-node` local node). + +```sh +# macOS / Linux +curl -fsSL https://raw.githubusercontent.com/DIG-Network/dig-installer/main/install.sh | sh +``` + +```powershell +# Windows (PowerShell) +irm https://raw.githubusercontent.com/DIG-Network/dig-installer/main/install.ps1 | iex +``` + +Then open a **new** terminal and check it works: + +```sh +dig-store --version +``` + +Already installed? Keep it current with the built-in self-updater: + +```sh +dig-store update # download + install the latest release, in place +dig-store update --check # just report whether a newer release exists +``` + +### Raw binary (macOS / Linux) + +Prefer the installer above. If you grab the raw per-OS binary from the +[Releases](https://github.com/DIG-Network/dig-store/releases) page instead, pick +the file for your CPU β€” **`dig-store--macos-arm64`** (Apple Silicon: M1/M2/M3) +or **`dig-store--macos-x64`** (Intel Mac); **`dig-store--linux-x64`** (or +the `aarch64-unknown-linux-gnu.tar.gz` for ARM Linux). A macOS/Linux binary has +**no file extension β€” that's normal**, not a broken download. + +Downloaded binaries lose their exec bit, and macOS quarantines an unsigned +download (Gatekeeper). Two commands fix both β€” **no `sudo` needed**: + +```sh +chmod +x dig-store--macos-arm64 # make it executable +xattr -d com.apple.quarantine dig-store--macos-arm64 # macOS only: clear Gatekeeper + +# then put it on your PATH under the name `dig-store` (Apple Silicon: no sudo): +mv dig-store--macos-arm64 /opt/homebrew/bin/dig-store # Intel mac / Linux: /usr/local/bin +dig-store --version +``` + +If you hit **`permission denied`**, it's the exec bit or quarantine above β€” not +privileges; `sudo` is the wrong fix. + +### Build from source (any platform) + +You need [Rust](https://rustup.rs) (pinned to 1.94.1 via `rust-toolchain.toml`). +The CLI embeds a WebAssembly guest, so build that first: + +```sh +rustup target add wasm32-unknown-unknown +cargo build -p digstore-guest --target wasm32-unknown-unknown --release +cargo build -p digstore-cli --release +``` + +The binary is at `target/release/dig-store` (`dig-store.exe` on Windows). Copy it +somewhere on your `PATH`. + +--- + +## Quick start + +Start **free** β€” scaffold a store from a template and preview it over the real +`chia://` read path locally, with **no wallet, no chain, and nothing spent**. Only +`init`/`deploy` touch mainnet (and cost `$DIG`). + +```sh +dig-store new static-site my-store # scaffold a working store β€” FREE, no wallet/chain +cd my-store +dig-store dev # live local preview over the real chia:// read path β€” FREE +``` + +When it's ready, publish it on Chia. This is the canonical publish flow β€” run it +top-to-bottom from inside your project (it spends the dynamic per-capsule `$DIG` +price + a small XCH fee, fetched live so you always pay the same as DIGHub): + +```sh +dig-store init # create the on-chain store (mints on Chia; store id = launcher id) +dig-store add -A # stage every file under the store root +dig-store add --discovery # publish the public /.well-known discovery manifest +dig-store commit -m "v3" # anchor a new capsule on-chain (dynamic per-capsule $DIG price + XCH fee) +dig-store push origin # push the deployment to DIGHub (rpc.dig.net) +``` + +Then inspect and read it back: + +```sh +dig-store log # list published capsules (each root hash = one capsule) +dig-store urn index.html # preview the URN a file will have β€” no guessing + +# read a resource back (store id + root come from `dig-store log --json`): +dig-store cat urn:dig:chia::/index.html +``` + +> Prefer JS? `npm create dig-app` scaffolds the same `static-site` template (and +> more) from Node β€” the same free, no-spend starting point. + +Commands discover the `.dig/` workspace by walking up from wherever you are (like +Git). `add`/`urn` operate on the store's **content root** (the current directory +by default; commonly a build dir β€” see below), and resource keys are always +relative to that root, so URNs are stable no matter which subdirectory you run +from. + +--- + +## Multiple stores per workspace + +A single `.dig/` workspace can hold many stores, each with its own content, +keys, and history (a store accrues a series of **capsules** as you commit). + +```sh +dig-store init site --dir dist # a store named "site" that captures ./dist +dig-store init docs --dir build/docs +dig-store stores # list stores; * marks the active one + capacity +dig-store use site # switch the active store + +dig-store --store site add -A # stage everything under dist/ into "site" +dig-store staged # what's staged + size + remaining headroom +dig-store unstage # clear staging +dig-store commit -m "v1" # seal a deployment; writes a local urns.json index +``` + +- **Store selection:** `--store ` > the active store (`use`) > the single + store if there's only one. (`--project` is a hidden, deprecated alias of `--store`.) +- **Content root:** each store captures a build directory (default: the current + dir; set with `--dir` at `init` or `dig-store dir `). `-C/--cwd ` + overrides it for one command. +- **Per-store cap:** each store is capped at **128 MB** of staged content, + enforced at `add` (and defensively at `commit`); remaining capacity is shown by + `add`, `status`, `staged`, and `dig-store stores`. +- **URN manifest:** `commit` writes a local `urns.json` / `urns.txt` β€” the + publisher's index of every shareable URN for that deployment. + +--- + +## How content is addressed: URNs + +Every resource is named by a URN. The URN alone locates **and** decrypts it: + +``` +urn:dig::[:][/] +``` + +| Part | Meaning | +|---|---| +| `` | Chain identifier, e.g. `chia` | +| `` | Your 64-hex store id (required) | +| `` | Optional: pin a specific deployment root; omit for the current one | +| `` | Optional: which resource (content-root-relative path) | + +`dig-store urn [PATHS]` previews the exact URN (and retrieval key) a file *will* +have against the active store β€” so you can check before you commit instead of +guessing. + +--- + +## Public vs private stores + +```sh +dig-store init # public: anyone with the URN can read +dig-store init --private # private: URN locates, but reading also needs a secret salt +``` + +- **Public** β€” the URN is sufficient to decrypt. +- **Private** β€” decryption also requires a secret salt the publisher holds and + shares out-of-band. Pass it with `--salt ` on `cat`/`checkout`. + +--- + +## Sharing over a remote + +A remote is an HTTPS endpoint that hosts and serves your `.wasm` module. + +```sh +# publisher +dig-store remote add origin https://example.com/stores/ +dig-store push origin + +# consumer (fresh directory) +dig-store clone https://example.com/stores/ +dig-store cat urn:dig:chia::/readme +dig-store pull origin # later: fetch the publisher's newer deployment +``` + +`clone`/`pull` **verify** what they download before installing it: the module must +match the store id you asked for, and the served root must carry the publisher's +signature. A malicious or broken server cannot feed you fabricated content β€” the +command fails instead. Remotes must be `https://` (plain `http://` is allowed only +for `localhost`). + +--- + +## Deploy from GitHub Actions (CI) + +Auto-publish your built site/dapp to your existing store on every push β€” a new +capsule, git-push-to-deploy. The store must already exist (you ran +`dig-store init` once); CI only **advances** it (it never mints). + +One-time setup, on the machine that created the store: + +```sh +dig-store log --json # copy the store_id +dig-store deploy-key export # copy the 64-hex publisher deploy key +``` + +Add two repository **secrets** β€” `DIG_MNEMONIC` (your funded deploy wallet) and +`DIG_DEPLOY_KEY` (the key above) β€” commit a `dig.toml` (see [`examples/dig.toml`](examples/dig.toml)), +then add the workflow ([`examples/github-actions-deploy.yml`](examples/github-actions-deploy.yml)): + +```yaml +- name: Deploy to DIG + uses: DIG-Network/dig-store@v0.5.29 # pin to a release tag + with: + mnemonic: ${{ secrets.DIG_MNEMONIC }} + deploy-key: ${{ secrets.DIG_DEPLOY_KEY }} + output-dir: dist +``` + +> **⚠ Security:** v1 ships the funded wallet mnemonic into CI as a secret β€” it can +> spend ALL of that wallet's DIG/XCH. Use a **dedicated, low-balance deploy +> wallet** funded with only enough `$DIG` for your expected deploys (each deploy +> costs a uniform per-capsule price in `$DIG` + a small XCH fee). For the on-chain +> root advance you can instead use a +> **revocable writer deploy token** (see [Writer deploy tokens](#writer-deploy-tokens--advance-the-root-without-the-owner-seed) +> below) so the owner key never enters CI; the funded wallet is still needed to pay +> the DIG + XCH fee. + +`dig-store deploy` reconstructs the store locally from the deploy key + the +on-chain root, stages your `output-dir`, advances the root, and pushes the new +capsule β€” all non-interactively. See `dig-store deploy --help`. + +### Preview a build without spending (free) + +`dig-store deploy --preview` builds a **free preview capsule** β€” it runs the real +compile β†’ verify β†’ decrypt read path on your `output-dir`, writes a local `.dig` +artifact, and prints its content address (`storeId:rootHash` + `dig://` URN). +**No chain, no wallet, no deploy key, nothing spent** β€” the preview store id is a +fresh ephemeral id, so a preview never touches (or impersonates) your real store. +Use it to verify a build, or to serve a shareable preview from CI: + +```sh +dig-store deploy --preview # β†’ /../.dig-preview/.dig +dig-store deploy --preview --preview-out p.dig # explicit artifact path +``` + +### Writer deploy tokens β€” advance the root without the owner seed + +The CI flow above ships the funded wallet into CI. To advance a store's root from +CI **without exposing the owner key**, use a **writer deploy token**: a revocable +delegate the owner pre-authorizes (the hub Teams "Deployer" flow / on-chain +`updateStoreOwnership`). A writer can change **only the metadata root** β€” it can +never change ownership or melt the store, and the owner revokes it at any time. + +```sh +dig-store commit -m "deploy" --writer-key $DIGSTORE_WRITER_KEY # writer-signed root advance +dig-store deploy --writer-key $DIGSTORE_WRITER_KEY # same, in the CI deploy flow +``` + +Prefer the `DIGSTORE_WRITER_KEY` env var so the key isn't visible in the process +table. The wallet seed still pays the per-capsule `$DIG` price + XCH fee; the writer +key only authorizes the on-chain root advance. (This is distinct from the Β§21 +publisher `--deploy-key`/`DIGSTORE_DEPLOY_KEY` above, which lets DIGHUb accept the +capsule.) + +--- + +## On-chain anchoring (Chia mainnet) + +Every store is **anchored on Chia mainnet**. `dig-store init` mints an empty store +singleton on-chain, and the singleton's **launcher id becomes the store id**. +Every `dig-store commit` then pushes the new deployment's root to that singleton +with an on-chain update and **blocks until the update confirms** before finalizing +the deployment locally. + +> **This spends real XCH and DIG.** Anchoring is mandatory β€” there is no offline mode. +> `init` and `commit` will not proceed without an unlocked wallet seed and enough +> funds, and they block on mainnet confirmation. All broadcast and chain reads go +> through [coinset.org](https://coinset.org) over HTTPS (no peer node or TLS cert +> to run). +> +> **`$DIG` fee (per capsule):** every `init` and every `commit`/`deploy` pays a +> `$DIG` fee to the DIG treasury β€” embedded atomically in the same spend bundle as +> the mint/update (memo = store id). The price is **dynamic and USD-pegged** +> (β‰ˆ $1/capsule/year of hosting Γ· the live DIG price), **uniform per capsule**; the +> hub computes the live amount in the browser. The CLI stays **deterministic** β€” it +> never fetches a price itself: it takes the amount as input and falls back to a +> protocol default if unset. Set it explicitly with `--dig-amount ` (e.g. +> `--dig-amount 87.5`), the `DIGSTORE_DIG_AMOUNT` env var, or `dig-amount` in +> `dig.toml` (precedence: flag > env > dig.toml > default). Before submitting, each +> command prints the cost and your current balance; if the wallet is short on XCH +> **or** `$DIG` the command blocks and tells you what's missing. Need `$DIG`? Get it +> on TibetSwap, dexie.space, or xch.9mm.pro. Use `dig-store balance` to check your +> spendable XCH (mojos) and DIG at any time. + +### 1. Set up a wallet seed + +dig-store keeps an encrypted BIP-39 seed in `~/.dig/seed.enc`. + +```sh +dig-store seed generate # create a new mnemonic (shown once β€” back it up) +# or +dig-store seed import # import an existing mnemonic +dig-store seed status # is a seed present / unlocked? +dig-store lock # clear the cached-unlock session +``` + +The seed is encrypted with a passphrase (Argon2id + AES-256-GCM). After unlock it +is cached for a configurable TTL; `DIGSTORE_PASSPHRASE` supplies the passphrase +non-interactively (for CI/scripts). Global settings live in `~/.dig/config.toml` +(`coinset_url`, `unlock_ttl`, `fee`). + +### 2. Fund the wallet + +Minting and updates cost both XCH (the transaction fee) and DIG (the DIG token, a +Chia CAT). The wallet derived from your seed needs **both**. Run `dig-store balance` +to see your current spendable XCH (mojos), DIG (3-decimal display), and the wallet +receive address. If either is short, `init`/`commit` block, disclose the exact cost +up front, and print the **receive address** to fund: + +``` +insufficient funds: need mojos, have ; fund xch1… +``` + +Both XCH and DIG are received at the same `xch1…` receive address (DIG as a CAT). +Send funds there, wait for them to confirm, then retry. + +**Where to get `$DIG`:** [TibetSwap](https://v2.tibetswap.io/), +[dexie.space](https://dexie.space), or [xch.9mm.pro](https://xch.9mm.pro) β€” the DIG +CAT is `a406d3a9…832f81`. Get XCH from any Chia exchange or wallet. + +### 3. Init mints, commit anchors + +```sh +dig-store init # mints the store singleton; store id = launcher id + # blocks until the mint confirms on mainnet + +dig-store add readme.txt --key readme +dig-store commit -m "first deployment" + # pushes the new root on-chain; blocks until + # confirmed, then finalizes the deployment locally +``` + +Both commands take `--wait-timeout ` (default `300`) for how long to wait on +confirmation. On a confirm-timeout the store is kept **pending** (and the local +deployment is *not* finalized) β€” it is resumable, not lost. + +### 4. Resume / inspect an anchor + +```sh +dig-store anchor # resume a pending anchor: confirm the chain coin + # and flip the store to confirmed (idempotent) +dig-store anchor status # read-only: show the store's anchor state +dig-store anchor status --json # machine-readable state +``` + +Per-store anchor state (network, store id / launcher, coin id, status, last root, +last tx id, confirmed height) is recorded in the store's `anchor.toml`. + +The compiled `.dig` module also embeds the on-chain pointer (network, launcher/store id, +current coin id, confirmed height, and a coinset endpoint hint) directly in its data +section. `dig-store anchor status` surfaces this alongside the local `anchor.toml` state +(use `--json` for machine-readable output); `dig-store anchor inspect ` dumps +the pointer from any module file without a local workspace. The embedded coinset URL is +a hint only β€” local config and flags always take precedence. + +> Note: `clone`/`pull` verify the publisher's signature over the served head **and** +> verify that the served root equals the store's current on-chain singleton root β€” +> read from the chain via the launcher id embedded in the module. They **fail closed** +> on a mismatch or an unreachable chain, making the chain the authority for the current +> root. (A module with no embedded on-chain pointer falls back to the head-signature +> gate.) See [`SECURITY.md`](SECURITY.md). + +--- + +## Command reference + +| Command | What it does | +|---|---| +| `dig-store init [name] [--dir ] [--private] [--wait-timeout ]` | Create a store (default name `default`); mints its singleton on mainnet (store id = launcher id); `--dir` sets its content root | +| `dig-store stores` | List stores with active marker, root, content root, capacity (the `projects` alias is hidden/deprecated) | +| `dig-store use ` | Set the active store | +| `dig-store dir []` | Show or set the active store's content root | +| `dig-store add [-A] [--key ]` | Stage files (`-A` = the whole content root) | +| `dig-store staged` / `dig-store unstage` | List the staging area / clear it | +| `dig-store commit [-m ] [--wait-timeout ] [--writer-key ]` | Seal a new deployment, anchor its root on mainnet (blocks until confirmed), compile the module, write the URN manifest. `--writer-key` advances the root with a revocable **writer deploy token** instead of the owner seed (the deprecated `--deploy-key` alias still works) | +| `dig-store status` | Show staged/modified/untracked + capacity | +| `dig-store log [--limit N]` / `dig-store diff ` | List / compare deployments | +| `dig-store urn [PATHS…] [--root ]` | Preview the URN(s) files will have | +| `dig-store cat [--salt ] [--verify-proof]` | Read a resource by URN | +| `dig-store checkout --out [--salt ]` | Write a whole deployment to a directory | +| `dig-store remote add\|list\|remove …` | Manage remotes | +| `dig-store clone ` / `push [remote]` / `pull [remote]` | Sync with a remote (verified) | +| `dig-store deploy [--store-id ] [--output-dir ] [--build-command ] [-m ] [--writer-key ]` | CI auto-deploy: advance an EXISTING store from a fresh checkout (reads `dig.toml`); never mints. `--writer-key` advances the root with a revocable writer deploy token (owner seed stays out of CI) | +| `dig-store deploy --preview [--preview-out ]` | Build a **free** preview capsule via the real read path (writes a local `.dig` artifact + content address); no chain, no wallet, nothing spent | +| `dig-store deploy-key export [--out ]` | Export the store's publisher deploy key (for a CI secret) | +| `dig-store anchor [--wait-timeout ]` | Resume a pending on-chain anchor (confirm the coin, flip to confirmed) | +| `dig-store anchor status [--json]` | Show the active store's anchor state + embedded module chain pointer (read-only) | +| `dig-store anchor inspect [--json]` | Dump the on-chain pointer embedded in any module file (read-only, no workspace needed) | +| `dig-store balance [--json]` | Show spendable XCH (mojos) and DIG (3-decimal) + the wallet receive address (read-only) | +| `dig-store seed generate\|import\|status` / `dig-store lock` | Manage the encrypted wallet seed used for anchoring | + +Global flags: `--store ` (target a specific store), `-C/--cwd ` +(operating directory for this command), `--dig-dir ` (workspace location), +`--json` (machine-readable), `--quiet`, `--verbose`, `--color `. + +### Agent-friendly surface (scripting / CI) + +dig-store is built to be driven by scripts and agents as well as humans: + +- **`--json`** β€” every command emits ONE structured object to stdout (human prose + goes to stderr). On **failure**, `--json` emits a structured error envelope to + stdout instead of prose: `{"ok":false,"error":{"code","exit_code","message","hint"}}` + β€” branch on `error.code` (a stable UPPER_SNAKE string) or the exit code, never on + the message text. +- **`dig-store --help-json`** β€” the whole invocation contract as one JSON object: + the command tree, the global flags, each arg's `choices`/`default`/`value_name`, + and the exit-code table below. One call yields everything needed to invoke the CLI. +- **`dig-store completion `** β€” shell completions. + +#### Exit codes + +Every failure maps to a distinct, stable exit code (and the matching `code` in the +`--json` envelope), so a script can tell failure classes apart: + +| Code | `code` | Meaning | +|---:|---|---| +| 0 | `OK` | success | +| 1 | `ERROR` | an unclassified error | +| 2 | `INVALID_ARGUMENT` | a bad/missing argument or flag value | +| 3 | `NO_STORE` | no dig-store workspace/store found here | +| 4 | `NOT_FOUND` | the requested resource/root/key was not found | +| 5 | `VERIFICATION_FAILED` | content failed verification (tamper, wrong salt/key) | +| 6 | `NETWORK` | a network/remote error | +| 7 | `NON_FAST_FORWARD` | the remote root advanced; pull before pushing | +| 8 | `UNAUTHORIZED` | missing/invalid credentials or signing key | +| 9 | `NO_SEED` | no wallet seed is set up | +| 10 | `BAD_PASSPHRASE` | wrong seed passphrase | +| 11 | `INVALID_MNEMONIC` | the BIP-39 mnemonic is invalid | +| 12 | `INSUFFICIENT_FUNDS` | not enough XCH or DIG to complete the spend | +| 13 | `CHAIN` | a Chia chain / coinset.org error | +| 14 | `CONFIRM_TIMEOUT` | on-chain confirmation timed out (resumable) | +| 15 | `MINT_FAILED` | the on-chain mint failed | +| 16 | `UPDATE_FAILED` | the on-chain root update failed | +| 17 | `TOO_LARGE` | the operation is too large for one transaction; split it into smaller batches | +| 18 | `NEEDS_CONSOLIDATION` | the wallet is spendable but too fragmented; consolidate coins (or pass `--consolidate`) first | + +`dig-store --help-json` emits this same table under `exit_codes` (generated from the +source, so it never drifts). + +### Wallet seed + +`dig-store seed generate|import|status` and `dig-store lock` manage the encrypted +BIP-39 wallet seed used for on-chain anchoring β€” see +[On-chain anchoring](#on-chain-anchoring-chia-mainnet) above for details. + +--- + +## What this gives you + +- **Encrypted at rest.** Content is encrypted with a key derived from its URN. + There is no key stored anywhere to recover β€” lose the URN, lose the read. +- **Provider-blind hosting.** Whoever hosts your store holds only ciphertext keyed + by hashes; they can't scan it or read requests. +- **Verified downloads.** `clone`/`pull` reject content that isn't the genuine, + publisher-signed store. +- **Uniform, self-contained.** A store is a single `.wasm`, padded to a uniform + size so its bytes reveal nothing about how much content it holds. Copy it to + back it up; run it to serve it. + +--- + +## Security + +Security posture, the hardening applied, and known residual risks are documented +in [`SECURITY.md`](SECURITY.md). Please report vulnerabilities privately to the +maintainer rather than opening a public issue. + +## Help & community + +- **Docs:** [docs.dig.net](https://docs.dig.net) +- **Discord:** [discord.gg/v78aygUZt](https://discord.gg/v78aygUZt) β€” questions, help, and project chat. + +## Contributing + +Build, test, and contribution guidelines are in +[`CONTRIBUTING.md`](CONTRIBUTING.md). + +## License + +Licensed under the [GNU General Public License v2.0](LICENSE) β€” the same license +as Git. diff --git a/SPEC.md b/SPEC.md index 3f934a86..b487be11 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,6 +1,6 @@ -# digstore β€” `.dig` store format & manifest specification +# dig-store β€” `.dig` store format & manifest specification -This is the NORMATIVE contract for the digstore `.dig` store format: the byte-exact +This is the NORMATIVE contract for the dig-store `.dig` store format: the byte-exact data-section blob, the capsule/generation model, per-resource crypto and merkle commitment, and the normalized public manifest. An independent implementation that reads or writes `.dig` modules MUST conform to this document. The single source of @@ -8,12 +8,12 @@ truth in code is `digstore-core` (`datasection`, `merkle`, `crypto`, `urn`, `public_manifest`); this spec MUST agree with it and with the ecosystem contracts in the superproject `SYSTEM.md` and the user-facing protocol pages under `docs.dig.net`. -**CLI binaries.** The `digstore-cli` crate ships TWO binaries, `digstore` and `digs`. -`digs` is a first-class alias: `digs ` behaves IDENTICALLY to `digstore ` +**CLI binaries.** The `digstore-cli` crate ships TWO binaries, `dig-store` and `digs`. +`digs` is a first-class alias: `digs ` behaves IDENTICALLY to `dig-store ` (same subcommands, flags, `--json`, and exit codes). Both share ONE codepath (`digstore_cli::run()`) and each reflects its own invoked name (arg0) in `--help`/`--version`/`completion`/`--help-json`. Both binaries MUST be shipped together -everywhere `digstore` ships (cargo-install, the universal installer, the apt `.deb`). +everywhere `dig-store` ships (cargo-install, the universal installer, the apt `.deb`). All multi-byte integers are **big-endian** (Chia streamable framing). The shared codec (`digstore_core::codec`) frames: `uN` as `N/8` BE bytes; `String` as `u32` byte length @@ -156,7 +156,7 @@ Entries are ordered **ascending by `path`** (UTF-8 byte order); the encoding is `schema_version` starts at `1`; future fields are only APPENDED, so a reader dispatches on the version and older bodies remain readable. -**JSON surface.** The CLI `digstore manifest --json`, the JSON-RPC `dig.getManifest`, and the +**JSON surface.** The CLI `dig-store manifest --json`, the JSON-RPC `dig.getManifest`, and the browser reader `readPublicManifest` all emit the SAME shape with the byte fields as 64-char lowercase hex: @@ -178,7 +178,7 @@ lowercase hex: - **Blob reader** β€” `digstore_core::datasection::read_public_manifest(blob)` returns `Option` (None when absent). `dig-client-wasm::readPublicManifest(blob)` exposes it to the browser as JSON. -- **CLI** β€” `digstore manifest [--json]` prints the normalized manifest. +- **CLI** β€” `dig-store manifest [--json]` prints the normalized manifest. ## 8. Conformance @@ -194,9 +194,9 @@ lowercase hex: Scope note: unlike Β§1–8, this section is a CLI/on-chain-authority contract, not a `.dig` byte-format contract β€” it is kept here because it is a public surface an independent -`digstore` reimplementation must also expose. +`dig-store` reimplementation must also expose. -- `digstore authorize-origin-as-writer [--pubkey ] [--dry-run] [--fee ]` +- `dig-store authorize-origin-as-writer [--pubkey ] [--dry-run] [--fee ]` authorizes an origin's DIG identity as a CHIP-0035 **writer** delegate on the active store's on-chain singleton, using the existing `digstore_chain::singleton` delegation primitive (`writer_delegated_puzzle` + `update_store_ownership`) β€” never a hand-rolled puzzle. @@ -205,7 +205,7 @@ byte-format contract β€” it is kept here because it is a public surface an indep contract for this endpoint (path, method, response shape, failure semantics) is normative in the superproject `SYSTEM.md` β†’ Shared contracts β†’ "Well-known origin pubkey discovery"; this repo is a CONSUMER (discovery client) only β€” the endpoint's SERVING side is implemented by - whichever origin is being authorized (e.g. a hub), not by digstore. + whichever origin is being authorized (e.g. a hub), not by dig-store. - **Merge semantics**: `update_store_ownership`'s delegated-puzzle set is a REPLACE, not an append, so the command reads the store's CURRENT delegated puzzles first and re-sends every existing delegate plus the new writer β€” an existing admin/writer/oracle delegate is never @@ -218,7 +218,7 @@ byte-format contract β€” it is kept here because it is a public surface an indep ## 10. Per-capsule $DIG price (commit / deploy) Scope note: like Β§9, this is a CLI/economic contract, not a `.dig` byte-format contract; it is -normative for how a `digstore` reimplementation prices a capsule. +normative for how a `dig-store` reimplementation prices a capsule. Minting a store (`init`) is **FREE of $DIG** (XCH network fee only). The $DIG payment is attached ONLY to a `commit` / root-advance (a capsule), atomic with the singleton update in one @@ -231,7 +231,7 @@ The per-capsule price is **dynamic and USD-pegged**, NOT a fixed token amount: is the same fixed size, so size-varying pricing is FORBIDDEN β€” it would re-leak content size). - **ONE canonical source.** The price is computed on the DIGHub server (the pure formula + USD-target constants; a live DIGβ†’USD oracle) and served at **`GET https://hub.dig.net/v1/pricing`** - as `mint_dig` (the capsule price in DIG **base units**; 1 DIG = 1000 base units). A `digstore` + as `mint_dig` (the capsule price in DIG **base units**; 1 DIG = 1000 base units). A `dig-store` implementation MUST consume this SAME source so it never diverges from what DIGHub charges β€” it MUST NOT hard-code a fixed price or reimplement the formula/oracle. The response is additive-only (`{dig_usd, computed_at, source, mint_dig, mint_usd, subdomain_dig, subdomain_usd, cert_dig, @@ -245,7 +245,7 @@ The per-capsule price is **dynamic and USD-pegged**, NOT a fixed token amount: unreachable/undecodable/omits a valid `mint_dig`, the command MUST error clearly (pointing the user at `--dig-amount`) and spend NOTHING β€” it MUST NOT silently fall back to a stale flat amount. (The endpoint has its own server-side fallback price, so a reachable endpoint always - returns a usable `mint_dig`; digstore surfaces a note when `source` is `"fallback"`/`"… (stale)"`.) + returns a usable `mint_dig`; dig-store surfaces a note when `source` is `"fallback"`/`"… (stale)"`.) - The amount displayed to the user (and in `--dry-run`'s `cost_dig`) is byte-for-byte the amount built into the on-chain DIG-CAT payment (`digstore_chain::cat::build_dig_store_payment`). @@ -253,8 +253,8 @@ The per-capsule price is **dynamic and USD-pegged**, NOT a fixed token amount: Scope note: a CLI/economic contract (not a `.dig` byte-format contract) governing how the money commands choose which XCH coins fund a spend, so a coin-fragmented wallet is never silently unable -to publish. This is digstore's expression of the ecosystem-wide **coin-management contract** -(`SYSTEM.md` β†’ coin-management; the shared primitive is `dig-l1-wallet`). A `digstore` +to publish. This is dig-store's expression of the ecosystem-wide **coin-management contract** +(`SYSTEM.md` β†’ coin-management; the shared primitive is `dig-l1-wallet`). A `dig-store` reimplementation MUST replicate it and MUST NOT hand-roll its own selection heuristic. Every XCH-funding spend built by `init` (mint fee), `commit` and `deploy` (root-advance XCH fee): @@ -284,7 +284,7 @@ Every XCH-funding spend built by `init` (mint fee), `commit` and `deploy` (root- round. - **Never hand-rolls the selection or the merge** β€” both are the `dig-l1-wallet` primitives (`select_for_spend` / `select_for_consolidation`); only the bundle construction (the datalayer_driver - / `chia-wallet-sdk` builder) stays digstore's. + / `chia-wallet-sdk` builder) stays dig-store's. The per-capsule $DIG (CAT) payment (Β§10) rides in the same commit/deploy bundle; its selection is largest-first. (Capping + consolidating the $DIG-CAT side under the same contract is a follow-up.) @@ -293,7 +293,7 @@ largest-first. (Capping + consolidating the $DIG-CAT side under the same contrac Scope note: like Β§9–10, this is a CLI/off-chain-JSON contract (`nft mint`/`nft bulk`/`collection create`/`collection mint`), not a `.dig` byte-format contract; it is normative for how a -`digstore` reimplementation reads/writes CHIP-0007 documents so third-party tooling (and the +`dig-store` reimplementation reads/writes CHIP-0007 documents so third-party tooling (and the `chip35_dl_coin` wasm) stays byte-compatible (see `SYSTEM.md` β†’ CHIP-0007 metadata contract). CHIP-0007 defines **two distinct attribute shapes** that MUST NOT be confused (issue #187): @@ -306,7 +306,7 @@ CHIP-0007 defines **two distinct attribute shapes** that MUST NOT be confused (i `CollectionRef.attributes`) β€” each entry is `{"type": "", "value": ""}`. The field is `type`, **NOT** `trait_type`. -A `digstore` implementation: +A `dig-store` implementation: - MUST serialize collection-level attributes with the field name `type` (never `trait_type`). - MUST serialize NFT-item attributes with the field name `trait_type` (never `type`). @@ -353,7 +353,7 @@ Every `--did` flag (`collection mint`, `collection show`, `nft mint --did`) acce - a 64-hex launcher id (a leading `0x` is tolerated), or - a `did:chia:1…` bech32m address β€” the form Sage and CNI display DIDs in. Chia's DID bech32m encoding uses the literal `"did:chia:"` (colon included) as the bech32 human-readable part, so - the FULL string (not a stripped suffix) is the bech32m payload; a `digstore` reimplementation + the FULL string (not a stripped suffix) is the bech32m payload; a `dig-store` reimplementation decodes it the same way it would decode an `xch1…`/`nft1…` bech32m address, then checks the decoded prefix is exactly `"did:chia:"`. @@ -398,7 +398,7 @@ split into cost-bounded batches. A reimplementation: - **Cost-bounds each batch, computed β€” never a hard-coded count.** The per-block CLVM cost ceiling is `MAX_BLOCK_COST_CLVM = 11_000_000_000` (Chia mainnet `ConsensusConstants::max_block_cost_clvm`). A batch's estimated cost MUST stay at or under a conservative fraction of that ceiling - (digstore uses `1/4`) so that estimate error, block contention, and gateway request-size limits are + (dig-store uses `1/4`) so that estimate error, block contention, and gateway request-size limits are all absorbed. The estimate is `base + per_item * n` where the per-item constant is proven conservative against the real Chia consensus cost model (`run_spendbundle` under `MAINNET_CONSTANTS`): the measured marginal per-item cost MUST NOT exceed the constant. The default @@ -428,7 +428,7 @@ split into cost-bounded batches. A reimplementation: ## 12. Release pipeline β€” nightly cron + manual dispatch -How the `digstore` CLI binary + its `digs` alias are built and released. The shape is copied from +How the `dig-store` CLI binary + its `digs` alias are built and released. The shape is copied from the ecosystem's reference nightlies implementation (`dig-updater`); the ops runbook is `runbooks/release.md`. @@ -450,7 +450,7 @@ selected channel(s). **60-day auto-disable caveat.** GitHub auto-disables a `schedule:` trigger after 60 days with no repo activity on a public repo, with no auto-re-enable β€” and since this cron is the ONLY automatic release trigger, a quiet repo can silently stop releasing with no error. Detect it with -`gh api repos/DIG-Network/digstore/actions/workflows/nightly-release.yml --jq .state` (a value of +`gh api repos/DIG-Network/dig-store/actions/workflows/nightly-release.yml --jq .state` (a value of `disabled_inactivity` means it was auto-disabled) and recover with `gh workflow enable nightly-release.yml` (see `runbooks/release.md`). Any repo activity resets the 60-day counter. @@ -494,12 +494,21 @@ Every night (and on demand) builds `main` HEAD for every OS/arch and publishes a The cross-OS build lives once in `.github/workflows/build-binaries.yml` (`on: workflow_call`, inputs `version` + `ref`). Both `release.yml` (stable) and the nightly channel call it, so the two paths -can never diverge. It builds `digstore` + the `digs` alias for `windows-x64`, `linux-x64`, +can never diverge. It builds `dig-store` + the `digs` alias for `windows-x64`, `linux-x64`, `linux-arm64` (native `ubuntu-24.04-arm` runner), `macos-arm64`, and `macos-x64`, in the two asset shapes (bare per-OS binaries + apt `.tar.gz`). BUILD PREREQ (Β§3.5 / BINDING contract D6): the `digstore-guest` wasm is built for `wasm32-unknown-unknown` BEFORE the CLI on every leg, because `digstore-cli`'s `build.rs` embeds it. +TRANSITIONAL DUAL-PUBLISH (rename epic #703): the primary binary was renamed `digstore` -> +`dig-store` (the Cargo package name `digstore-cli` and all library crate names are UNCHANGED). For +one transition cycle every asset is published under BOTH the new `dig-store--` stem +AND the legacy `digstore--` stem (bare binaries + apt `.tar.gz`), and each apt tarball +ships a `digstore` -> `dig-store` compat symlink at its root, so apt.dig.net + dig-installer stay +green until they cut over. The `digs` alias asset name is derived independently (there is no +`dig-store` -> `digs` substring). The legacy stem + symlink drop in a later release once both +installers have cut over. + ### 12.5 RELEASE_TOKEN posture Releasing uses the `RELEASE_TOKEN` org PAT, not `GITHUB_TOKEN`. If `RELEASE_TOKEN` is absent, EVERY diff --git a/action.yml b/action.yml index f2299519..510af554 100644 --- a/action.yml +++ b/action.yml @@ -1,4 +1,4 @@ -name: "Deploy to DIG (digstore)" +name: "Deploy to DIG (dig-store)" description: >- Auto-deploy a built site/dapp to your existing DIG store on every push β€” a new capsule, published to DIGHUb β€” git-push-to-deploy. Advances an @@ -26,7 +26,7 @@ inputs: required: true deploy-key: description: >- - The store's publisher deploy key (64-hex), from `digstore deploy-key export` + The store's publisher deploy key (64-hex), from `dig-store deploy-key export` run once on the machine that created the store. Authorizes publishing new capsules to DIGHUb. It has NO spend authority, but it is still a credential β€” pass it as a repository secret. @@ -123,7 +123,7 @@ runs: - name: Checkout digstore (${{ inputs.digstore-ref }}) uses: actions/checkout@v4 with: - repository: DIG-Network/digstore + repository: DIG-Network/dig-store ref: ${{ inputs.digstore-ref }} path: .digstore-src persist-credentials: false @@ -146,9 +146,11 @@ runs: working-directory: .digstore-src run: | # BINDING contract D6: the guest wasm must exist before the CLI build. + # `cargo install` on the digstore-cli package produces the renamed `dig-store` + # binary (+ the `digs` alias) on PATH (package name unchanged; #703). cargo build -p digstore-guest --target wasm32-unknown-unknown --release cargo install --path crates/digstore-cli --force --locked - digstore --version + dig-store --version # 2. Import the funded wallet seed non-interactively. The mnemonic + # passphrase are masked and never printed; the seed lives in a throwaway @@ -169,7 +171,7 @@ runs: echo "::add-mask::$PASS" echo "DIGSTORE_PASSPHRASE=$PASS" >> "$GITHUB_ENV" DIGSTORE_HOME="$DIGSTORE_HOME" DIGSTORE_PASSPHRASE="$PASS" \ - digstore seed import --mnemonic "$DIG_MNEMONIC" + dig-store seed import --mnemonic "$DIG_MNEMONIC" # 3. Deploy: reconstruct the store locally with the deploy key, stage the # output, advance the on-chain root, and push the new capsule to DIGHUb. @@ -200,7 +202,7 @@ runs: args+=(--message "$msg") # Run the deploy, capturing the machine-readable JSON result. - out="$(digstore "${args[@]}")" + out="$(dig-store "${args[@]}")" echo "$out" capsule="$(printf '%s' "$out" | jq -r '.capsule // empty')" diff --git a/crates/dig-client-wasm/Cargo.toml b/crates/dig-client-wasm/Cargo.toml index c6cedbd4..5d4ec0e8 100644 --- a/crates/dig-client-wasm/Cargo.toml +++ b/crates/dig-client-wasm/Cargo.toml @@ -3,7 +3,7 @@ name = "dig-client-wasm" version = "0.1.0" edition = "2021" license = "GPL-2.0-only" -repository = "https://github.com/DIG-Network/digstore" +repository = "https://github.com/DIG-Network/dig-store" description = "Browser read-crypto for dighub: URN reconstruction, AES-256-GCM-SIV decryption, and merkle inclusion-proof verification, compiled to WASM (globalThis.digClient)." # NOTE: this crate is intentionally EXCLUDED from the digstore_wasm workspace diff --git a/crates/dig-client-wasm/PUBLISHED_README.md b/crates/dig-client-wasm/PUBLISHED_README.md index 569115fe..bfd1cc68 100644 --- a/crates/dig-client-wasm/PUBLISHED_README.md +++ b/crates/dig-client-wasm/PUBLISHED_README.md @@ -99,5 +99,5 @@ maintaining a private copy. The vendored fallbacks can be removed once switched. ## License -GPL-2.0-only. Source: https://github.com/DIG-Network/digstore (crate +GPL-2.0-only. Source: https://github.com/DIG-Network/dig-store (crate `crates/dig-client-wasm`). diff --git a/crates/dig-client-wasm/scripts/assemble-pkg.mjs b/crates/dig-client-wasm/scripts/assemble-pkg.mjs index 084b0eaa..1e6bf7b1 100644 --- a/crates/dig-client-wasm/scripts/assemble-pkg.mjs +++ b/crates/dig-client-wasm/scripts/assemble-pkg.mjs @@ -41,7 +41,7 @@ import { fileURLToPath } from "node:url"; import { dirname, resolve } from "node:path"; const SCOPED_NAME = "@dignetwork/dig-client"; -const REPO_URL = "https://github.com/DIG-Network/digstore"; +const REPO_URL = "https://github.com/DIG-Network/dig-store"; const here = dirname(fileURLToPath(import.meta.url)); const root = resolve(here, ".."); diff --git a/crates/digstore-cli/Cargo.toml b/crates/digstore-cli/Cargo.toml index b51a22e7..ff572a85 100644 --- a/crates/digstore-cli/Cargo.toml +++ b/crates/digstore-cli/Cargo.toml @@ -4,13 +4,16 @@ license = "GPL-2.0-only" version.workspace = true edition = "2021" +# The primary CLI binary. Renamed `digstore` -> `dig-store` (rename epic #703) to +# match the repo's canonical name; a transitional `digstore` -> `dig-store` symlink +# ships in the release tarball + .deb for one cycle so existing scripts keep working. [[bin]] -name = "digstore" +name = "dig-store" path = "src/main.rs" -# `digs` β€” a FIRST-CLASS alias binary for `digstore` (issue #434). It shares the +# `digs` β€” a FIRST-CLASS alias binary for `dig-store` (issue #434). It shares the # SINGLE entrypoint `digstore_cli::run()` (see src/bin/digs.rs) so there is no -# duplicated logic; `cargo install` produces BOTH `digstore` and `digs`, and each +# duplicated logic; `cargo install` produces BOTH `dig-store` and `digs`, and each # reflects its own invoked name in --help/--version/completions. [[bin]] name = "digs" @@ -51,7 +54,7 @@ dirs = "5" hex = "0.4" base64 = "0.22" getrandom = "0.2" -# `digstore update` self-updater: extract the `digstore` binary from a release +# `dig-store update` self-updater: extract the `dig-store` binary from a release # `.tar.gz` asset (Linux aarch64 ships only a tarball). Pure-Rust (miniz_oxide). flate2 = "1" tar = "0.4" diff --git a/crates/digstore-cli/src/commands/update.rs b/crates/digstore-cli/src/commands/update.rs index 4133982b..602184a5 100644 --- a/crates/digstore-cli/src/commands/update.rs +++ b/crates/digstore-cli/src/commands/update.rs @@ -15,8 +15,8 @@ use crate::cli::UpdateArgs; use crate::context::CliContext; use crate::error::CliError; -/// Upstream repository whose releases drive `digstore update`. -pub const RELEASES_API: &str = "https://api.github.com/repos/DIG-Network/digstore/releases/latest"; +/// Upstream repository whose releases drive `dig-store update`. +pub const RELEASES_API: &str = "https://api.github.com/repos/DIG-Network/dig-store/releases/latest"; /// GitHub requires a non-empty User-Agent on every API request. pub const USER_AGENT: &str = concat!("digstore-cli/", env!("CARGO_PKG_VERSION")); @@ -152,15 +152,35 @@ fn is_installer_name(name: &str) -> bool { || (n.ends_with(".exe") && n.contains("setup")) } -/// Select the release asset carrying the `digstore` binary for `os`/`arch` (from +/// Rank a release asset by which CLI binary it carries, LOWEST first. A release ships +/// several assets per OS/arch: the primary `dig-store-*`, the transitional legacy +/// `digstore-*` (dual-published for one cycle, rename epic #703), and the `digs-*` +/// alias β€” all three share the OS+arch tokens the filter below matches on, so the +/// self-updater must deterministically prefer the primary `dig-store` binary and never +/// accidentally replace itself with the short `digs` alias. Unknown stems sort last. +fn binary_stem_rank(name: &str) -> u8 { + let n = name.to_ascii_lowercase(); + if n.starts_with("dig-store") { + 0 + } else if n.starts_with("digstore") { + 1 + } else if n.starts_with("digs") { + 2 + } else { + 3 + } +} + +/// Select the release asset carrying the `dig-store` binary for `os`/`arch` (from /// `std::env::consts`). Matches an asset whose name mentions BOTH an OS token and an -/// arch token, excluding GUI installers/disk-images. Prefers a raw executable; falls -/// back to a tarball when that is the only match (e.g. Linux aarch64 ships only a -/// `.tar.gz`). Returns `None` when nothing matches (caller fails loud with manual steps). +/// arch token, excluding GUI installers/disk-images. Prefers the primary `dig-store` +/// stem over the transitional `digstore`/`digs` assets, then a raw executable over a +/// tarball (e.g. Linux aarch64 ships only a `.tar.gz`). Returns `None` when nothing +/// matches (caller fails loud with manual steps). pub fn select_binary_asset(assets: &[Asset], os: &str, arch: &str) -> Option { let oss = os_tokens(os); let ars = arch_tokens(arch); - let matches: Vec<&Asset> = assets + let mut matches: Vec<&Asset> = assets .iter() .filter(|a| { let n = a.name.to_ascii_lowercase(); @@ -169,11 +189,10 @@ pub fn select_binary_asset(assets: &[Asset], os: &str, arch: &str) -> Option bool { } } -/// Extract the `digstore` executable from a gzip-compressed tarball's raw bytes. -/// Returns the binary's bytes, or an error if the archive holds no `digstore` entry. +/// Extract the CLI executable from a gzip-compressed tarball's raw bytes. Returns the +/// binary's bytes, or an error if the archive holds no CLI entry. +/// +/// The release tarball's primary entry is `dig-store` (rename epic #703); for one +/// transition cycle it ALSO carries a `digstore` -> `dig-store` compat symlink, so this +/// only accepts a REGULAR FILE (never the symlink) and prefers the primary `dig-store` +/// name, falling back to the legacy `digstore` name so a `dig-store` binary can still +/// self-update from an older, pre-rename release tarball. pub fn extract_digstore_from_targz(bytes: &[u8]) -> Result, CliError> { use std::io::Read; + // Collect regular-file entries keyed by basename, so the primary `dig-store` can be + // preferred over the legacy `digstore` regardless of their order in the archive. + let mut by_name: std::collections::HashMap> = std::collections::HashMap::new(); let mut archive = tar::Archive::new(flate2::read::GzDecoder::new(bytes)); let entries = archive .entries() @@ -218,22 +246,34 @@ pub fn extract_digstore_from_targz(bytes: &[u8]) -> Result, CliError> { for entry in entries { let mut entry = entry.map_err(|e| CliError::UpdateFailed(format!("read release archive: {e}")))?; - let is_digstore = entry + // Skip the transitional `digstore` -> `dig-store` symlink (and any non-file). + if !entry.header().entry_type().is_file() { + continue; + } + let name = entry .path() .ok() - .and_then(|p| p.file_name().and_then(|s| s.to_str()).map(str::to_owned)) - .map(|n| n == "digstore" || n == "digstore.exe") - .unwrap_or(false); - if is_digstore { + .and_then(|p| p.file_name().and_then(|s| s.to_str()).map(str::to_owned)); + let Some(name) = name else { continue }; + if matches!( + name.as_str(), + "dig-store" | "dig-store.exe" | "digstore" | "digstore.exe" + ) { let mut buf = Vec::new(); entry .read_to_end(&mut buf) - .map_err(|e| CliError::UpdateFailed(format!("extract digstore: {e}")))?; - return Ok(buf); + .map_err(|e| CliError::UpdateFailed(format!("extract {name}: {e}")))?; + by_name.insert(name, buf); + } + } + // Prefer the primary `dig-store` entry; fall back to the legacy `digstore` name. + for key in ["dig-store", "dig-store.exe", "digstore", "digstore.exe"] { + if let Some(bytes) = by_name.remove(key) { + return Ok(bytes); } } Err(CliError::UpdateFailed( - "the release archive contains no `digstore` executable".to_string(), + "the release archive contains no `dig-store` executable".to_string(), )) } @@ -403,8 +443,8 @@ fn display_version(v: &str) -> String { /// Carry out the update once we know a newer release exists. /// -/// One unified path on EVERY platform (Windows included, #303): `digstore` -/// updates ITS OWN binary in place β€” resolve the raw per-OS/arch `digstore` +/// One unified path on EVERY platform (Windows included, #303): `dig-store` +/// updates ITS OWN binary in place β€” resolve the raw per-OS/arch `dig-store` /// binary asset via [`select_binary_asset`], download it, sanity-check it, /// then atomically self-replace via [`install_binary`]. There is no GUI /// installer step here; the bundled installer moved to dig-installer. @@ -431,7 +471,7 @@ fn perform_update( ui.line(format!("release: {}", release.html_url)); } return Err(CliError::NotFound(format!( - "no digstore binary asset for {os}/{arch} in release {latest}; \ + "no dig-store binary asset for {os}/{arch} in release {latest}; \ download it from the release page above and place it on your PATH" ))); }; @@ -452,7 +492,7 @@ fn perform_update( }; if !looks_like_native_binary(&binary, os) { return Err(CliError::UpdateFailed( - "the downloaded asset is not a valid digstore binary (aborting; nothing changed)" + "the downloaded asset is not a valid dig-store binary (aborting; nothing changed)" .to_string(), )); } @@ -622,11 +662,11 @@ fn not_writable(target: &std::path::Path, e: &std::io::Error) -> CliError { CliError::UpdateFailed(format!( "cannot write {} ({e}).\n\ Manual update:\n \ - 1. Download the digstore binary for your OS/arch from \ - https://github.com/DIG-Network/digstore/releases/latest\n \ - 2. chmod +x ./digstore\n \ - 3. (macOS) xattr -d com.apple.quarantine ./digstore\n \ - 4. mv ./digstore \"{}\" # or re-run with write permission / the installer", + 1. Download the dig-store binary for your OS/arch from \ + https://github.com/DIG-Network/dig-store/releases/latest\n \ + 2. chmod +x ./dig-store\n \ + 3. (macOS) xattr -d com.apple.quarantine ./dig-store\n \ + 4. mv ./dig-store \"{}\" # or re-run with write permission / the installer", target.display(), target.display(), )) @@ -783,6 +823,28 @@ mod tests { assert!(select_binary_asset(&assets, "macos", "aarch64").is_none()); } + /// rename epic #703: a dual-published release ships the primary `dig-store-*`, the + /// transitional legacy `digstore-*`, AND the `digs-*` alias for the SAME OS/arch β€” + /// all three match the OS+arch filter. The selector must deterministically pick the + /// primary `dig-store` binary, never the short `digs` alias or the legacy name. + #[test] + fn prefers_dig_store_over_legacy_and_digs_alias() { + // Deliberately listed with `digs` + `digstore` BEFORE `dig-store` so a naive + // "first match" would pick the wrong one. + let assets = vec![ + asset("digs-0.14.0-linux-x64"), + asset("digstore-0.14.0-linux-x64"), + asset("dig-store-0.14.0-linux-x64"), + asset("dig-store-0.14.0-x86_64-unknown-linux-gnu.tar.gz"), + ]; + let a = select_binary_asset(&assets, "linux", "x86_64").unwrap(); + assert_eq!(a.name, "dig-store-0.14.0-linux-x64"); + assert!( + !a.is_tarball, + "prefer the raw primary binary over the tarball" + ); + } + // --- #127: downloaded-binary sanity check --------------------------------- #[test] @@ -828,6 +890,39 @@ mod tests { assert!(extract_digstore_from_targz(&gz).is_err()); } + /// rename epic #703: the primary tarball entry is `dig-store`; a `dig-store` binary + /// self-updating from a NEW release must extract it. + #[test] + fn extracts_dig_store_from_a_targz() { + let payload = b"\x7fELF this-is-the-dig-store-binary"; + let gz = targz_with("dig-store", payload); + assert_eq!(extract_digstore_from_targz(&gz).unwrap(), payload); + } + + /// The transition tarball carries BOTH the real `dig-store` file AND a `digstore` + /// -> `dig-store` compat symlink. Extraction must return the REAL `dig-store` + /// bytes and never the (empty) symlink entry. + #[test] + fn extracts_dig_store_and_skips_the_compat_symlink() { + let payload = b"\x7fELF real-dig-store"; + let enc = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast()); + let mut tb = tar::Builder::new(enc); + // A `digstore` -> `dig-store` symlink (no content), then the real `dig-store`. + let mut link = tar::Header::new_gnu(); + link.set_entry_type(tar::EntryType::Symlink); + link.set_size(0); + link.set_mode(0o777); + tb.append_link(&mut link, "digstore", "dig-store").unwrap(); + let mut file = tar::Header::new_gnu(); + file.set_size(payload.len() as u64); + file.set_mode(0o755); + file.set_cksum(); + tb.append_data(&mut file, "dig-store", &payload[..]) + .unwrap(); + let gz = tb.into_inner().unwrap().finish().unwrap(); + assert_eq!(extract_digstore_from_targz(&gz).unwrap(), payload); + } + // --- #127: atomic self-replace (against a TEMP file, NEVER the live binary) -- #[test] diff --git a/crates/digstore-cli/tests/cli_add.rs b/crates/digstore-cli/tests/cli_add.rs index ac1576df..4c5ee557 100644 --- a/crates/digstore-cli/tests/cli_add.rs +++ b/crates/digstore-cli/tests/cli_add.rs @@ -3,7 +3,7 @@ use assert_cmd::Command; use common::tmp_dig; fn dig_in(dir: &std::path::Path) -> Command { - let mut c = Command::cargo_bin("digstore").unwrap(); + let mut c = Command::cargo_bin("dig-store").unwrap(); c.current_dir(dir); common::seed_mock_env(&mut c, dir); c diff --git a/crates/digstore-cli/tests/cli_add_status.rs b/crates/digstore-cli/tests/cli_add_status.rs index 40fe274d..8a40a374 100644 --- a/crates/digstore-cli/tests/cli_add_status.rs +++ b/crates/digstore-cli/tests/cli_add_status.rs @@ -3,7 +3,7 @@ use assert_cmd::Command; use common::tmp_dig; fn dig_in(dir: &std::path::Path) -> Command { - let mut c = Command::cargo_bin("digstore").unwrap(); + let mut c = Command::cargo_bin("dig-store").unwrap(); c.current_dir(dir); common::seed_mock_env(&mut c, dir); c diff --git a/crates/digstore-cli/tests/cli_dev.rs b/crates/digstore-cli/tests/cli_dev.rs index cc2152f1..651df5f5 100644 --- a/crates/digstore-cli/tests/cli_dev.rs +++ b/crates/digstore-cli/tests/cli_dev.rs @@ -167,7 +167,7 @@ fn spawn_port_reader(child: &mut Child) -> std::sync::mpsc::Receiver { fn dev_serves_real_read_path_with_injected_shims() { let td = tmp_dir(); // Scaffold a static site to serve. - Command::new(cargo_bin("digstore")) + Command::new(cargo_bin("dig-store")) .args(["new", "static-site"]) .arg(td.path()) .arg("--force") @@ -177,7 +177,7 @@ fn dev_serves_real_read_path_with_injected_shims() { // `--port 0` => the OS assigns a free port; `--json` makes `dev` print the // real bound port once it is accepting connections. This removes both the // fixed-port collision and the startup race that made this test flaky in CI. - let mut child = Command::new(cargo_bin("digstore")) + let mut child = Command::new(cargo_bin("dig-store")) .current_dir(td.path()) .args(["--json", "dev", "--port", "0"]) .stdout(Stdio::piped()) diff --git a/crates/digstore-cli/tests/cli_digs_alias.rs b/crates/digstore-cli/tests/cli_digs_alias.rs index 61266e1c..86930b86 100644 --- a/crates/digstore-cli/tests/cli_digs_alias.rs +++ b/crates/digstore-cli/tests/cli_digs_alias.rs @@ -27,7 +27,7 @@ fn digs(dir: &TempDir) -> Command { /// OWN program name (clap prints " "): `digs 0.x.y` vs `digstore 0.x.y`. #[test] fn digs_and_digstore_report_the_same_version() { - let ds_out = Command::cargo_bin("digstore") + let ds_out = Command::cargo_bin("dig-store") .unwrap() .arg("--version") .output() @@ -45,8 +45,8 @@ fn digs_and_digstore_report_the_same_version() { let dg_ver = dg.split_whitespace().last().unwrap(); assert_eq!(ds_ver, dg_ver, "same version: `{ds}` vs `{dg}`"); assert!( - ds.starts_with("digstore "), - "digstore leads with its name: {ds}" + ds.starts_with("dig-store "), + "dig-store leads with its name: {ds}" ); assert!( dg.starts_with("digs "), @@ -70,7 +70,7 @@ fn digs_help_usage_shows_digs() { /// each reporting its own invoked binary name. #[test] fn digs_and_digstore_share_the_same_command_surface() { - let ds = Command::cargo_bin("digstore") + let ds = Command::cargo_bin("dig-store") .unwrap() .arg("--help-json") .output() @@ -84,7 +84,7 @@ fn digs_and_digstore_share_the_same_command_surface() { let dsv: serde_json::Value = serde_json::from_slice(&ds.stdout).unwrap(); let dgv: serde_json::Value = serde_json::from_slice(&dg.stdout).unwrap(); // Each reports its own invoked name. - assert_eq!(dsv["name"].as_str(), Some("digstore")); + assert_eq!(dsv["name"].as_str(), Some("dig-store")); assert_eq!(dgv["name"].as_str(), Some("digs")); // Everything else is identical β€” the SAME CLI, byte-for-byte. assert_eq!(dsv["commands"], dgv["commands"], "identical command tree"); diff --git a/crates/digstore-cli/tests/cli_errors.rs b/crates/digstore-cli/tests/cli_errors.rs index 9fda2ffb..2d93b47d 100644 --- a/crates/digstore-cli/tests/cli_errors.rs +++ b/crates/digstore-cli/tests/cli_errors.rs @@ -6,7 +6,7 @@ use predicates::prelude::*; #[test] fn missing_store_shows_help_hint() { let d = tmp_dig(); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .current_dir(d.path()) .args(["status"]) // no store here diff --git a/crates/digstore-cli/tests/cli_help.rs b/crates/digstore-cli/tests/cli_help.rs index a1f01a1f..c5ede130 100644 --- a/crates/digstore-cli/tests/cli_help.rs +++ b/crates/digstore-cli/tests/cli_help.rs @@ -6,7 +6,7 @@ use predicates::prelude::*; #[test] fn add_help_shows_examples() { let d = tmp_dig(); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .current_dir(d.path()) .args(["add", "--help"]) @@ -22,7 +22,7 @@ fn add_help_shows_examples() { #[test] fn log_help_uses_capsule_vocabulary() { let d = tmp_dig(); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .current_dir(d.path()) .args(["log", "--help"]) @@ -40,7 +40,7 @@ fn log_help_uses_capsule_vocabulary() { #[test] fn checkout_help_uses_capsule_vocabulary() { let d = tmp_dig(); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .current_dir(d.path()) .args(["checkout", "--help"]) @@ -55,7 +55,7 @@ fn checkout_help_uses_capsule_vocabulary() { #[test] fn top_level_help_is_task_first() { let d = tmp_dig(); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .current_dir(d.path()) .args(["--help"]) @@ -75,7 +75,7 @@ fn top_level_help_is_task_first() { #[test] fn new_help_lists_templates_and_says_free() { let d = tmp_dig(); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .current_dir(d.path()) .args(["new", "--help"]) @@ -93,7 +93,7 @@ fn new_help_lists_templates_and_says_free() { #[test] fn new_help_cross_links_create_dig_app() { let d = tmp_dig(); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .current_dir(d.path()) .args(["new", "--help"]) @@ -109,7 +109,7 @@ fn new_help_cross_links_create_dig_app() { #[test] fn dev_help_describes_free_local_loop() { let d = tmp_dig(); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .current_dir(d.path()) .args(["dev", "--help"]) @@ -122,7 +122,7 @@ fn dev_help_describes_free_local_loop() { #[test] fn commit_help_documents_dry_run() { let d = tmp_dig(); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .current_dir(d.path()) .args(["commit", "--help"]) @@ -138,13 +138,13 @@ fn commit_help_documents_dry_run() { #[test] fn projects_alias_help_resolves() { let d = tmp_dig(); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .current_dir(d.path()) .args(["projects", "--help"]) .assert() .success(); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .current_dir(d.path()) .args(["stores", "--help"]) diff --git a/crates/digstore-cli/tests/cli_help_json.rs b/crates/digstore-cli/tests/cli_help_json.rs index 99dccc64..622d5326 100644 --- a/crates/digstore-cli/tests/cli_help_json.rs +++ b/crates/digstore-cli/tests/cli_help_json.rs @@ -15,7 +15,7 @@ fn help_json_is_a_complete_contract() { let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap(); // Top-level shape. - assert_eq!(v["name"].as_str(), Some("digstore")); + assert_eq!(v["name"].as_str(), Some("dig-store")); assert!(v["version"].is_string()); // Globals are documented once at the root and include the headline flags. diff --git a/crates/digstore-cli/tests/cli_init.rs b/crates/digstore-cli/tests/cli_init.rs index fb3bdc0a..acb68036 100644 --- a/crates/digstore-cli/tests/cli_init.rs +++ b/crates/digstore-cli/tests/cli_init.rs @@ -107,7 +107,7 @@ fn init_json_emits_store_id() { use assert_cmd::Command; fn init_in(dir: &std::path::Path) -> assert_cmd::assert::Assert { - let mut cmd = Command::cargo_bin("digstore").unwrap(); + let mut cmd = Command::cargo_bin("dig-store").unwrap(); cmd.current_dir(dir); common::seed_mock_env(&mut cmd, dir); cmd.arg("init").assert() @@ -317,7 +317,7 @@ fn init_without_seed_exits_9() { // A bare DIGSTORE_HOME with NO session β†’ unlock yields NoSeed (exit 9). let dir = tmp_dig(); let home = tmp_dig(); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .arg("--dig-dir") .arg(dir.path().join(".dig")) diff --git a/crates/digstore-cli/tests/cli_link_onboard.rs b/crates/digstore-cli/tests/cli_link_onboard.rs index a2583c5c..c9618834 100644 --- a/crates/digstore-cli/tests/cli_link_onboard.rs +++ b/crates/digstore-cli/tests/cli_link_onboard.rs @@ -118,7 +118,7 @@ fn auth_alias_runs_setup() { .success(); } -/// `digstore completion bash` prints a usable bash completion script naming the binary. +/// `dig-store completion bash` prints a usable bash completion script naming the binary. #[test] fn completion_bash_prints_script() { let d = tmp_dig(); @@ -126,7 +126,7 @@ fn completion_bash_prints_script() { .args(["completion", "bash"]) .assert() .success() - .stdout(predicate::str::contains("digstore").and(predicate::str::contains("complete"))); + .stdout(predicate::str::contains("dig-store").and(predicate::str::contains("complete"))); } /// Every supported shell produces a non-empty script. @@ -152,7 +152,7 @@ fn help_json_prints_command_schema() { String::from_utf8_lossy(&out.stderr) ); let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap(); - assert_eq!(v["name"].as_str(), Some("digstore")); + assert_eq!(v["name"].as_str(), Some("dig-store")); let cmds = v["commands"].as_array().unwrap(); let names: Vec<&str> = cmds.iter().filter_map(|c| c["name"].as_str()).collect(); for expected in [ diff --git a/crates/digstore-cli/tests/cli_multistore.rs b/crates/digstore-cli/tests/cli_multistore.rs index 0bb79a55..0e9e3cb7 100644 --- a/crates/digstore-cli/tests/cli_multistore.rs +++ b/crates/digstore-cli/tests/cli_multistore.rs @@ -18,7 +18,7 @@ use tempfile::TempDir; /// A `digstore` invocation whose CWD is the temp project root. Workspace /// discovery then anchors at `/.dig`, exactly like a real checkout. fn dig_at(project: &Path) -> Command { - let mut c = Command::cargo_bin("digstore").unwrap(); + let mut c = Command::cargo_bin("dig-store").unwrap(); c.current_dir(project); common::seed_mock_env(&mut c, project); c diff --git a/crates/digstore-cli/tests/cli_new.rs b/crates/digstore-cli/tests/cli_new.rs index 8ea98362..bfb0fb5d 100644 --- a/crates/digstore-cli/tests/cli_new.rs +++ b/crates/digstore-cli/tests/cli_new.rs @@ -15,7 +15,7 @@ use predicates::prelude::*; fn new_static_site_scaffolds_without_minting() { let td = tmp_dig(); let target = td.path().join("site"); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .args(["new", "static-site"]) .arg(&target) @@ -37,7 +37,7 @@ fn new_static_site_scaffolds_without_minting() { fn new_dapp_includes_window_chia_example() { let td = tmp_dig(); let target = td.path().join("dapp"); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .args(["new", "dapp-window-chia"]) .arg(&target) @@ -50,7 +50,7 @@ fn new_dapp_includes_window_chia_example() { /// `--list` prints the catalog and exits 0 (works as JSON too). #[test] fn new_list_shows_all_templates() { - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .args(["new", "x", "--list"]) .assert() @@ -68,7 +68,7 @@ fn new_list_shows_all_templates() { #[test] fn new_unknown_template_errors() { let td = tmp_dig(); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .args(["new", "not-a-template"]) .arg(td.path().join("out")) @@ -82,7 +82,7 @@ fn new_unknown_template_errors() { fn new_refuses_nonempty_dir_without_force() { let td = tmp_dig(); std::fs::write(td.path().join("keep.txt"), b"hi").unwrap(); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .current_dir(td.path()) .args(["new", "static-site"]) @@ -97,7 +97,7 @@ fn new_refuses_nonempty_dir_without_force() { fn new_vite_react_is_build_ready() { let td = tmp_dig(); let target = td.path().join("app"); - Command::cargo_bin("digstore") + Command::cargo_bin("dig-store") .unwrap() .args(["new", "vite-react"]) .arg(&target) diff --git a/crates/digstore-cli/tests/cli_status.rs b/crates/digstore-cli/tests/cli_status.rs index 6f0764a7..1d3231b3 100644 --- a/crates/digstore-cli/tests/cli_status.rs +++ b/crates/digstore-cli/tests/cli_status.rs @@ -3,7 +3,7 @@ use assert_cmd::Command; use common::tmp_dig; fn dig_in(dir: &std::path::Path) -> Command { - let mut c = Command::cargo_bin("digstore").unwrap(); + let mut c = Command::cargo_bin("dig-store").unwrap(); c.current_dir(dir); common::seed_mock_env(&mut c, dir); c diff --git a/crates/digstore-cli/tests/common/mod.rs b/crates/digstore-cli/tests/common/mod.rs index 5d4c41f8..ac231a25 100644 --- a/crates/digstore-cli/tests/common/mod.rs +++ b/crates/digstore-cli/tests/common/mod.rs @@ -75,7 +75,7 @@ pub fn seed_mock_env(cmd: &mut Command, dir: &std::path::Path) { /// Transparently carries the seeded mock anchoring env (see [`seed_mock_env`]) so /// `init` mints against the in-memory mock instead of Chia mainnet. pub fn dig(dir: &TempDir) -> Command { - let mut cmd = Command::cargo_bin("digstore").unwrap(); + let mut cmd = Command::cargo_bin("dig-store").unwrap(); cmd.arg("--dig-dir") .arg(dir.path().join(".dig")) .current_dir(dir.path()); diff --git a/crates/digstore-cli/tests/e2e_mainnet.rs b/crates/digstore-cli/tests/e2e_mainnet.rs index e4f64747..56f91fe7 100644 --- a/crates/digstore-cli/tests/e2e_mainnet.rs +++ b/crates/digstore-cli/tests/e2e_mainnet.rs @@ -52,7 +52,7 @@ fn mainnet_init_add_commit_anchor_status() { // A `digstore` invocation in the throwaway project + home. Crucially this // does NOT set DIGSTORE_ANCHOR_MOCK β€” it is the real on-chain path. let dig = || { - let mut cmd = Command::cargo_bin("digstore").unwrap(); + let mut cmd = Command::cargo_bin("dig-store").unwrap(); cmd.arg("--dig-dir") .arg(project.path().join(".dig")) .current_dir(project.path()) diff --git a/crates/digstore-cli/tests/nightly_release_workflow_shape.rs b/crates/digstore-cli/tests/nightly_release_workflow_shape.rs index 1330e066..f66ab36d 100644 --- a/crates/digstore-cli/tests/nightly_release_workflow_shape.rs +++ b/crates/digstore-cli/tests/nightly_release_workflow_shape.rs @@ -239,17 +239,69 @@ fn reusable_build_workflow_is_workflow_call_and_shared() { } /// `digs` is a first-class alias binary (issue #434): the reusable build MUST compile + stage it -/// beside `digstore` so every release (stable AND nightly) carries the `digs--` +/// beside `dig-store` so every release (stable AND nightly) carries the `digs--` /// asset β€” the producer-side counterpart to the dig-installer's `digs` matcher. #[test] fn reusable_build_ships_the_digs_alias() { let build = workflow("build-binaries.yml"); assert!( - build.contains("--bin digstore --bin digs"), - "build-binaries.yml must `cargo build … --bin digstore --bin digs` so the alias ships" + build.contains("--bin dig-store --bin digs"), + "build-binaries.yml must `cargo build … --bin dig-store --bin digs` so the alias ships" ); assert!( build.contains("dist/digs-${VERSION}-${{ matrix.out_name }}"), "build-binaries.yml must stage a `digs--` release asset" ); } + +/// Rename epic #703: the primary binary is `dig-store`, but for one transition cycle every asset +/// is DUAL-PUBLISHED under BOTH the new `dig-store-*` stem AND the legacy `digstore-*` stem so the +/// two installers (apt.dig.net + dig-installer) stay green until they cut over. This guards the +/// dual-publish (both stems staged, both tarballs cut) and the transitional compat symlink. +#[test] +fn reusable_build_dual_publishes_new_and_legacy_stems() { + let build = workflow("build-binaries.yml"); + // Bare per-OS binaries: both stems. + assert!( + build.contains("dist/dig-store-${VERSION}-${{ matrix.out_name }}"), + "build-binaries.yml must stage the new `dig-store--` bare binary" + ); + assert!( + build.contains("dist/digstore-${VERSION}-${{ matrix.out_name }}"), + "build-binaries.yml must ALSO stage the legacy `digstore--` bare binary \ + (transitional dual-publish) until dig-installer cuts over" + ); + // Tarballs: both stems. + assert!( + build.contains("dist/dig-store-${VERSION}-${ARCH}.tar.gz"), + "build-binaries.yml must cut the new `dig-store--.tar.gz` for apt" + ); + assert!( + build.contains("dist/digstore-${VERSION}-${ARCH}.tar.gz"), + "build-binaries.yml must ALSO cut the legacy `digstore--.tar.gz` (transitional) \ + until apt.dig.net cuts over" + ); + // Transitional compat symlink inside the tarball. + assert!( + build.contains("ln -s dig-store"), + "the tarball must ship a `digstore` -> `dig-store` compat symlink for one transition cycle" + ); +} + +/// The `digs` asset name must be derived INDEPENDENTLY, never by a `dig-store`->`digs` substring +/// replacement on the primary bin: after the #703 rename `dig-store` contains no `digs` substring, +/// so the old `${DIGS_BIN/digstore/digs}` trick would silently produce a wrong/empty name. This +/// pins the explicit derivation so a careless revert to the substring hack fails loudly. +#[test] +fn digs_asset_name_is_derived_independently_not_by_substring() { + let build = workflow("build-binaries.yml"); + assert!( + !build.contains("/digstore/digs}") && !build.contains("/dig-store/digs}"), + "build-binaries.yml must NOT derive the `digs` name by substring replacement on the \ + primary bin (broken after the #703 rename); derive it independently" + ); + assert!( + build.contains("DIGS_SRC=\"$REL/digs${EXE}\""), + "the `digs` binary path must be built independently as `digs` + the OS exe suffix" + ); +} diff --git a/crates/digstore-cli/tests/seed_cmd.rs b/crates/digstore-cli/tests/seed_cmd.rs index a27a561c..4d254cfd 100644 --- a/crates/digstore-cli/tests/seed_cmd.rs +++ b/crates/digstore-cli/tests/seed_cmd.rs @@ -6,7 +6,7 @@ use assert_cmd::Command; use predicates::str::contains; fn digstore(home: &std::path::Path) -> Command { - let mut cmd = Command::cargo_bin("digstore").unwrap(); + let mut cmd = Command::cargo_bin("dig-store").unwrap(); cmd.env("DIGSTORE_HOME", home); cmd.env("DIGSTORE_PASSPHRASE", "test-pass"); cmd diff --git a/crates/digstore-core/Cargo.toml b/crates/digstore-core/Cargo.toml index f46142dc..48da31f6 100644 --- a/crates/digstore-core/Cargo.toml +++ b/crates/digstore-core/Cargo.toml @@ -4,7 +4,7 @@ version.workspace = true edition.workspace = true license.workspace = true description = "Core read-crypto and format primitives for the DIG Network store (.dig) format: capsules, manifests, merkle proofs, URN grammar, and the AES-256-GCM-SIV chunk seal." -repository = "https://github.com/DIG-Network/digstore" +repository = "https://github.com/DIG-Network/dig-store" readme = "README.md" keywords = ["dig", "chia", "content-addressed", "merkle", "storage"] categories = ["cryptography", "encoding", "data-structures"] diff --git a/crates/digstore-core/README.md b/crates/digstore-core/README.md index 70977973..b784f01e 100644 --- a/crates/digstore-core/README.md +++ b/crates/digstore-core/README.md @@ -31,4 +31,4 @@ Disable default features (`default-features = false`) for a `no_std`, wasm-clean ## License -GPL-2.0-only. See [LICENSE](https://github.com/DIG-Network/digstore/blob/main/LICENSE). +GPL-2.0-only. See [LICENSE](https://github.com/DIG-Network/dig-store/blob/main/LICENSE). diff --git a/runbooks/release.md b/runbooks/release.md index 136ccd9f..51435d4a 100644 --- a/runbooks/release.md +++ b/runbooks/release.md @@ -1,6 +1,6 @@ -# Runbook β€” releasing digstore (nightly cron + manual dispatch) +# Runbook β€” releasing dig-store (nightly cron + manual dispatch) -How this repo's `digstore` CLI (+ the `digs` alias) is built and released. The shape is copied from +How this repo's `dig-store` CLI (+ the `digs` alias) is built and released. The shape is copied from the ecosystem's **reference nightlies system** (`dig-updater`, dig_ecosystem #590/#592); the normative contract is `SPEC.md` Β§12. @@ -31,9 +31,9 @@ GitHub disables a `schedule:` trigger after **60 days of no repo activity** on a repo can go dark with no error. If nightlies (or a long-overdue stable release) stop appearing: ```bash -gh api repos/DIG-Network/digstore/actions/workflows/nightly-release.yml --jq .state +gh api repos/DIG-Network/dig-store/actions/workflows/nightly-release.yml --jq .state # "disabled_inactivity" means GitHub turned it off β€” re-enable it: -gh workflow enable nightly-release.yml --repo DIG-Network/digstore +gh workflow enable nightly-release.yml --repo DIG-Network/dig-store ``` Any repo activity (a merged PR, a manual dispatch) resets the 60-day counter. @@ -49,6 +49,14 @@ Any repo activity (a merged PR, a manual dispatch) resets the 60-day counter. publishes the GitHub Release (bare binaries + `digs` + apt tarballs), and uploads the Linux binary to S3. +> TRANSITIONAL (rename epic #703): the primary binary was renamed `digstore` -> `dig-store`. For one +> transition cycle every asset is DUAL-PUBLISHED under BOTH the new `dig-store-*` stem AND the legacy +> `digstore-*` stem (bare binaries + apt `.tar.gz`), and each apt tarball ships a `digstore` -> +> `dig-store` compat symlink at its root, so apt.dig.net + dig-installer stay green until they cut +> over to `dig-store-*`. The dighub S3 hub-worker layout (`digstore//digstore`) is UNCHANGED +> (it is the compile-worker's contract). Drop the legacy stem + symlink in a later release once both +> installers have cut over. + ### Cut a stable release NOW / re-cut - Now: Actions β†’ **Nightly + stable release** β†’ **Run workflow** β†’ `channel: stable` (or `both`). @@ -63,9 +71,9 @@ Actions β†’ **Nightly + stable release** β†’ **Run workflow** β†’ `channel: nigh ## Verify a release went live -- **Stable:** `gh release view vX.Y.Z --repo DIG-Network/digstore` β€” bare per-OS binaries + `digs` + +- **Stable:** `gh release view vX.Y.Z --repo DIG-Network/dig-store` β€” bare per-OS binaries + `digs` + apt `.tar.gz` (x86_64 + aarch64). Watch: `gh run watch `. -- **Nightly:** `gh release view nightly --repo DIG-Network/digstore` (rolling) or +- **Nightly:** `gh release view nightly --repo DIG-Network/dig-store` (rolling) or `gh release view nightly-YYYYMMDD` β€” `prerelease: true`. ## Workflows

+ CI + Release + License: GPL-2.0 + Platforms + Rust 1.94.1 +