Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
8b99ea8
fix(deps): update platform while preserving existing profiles
lklimek Sep 10, 2026
9cbf464
fix(deps): pin platform to 63cf57f
lklimek Sep 10, 2026
3057fe3
docs(migration): add fixture contract, capture recipe and matrix design
lklimek Sep 10, 2026
8ff28cf
feat(mcp): add read-only identity_list and app_storage_status tools
lklimek Sep 11, 2026
931dc29
test(migration): add cross-version migration matrix harness
lklimek Sep 11, 2026
b1db9ce
Merge branch 'worktree-agent-a2fcb5657487e74bb' into feat/migration-t…
lklimek Sep 11, 2026
7b31feb
Merge branch 'worktree-agent-a4b3674e1b8943e08' into feat/migration-t…
lklimek Sep 11, 2026
474e440
docs(migration): record real v0.9.3 fixture capture and its proof-for…
lklimek Sep 11, 2026
e502a57
ci(migration): add migration-matrix workflow, fixture scripts and pin…
lklimek Sep 11, 2026
c236e4f
fix(migration): resolve fixture archive by documented manifest key
lklimek Sep 11, 2026
c127508
ci(migration): add fixture bootstrap upload and make the matrix fail …
lklimek Sep 11, 2026
f82f38e
test(migration): record v0.9.3 fixture artifact run
lklimek Sep 11, 2026
a213fe7
fix(migration-matrix): stop a test-env leak from clobbering DET_CLI_BIN
lklimek Sep 11, 2026
ac06bd2
docs(ai-design): add platform_compatibility bridge pattern reference
lklimek Sep 11, 2026
b197b1b
fix(mcp): restore the legacy network before the det-cli boot picks one
lklimek Sep 11, 2026
c9848f8
test(migration-matrix): assert the read-only data.db contract and uma…
lklimek Sep 11, 2026
4f0b519
test(migration-matrix): expect StorageUpdateNeedsDesktop for password…
lklimek Sep 11, 2026
d8f2522
fix(logging): cap rmcp logging so raw MCP tool arguments never reach …
lklimek Sep 11, 2026
94512c7
feat(storage-update): finish a protected-wallet storage update non-in…
lklimek Sep 11, 2026
3cb16a5
test(migration): boot the v0.9.3 fixture again with its password supp…
lklimek Sep 11, 2026
d4ec6aa
docs(storage-update): record the non-interactive password decision
lklimek Sep 11, 2026
eded628
fix(det-cli): keep wallet passwords off plain HTTP and out of stdin's…
lklimek Sep 11, 2026
b977562
fix(migration-matrix): skip SPV-gated address derivation for legacy-d…
lklimek Sep 11, 2026
3f46f12
test(migration): real-data platform_compatibility fixture from v1.0.0…
lklimek Sep 11, 2026
4d74032
ci(migration): relay large fixture archives through a draft release a…
lklimek Sep 11, 2026
6faaf8f
chore(migration-fixtures): record the weekly-20260908 fixture's uploa…
lklimek Sep 11, 2026
b82ccd6
Merge origin/v1.0-dev into feat/migration-test-matrix
lklimek Sep 11, 2026
d718695
Merge branch 'v1.0-dev' into feat/migration-test-matrix
lklimek Sep 14, 2026
e855f4f
ci(migration): run the matrix on every PR 983 branch push
lklimek Sep 14, 2026
0280335
test: harden migration matrix outcomes and password handling
lklimek Sep 14, 2026
579b0bd
fix(migration): enforce fixture integrity and credential isolation
lklimek Sep 14, 2026
c76de0d
fix(mcp): inspect storage without initialization and report wallet ow…
lklimek Sep 14, 2026
25b491b
ci(migration): validate fixture scripts before building
lklimek Sep 14, 2026
d74a66f
test(migration): use runner-provided grep for script assertions
lklimek Sep 14, 2026
a8a826b
test(cli): check request read in redirect regression
lklimek Sep 14, 2026
d9c297c
Merge remote-tracking branch 'origin/v1.0-dev' into feat/migration-te…
lklimek Sep 14, 2026
4e65659
fix(migration): address retry and matrix review findings
lklimek Sep 15, 2026
d0d1d37
Merge remote-tracking branch 'origin/v1.0-dev' into feat/migration-te…
lklimek Sep 15, 2026
d1d032a
Merge branch 'v1.0-dev' into feat/migration-test-matrix
lklimek Sep 16, 2026
883df2a
fix(migration): preserve captured data and secure CLI transport
lklimek Sep 16, 2026
24d2a59
fix(cli): preserve active context when network settings fail
lklimek Sep 16, 2026
1ebefee
Merge branch 'v1.0-dev' into feat/migration-test-matrix
lklimek Sep 17, 2026
e4aea27
fix(ci): run migration matrix for PR 983
lklimek Sep 17, 2026
43982fa
fix(test): update migration fixture RNG import for rand 0.10
lklimek Sep 17, 2026
3e6f29e
fix: keep passphrase-limit import in single-key tests
lklimek Sep 17, 2026
3c0679c
test(migration): cover historical public identities and DPNS
lklimek Sep 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions .github/workflows/migration-fixture-bootstrap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
name: Migration Fixture Bootstrap

# Manual upload path for a migration fixture archive captured OUTSIDE CI — the
# v0.9.3 baseline needs a GUI capture against a released binary, see
# tests/migration-fixtures/README.md. It turns local bytes into a GitHub
# Actions build artifact without them ever entering the git tree: git history
# is permanent, while an artifact can be deleted or left to expire.
#
# The archive travels base64-encoded in a workflow_dispatch input. The inputs
# payload limit is 65,535 characters, so archives up to ~48 KB fit; the v0.9.3
# baseline is 6 KB. Everything else — artifact name, file name, size, sha256 —
# comes from the COMMITTED manifest entry named by `fixture_id`, so a dispatch
# can only publish bytes a reviewed manifest already describes.
#
# This is not capture automation: it captures nothing, runs on no schedule and
# opens no PR. A human dispatches it and records the run id in the manifest.
#
# USAGE:
# gh workflow run migration-fixture-bootstrap.yml \
# --repo dashpay/dash-evo-tool --ref <branch-carrying-the-manifest-entry> \
# -f fixture_id=v0.9.3-wallet-only \
# -f archive_b64="$(base64 -w0 dash-evo-tool-v093-fixture.tar.zst)"
# then copy the values from the run's step summary into the entry's `artifact`
# block in tests/migration-fixtures/manifest.json.
#
# RETENTION HAZARD: artifacts expire, and 90 days is the ceiling for a public
# repository. Before the manifest's `expires_at`, re-dispatch with the same
# bytes and record the new `workflow_run_id`/`expires_at`. Once the artifact
# has expired, the migration matrix fails (download-fixtures.sh --strict)
# until that is done.
#
# REVOCATION: `gh run delete <run-id>` removes the artifact together with the
# run's inputs payload.

on:
workflow_dispatch:
inputs:
fixture_id:
description: "Manifest entry id (tests/migration-fixtures/manifest.json .fixtures[].id)"
required: true
type: string
archive_b64:
description: "The fixture archive, base64-encoded without line wraps (base64 -w0)"
required: true
type: string
# Registration only. GitHub dispatches a workflow only once it exists on the
# default branch or has already run; this trigger makes the pull request that
# adds or edits this file register it, so it can be dispatched with `--ref`
# against that PR's branch before it reaches the default branch. The job
# below never runs for it.
pull_request:
paths:
- ".github/workflows/migration-fixture-bootstrap.yml"

concurrency:
group: ${{ github.workflow }}-${{ inputs.fixture_id || github.ref }}
cancel-in-progress: false

permissions: {}

jobs:
upload:
name: Upload fixture artifact
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
# Reads back the uploaded artifact's expiry for the step summary.
actions: read
env:
# Platform ceiling for a public repository; see RETENTION HAZARD above.
RETENTION_DAYS: 90
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Resolve manifest entry
id: entry
env:
FIXTURE_ID: ${{ inputs.fixture_id }}
run: |
set -euo pipefail
manifest=tests/migration-fixtures/manifest.json
entry="$(jq -c --arg id "$FIXTURE_ID" \
'[.fixtures[] | select(.id == $id)] | if length == 1 then .[0] else empty end' "$manifest")"
if [ -z "$entry" ]; then
echo "::error::$manifest has no single fixture entry with id '$FIXTURE_ID'."
exit 1
fi
# Each value lands in $GITHUB_OUTPUT and later in a file path, so it
# is held to a strict shape rather than trusted as free text.
check() {
local key="$1" pattern="$2" value
value="$(jq -r --arg k "$key" '.artifact[$k] // empty' <<<"$entry")"
if ! [[ "$value" =~ $pattern ]]; then
echo "::error::Manifest entry '$FIXTURE_ID' has a missing or malformed artifact.$key."
exit 1
fi
echo "$key=$value" >>"$GITHUB_OUTPUT"
}
check artifact_name '^[A-Za-z0-9][A-Za-z0-9._-]*$'
check archive_filename '^[A-Za-z0-9][A-Za-z0-9._-]*\.tar\.(zst|gz)$'
check sha256 '^[0-9a-f]{64}$'
check bytes '^[1-9][0-9]*$'

- name: Decode and verify archive
id: archive
env:
# Passed through the environment, never interpolated into the
# script, and never echoed: it is dispatch input.
ARCHIVE_B64: ${{ inputs.archive_b64 }}
ARCHIVE_FILENAME: ${{ steps.entry.outputs.archive_filename }}
EXPECTED_SHA256: ${{ steps.entry.outputs.sha256 }}
EXPECTED_BYTES: ${{ steps.entry.outputs.bytes }}
run: |
set -euo pipefail
dir="$RUNNER_TEMP/fixture"
mkdir -p "$dir"
archive="$dir/$ARCHIVE_FILENAME"
if ! printf '%s' "$ARCHIVE_B64" | base64 --decode >"$archive"; then
echo "::error::archive_b64 is not valid base64."
exit 1
fi
actual_bytes="$(stat -c %s "$archive")"
actual_sha="$(sha256sum "$archive" | cut -d' ' -f1)"
if [ "$actual_bytes" != "$EXPECTED_BYTES" ] || [ "$actual_sha" != "$EXPECTED_SHA256" ]; then
echo "::error::The dispatched archive does not match the manifest entry (manifest: $EXPECTED_BYTES bytes, sha256 $EXPECTED_SHA256; received: $actual_bytes bytes, sha256 $actual_sha). Nothing was uploaded."
exit 1
fi
# The checksum proves these are the bytes the manifest describes; this
# proves the manifest describes a readable archive.
tar -tf "$archive" >/dev/null
# Same sidecar format pack.sh writes, so the artifact matches what a
# CI capture would have uploaded.
printf '%s %s\n' "$actual_sha" "$ARCHIVE_FILENAME" >"$archive.sha256"
echo "dir=$dir" >>"$GITHUB_OUTPUT"
echo "Verified $ARCHIVE_FILENAME: $actual_bytes bytes, sha256 $actual_sha."

- name: Upload fixture artifact
id: upload
uses: actions/upload-artifact@v4
with:
name: ${{ steps.entry.outputs.artifact_name }}
path: ${{ steps.archive.outputs.dir }}
retention-days: ${{ env.RETENTION_DAYS }}
if-no-files-found: error
# A name clash inside one run is a mis-dispatch, not a refresh.
overwrite: false
# Already zstd/gzip-compressed; zipping it again gains nothing.
compression-level: 0

- name: Record manifest values
env:
GH_TOKEN: ${{ github.token }}
ARTIFACT_ID: ${{ steps.upload.outputs.artifact-id }}
FIXTURE_ID: ${{ inputs.fixture_id }}
run: |
set -euo pipefail
# The expiry actually granted: a repository-level retention setting
# can cap what RETENTION_DAYS asked for.
expires_at="$(gh api "repos/$GITHUB_REPOSITORY/actions/artifacts/$ARTIFACT_ID" --jq .expires_at)"
workflow="${GITHUB_WORKFLOW_REF%@*}"
workflow="${workflow##*/}"
{
echo "### Fixture \`$FIXTURE_ID\` uploaded"
echo
echo "Set these in the fixture's \`artifact\` block of \`tests/migration-fixtures/manifest.json\` and drop \`staging_note\`:"
echo
echo '```json'
jq -n --arg workflow "$workflow" --argjson run "$GITHUB_RUN_ID" \
--argjson days "$RETENTION_DAYS" --arg expires "$expires_at" \
'{workflow_name: $workflow, workflow_run_id: $run, retention_days: $days, expires_at: $expires}'
echo '```'
} >>"$GITHUB_STEP_SUMMARY"
Loading
Loading