mirror of
https://github.com/rustfs/rustfs.git
synced 2026-11-10 12:19:07 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34e6449ed9 | |||
| 0f9258594c | |||
| 33fc735ee2 | |||
| 509869cfc5 |
@@ -1,77 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"plan_id": "rustfs-release-validation",
|
||||
"description": "Versioned certification plan for every public RustFS release exit (rustfs/backlog#2662, R01). preview, prerelease and stable all require this explicit plan. Scripts/release/release_gate.py fail-closes publication on any missing, duplicate, stale, substituted or non-PASS required item.",
|
||||
"release_types": ["preview", "prerelease", "stable"],
|
||||
"targets": [
|
||||
{"os": "linux", "arch": "x86_64", "abi": "musl"},
|
||||
{"os": "linux", "arch": "aarch64", "abi": "musl"},
|
||||
{"os": "linux", "arch": "x86_64", "abi": "gnu"},
|
||||
{"os": "linux", "arch": "aarch64", "abi": "gnu"},
|
||||
{"os": "macos", "arch": "aarch64", "abi": "none"},
|
||||
{"os": "windows", "arch": "x86_64", "abi": "msvc"}
|
||||
],
|
||||
"targets_basis": "Platform matrix of .github/workflows/build.yml (prepare-platform-matrix). gnu and macOS targets build with the pyroscope feature; musl and Windows targets build with no extra features.",
|
||||
"features": ["pyroscope"],
|
||||
"trusted_workflows": [
|
||||
{"repository": "rustfs/s3chaos", "workflow_id": "release-gate.yml"}
|
||||
],
|
||||
"trusted_workflows_basis": "The shared evidence envelope producer lives in rustfs/s3chaos (G01, fault/release_gate.rs) and its plan/live split workflow is .github/workflows/release-gate.yml there. Only results from these identities may enter a release certification; PR-uploaded JSON never gains release authority. R02 must extend this list when additional verifier workflows land, never widen it to untrusted sources.",
|
||||
"lanes": [
|
||||
{
|
||||
"lane": "identity",
|
||||
"harness_repository": "rustfs/s3chaos",
|
||||
"harness_sha": "9a48f55c1d6d58ce63a8fb1ea7317e024f32f11d",
|
||||
"harness_basis": "Fixed G01 evidence-envelope producer revision (rustfs/s3chaos PR #102 head recorded in rustfs/backlog#2658). R02 re-pins this when the live verifier ships.",
|
||||
"timeout_seconds": 1800,
|
||||
"environment_capabilities": ["process-exec"],
|
||||
"cases": ["artifact-identity", "startup"],
|
||||
"applies_to": {
|
||||
"release_types": ["preview", "prerelease", "stable"],
|
||||
"platforms": "all"
|
||||
},
|
||||
"applies_basis": "Artifact identity and startup checks are mandatory for every release type, preview included, on every shipped target (rustfs/backlog#2662 frozen design item 5)."
|
||||
},
|
||||
{
|
||||
"lane": "upgrade",
|
||||
"harness_repository": "rustfs/s3chaos",
|
||||
"harness_sha": "9a48f55c1d6d58ce63a8fb1ea7317e024f32f11d",
|
||||
"harness_basis": "Same fixed envelope producer as the identity lane; the workflow SHA and the product build SHA are bound separately and need not be equal.",
|
||||
"timeout_seconds": 5400,
|
||||
"environment_capabilities": ["process-exec", "previous-release-artifact"],
|
||||
"cases": [
|
||||
"direct-upgrade",
|
||||
"mixed-version-upgrade",
|
||||
"bucket-config-upgrade",
|
||||
"bucket-config-rollback",
|
||||
"multipart-layout-upgrade",
|
||||
"multipart-layout-baseline"
|
||||
],
|
||||
"cases_basis": "Case IDs of the pinned previous-release matrix in .github/workflows/e2e-upgrade.yml (source.version/asset/sha256 and the case list mapped there).",
|
||||
"applies_to": {
|
||||
"release_types": ["prerelease", "stable"],
|
||||
"platforms": [{"os": "linux", "arch": "x86_64", "abi": "gnu"}]
|
||||
},
|
||||
"applies_basis": "preview releases use the pre-declared smaller set (identity lane only). The upgrade lane is only exercised on linux-x86_64-gnu, the platform of the pinned source assets."
|
||||
}
|
||||
],
|
||||
"upgrade_sources": {
|
||||
"basis": "Exactly the two previous-release sources pinned in .github/workflows/e2e-upgrade.yml are exercised today (source.version / source.asset / source.sha256). No other N-1/N-2 compatibility is claimed, and these digests are not inferred from tags.",
|
||||
"sources": [
|
||||
{
|
||||
"version": "1.0.1",
|
||||
"role": "N-1",
|
||||
"artifact_name": "rustfs-linux-x86_64-gnu-v1.0.1.zip",
|
||||
"artifact_sha256": "9742e183cb0dfb0d48fa909797af572fe47b7f827569036baa4156e0d3958ee5"
|
||||
},
|
||||
{
|
||||
"version": "1.0.0-rc.5",
|
||||
"role": "N-2",
|
||||
"artifact_name": "rustfs-linux-x86_64-gnu-v1.0.0-rc.5.zip",
|
||||
"artifact_sha256": "3ee8df71e8edcfada533be452c4135868f697bc515460ae97b027313eade7a3d"
|
||||
}
|
||||
],
|
||||
"rollback_supported": false,
|
||||
"rollback_basis": "Only the bucket-config-rollback case (rollback_to_previous_release_reads_current_bucket_metadata) is exercised. Full data-plane rollback is not certified and is never a default claim."
|
||||
}
|
||||
}
|
||||
+5
-311
@@ -814,18 +814,13 @@ jobs:
|
||||
export AWS_SECRET_ACCESS_KEY="$R2_SECRET_ACCESS_KEY"
|
||||
export AWS_DEFAULT_REGION="auto"
|
||||
|
||||
# Determine upload path based on build type. Release and prerelease
|
||||
# builds only stage their bytes here: the official
|
||||
# artifacts/rustfs/release/ path is written solely by the
|
||||
# certification-gated promote-release-artifacts job, from these same
|
||||
# staged objects. Development builds keep the uncertified dev channel
|
||||
# and never touch official or latest channels.
|
||||
# Determine upload path based on build type
|
||||
if [[ "$BUILD_TYPE" == "development" ]]; then
|
||||
R2_PATH="s3://${R2_BUCKET}/artifacts/rustfs/dev/"
|
||||
echo "📤 Uploading development build to R2 dev directory"
|
||||
else
|
||||
R2_PATH="s3://${R2_BUCKET}/artifacts/rustfs/staging/${GITHUB_RUN_ID}/${GITHUB_RUN_ATTEMPT}/"
|
||||
echo "📤 Staging release build for certification-gated promotion"
|
||||
R2_PATH="s3://${R2_BUCKET}/artifacts/rustfs/release/"
|
||||
echo "📤 Uploading release build to R2 release directory"
|
||||
fi
|
||||
|
||||
# Upload all rustfs zip files to R2 using S3-compatible API
|
||||
@@ -874,221 +869,6 @@ jobs:
|
||||
echo "This run does not trigger Docker publishing."
|
||||
fi
|
||||
|
||||
# Aggregate the parent candidate batch. This job always collects results and
|
||||
# retains its evidence, including on failure, and every publication job
|
||||
# below requires its success. Missing, stale, substituted or non-PASS
|
||||
# evidence fails closed here: only the trusted verification sources
|
||||
# (rustfs/s3chaos release-gate envelopes, rustfs/backlog#2658) can satisfy
|
||||
# it, so a PR-uploaded JSON set never gains release authority.
|
||||
release-certification:
|
||||
name: Release Certification
|
||||
needs: [ build-check, build-rustfs ]
|
||||
if: ${{ !cancelled() && needs.build-check.outputs.should_publish == 'true' && startsWith(github.ref, 'refs/tags/') }}
|
||||
runs-on: ubuntu-latest # gh CLI required; the sm-standard-2 fleet does not ship it
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download all build artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: ./artifacts
|
||||
pattern: rustfs-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Write candidate and collect trusted validation results
|
||||
env:
|
||||
# Cross-repo read of the trusted verification run. Falls back to the
|
||||
# workflow token, which cannot read rustfs/s3chaos; a missing token
|
||||
# therefore fail-closes instead of certifying.
|
||||
GH_TOKEN: ${{ secrets.RELEASE_VALIDATION_TOKEN || github.token }}
|
||||
PRODUCT_COMMIT: ${{ github.sha }}
|
||||
VERSION: ${{ needs.build-check.outputs.version }}
|
||||
BUILD_TYPE: ${{ needs.build-check.outputs.build_type }}
|
||||
FEATURES: ${{ needs.build-check.outputs.build_type == 'development' && '' || 'pyroscope' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
mkdir -p results release-gate-output
|
||||
PLAN_SHA256=$(python3 -c "import hashlib,json,sys; sys.path.insert(0,'scripts/release'); import release_gate; print(release_gate.canonical_sha256(json.load(open('.config/release-validation-plan.json'))))")
|
||||
|
||||
python3 - "$PLAN_SHA256" > candidate.json <<'PY'
|
||||
import hashlib, json, os, sys, zipfile
|
||||
|
||||
plan_sha256 = sys.argv[1]
|
||||
profile = {"release": "stable", "prerelease": "prerelease", "preview": "preview"}.get(os.environ["BUILD_TYPE"])
|
||||
if profile is None:
|
||||
raise SystemExit("release certification only covers preview, prerelease and stable builds")
|
||||
|
||||
def sha256(path):
|
||||
digest = hashlib.sha256()
|
||||
with open(path, "rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
deliverables = []
|
||||
for name in sorted(os.listdir("artifacts")):
|
||||
if not name.startswith("rustfs-") or not name.endswith(".zip"):
|
||||
continue
|
||||
path = os.path.join("artifacts", name)
|
||||
members = zipfile.ZipFile(path).namelist()
|
||||
binary_name = "rustfs.exe" if "rustfs.exe" in members else "rustfs"
|
||||
with zipfile.ZipFile(path) as archive:
|
||||
binary_bytes = archive.read(binary_name)
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile() as handle:
|
||||
handle.write(binary_bytes)
|
||||
handle.flush()
|
||||
binary_sha = sha256(handle.name)
|
||||
deliverables.append({
|
||||
"kind": "zip",
|
||||
"name": name,
|
||||
"location": "https://github.com/rustfs/rustfs/releases/download/{}/{}".format(os.environ["VERSION"], name),
|
||||
"sha256": sha256(path),
|
||||
"size": os.path.getsize(path),
|
||||
"binary_sha256": binary_sha,
|
||||
})
|
||||
if not deliverables:
|
||||
raise SystemExit("no built deliverables found to certify")
|
||||
candidate = {
|
||||
"schema_version": 1,
|
||||
"product_commit": os.environ["PRODUCT_COMMIT"],
|
||||
"version": os.environ["VERSION"],
|
||||
"release_profile": profile,
|
||||
"features": [f for f in os.environ.get("FEATURES", "").split(",") if f],
|
||||
"plan_sha256": plan_sha256,
|
||||
"validation_batch": {"run_id": os.environ["GITHUB_RUN_ID"], "run_attempt": int(os.environ["GITHUB_RUN_ATTEMPT"])},
|
||||
"deliverables": deliverables,
|
||||
}
|
||||
json.dump(candidate, sys.stdout, indent=2, sort_keys=True)
|
||||
print()
|
||||
PY
|
||||
|
||||
# Trusted verification evidence only: the release-gate envelopes of
|
||||
# rustfs/s3chaos for this exact product commit. Absence blocks below.
|
||||
TRUSTED_REPO="rustfs/s3chaos"
|
||||
TRUSTED_WORKFLOW="release-gate.yml"
|
||||
TRUSTED_RUN=$(gh api "repos/${TRUSTED_REPO}/actions/workflows/${TRUSTED_WORKFLOW}/runs?head_sha=${PRODUCT_COMMIT}&status=success&per_page=1" --jq '.workflow_runs[0].id // empty' || true)
|
||||
if [[ -z "$TRUSTED_RUN" ]]; then
|
||||
echo "❌ No trusted verification run of ${TRUSTED_REPO}/${TRUSTED_WORKFLOW} for ${PRODUCT_COMMIT}; refusing to certify" >&2
|
||||
else
|
||||
gh run download "$TRUSTED_RUN" -R "$TRUSTED_REPO" -n release-validation-evidence -D results || \
|
||||
echo "❌ Trusted verification run ${TRUSTED_RUN} published no release-validation-evidence artifact" >&2
|
||||
fi
|
||||
|
||||
python3 scripts/release/release_gate.py validate \
|
||||
--plan .config/release-validation-plan.json \
|
||||
--candidate candidate.json \
|
||||
--results results \
|
||||
--output release-gate-output
|
||||
|
||||
- name: Upload certification evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: release-certification
|
||||
path: |
|
||||
candidate.json
|
||||
results/
|
||||
release-gate-output/
|
||||
if-no-files-found: warn
|
||||
retention-days: 30
|
||||
|
||||
# The only writer of the official R2 release path. Promotion re-checks the
|
||||
# exact bytes about to be published against the passed certification list
|
||||
# and copies the staged objects byte-for-byte: no rebuild-after-verify.
|
||||
promote-release-artifacts:
|
||||
name: Promote Release Artifacts
|
||||
needs: [ build-check, build-rustfs, release-certification ]
|
||||
# Target-channel lock for the official R2 release path.
|
||||
concurrency:
|
||||
group: release-channel-r2-release
|
||||
cancel-in-progress: false
|
||||
if: needs.build-check.outputs.should_publish == 'true' && startsWith(github.ref, 'refs/tags/') && (needs.build-check.outputs.build_type == 'release' || needs.build-check.outputs.build_type == 'prerelease')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
env:
|
||||
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
||||
R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
|
||||
R2_BUCKET: ${{ secrets.R2_BUCKET }}
|
||||
AWS_EC2_METADATA_DISABLED: true
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download build and certification artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: ./artifacts
|
||||
pattern: rustfs-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download certification evidence
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: release-certification
|
||||
path: ./certification
|
||||
|
||||
- name: Verify and promote staged release artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "$R2_ACCESS_KEY_ID" || -z "$R2_SECRET_ACCESS_KEY" || -z "$R2_ENDPOINT" || -z "$R2_BUCKET" ]]; then
|
||||
echo "❌ R2 credentials missing; refusing to promote to the official channel" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for zip_file in artifacts/rustfs-*.zip; do
|
||||
[[ -f "$zip_file" ]] || continue
|
||||
name="$(basename "$zip_file")"
|
||||
python3 scripts/release/release_gate.py verify-promotion \
|
||||
--plan .config/release-validation-plan.json \
|
||||
--candidate certification/candidate.json \
|
||||
--summary certification/release-gate-output \
|
||||
--artifact "$zip_file" \
|
||||
--artifact-name "$name"
|
||||
done
|
||||
|
||||
if ! command -v aws >/dev/null 2>&1; then
|
||||
sudo apt-get update && sudo apt-get install -y awscli
|
||||
fi
|
||||
export AWS_ACCESS_KEY_ID="$R2_ACCESS_KEY_ID"
|
||||
export AWS_SECRET_ACCESS_KEY="$R2_SECRET_ACCESS_KEY"
|
||||
export AWS_DEFAULT_REGION="auto"
|
||||
|
||||
STAGING_PATH="s3://${R2_BUCKET}/artifacts/rustfs/staging/${GITHUB_RUN_ID}/${GITHUB_RUN_ATTEMPT}/"
|
||||
RELEASE_PATH="s3://${R2_BUCKET}/artifacts/rustfs/release/"
|
||||
for zip_file in artifacts/rustfs-*.zip; do
|
||||
[[ -f "$zip_file" ]] || continue
|
||||
name="$(basename "$zip_file")"
|
||||
staged="$(mktemp)"
|
||||
aws s3 cp "${STAGING_PATH}${name}" "$staged" --endpoint-url "$R2_ENDPOINT" --only-show-errors
|
||||
staged_sha="$(sha256sum "$staged" | awk '{print $1}')"
|
||||
built_sha="$(sha256sum "$zip_file" | awk '{print $1}')"
|
||||
if [[ "$staged_sha" != "$built_sha" ]]; then
|
||||
echo "❌ Staged object ${name} does not match the verified bytes" >&2
|
||||
exit 1
|
||||
fi
|
||||
aws s3 cp "$staged" "${RELEASE_PATH}${name}" --endpoint-url "$R2_ENDPOINT" --only-show-errors
|
||||
rm -f "$staged"
|
||||
echo "✅ Promoted ${name} (${built_sha})"
|
||||
done
|
||||
|
||||
# Create GitHub Release for every valid release tag, including previews
|
||||
create-release:
|
||||
name: Create GitHub Release
|
||||
@@ -1248,10 +1028,6 @@ jobs:
|
||||
update-latest-version:
|
||||
name: Update Latest Version
|
||||
needs: [ build-check, publish-release ]
|
||||
# Target-channel lock for the latest.json version pointer.
|
||||
concurrency:
|
||||
group: release-channel-oss-latest
|
||||
cancel-in-progress: false
|
||||
if: needs.build-check.outputs.should_publish == 'true' && startsWith(github.ref, 'refs/tags/') && (needs.build-check.outputs.build_type == 'release' || needs.build-check.outputs.build_type == 'prerelease')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
@@ -1292,45 +1068,6 @@ jobs:
|
||||
rm -rf "$OSSUTIL_DIR" "$OSSUTIL_ZIP"
|
||||
chmod +x "$OSSUTIL_BIN"
|
||||
|
||||
# Candidate/pointer ordering: an older candidate must never overwrite
|
||||
# a newer pointer, even if its promotion job finishes later.
|
||||
CURRENT_LATEST="${RUNNER_TEMP}/current-latest.json"
|
||||
if "$OSSUTIL_BIN" cp oss://rustfs-version/latest.json "$CURRENT_LATEST" 2>/dev/null; then
|
||||
python3 - "$CURRENT_LATEST" "$VERSION" <<'PY'
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
def key(version):
|
||||
match = re.match(r"^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$", version)
|
||||
if not match:
|
||||
raise SystemExit(3)
|
||||
core = tuple(int(part) for part in match.groups()[:3])
|
||||
prerelease = match.group(4)
|
||||
if prerelease is None:
|
||||
return core + (1,)
|
||||
return core + (0, tuple(int(p) if p.isdigit() else p for p in re.split(r"[.-]", prerelease)))
|
||||
|
||||
try:
|
||||
current = json.load(open(sys.argv[1], encoding="utf-8")).get("version", "")
|
||||
proposed = sys.argv[2]
|
||||
if current and key(current) > key(proposed):
|
||||
raise SystemExit(1)
|
||||
except SystemExit:
|
||||
raise
|
||||
except (OSError, ValueError, TypeError):
|
||||
raise SystemExit(3)
|
||||
PY
|
||||
guard_status=$?
|
||||
if [[ "$guard_status" -eq 1 ]]; then
|
||||
echo "Current latest.json points at a newer version; refusing to downgrade" >&2
|
||||
exit 0
|
||||
elif [[ "$guard_status" -ne 0 ]]; then
|
||||
echo "Existing latest.json is unparsable; refusing to overwrite it blindly" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create latest.json
|
||||
cat > latest.json << EOF
|
||||
{
|
||||
@@ -1347,59 +1084,16 @@ jobs:
|
||||
|
||||
echo "✅ Updated latest.json for ${RELEASE_TYPE} release $VERSION"
|
||||
|
||||
# Publish release (remove draft status). Requires the release-certification
|
||||
# aggregate: without a complete legal success there is no public write.
|
||||
# Publish release (remove draft status)
|
||||
publish-release:
|
||||
name: Publish Release
|
||||
needs: [ build-check, create-release, upload-release-assets, release-certification ]
|
||||
# Target-channel lock: concurrent releases serialize on this channel so an
|
||||
# older candidate cannot interleave its publish with a newer one.
|
||||
concurrency:
|
||||
group: release-channel-github-release
|
||||
cancel-in-progress: false
|
||||
needs: [ build-check, create-release, upload-release-assets ]
|
||||
if: needs.build-check.outputs.should_publish == 'true' && startsWith(github.ref, 'refs/tags/') && (needs.build-check.outputs.build_type == 'preview' || needs.build-check.outputs.build_type == 'release' || needs.build-check.outputs.build_type == 'prerelease')
|
||||
runs-on: ubuntu-latest # gh CLI required; the sm-standard-2 fleet does not ship it
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download certification evidence
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: release-certification
|
||||
path: ./certification
|
||||
|
||||
- name: Verify release assets against certification
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="${{ needs.build-check.outputs.version }}"
|
||||
mkdir -p "$RUNNER_TEMP/promotion-assets"
|
||||
gh release download "$TAG" --dir "$RUNNER_TEMP/promotion-assets" --pattern '*.zip'
|
||||
found=0
|
||||
for zip_file in "$RUNNER_TEMP"/promotion-assets/*.zip; do
|
||||
[[ -f "$zip_file" ]] || continue
|
||||
found=1
|
||||
python3 scripts/release/release_gate.py verify-promotion \
|
||||
--plan .config/release-validation-plan.json \
|
||||
--candidate certification/candidate.json \
|
||||
--summary certification/release-gate-output \
|
||||
--artifact "$zip_file" \
|
||||
--artifact-name "$(basename "$zip_file")"
|
||||
done
|
||||
if [[ "$found" -ne 1 ]]; then
|
||||
echo "No release zip assets to verify against the certification" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Publish release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
@@ -329,75 +329,13 @@ jobs:
|
||||
echo " - Create latest: $create_latest"
|
||||
echo " - Source ref: $source_ref"
|
||||
|
||||
# Consume the trusted release certification for the exact version this
|
||||
# workflow wraps and verify the certified input digests. A manual dispatch
|
||||
# without that certification fails here and pushes no official tag; only
|
||||
# results from the trusted verification sources carry release authority.
|
||||
release-certification:
|
||||
name: Release Certification
|
||||
needs: build-check
|
||||
if: needs.build-check.outputs.should_build == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.build-check.outputs.source_ref }}
|
||||
|
||||
- name: Download and verify release certification
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_VALIDATION_TOKEN || github.token }}
|
||||
TRIGGERING_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
VERSION: ${{ needs.build-check.outputs.version }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p certification
|
||||
run_id="$TRIGGERING_RUN_ID"
|
||||
if [[ -z "$run_id" ]]; then
|
||||
run_id="$(gh api --method GET \
|
||||
"repos/${GITHUB_REPOSITORY}/actions/workflows/build.yml/runs" \
|
||||
-f branch="$VERSION" -f status=success -F per_page=1 \
|
||||
--jq '.workflow_runs[0].id // empty' || true)"
|
||||
fi
|
||||
if [[ -z "$run_id" ]]; then
|
||||
echo "No certified Build and Release run found for ${VERSION}; failing closed" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! gh run download "$run_id" -n release-certification -D certification; then
|
||||
echo "Build run ${run_id} published no release-certification artifact; failing closed" >&2
|
||||
exit 1
|
||||
fi
|
||||
python3 scripts/release/release_gate.py verify-promotion \
|
||||
--plan .config/release-validation-plan.json \
|
||||
--candidate certification/candidate.json \
|
||||
--summary certification/release-gate-output \
|
||||
--artifact-sha256 "$(jq -r '.deliverables[0].sha256' certification/candidate.json)" \
|
||||
--artifact-name "$(jq -r '.deliverables[0].name' certification/candidate.json)"
|
||||
|
||||
# Input digest binding: every zip this image wraps must be the
|
||||
# certified byte-for-byte content, never a mutable latest pointer.
|
||||
while IFS=$'\t' read -r name location expected; do
|
||||
echo "Verifying certified input ${name}"
|
||||
actual="$(curl -fsSL "$location" | sha256sum | awk '{print $1}')"
|
||||
if [[ "$actual" != "$expected" ]]; then
|
||||
echo "Certified input ${name} does not match its recorded digest" >&2
|
||||
exit 1
|
||||
fi
|
||||
done < <(jq -r '.deliverables[] | select(.name | contains("linux")) | [.name, .location, .sha256] | @tsv' certification/candidate.json)
|
||||
|
||||
# Build multi-arch Docker images
|
||||
# Strategy: Build images using pre-built binaries from dl.rustfs.com
|
||||
# Supports both release and dev channel binaries based on build context
|
||||
# Only runs when should_build is true (which includes workflow success check)
|
||||
build-docker:
|
||||
name: Build Docker Images
|
||||
needs: [ build-check, release-certification ]
|
||||
needs: build-check
|
||||
if: needs.build-check.outputs.should_build == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
@@ -505,14 +443,6 @@ jobs:
|
||||
# Output tags
|
||||
echo "tags=$TAGS" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Staging identity for this run's promotion: official tags are only
|
||||
# created after the staged image verifies against the certification,
|
||||
# and they point at this same digest (no rebuild-after-verify).
|
||||
STAGING_TAG="staging-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}${VARIANT_SUFFIX}"
|
||||
STAGING_TAGS="${{ env.REGISTRY_DOCKERHUB }}:${STAGING_TAG},${{ env.REGISTRY_GHCR }}:${STAGING_TAG},${{ env.REGISTRY_QUAY }}:${STAGING_TAG}"
|
||||
echo "staging_tags=$STAGING_TAGS" >> "$GITHUB_OUTPUT"
|
||||
echo "staging_tag=$STAGING_TAG" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Generate labels
|
||||
LABELS="org.opencontainers.image.title=RustFS"
|
||||
LABELS="$LABELS,org.opencontainers.image.description=RustFS distributed object storage system"
|
||||
@@ -537,9 +467,7 @@ jobs:
|
||||
file: ${{ matrix.file }}
|
||||
platforms: ${{ env.DOCKER_PLATFORMS }}
|
||||
push: ${{ needs.build-check.outputs.should_push == 'true' }}
|
||||
# Push to staging tags only: official tags are created by the
|
||||
# certification-gated promotion step below from this same digest.
|
||||
tags: ${{ steps.meta.outputs.staging_tags }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
# No layer cache. This build compiles nothing — it downloads a
|
||||
# release zip and runs apk/apt — so the cache could only save the
|
||||
@@ -565,114 +493,6 @@ jobs:
|
||||
no-cache: false
|
||||
pull: true
|
||||
|
||||
# A later-built wrapper: the staged image must complete this platform's
|
||||
# own binary identity verification before any official tag is written.
|
||||
# It may not inherit an unverified package's conclusion.
|
||||
- name: Verify staged image against certification
|
||||
if: needs.build-check.outputs.should_push == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_VALIDATION_TOKEN || github.token }}
|
||||
TRIGGERING_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
VERSION: ${{ needs.build-check.outputs.version }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p certification
|
||||
run_id="$TRIGGERING_RUN_ID"
|
||||
if [[ -z "$run_id" ]]; then
|
||||
run_id="$(gh api --method GET \
|
||||
"repos/${GITHUB_REPOSITORY}/actions/workflows/build.yml/runs" \
|
||||
-f branch="$VERSION" -f status=success -F per_page=1 \
|
||||
--jq '.workflow_runs[0].id // empty' || true)"
|
||||
fi
|
||||
gh run download "$run_id" -n release-certification -D certification
|
||||
|
||||
STAGING_REF="${{ env.REGISTRY_DOCKERHUB }}:staging-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}${{ matrix.suffix }}"
|
||||
case "${{ matrix.variant }}" in
|
||||
musl) libc="musl" ;;
|
||||
glibc) libc="gnu" ;;
|
||||
esac
|
||||
for platform in linux/amd64 linux/arm64; do
|
||||
case "$platform" in
|
||||
linux/amd64) arch_sub="x86_64" ;;
|
||||
linux/arm64) arch_sub="aarch64" ;;
|
||||
esac
|
||||
docker pull --platform "$platform" "$STAGING_REF"
|
||||
container="$(docker create --platform "$platform" "$STAGING_REF")"
|
||||
docker cp "$container:/usr/bin/rustfs" "$RUNNER_TEMP/staged-rustfs"
|
||||
docker rm "$container" >/dev/null
|
||||
actual="$(sha256sum "$RUNNER_TEMP/staged-rustfs" | awk '{print $1}')"
|
||||
expected="$(jq -r --arg needle "linux-${arch_sub}-${libc}" \
|
||||
'[.deliverables[] | select(.name | contains($needle)) | .binary_sha256][0] // empty' \
|
||||
certification/candidate.json)"
|
||||
if [[ -z "$expected" || "$actual" != "$expected" ]]; then
|
||||
echo "Staged image binary for ${platform} does not match the certified binary digest" >&2
|
||||
exit 1
|
||||
fi
|
||||
python3 scripts/release/release_gate.py verify-promotion \
|
||||
--plan .config/release-validation-plan.json \
|
||||
--candidate certification/candidate.json \
|
||||
--summary certification/release-gate-output \
|
||||
--artifact "$RUNNER_TEMP/staged-rustfs" \
|
||||
--artifact-role binary
|
||||
done
|
||||
|
||||
- name: Promote staged image to official tags
|
||||
if: needs.build-check.outputs.should_push == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
STAGING_REF="${{ env.REGISTRY_DOCKERHUB }}:staging-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}${{ matrix.suffix }}"
|
||||
IFS=',' read -r -a official_tags <<< "${{ steps.meta.outputs.tags }}"
|
||||
for tag in "${official_tags[@]}"; do
|
||||
docker buildx imagetools create -t "$tag" "$STAGING_REF"
|
||||
echo "Promoted $tag -> $STAGING_REF"
|
||||
done
|
||||
|
||||
# Downstream consumers (Helm) verify this record instead of inheriting
|
||||
# an unverified wrapper's conclusion.
|
||||
- name: Record image promotion
|
||||
if: needs.build-check.outputs.should_push == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_VALIDATION_TOKEN || github.token }}
|
||||
TRIGGERING_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
VERSION: ${{ needs.build-check.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p certification
|
||||
run_id="$TRIGGERING_RUN_ID"
|
||||
if [[ -z "$run_id" ]]; then
|
||||
run_id="$(gh api --method GET \
|
||||
"repos/${GITHUB_REPOSITORY}/actions/workflows/build.yml/runs" \
|
||||
-f branch="$VERSION" -f status=success -F per_page=1 \
|
||||
--jq '.workflow_runs[0].id // empty' || true)"
|
||||
fi
|
||||
gh run download "$run_id" -n release-certification -D certification
|
||||
STAGING_REF="${{ env.REGISTRY_DOCKERHUB }}:staging-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}${{ matrix.suffix }}"
|
||||
digest="$(docker buildx imagetools inspect "$STAGING_REF" --format '{{json .Manifest}}' | jq -r '.digest')"
|
||||
candidate_sha="$(python3 -c "import json,hashlib,sys; sys.path.insert(0,'scripts/release'); import release_gate; print(release_gate.canonical_sha256(json.load(open('certification/candidate.json'))))")"
|
||||
jq -n \
|
||||
--arg version "$VERSION" \
|
||||
--arg digest "$digest" \
|
||||
--arg staging_ref "$STAGING_REF" \
|
||||
--arg candidate_sha256 "$candidate_sha" \
|
||||
--arg tags "${{ steps.meta.outputs.tags }}" \
|
||||
--slurpfile candidate certification/candidate.json \
|
||||
'{schema_version: 1, version: $version, image_digest: $digest, staging_ref: $staging_ref,
|
||||
promoted_tags: ($tags | split(",")), candidate_sha256: $candidate_sha256,
|
||||
binary_sha256s: ($candidate[0].deliverables | map(.binary_sha256) | unique)}' \
|
||||
> image-promotion-record.json
|
||||
|
||||
- name: Upload image promotion record
|
||||
if: needs.build-check.outputs.should_push == 'true'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: image-promotion-record${{ matrix.suffix }}
|
||||
path: image-promotion-record.json
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
|
||||
# Note: Manifest creation is no longer needed as we only build one variant
|
||||
# Multi-arch manifests are automatically created by docker/build-push-action
|
||||
|
||||
|
||||
@@ -30,102 +30,9 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# Consume the same trusted release certification as every other publication
|
||||
# exit and verify the image promotion record of the Docker workflow. The
|
||||
# chart wraps no binary of its own, so it may only reference an image whose
|
||||
# platform verification already passed; it must not inherit an unverified
|
||||
# package's conclusion. A run without that evidence fails closed here and
|
||||
# never reaches the chart repository.
|
||||
release-certification:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.raw_tag }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Normalize release version
|
||||
id: version
|
||||
env:
|
||||
RAW_INPUT: ${{ github.event.inputs.version }}
|
||||
RAW_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
RAW="$RAW_INPUT"
|
||||
else
|
||||
RAW="$RAW_BRANCH"
|
||||
fi
|
||||
case "$RAW" in
|
||||
refs/tags/*) RAW_TAG="${RAW#refs/tags/}" ;;
|
||||
*) RAW_TAG="$RAW" ;;
|
||||
esac
|
||||
echo "raw_tag=$RAW_TAG" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Download and verify release certification and image record
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_VALIDATION_TOKEN || github.token }}
|
||||
TRIGGERING_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
RAW_TAG: ${{ steps.version.outputs.raw_tag }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p certification image-record
|
||||
|
||||
build_run="$TRIGGERING_RUN_ID"
|
||||
if [[ -z "$build_run" ]]; then
|
||||
build_run="$(gh api --method GET \
|
||||
"repos/${GITHUB_REPOSITORY}/actions/workflows/build.yml/runs" \
|
||||
-f branch="$RAW_TAG" -f status=success -F per_page=1 \
|
||||
--jq '.workflow_runs[0].id // empty' || true)"
|
||||
fi
|
||||
if [[ -z "$build_run" ]]; then
|
||||
echo "No certified Build and Release run found for ${RAW_TAG}; failing closed" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! gh run download "$build_run" -n release-certification -D certification; then
|
||||
echo "Build run ${build_run} published no release-certification artifact; failing closed" >&2
|
||||
exit 1
|
||||
fi
|
||||
python3 scripts/release/release_gate.py verify-promotion \
|
||||
--plan .config/release-validation-plan.json \
|
||||
--candidate certification/candidate.json \
|
||||
--summary certification/release-gate-output \
|
||||
--artifact-sha256 "$(jq -r '.deliverables[0].sha256' certification/candidate.json)" \
|
||||
--artifact-name "$(jq -r '.deliverables[0].name' certification/candidate.json)"
|
||||
|
||||
docker_run="$(gh api --method GET \
|
||||
"repos/${GITHUB_REPOSITORY}/actions/workflows/docker.yml/runs" \
|
||||
-f branch="$RAW_TAG" -f status=success -F per_page=1 \
|
||||
--jq '.workflow_runs[0].id // empty' || true)"
|
||||
if [[ -z "$docker_run" ]] || ! gh run download "$docker_run" -n image-promotion-record -D image-record; then
|
||||
echo "No image promotion record for ${RAW_TAG}; a chart may not wrap an unverified image" >&2
|
||||
exit 1
|
||||
fi
|
||||
candidate_sha="$(python3 -c "import json,hashlib,sys; sys.path.insert(0,'scripts/release'); import release_gate; print(release_gate.canonical_sha256(json.load(open('certification/candidate.json'))))")"
|
||||
record_sha="$(jq -r '.candidate_sha256 // empty' image-record/image-promotion-record.json)"
|
||||
if [[ "$record_sha" != "$candidate_sha" ]]; then
|
||||
echo "Image promotion record does not belong to this certification" >&2
|
||||
exit 1
|
||||
fi
|
||||
overlap="$(jq -r --slurpfile candidate certification/candidate.json \
|
||||
'[.binary_sha256s[]] - ($candidate[0].deliverables | map(.binary_sha256)) | length' \
|
||||
image-record/image-promotion-record.json)"
|
||||
if [[ "$overlap" -ne 0 ]]; then
|
||||
echo "Image promotion record carries binary digests outside the certification" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
build-helm-package:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
needs: [ release-certification ]
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' && !contains(github.event.inputs.version, '-preview')) ||
|
||||
(
|
||||
|
||||
+14
-210
@@ -225,57 +225,10 @@ jobs:
|
||||
echo " Build run ID: $BUILD_RUN_ID"
|
||||
echo " Build run number: $RUN_NUMBER"
|
||||
|
||||
# Consume the trusted release certification of the resolved build run and
|
||||
# verify the certified input digests before any package is wrapped. A run
|
||||
# without that certification fails closed here; packages built later must
|
||||
# complete their own verification and cannot inherit an unverified
|
||||
# conclusion.
|
||||
release-certification:
|
||||
name: Release Certification
|
||||
needs: resolve
|
||||
if: needs.resolve.result == 'success'
|
||||
runs-on: ubuntu-latest # gh CLI required; the sm-standard-2 fleet does not ship it
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download and verify release certification
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_VALIDATION_TOKEN || github.token }}
|
||||
BUILD_RUN_ID: ${{ needs.resolve.outputs.build_run_id }}
|
||||
BUILD_TYPE: ${{ needs.resolve.outputs.build_type }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p certification
|
||||
if [[ "$BUILD_TYPE" == "development" ]]; then
|
||||
# Development artifacts are explicitly uncertified and never touch
|
||||
# official or latest channels; only the staging/dev paths remain.
|
||||
jq -n '{schema_version: 1, certified: false, reason: "development artifacts are explicitly uncertified"}' \
|
||||
> certification/development-uncertified.json
|
||||
exit 0
|
||||
fi
|
||||
if ! gh run download "$BUILD_RUN_ID" -n release-certification -D certification; then
|
||||
echo "Build run ${BUILD_RUN_ID} published no release-certification artifact; failing closed" >&2
|
||||
exit 1
|
||||
fi
|
||||
python3 scripts/release/release_gate.py verify-promotion \
|
||||
--plan .config/release-validation-plan.json \
|
||||
--candidate certification/candidate.json \
|
||||
--summary certification/release-gate-output \
|
||||
--artifact-sha256 "$(jq -r '.deliverables[0].sha256' certification/candidate.json)" \
|
||||
--artifact-name "$(jq -r '.deliverables[0].name' certification/candidate.json)"
|
||||
|
||||
# Build DEB and RPM packages for each architecture and libc variant
|
||||
package:
|
||||
name: Package (${{ matrix.arch }}-${{ matrix.libc }})
|
||||
needs: [ resolve, release-certification ]
|
||||
needs: resolve
|
||||
runs-on: ubuntu-latest # gh CLI required; the sm-standard-2 fleet does not ship it
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
@@ -541,55 +494,6 @@ jobs:
|
||||
echo "rpm_file=$RPM_FILE" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ RPM built: $RPM_FILE"
|
||||
|
||||
- name: Download certification evidence
|
||||
if: needs.resolve.outputs.build_type != 'development'
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: release-certification
|
||||
path: ./certification
|
||||
|
||||
# Later-built wrapper verification: the binary inside each package must
|
||||
# match the certified binary digest for this platform before anything is
|
||||
# uploaded, and the package itself stays on staging until promotion.
|
||||
- name: Verify wrapped binary against certification
|
||||
if: needs.resolve.outputs.build_type != 'development'
|
||||
shell: bash
|
||||
env:
|
||||
DEB_FILE: ${{ steps.deb.outputs.deb_file }}
|
||||
RPM_FILE: ${{ steps.rpm.outputs.rpm_file }}
|
||||
ARTIFACT_NAME: ${{ matrix.artifact_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
expected="$(jq -r --arg needle "$ARTIFACT_NAME" \
|
||||
'[.deliverables[] | select(.name | contains($needle)) | .binary_sha256][0] // empty' \
|
||||
certification/candidate.json)"
|
||||
if [[ -z "$expected" ]]; then
|
||||
echo "No certified binary digest recorded for ${ARTIFACT_NAME}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v rpm2cpio >/dev/null 2>&1; then
|
||||
sudo apt-get update && sudo apt-get install -y rpm2cpio
|
||||
fi
|
||||
if ! command -v cpio >/dev/null 2>&1; then
|
||||
sudo apt-get update && sudo apt-get install -y cpio
|
||||
fi
|
||||
for package_file in "$DEB_FILE" "$RPM_FILE"; do
|
||||
case "$package_file" in
|
||||
*.deb)
|
||||
dpkg-deb --fsys-tarfile "$package_file" | tar -xOf - ./usr/bin/rustfs > "$RUNNER_TEMP/wrapped-rustfs"
|
||||
;;
|
||||
*.rpm)
|
||||
rpm2cpio "$package_file" | cpio -i --to-stdout ./usr/bin/rustfs > "$RUNNER_TEMP/wrapped-rustfs"
|
||||
;;
|
||||
esac
|
||||
python3 scripts/release/release_gate.py verify-promotion \
|
||||
--plan .config/release-validation-plan.json \
|
||||
--candidate certification/candidate.json \
|
||||
--summary certification/release-gate-output \
|
||||
--artifact "$RUNNER_TEMP/wrapped-rustfs" \
|
||||
--artifact-role binary
|
||||
done
|
||||
|
||||
- name: Upload packages to artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
@@ -627,14 +531,10 @@ jobs:
|
||||
export AWS_SECRET_ACCESS_KEY="$R2_SECRET_ACCESS_KEY"
|
||||
export AWS_DEFAULT_REGION="auto"
|
||||
|
||||
# Build-phase writes stay on staging for release types: the official
|
||||
# packages/release and packages/latest paths are written only by the
|
||||
# certification-gated promote-packages job, from these same staged
|
||||
# objects. Development packages keep the uncertified dev channel.
|
||||
if [[ "$BUILD_TYPE" == "development" ]]; then
|
||||
R2_PREFIX="artifacts/rustfs/packages/dev"
|
||||
else
|
||||
R2_PREFIX="artifacts/rustfs/packages/staging/${GITHUB_RUN_ID}/${GITHUB_RUN_ATTEMPT}"
|
||||
R2_PREFIX="artifacts/rustfs/packages/release"
|
||||
fi
|
||||
R2_PATH="s3://${R2_BUCKET}/${R2_PREFIX}/"
|
||||
|
||||
@@ -649,6 +549,18 @@ jobs:
|
||||
|
||||
echo "✅ Upload complete"
|
||||
|
||||
# Also upload as latest for release/prerelease
|
||||
if [[ "$BUILD_TYPE" == "release" || "$BUILD_TYPE" == "prerelease" ]]; then
|
||||
LATEST_PATH="s3://${R2_BUCKET}/artifacts/rustfs/packages/latest/"
|
||||
for f in "$DEB_FILE" "$RPM_FILE"; do
|
||||
if [[ -n "$f" && -f "$f" ]]; then
|
||||
echo "Uploading latest: $(basename "$f")"
|
||||
aws s3 cp "$f" "$LATEST_PATH" --endpoint-url "$R2_ENDPOINT" --only-show-errors
|
||||
fi
|
||||
done
|
||||
echo "✅ Latest packages updated"
|
||||
fi
|
||||
|
||||
- name: Upload packages to GitHub Release
|
||||
if: needs.resolve.outputs.tag != ''
|
||||
env:
|
||||
@@ -702,114 +614,6 @@ jobs:
|
||||
|
||||
echo "✅ GitHub release assets updated"
|
||||
|
||||
# The only writer of the official packages/release and packages/latest R2
|
||||
# paths. Promotion re-checks each package's wrapped binary against the
|
||||
# passed certification list and copies the staged objects byte-for-byte:
|
||||
# no rebuild-after-verify, no mutable latest resolution downstream.
|
||||
promote-packages:
|
||||
name: Promote Packages
|
||||
needs: [ resolve, package, release-certification ]
|
||||
# Target-channel lock for the official package paths.
|
||||
concurrency:
|
||||
group: release-channel-r2-packages
|
||||
cancel-in-progress: false
|
||||
if: needs.resolve.outputs.tag != '' && (needs.resolve.outputs.build_type == 'release' || needs.resolve.outputs.build_type == 'prerelease')
|
||||
runs-on: ubuntu-latest # gh CLI required; the sm-standard-2 fleet does not ship it
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
env:
|
||||
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
||||
R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
|
||||
R2_BUCKET: ${{ secrets.R2_BUCKET }}
|
||||
AWS_EC2_METADATA_DISABLED: true
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download packages and certification
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: ./packages
|
||||
pattern: packages-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download certification evidence
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: release-certification
|
||||
path: ./certification
|
||||
|
||||
- name: Verify and promote staged packages
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "$R2_ACCESS_KEY_ID" || -z "$R2_SECRET_ACCESS_KEY" || -z "$R2_ENDPOINT" || -z "$R2_BUCKET" ]]; then
|
||||
echo "❌ R2 credentials missing; refusing to promote to the official channel" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STAGING_PREFIX="s3://${R2_BUCKET}/artifacts/rustfs/packages/staging/${GITHUB_RUN_ID}/${GITHUB_RUN_ATTEMPT}/"
|
||||
RELEASE_PATH="s3://${R2_BUCKET}/artifacts/rustfs/packages/release/"
|
||||
LATEST_PATH="s3://${R2_BUCKET}/artifacts/rustfs/packages/latest/"
|
||||
|
||||
if ! command -v aws >/dev/null 2>&1; then
|
||||
sudo apt-get update && sudo apt-get install -y awscli
|
||||
fi
|
||||
export AWS_ACCESS_KEY_ID="$R2_ACCESS_KEY_ID"
|
||||
export AWS_SECRET_ACCESS_KEY="$R2_SECRET_ACCESS_KEY"
|
||||
export AWS_DEFAULT_REGION="auto"
|
||||
|
||||
if ! command -v rpm2cpio >/dev/null 2>&1; then
|
||||
sudo apt-get install -y rpm2cpio
|
||||
fi
|
||||
if ! command -v cpio >/dev/null 2>&1; then
|
||||
sudo apt-get install -y cpio
|
||||
fi
|
||||
|
||||
promoted=0
|
||||
for package_file in packages/*.deb packages/*.rpm; do
|
||||
[[ -f "$package_file" ]] || continue
|
||||
name="$(basename "$package_file")"
|
||||
case "$package_file" in
|
||||
*.deb)
|
||||
dpkg-deb --fsys-tarfile "$package_file" | tar -xOf - ./usr/bin/rustfs > "$RUNNER_TEMP/wrapped-rustfs"
|
||||
;;
|
||||
*.rpm)
|
||||
rpm2cpio "$package_file" | cpio -i --to-stdout ./usr/bin/rustfs > "$RUNNER_TEMP/wrapped-rustfs"
|
||||
;;
|
||||
esac
|
||||
python3 scripts/release/release_gate.py verify-promotion \
|
||||
--plan .config/release-validation-plan.json \
|
||||
--candidate certification/candidate.json \
|
||||
--summary certification/release-gate-output \
|
||||
--artifact "$RUNNER_TEMP/wrapped-rustfs" \
|
||||
--artifact-role binary
|
||||
|
||||
staged="$(mktemp)"
|
||||
aws s3 cp "${STAGING_PREFIX}${name}" "$staged" --endpoint-url "$R2_ENDPOINT" --only-show-errors
|
||||
staged_sha="$(sha256sum "$staged" | awk '{print $1}')"
|
||||
built_sha="$(sha256sum "$package_file" | awk '{print $1}')"
|
||||
if [[ "$staged_sha" != "$built_sha" ]]; then
|
||||
echo "❌ Staged package ${name} does not match the verified bytes" >&2
|
||||
exit 1
|
||||
fi
|
||||
aws s3 cp "$staged" "${RELEASE_PATH}${name}" --endpoint-url "$R2_ENDPOINT" --only-show-errors
|
||||
aws s3 cp "$staged" "${LATEST_PATH}${name}" --endpoint-url "$R2_ENDPOINT" --only-show-errors
|
||||
rm -f "$staged"
|
||||
promoted=$((promoted + 1))
|
||||
echo "✅ Promoted ${name} (${built_sha})"
|
||||
done
|
||||
if [[ "$promoted" -eq 0 ]]; then
|
||||
echo "No staged packages found to promote" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Summary
|
||||
summary:
|
||||
name: Summary
|
||||
|
||||
Generated
+52
-52
@@ -4136,7 +4136,7 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "e2e_test"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
@@ -9520,7 +9520,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
@@ -9680,7 +9680,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-audit"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"const-str",
|
||||
"futures",
|
||||
@@ -9702,7 +9702,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-checksums"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"base64-simd",
|
||||
"bytes",
|
||||
@@ -9718,7 +9718,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-common"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"hotpath",
|
||||
@@ -9733,7 +9733,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-concurrency"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"hotpath",
|
||||
"insta",
|
||||
@@ -9746,7 +9746,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-config"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"const-str",
|
||||
"hotpath",
|
||||
@@ -9756,7 +9756,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-connect-symbol-catalog"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"hex-simd",
|
||||
"object 0.40.0",
|
||||
@@ -9768,7 +9768,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-credentials"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"base64-simd",
|
||||
"hmac 0.13.0",
|
||||
@@ -9782,7 +9782,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-crypto"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"argon2",
|
||||
@@ -9803,7 +9803,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-data-usage"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"hotpath",
|
||||
"rmp-serde",
|
||||
@@ -9813,7 +9813,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-ecstore"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"async-channel",
|
||||
@@ -9951,7 +9951,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-extension-schema"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"hotpath",
|
||||
"serde",
|
||||
@@ -9961,7 +9961,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-filemeta"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"base64-simd",
|
||||
@@ -9990,7 +9990,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-heal"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64-simd",
|
||||
@@ -10031,7 +10031,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-heal-contracts"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -10041,7 +10041,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-iam"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"async-trait",
|
||||
@@ -10091,7 +10091,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-io-core"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hotpath",
|
||||
@@ -10104,7 +10104,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-io-metrics"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"hotpath",
|
||||
@@ -10168,7 +10168,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-keystone"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
@@ -10194,7 +10194,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-kms"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
@@ -10244,14 +10244,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-license"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-lifecycle"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"hotpath",
|
||||
@@ -10273,7 +10273,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-lock"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"compact_str",
|
||||
@@ -10297,7 +10297,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-log-analyzer"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"flate2",
|
||||
@@ -10316,7 +10316,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-madmin"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"hotpath",
|
||||
"http 1.5.0",
|
||||
@@ -10354,7 +10354,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-notify"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"async-trait",
|
||||
@@ -10389,7 +10389,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-object-capacity"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"futures",
|
||||
@@ -10408,7 +10408,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-object-data-cache"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"criterion",
|
||||
@@ -10425,7 +10425,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-obs"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossbeam-channel",
|
||||
@@ -10488,7 +10488,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-policy"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64-simd",
|
||||
@@ -10519,7 +10519,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-protocols"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"async-trait",
|
||||
@@ -10582,7 +10582,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-protos"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"flatbuffers",
|
||||
"hotpath",
|
||||
@@ -10607,7 +10607,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-replication"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
@@ -10625,7 +10625,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-rio"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"arc-swap",
|
||||
@@ -10671,7 +10671,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-rio-v2"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"bytes",
|
||||
@@ -10694,7 +10694,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-s3-client"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"base64-simd",
|
||||
"bytes",
|
||||
@@ -10738,7 +10738,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-s3-ops"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"hotpath",
|
||||
"rustfs-s3-types",
|
||||
@@ -10746,7 +10746,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-s3-types"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"hotpath",
|
||||
"serde",
|
||||
@@ -10755,7 +10755,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-s3select-api"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"async-compression",
|
||||
@@ -10796,7 +10796,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-s3select-query"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@@ -10818,7 +10818,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-scanner"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@@ -10869,7 +10869,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-scanner-metrics"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"jiff",
|
||||
@@ -10884,7 +10884,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-security-governance"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"hotpath",
|
||||
"thiserror 2.0.21",
|
||||
@@ -10892,7 +10892,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-signer"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"base64-simd",
|
||||
"bytes",
|
||||
@@ -10911,7 +10911,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-storage-api"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"hotpath",
|
||||
@@ -10926,7 +10926,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-targets"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"async-nats",
|
||||
@@ -10980,7 +10980,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-test-utils"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"hotpath",
|
||||
"rustfs-data-usage",
|
||||
@@ -10996,7 +10996,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-tls-runtime"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"hotpath",
|
||||
@@ -11035,7 +11035,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-trusted-proxies"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
@@ -11072,7 +11072,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-utils"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"base64-simd",
|
||||
@@ -11115,7 +11115,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfs-zip"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"futures",
|
||||
|
||||
+51
-51
@@ -75,7 +75,7 @@ edition = "2024"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/rustfs/rustfs"
|
||||
rust-version = "1.99.0"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
homepage = "https://rustfs.com"
|
||||
description = "RustFS is a high-performance distributed object storage software built using Rust, one of the most popular languages worldwide. "
|
||||
keywords = ["RustFS", "Minio", "object-storage", "filesystem", "s3"]
|
||||
@@ -92,56 +92,56 @@ redundant_clone = "warn"
|
||||
|
||||
[workspace.dependencies]
|
||||
# RustFS Internal Crates
|
||||
rustfs = { path = "./rustfs", version = "1.0.2" }
|
||||
rustfs-heal = { path = "crates/heal", version = "1.0.2" }
|
||||
rustfs-heal-contracts = { path = "crates/heal-contracts", version = "1.0.2" }
|
||||
rustfs-scanner-metrics = { path = "crates/scanner-metrics", version = "1.0.2" }
|
||||
rustfs-audit = { path = "crates/audit", version = "1.0.2" }
|
||||
rustfs-checksums = { path = "crates/checksums", version = "1.0.2" }
|
||||
rustfs-common = { path = "crates/common", version = "1.0.2" }
|
||||
rustfs-data-usage = { path = "crates/data-usage", version = "1.0.2" }
|
||||
rustfs-config = { path = "./crates/config", version = "1.0.2" }
|
||||
rustfs-concurrency = { path = "./crates/concurrency", version = "1.0.2" }
|
||||
rustfs-credentials = { path = "crates/credentials", version = "1.0.2" }
|
||||
rustfs-crypto = { path = "crates/crypto", version = "1.0.2" }
|
||||
rustfs-ecstore = { path = "crates/ecstore", version = "1.0.2" }
|
||||
rustfs-filemeta = { path = "crates/filemeta", version = "1.0.2" }
|
||||
rustfs-iam = { path = "crates/iam", version = "1.0.2" }
|
||||
rustfs-keystone = { path = "crates/keystone", version = "1.0.2" }
|
||||
rustfs-license = { path = "crates/license", version = "1.0.2" }
|
||||
rustfs-lifecycle = { path = "crates/lifecycle", version = "1.0.2" }
|
||||
rustfs-kms = { path = "crates/kms", version = "1.0.2" }
|
||||
rustfs-lock = { path = "crates/lock", version = "1.0.2" }
|
||||
rustfs-madmin = { path = "crates/madmin", version = "1.0.2" }
|
||||
rustfs-notify = { path = "crates/notify", version = "1.0.2" }
|
||||
rustfs-io-metrics = { path = "crates/io-metrics", version = "1.0.2" }
|
||||
rustfs-io-core = { path = "crates/io-core", version = "1.0.2" }
|
||||
rustfs-object-capacity = { path = "crates/object-capacity", version = "1.0.2" }
|
||||
rustfs-object-data-cache = { path = "crates/object-data-cache", version = "1.0.2", default-features = false }
|
||||
rustfs-log-analyzer = { path = "crates/log-analyzer", version = "1.0.2" }
|
||||
rustfs-obs = { path = "crates/obs", version = "1.0.2" }
|
||||
rustfs-policy = { path = "crates/policy", version = "1.0.2" }
|
||||
rustfs-protos = { path = "crates/protos", version = "1.0.2" }
|
||||
rustfs-protocols = { path = "crates/protocols", version = "1.0.2" }
|
||||
rustfs-replication = { path = "crates/replication", version = "1.0.2" }
|
||||
rustfs-rio = { path = "crates/rio", version = "1.0.2" }
|
||||
rustfs-rio-v2 = { path = "crates/rio-v2", version = "1.0.2" }
|
||||
rustfs-s3-client = { path = "crates/s3-client", version = "1.0.2" }
|
||||
rustfs-s3-types = { path = "crates/s3-types", version = "1.0.2" }
|
||||
rustfs-s3-ops = { path = "crates/s3-ops", version = "1.0.2" }
|
||||
rustfs-s3select-api = { path = "crates/s3select-api", version = "1.0.2" }
|
||||
rustfs-s3select-query = { path = "crates/s3select-query", version = "1.0.2" }
|
||||
rustfs-scanner = { path = "crates/scanner", version = "1.0.2" }
|
||||
rustfs-security-governance = { path = "crates/security-governance", version = "1.0.2" }
|
||||
rustfs-extension-schema = { path = "crates/extension-schema", version = "1.0.2" }
|
||||
rustfs-signer = { path = "crates/signer", version = "1.0.2" }
|
||||
rustfs-storage-api = { path = "crates/storage-api", version = "1.0.2" }
|
||||
rustfs-trusted-proxies = { path = "crates/trusted-proxies", version = "1.0.2" }
|
||||
rustfs-targets = { path = "crates/targets", version = "1.0.2" }
|
||||
rustfs-test-utils = { path = "crates/test-utils", version = "1.0.2" }
|
||||
rustfs-tls-runtime = { path = "crates/tls-runtime", version = "1.0.2" }
|
||||
rustfs-utils = { path = "crates/utils", version = "1.0.2" }
|
||||
rustfs-zip = { path = "./crates/zip", version = "1.0.2" }
|
||||
rustfs = { path = "./rustfs", version = "1.0.5" }
|
||||
rustfs-heal = { path = "crates/heal", version = "1.0.5" }
|
||||
rustfs-heal-contracts = { path = "crates/heal-contracts", version = "1.0.5" }
|
||||
rustfs-scanner-metrics = { path = "crates/scanner-metrics", version = "1.0.5" }
|
||||
rustfs-audit = { path = "crates/audit", version = "1.0.5" }
|
||||
rustfs-checksums = { path = "crates/checksums", version = "1.0.5" }
|
||||
rustfs-common = { path = "crates/common", version = "1.0.5" }
|
||||
rustfs-data-usage = { path = "crates/data-usage", version = "1.0.5" }
|
||||
rustfs-config = { path = "./crates/config", version = "1.0.5" }
|
||||
rustfs-concurrency = { path = "./crates/concurrency", version = "1.0.5" }
|
||||
rustfs-credentials = { path = "crates/credentials", version = "1.0.5" }
|
||||
rustfs-crypto = { path = "crates/crypto", version = "1.0.5" }
|
||||
rustfs-ecstore = { path = "crates/ecstore", version = "1.0.5" }
|
||||
rustfs-filemeta = { path = "crates/filemeta", version = "1.0.5" }
|
||||
rustfs-iam = { path = "crates/iam", version = "1.0.5" }
|
||||
rustfs-keystone = { path = "crates/keystone", version = "1.0.5" }
|
||||
rustfs-license = { path = "crates/license", version = "1.0.5" }
|
||||
rustfs-lifecycle = { path = "crates/lifecycle", version = "1.0.5" }
|
||||
rustfs-kms = { path = "crates/kms", version = "1.0.5" }
|
||||
rustfs-lock = { path = "crates/lock", version = "1.0.5" }
|
||||
rustfs-madmin = { path = "crates/madmin", version = "1.0.5" }
|
||||
rustfs-notify = { path = "crates/notify", version = "1.0.5" }
|
||||
rustfs-io-metrics = { path = "crates/io-metrics", version = "1.0.5" }
|
||||
rustfs-io-core = { path = "crates/io-core", version = "1.0.5" }
|
||||
rustfs-object-capacity = { path = "crates/object-capacity", version = "1.0.5" }
|
||||
rustfs-object-data-cache = { path = "crates/object-data-cache", version = "1.0.5", default-features = false }
|
||||
rustfs-log-analyzer = { path = "crates/log-analyzer", version = "1.0.5" }
|
||||
rustfs-obs = { path = "crates/obs", version = "1.0.5" }
|
||||
rustfs-policy = { path = "crates/policy", version = "1.0.5" }
|
||||
rustfs-protos = { path = "crates/protos", version = "1.0.5" }
|
||||
rustfs-protocols = { path = "crates/protocols", version = "1.0.5" }
|
||||
rustfs-replication = { path = "crates/replication", version = "1.0.5" }
|
||||
rustfs-rio = { path = "crates/rio", version = "1.0.5" }
|
||||
rustfs-rio-v2 = { path = "crates/rio-v2", version = "1.0.5" }
|
||||
rustfs-s3-client = { path = "crates/s3-client", version = "1.0.5" }
|
||||
rustfs-s3-types = { path = "crates/s3-types", version = "1.0.5" }
|
||||
rustfs-s3-ops = { path = "crates/s3-ops", version = "1.0.5" }
|
||||
rustfs-s3select-api = { path = "crates/s3select-api", version = "1.0.5" }
|
||||
rustfs-s3select-query = { path = "crates/s3select-query", version = "1.0.5" }
|
||||
rustfs-scanner = { path = "crates/scanner", version = "1.0.5" }
|
||||
rustfs-security-governance = { path = "crates/security-governance", version = "1.0.5" }
|
||||
rustfs-extension-schema = { path = "crates/extension-schema", version = "1.0.5" }
|
||||
rustfs-signer = { path = "crates/signer", version = "1.0.5" }
|
||||
rustfs-storage-api = { path = "crates/storage-api", version = "1.0.5" }
|
||||
rustfs-trusted-proxies = { path = "crates/trusted-proxies", version = "1.0.5" }
|
||||
rustfs-targets = { path = "crates/targets", version = "1.0.5" }
|
||||
rustfs-test-utils = { path = "crates/test-utils", version = "1.0.5" }
|
||||
rustfs-tls-runtime = { path = "crates/tls-runtime", version = "1.0.5" }
|
||||
rustfs-utils = { path = "crates/utils", version = "1.0.5" }
|
||||
rustfs-zip = { path = "./crates/zip", version = "1.0.5" }
|
||||
|
||||
# Async Runtime and Networking
|
||||
async-channel = "2.5.0"
|
||||
|
||||
@@ -17700,6 +17700,16 @@ impl ECStore {
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-util")]
|
||||
pub async fn complete_scanner_pause_backlog_retirement_for_test(&self, source_pool_index: usize) -> Result<()> {
|
||||
// Native backlog fixtures use bounded capacity inputs rather than the
|
||||
// host filesystem's unrelated usage. Keep the real ILM completion gate.
|
||||
let run_token = self.durable_ilm_receipt_run_token(source_pool_index).await?;
|
||||
self.persist_decommission_durable_ilm_manifest_for_run(source_pool_index, &run_token)
|
||||
.await?;
|
||||
self.complete_decommission(source_pool_index).await
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-util")]
|
||||
pub async fn retire_scanner_pause_backlog_for_test(
|
||||
self: &Arc<Self>,
|
||||
|
||||
@@ -85,6 +85,7 @@ impl ScannerPauseBacklogRetirementPhase {
|
||||
pub struct ScannerPauseBacklogRetirementReplica {
|
||||
pub pool_index: usize,
|
||||
pub set_index: usize,
|
||||
pub set_id: Option<uuid::Uuid>,
|
||||
/// False for retiring members retained only to verify the old commit proof.
|
||||
pub writable: bool,
|
||||
pub data: Option<Vec<u8>>,
|
||||
@@ -140,6 +141,7 @@ async fn read_replica((set, writable): (Arc<SetDisks>, bool)) -> Result<ScannerP
|
||||
let mut replica = ScannerPauseBacklogRetirementReplica {
|
||||
pool_index: set.pool_index,
|
||||
set_index: set.set_index,
|
||||
set_id: Some(set.persistent_set_id()?),
|
||||
writable,
|
||||
data: None,
|
||||
};
|
||||
@@ -222,6 +224,7 @@ pub(crate) fn plan_scanner_pause_backlog_retirement(
|
||||
.map(|read| ScannerPauseBacklogRetirementReplica {
|
||||
pool_index: read.replica.pool_index,
|
||||
set_index: read.replica.set_index,
|
||||
set_id: read.replica.set_id,
|
||||
writable: read.replica.writable,
|
||||
data: read.replica.data.clone(),
|
||||
})
|
||||
|
||||
@@ -685,12 +685,8 @@ async fn clear_bucket_usage_memory(bucket: &str, guard: Option<&rustfs_lock::Nam
|
||||
}
|
||||
|
||||
pub async fn remove_bucket_usage_from_backend(store: Arc<ECStore>, bucket: &str) -> Result<(), Error> {
|
||||
remove_bucket_usage_for_namespace_change(store.as_ref(), bucket).await
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_bucket_usage_for_namespace_change(store: &ECStore, bucket: &str) -> Result<(), Error> {
|
||||
prepare_bucket_usage_for_namespace_change(bucket, None).await?;
|
||||
remove_bucket_usage_from_backend_with_guard_fenced(store, bucket, None).await
|
||||
remove_bucket_usage_with_owned_namespace(store, bucket, None).await
|
||||
}
|
||||
|
||||
pub(crate) async fn prepare_bucket_usage_for_namespace_change(
|
||||
@@ -730,11 +726,37 @@ where
|
||||
pub(crate) async fn remove_bucket_usage_from_backend_with_guard_fenced(
|
||||
store: &ECStore,
|
||||
bucket: &str,
|
||||
guard: Option<&rustfs_lock::NamespaceLockGuard>,
|
||||
guard: Option<&Arc<rustfs_lock::NamespaceLockGuard>>,
|
||||
) -> Result<(), Error> {
|
||||
let result = remove_bucket_usage_from_backend_with_store_and_guard_and_publication(store, bucket, guard, Some(store)).await;
|
||||
invalidate_bucket_usage_snapshot_caches(guard, bucket).await?;
|
||||
result
|
||||
let owned_store = crate::bucket::metadata_sys::object_store_in(&store.ctx).await?;
|
||||
if !std::ptr::eq(store, owned_store.as_ref()) {
|
||||
return Err(Error::other("usage cleanup requires its own metadata store"));
|
||||
}
|
||||
remove_bucket_usage_with_owned_namespace(owned_store, bucket, guard.cloned()).await
|
||||
}
|
||||
|
||||
async fn remove_bucket_usage_with_owned_namespace(
|
||||
store: Arc<ECStore>,
|
||||
bucket: &str,
|
||||
guard: Option<Arc<rustfs_lock::NamespaceLockGuard>>,
|
||||
) -> Result<(), Error> {
|
||||
let bucket = bucket.to_owned();
|
||||
let ctx = Arc::clone(&store.ctx);
|
||||
// Retain the bucket generation and movement/object guards through every
|
||||
// physical commit even when the namespace request stops waiting.
|
||||
ctx.run_detached_mutation(async move {
|
||||
let result = remove_bucket_usage_from_backend_with_store_and_guard_and_publication(
|
||||
store.as_ref(),
|
||||
&bucket,
|
||||
guard.as_deref(),
|
||||
Some(store.as_ref()),
|
||||
)
|
||||
.await;
|
||||
invalidate_bucket_usage_snapshot_caches(guard.as_deref(), &bucket).await?;
|
||||
result
|
||||
})
|
||||
.await
|
||||
.map_err(Error::from)?
|
||||
}
|
||||
|
||||
async fn invalidate_bucket_usage_snapshot_caches(
|
||||
@@ -849,9 +871,9 @@ where
|
||||
let primary_seed_epoch = match publication_store {
|
||||
Some(publication_store) => Some(
|
||||
publication_store
|
||||
.scanner_data_usage_publication_epoch()
|
||||
.data_usage_namespace_cleanup_epoch()
|
||||
.await
|
||||
.ok_or_else(|| Error::other("data usage publication is blocked by data movement"))?,
|
||||
.ok_or_else(|| Error::other("data usage namespace cleanup epoch is unavailable"))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
@@ -871,9 +893,9 @@ where
|
||||
let backup_seed_epoch = match publication_store {
|
||||
Some(publication_store) => Some(
|
||||
publication_store
|
||||
.scanner_data_usage_publication_epoch()
|
||||
.data_usage_namespace_cleanup_epoch()
|
||||
.await
|
||||
.ok_or_else(|| Error::other("data usage publication is blocked by data movement"))?,
|
||||
.ok_or_else(|| Error::other("data usage namespace cleanup epoch is unavailable"))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
@@ -985,9 +1007,9 @@ where
|
||||
let read_epoch = match publication {
|
||||
Some((publication_store, expected_publication_epoch)) => {
|
||||
let epoch = publication_store
|
||||
.scanner_data_usage_publication_epoch()
|
||||
.data_usage_namespace_cleanup_epoch()
|
||||
.await
|
||||
.ok_or_else(|| Error::other("data usage publication is blocked by data movement"))?;
|
||||
.ok_or_else(|| Error::other("data usage namespace cleanup epoch is unavailable"))?;
|
||||
if expected_publication_epoch.is_some_and(|expected| expected != epoch) {
|
||||
return Err(Error::other("data usage publication epoch changed before snapshot read"));
|
||||
}
|
||||
@@ -1022,9 +1044,8 @@ where
|
||||
ensure_bucket_namespace_guard(guard, bucket, "data usage snapshot commit")?;
|
||||
let publication_guard = match publication {
|
||||
Some((publication_store, expected_publication_epoch)) => {
|
||||
let Some((guard, publication_epoch)) = publication_store.scanner_data_usage_publication_admission_guard().await
|
||||
else {
|
||||
return Err(Error::other("data usage publication is blocked by data movement"));
|
||||
let Some((guard, publication_epoch)) = publication_store.data_usage_namespace_cleanup_guard().await else {
|
||||
return Err(Error::other("data usage namespace cleanup epoch is unavailable"));
|
||||
};
|
||||
if expected_publication_epoch
|
||||
.or(read_epoch)
|
||||
@@ -1036,18 +1057,27 @@ where
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let save_result = store
|
||||
.put_object(
|
||||
RUSTFS_META_BUCKET,
|
||||
object,
|
||||
&mut put_data,
|
||||
&ObjectOptions {
|
||||
max_parity: true,
|
||||
http_preconditions: Some(http_preconditions),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let save_result = match publication {
|
||||
Some((publication_store, _)) => {
|
||||
publication_store
|
||||
.put_usage_snapshot_for_namespace_cleanup(object, revision.as_deref(), &mut put_data, guard)
|
||||
.await
|
||||
}
|
||||
None => {
|
||||
store
|
||||
.put_object(
|
||||
RUSTFS_META_BUCKET,
|
||||
object,
|
||||
&mut put_data,
|
||||
&ObjectOptions {
|
||||
max_parity: true,
|
||||
http_preconditions: Some(http_preconditions),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
};
|
||||
drop(publication_guard);
|
||||
match save_result.map_err(|err| crate::config::com::map_system_metadata_write_error(err, object)) {
|
||||
Ok(_) => return Ok(()),
|
||||
|
||||
@@ -93,6 +93,7 @@ use super::{
|
||||
delete_tier_mutation_intent_record_if_current, list_tier_coordinator_mutation_intent_records,
|
||||
list_tier_mutation_intent_records, load_tier_coordinator_mutation_intent_record_with_etag,
|
||||
load_tier_mutation_intent_record_with_etag, save_tier_coordinator_mutation_intent_record_if_absent,
|
||||
save_tier_mutation_intent_record_if_current,
|
||||
},
|
||||
warm_backend::WarmBackendImpl,
|
||||
};
|
||||
@@ -620,7 +621,11 @@ struct CommittedMutationRecoveryPlan {
|
||||
|
||||
impl TierMutationRecoverySnapshot {
|
||||
fn requires_config_lock(&self) -> bool {
|
||||
self.has_committed_mutation_blocks || self.intents.iter().any(|recovered| recovered.has_coordinator_record)
|
||||
self.has_committed_mutation_blocks
|
||||
|| self.intents.iter().any(|recovered| {
|
||||
recovered.has_coordinator_record
|
||||
|| (recovered.has_peer_record && recovered.intent.state == TierMutationIntentState::Prepared)
|
||||
})
|
||||
}
|
||||
|
||||
fn allowance(&self) -> MutationBlockAllowance {
|
||||
@@ -5102,6 +5107,9 @@ impl TierConfigMgr {
|
||||
|
||||
Self::reconcile_terminal_dual_prefix_mutation_intents(api.clone(), &mut snapshot).await?;
|
||||
Self::recover_prepared_coordinator_mutation_intents(api.clone(), &mut snapshot.coordinator_intents).await?;
|
||||
if config_lock.is_some() {
|
||||
Self::recover_expired_peer_only_prepares(api.clone(), &snapshot).await?;
|
||||
}
|
||||
|
||||
snapshot = Self::load_and_reconcile_mutation_recovery_snapshot(handle, api.clone(), true).await?;
|
||||
if snapshot.requires_config_lock() && config_lock.is_none() {
|
||||
@@ -5502,6 +5510,58 @@ impl TierConfigMgr {
|
||||
}
|
||||
}
|
||||
|
||||
async fn recover_expired_peer_only_prepares<S>(api: Arc<S>, snapshot: &TierMutationRecoverySnapshot) -> io::Result<()>
|
||||
where
|
||||
S: EcstoreObjectIO,
|
||||
{
|
||||
let now = i64::try_from(OffsetDateTime::now_utc().unix_timestamp_nanos()).unwrap_or(i64::MAX);
|
||||
let orphaned = snapshot
|
||||
.intents
|
||||
.iter()
|
||||
.filter(|recovered| {
|
||||
recovered.has_peer_record
|
||||
&& !recovered.has_coordinator_record
|
||||
&& recovered.intent.state == TierMutationIntentState::Prepared
|
||||
&& Self::tier_mutation_tombstone_retention_elapsed(&recovered.intent, now)
|
||||
})
|
||||
.map(|recovered| recovered.intent.clone())
|
||||
.collect::<Vec<_>>();
|
||||
if orphaned.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// The caller holds the tier-config namespace lock. Only the exact
|
||||
// persisted candidate proves a commit; an old or different config
|
||||
// leaves the peer fence intact.
|
||||
let loaded = load_tier_config_for_recovery(api.clone()).await?;
|
||||
let Some(config_etag) = loaded.etag else {
|
||||
return Ok(());
|
||||
};
|
||||
for intent in orphaned {
|
||||
if loaded.persisted_digest != Some(intent.candidate_digest) {
|
||||
continue;
|
||||
}
|
||||
let _mutation_guard = acquire_tier_mutation_mutex(intent.mutation_id).await;
|
||||
let (mut current, record_etag) =
|
||||
match load_tier_mutation_intent_record_with_etag(api.clone(), intent.mutation_id).await {
|
||||
Ok(record) => record,
|
||||
Err(Error::ConfigNotFound) => continue,
|
||||
Err(err) => return Err(tier_mutation_replay_error(err)),
|
||||
};
|
||||
if current != intent {
|
||||
continue;
|
||||
}
|
||||
current
|
||||
.advance(TierMutationIntentState::Committed, Some(config_etag.clone()))
|
||||
.map_err(io::Error::other)?;
|
||||
match save_tier_mutation_intent_record_if_current(api.clone(), ¤t, &record_etag).await {
|
||||
Ok(()) | Err(Error::PreconditionFailed) => {}
|
||||
Err(err) => return Err(tier_mutation_replay_error(err)),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recover_prepared_coordinator_mutation_intents<S>(api: Arc<S>, intents: &mut [TierMutationIntent]) -> io::Result<()>
|
||||
where
|
||||
S: EcstoreObjectIO,
|
||||
@@ -13379,6 +13439,208 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reload_recovers_expired_peer_only_prepare_for_persisted_add() {
|
||||
use crate::services::tier::tier_mutation_intent::save_tier_mutation_intent_record;
|
||||
|
||||
let store = Arc::new(CasConfigStore::default());
|
||||
let previous = empty_mgr();
|
||||
let mut candidate = empty_mgr();
|
||||
candidate.tiers.insert("COLD-A".to_string(), build_rustfs_tier("COLD-A"));
|
||||
let targets = build_tier_mutation_affected_targets(
|
||||
TierMutationIntentKind::Add,
|
||||
HashSet::from(["COLD-A".to_string()]),
|
||||
&previous,
|
||||
&candidate,
|
||||
)
|
||||
.expect("added tier should have a target");
|
||||
let mut intent = build_coordinator_tier_mutation_intent(TierMutationIntentKind::Add, None, &candidate, targets)
|
||||
.expect("intent should build")
|
||||
.expect("add should require an intent");
|
||||
intent.expires_at_unix_nanos = 1;
|
||||
candidate
|
||||
.save_tiering_config_if_current(store.clone(), None)
|
||||
.await
|
||||
.expect("candidate should persist");
|
||||
save_tier_mutation_intent_record(store.clone(), &intent)
|
||||
.await
|
||||
.expect("peer prepare should persist without a coordinator record");
|
||||
|
||||
let manager = TierConfigMgr::new();
|
||||
TIER_MUTATION_TEST_PEERS
|
||||
.scope(Vec::new(), async {
|
||||
TIER_DRIVER_TEST_FACTORY
|
||||
.scope(healthy_driver_factory(), async {
|
||||
TierConfigMgr::reload_handle_with(&manager, store.clone())
|
||||
.await
|
||||
.expect("matching saved candidate should recover");
|
||||
TierConfigMgr::acquire_operation_lease(&manager, "COLD-A")
|
||||
.await
|
||||
.expect("recovered tier should be readable");
|
||||
})
|
||||
.await;
|
||||
})
|
||||
.await;
|
||||
assert!(
|
||||
TierConfigMgr::load_tier_mutation_intents(store)
|
||||
.await
|
||||
.expect("peer scan should succeed")
|
||||
.iter()
|
||||
.all(|record| record.state != TierMutationIntentState::Prepared),
|
||||
"the orphaned prepare must no longer block later reloads"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reload_keeps_peer_only_prepare_without_commit_proof() {
|
||||
use crate::services::tier::tier_mutation_intent::{
|
||||
save_tier_mutation_intent_record, tier_mutation_intent_record_object_name,
|
||||
};
|
||||
|
||||
for case in [
|
||||
"unexpired",
|
||||
"recently_expired",
|
||||
"different_candidate",
|
||||
"missing_config",
|
||||
"concurrent_change",
|
||||
] {
|
||||
let store = Arc::new(CasConfigStore::default());
|
||||
let previous = empty_mgr();
|
||||
let mut candidate = empty_mgr();
|
||||
candidate.tiers.insert("COLD-A".to_string(), build_rustfs_tier("COLD-A"));
|
||||
let targets = build_tier_mutation_affected_targets(
|
||||
TierMutationIntentKind::Add,
|
||||
HashSet::from(["COLD-A".to_string()]),
|
||||
&previous,
|
||||
&candidate,
|
||||
)
|
||||
.expect("added tier should have a target");
|
||||
let mut intent = build_coordinator_tier_mutation_intent(TierMutationIntentKind::Add, None, &candidate, targets)
|
||||
.expect("intent should build")
|
||||
.expect("add should require an intent");
|
||||
intent.expires_at_unix_nanos = match case {
|
||||
"unexpired" => tier_mutation_intent_expiry_unix_nanos(),
|
||||
"recently_expired" => {
|
||||
i64::try_from(OffsetDateTime::now_utc().unix_timestamp_nanos()).expect("test clock should fit")
|
||||
- 1_000_000_000
|
||||
}
|
||||
_ => 1,
|
||||
};
|
||||
if case == "different_candidate" {
|
||||
intent.candidate_digest = [0x51; 32];
|
||||
}
|
||||
if case != "missing_config" {
|
||||
candidate
|
||||
.save_tiering_config_if_current(store.clone(), None)
|
||||
.await
|
||||
.expect("candidate should persist");
|
||||
}
|
||||
save_tier_mutation_intent_record(store.clone(), &intent)
|
||||
.await
|
||||
.expect("peer prepare should persist");
|
||||
let expected = if case == "concurrent_change" {
|
||||
let mut changed = intent.clone();
|
||||
changed.candidate_digest = [0x52; 32];
|
||||
let object = tier_mutation_intent_record_object_name(intent.mutation_id).expect("record path should build");
|
||||
store
|
||||
.rewrite_on_next_if_match(object, changed.encode().expect("changed intent should encode"))
|
||||
.await;
|
||||
changed
|
||||
} else {
|
||||
intent
|
||||
};
|
||||
|
||||
let manager = TierConfigMgr::new();
|
||||
TIER_MUTATION_TEST_PEERS
|
||||
.scope(Vec::new(), async {
|
||||
TierConfigMgr::reload_handle_with(&manager, store.clone())
|
||||
.await
|
||||
.expect("unproven prepare should remain fenced");
|
||||
})
|
||||
.await;
|
||||
let error = match TierConfigMgr::acquire_operation_lease(&manager, "COLD-A").await {
|
||||
Ok(_) => panic!("{case}: unproven prepare must block reads"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert!(error.message.contains("being replaced"), "{case}: {error}");
|
||||
let records = TierConfigMgr::load_tier_mutation_intents(store)
|
||||
.await
|
||||
.expect("peer scan should succeed");
|
||||
assert_eq!(records, vec![expected], "{case}: prepared evidence must remain unchanged");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn peer_only_prepare_recovery_retains_fence_until_peer_commit_succeeds() {
|
||||
use crate::services::tier::tier_mutation_intent::save_tier_mutation_intent_record;
|
||||
|
||||
let store = Arc::new(CasConfigStore::default());
|
||||
let previous = empty_mgr();
|
||||
let mut candidate = empty_mgr();
|
||||
candidate.tiers.insert("COLD-A".to_string(), build_rustfs_tier("COLD-A"));
|
||||
let targets = build_tier_mutation_affected_targets(
|
||||
TierMutationIntentKind::Add,
|
||||
HashSet::from(["COLD-A".to_string()]),
|
||||
&previous,
|
||||
&candidate,
|
||||
)
|
||||
.expect("added tier should have a target");
|
||||
let mut intent = build_coordinator_tier_mutation_intent(TierMutationIntentKind::Add, None, &candidate, targets)
|
||||
.expect("intent should build")
|
||||
.expect("add should require an intent");
|
||||
intent.expires_at_unix_nanos = 1;
|
||||
candidate
|
||||
.save_tiering_config_if_current(store.clone(), None)
|
||||
.await
|
||||
.expect("candidate should persist");
|
||||
save_tier_mutation_intent_record(store.clone(), &intent)
|
||||
.await
|
||||
.expect("peer prepare should persist");
|
||||
|
||||
let manager = TierConfigMgr::new();
|
||||
let calls = Arc::new(Mutex::new(Vec::new()));
|
||||
TIER_MUTATION_TEST_PEERS
|
||||
.scope(vec![FakeTierMutationPeer::boxed("peer-a", calls.clone(), Err("offline"))], async {
|
||||
TierConfigMgr::reload_handle_with(&manager, store.clone())
|
||||
.await
|
||||
.expect_err("failed peer commit must leave recovery incomplete");
|
||||
})
|
||||
.await;
|
||||
let retained = TierConfigMgr::load_tier_mutation_intents(store.clone())
|
||||
.await
|
||||
.expect("peer scan should succeed");
|
||||
assert_eq!(retained.len(), 1);
|
||||
assert_eq!(retained[0].state, TierMutationIntentState::Committed);
|
||||
let error = match TierConfigMgr::acquire_operation_lease(&manager, "COLD-A").await {
|
||||
Ok(_) => panic!("failed peer commit must retain the fence"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert!(error.message.contains("being replaced"), "{error}");
|
||||
|
||||
TIER_MUTATION_TEST_PEERS
|
||||
.scope(
|
||||
vec![FakeTierMutationPeer::boxed(
|
||||
"peer-a",
|
||||
calls.clone(),
|
||||
Ok(PeerTierMutationState::Committed),
|
||||
)],
|
||||
async {
|
||||
TIER_DRIVER_TEST_FACTORY
|
||||
.scope(healthy_driver_factory(), async {
|
||||
TierConfigMgr::reload_handle_with(&manager, store.clone())
|
||||
.await
|
||||
.expect("peer commit retry should recover");
|
||||
TierConfigMgr::acquire_operation_lease(&manager, "COLD-A")
|
||||
.await
|
||||
.expect("tier should be readable after the retry");
|
||||
})
|
||||
.await;
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert_eq!(lock_unpoisoned(&calls).len(), 2, "recovery should retry the peer commit");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn coordinator_mutation_recovery_scan_blocks_new_operation_lease() {
|
||||
let store = Arc::new(CasConfigStore::default());
|
||||
|
||||
@@ -4608,6 +4608,19 @@ fn marker_purge_receipt_path(
|
||||
}
|
||||
|
||||
impl SetDisks {
|
||||
/// The first disk slot UUID is assigned when this erasure set is formatted.
|
||||
/// It survives pool reordering and replacement of a disk in that slot.
|
||||
pub fn persistent_set_id(&self) -> Result<Uuid> {
|
||||
self.format
|
||||
.erasure
|
||||
.sets
|
||||
.get(self.set_index)
|
||||
.and_then(|disks| disks.first())
|
||||
.copied()
|
||||
.filter(|id| !id.is_nil())
|
||||
.ok_or_else(|| Error::other("erasure set has no persistent format identity"))
|
||||
}
|
||||
|
||||
/// Persist a quorum DELETE receipt outside the object marker itself. The
|
||||
/// receipt is written to the metadata bucket so it survives a node restart
|
||||
/// while an inaccessible member still holds the stale marker.
|
||||
|
||||
@@ -443,6 +443,7 @@ impl ECStore {
|
||||
},
|
||||
other => StorageError::Lock(other),
|
||||
})?;
|
||||
let ns_guard = Arc::new(ns_guard);
|
||||
|
||||
let recover = Box::pin(async {
|
||||
self.ensure_bucket_volume_absent_on_all_sets(bucket).await?;
|
||||
@@ -930,15 +931,16 @@ impl ECStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn cleanup_bucket_usage(&self, bucket: &str, guard: Option<&rustfs_lock::NamespaceLockGuard>) -> Result<()> {
|
||||
run_bucket_usage_cleanup(guard, bucket, async {
|
||||
crate::data_usage::prepare_bucket_usage_for_namespace_change(bucket, guard).await?;
|
||||
async fn cleanup_bucket_usage(&self, bucket: &str, guard: Option<&Arc<rustfs_lock::NamespaceLockGuard>>) -> Result<()> {
|
||||
let namespace_guard = guard.map(Arc::as_ref);
|
||||
run_bucket_usage_cleanup(namespace_guard, bucket, async {
|
||||
crate::data_usage::prepare_bucket_usage_for_namespace_change(bucket, namespace_guard).await?;
|
||||
crate::data_usage::remove_bucket_usage_from_backend_with_guard_fenced(self, bucket, guard).await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn cleanup_bucket_usage_best_effort(&self, bucket: &str, guard: Option<&rustfs_lock::NamespaceLockGuard>) {
|
||||
async fn cleanup_bucket_usage_best_effort(&self, bucket: &str, guard: Option<&Arc<rustfs_lock::NamespaceLockGuard>>) {
|
||||
if let Err(err) = self.cleanup_bucket_usage(bucket, guard).await {
|
||||
warn!(
|
||||
bucket = %bucket,
|
||||
@@ -1029,6 +1031,7 @@ impl ECStore {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ns_guard = ns_guard.map(Arc::new);
|
||||
|
||||
let existing_bucket_info = match self.get_bucket_info_from_sets(bucket, &BucketOptions::default()).await {
|
||||
Ok(info) => Some(info),
|
||||
@@ -1146,7 +1149,7 @@ impl ECStore {
|
||||
metadata_sys::set_new_bucket_metadata_intent_in(&self.ctx, meta.clone()).await?;
|
||||
if bucket_lifecycle_guard.as_ref().is_some_and(|guard| guard.is_lock_lost())
|
||||
|| metadata_transaction_guard.as_ref().is_some_and(|guard| guard.is_lock_lost())
|
||||
|| ns_guard.as_ref().is_some_and(|guard| guard.is_lock_lost())
|
||||
|| ns_guard.as_deref().is_some_and(|guard| guard.is_lock_lost())
|
||||
{
|
||||
return Err(Error::other(format!("bucket metadata creation intent lock was lost: {bucket}")));
|
||||
}
|
||||
@@ -1162,7 +1165,7 @@ impl ECStore {
|
||||
|
||||
if let Err(err) = await_bucket_lifecycle_operation(
|
||||
bucket_lifecycle_guard.as_ref(),
|
||||
ns_guard.as_ref(),
|
||||
ns_guard.as_deref(),
|
||||
bucket,
|
||||
"physical bucket creation",
|
||||
await_bucket_namespace_operation(
|
||||
@@ -1191,9 +1194,9 @@ impl ECStore {
|
||||
{
|
||||
warn!("best-effort bucket heal after BucketExists failed: {heal_err}");
|
||||
}
|
||||
if confirmed_missing && !is_err_bucket_exists(&err) && ns_guard.as_ref().is_none_or(|guard| !guard.is_lock_lost()) {
|
||||
if confirmed_missing && !is_err_bucket_exists(&err) && ns_guard.as_deref().is_none_or(|guard| !guard.is_lock_lost()) {
|
||||
error!("make bucket failed: {err}");
|
||||
self.rollback_failed_bucket_creation(bucket, bucket_lifecycle_guard.as_ref(), ns_guard.as_ref())
|
||||
self.rollback_failed_bucket_creation(bucket, bucket_lifecycle_guard.as_ref(), ns_guard.as_deref())
|
||||
.await;
|
||||
}
|
||||
return Err(err);
|
||||
@@ -1214,10 +1217,10 @@ impl ECStore {
|
||||
.await;
|
||||
let metadata_lock_lost = bucket_lifecycle_guard.as_ref().is_some_and(|guard| guard.is_lock_lost())
|
||||
|| metadata_transaction_guard.as_ref().is_some_and(|guard| guard.is_lock_lost())
|
||||
|| ns_guard.as_ref().is_some_and(|guard| guard.is_lock_lost());
|
||||
|| ns_guard.as_deref().is_some_and(|guard| guard.is_lock_lost());
|
||||
if let Err(err) = metadata_result {
|
||||
if !metadata_lock_lost {
|
||||
self.rollback_failed_bucket_creation(bucket, bucket_lifecycle_guard.as_ref(), ns_guard.as_ref())
|
||||
self.rollback_failed_bucket_creation(bucket, bucket_lifecycle_guard.as_ref(), ns_guard.as_deref())
|
||||
.await;
|
||||
}
|
||||
return Err(err);
|
||||
@@ -1580,6 +1583,7 @@ impl ECStore {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ns_guard = ns_guard.map(Arc::new);
|
||||
|
||||
let sr_mark_delete = opts.srdelete_op == SRBucketDeleteOp::MarkDelete;
|
||||
let sr_purge = opts.srdelete_op == SRBucketDeleteOp::Purge;
|
||||
@@ -1615,7 +1619,7 @@ impl ECStore {
|
||||
crate::bucket::metadata_sys::get_object_lock_config_and_incarnation_from_disk_with_guard_in(
|
||||
&self.ctx,
|
||||
bucket,
|
||||
Some(ns_guard.as_ref().unwrap_or(&guard)),
|
||||
Some(ns_guard.as_deref().unwrap_or(&guard)),
|
||||
)
|
||||
.await?;
|
||||
match state {
|
||||
@@ -1641,7 +1645,7 @@ impl ECStore {
|
||||
if sr_mark_delete {
|
||||
await_bucket_lifecycle_operation(
|
||||
bucket_lifecycle_guard.as_ref(),
|
||||
ns_guard.as_ref(),
|
||||
ns_guard.as_deref(),
|
||||
bucket,
|
||||
"bucket delete marker creation",
|
||||
await_bucket_namespace_operation(
|
||||
@@ -1680,7 +1684,7 @@ impl ECStore {
|
||||
object_lock_metadata_guard.as_ref().map(|(guard, _)| guard),
|
||||
bucket,
|
||||
"Object Lock configuration fence during bucket deletion",
|
||||
run_physical_bucket_deletion(ns_guard.as_ref(), bucket, async {
|
||||
run_physical_bucket_deletion(ns_guard.as_deref(), bucket, async {
|
||||
self.delete_bucket_on_sets(bucket, &delete_opts)
|
||||
.await
|
||||
.map_err(|err| to_object_err(err, vec![bucket]))
|
||||
@@ -1709,12 +1713,12 @@ impl ECStore {
|
||||
if let Some(guard) = bucket_lifecycle_guard.as_ref() {
|
||||
record_opts.add_bucket_lifecycle_lock_guard(guard);
|
||||
}
|
||||
if let Some(guard) = ns_guard.as_ref() {
|
||||
if let Some(guard) = ns_guard.as_deref() {
|
||||
record_opts.add_namespace_lock_guard(guard);
|
||||
}
|
||||
await_bucket_lifecycle_operation(
|
||||
bucket_lifecycle_guard.as_ref(),
|
||||
ns_guard.as_ref(),
|
||||
ns_guard.as_deref(),
|
||||
bucket,
|
||||
"bucket retirement publication",
|
||||
crate::bucket::retirement::commit_retirement(store.clone(), bucket, incarnation, &record_opts),
|
||||
@@ -1724,7 +1728,7 @@ impl ECStore {
|
||||
// V1 ledger must not block quota admission after same-name recreation.
|
||||
await_bucket_lifecycle_operation(
|
||||
bucket_lifecycle_guard.as_ref(),
|
||||
ns_guard.as_ref(),
|
||||
ns_guard.as_deref(),
|
||||
bucket,
|
||||
"retired bucket quota ledger cleanup",
|
||||
crate::bucket::quota::reservation::cleanup_retired_bucket_ledger(store, bucket, incarnation),
|
||||
@@ -1735,7 +1739,7 @@ impl ECStore {
|
||||
self.cleanup_bucket_usage_best_effort(bucket, ns_guard.as_ref()).await;
|
||||
|
||||
if let Err(err) = self
|
||||
.cleanup_deleted_bucket_metadata(bucket, sr_purge, ns_guard.as_ref())
|
||||
.cleanup_deleted_bucket_metadata(bucket, sr_purge, ns_guard.as_deref())
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
|
||||
@@ -1120,6 +1120,20 @@ impl ECStore {
|
||||
Some(epoch)
|
||||
}
|
||||
|
||||
/// Namespace cleanup edits an existing usage baseline; it does not certify
|
||||
/// a scan. Retain the movement fence without requiring scanning to resume.
|
||||
pub(crate) async fn data_usage_namespace_cleanup_guard(&self) -> Option<(tokio::sync::OwnedRwLockReadGuard<()>, u64)> {
|
||||
let operation_guard = self.ctx.data_movement_operation_gate().read_owned().await;
|
||||
if self.ctx.data_movement_operation_epoch_exhausted() || self.ctx.data_movement_generation_exhausted() {
|
||||
return None;
|
||||
}
|
||||
Some((operation_guard, self.ctx.data_movement_operation_epoch()))
|
||||
}
|
||||
|
||||
pub(crate) async fn data_usage_namespace_cleanup_epoch(&self) -> Option<u64> {
|
||||
self.data_usage_namespace_cleanup_guard().await.map(|(_, epoch)| epoch)
|
||||
}
|
||||
|
||||
/// Acquire a storage-owned read admission for a coordinator's final
|
||||
/// scanner publication. The guard remains in the context's lease table,
|
||||
/// so a local movement writer cannot pass the peer while its authoritative
|
||||
|
||||
@@ -4937,6 +4937,87 @@ impl ECStore {
|
||||
Ok((idx, result))
|
||||
}
|
||||
|
||||
/// Compare the logical revision across all pools before publishing a
|
||||
/// namespace-only usage update to a writable pool. A suspended source is
|
||||
/// retained for decommission to reconcile against the newer target.
|
||||
pub(crate) async fn put_usage_snapshot_for_namespace_cleanup(
|
||||
&self,
|
||||
object: &str,
|
||||
expected_revision: Option<&str>,
|
||||
data: &mut PutObjReader,
|
||||
namespace_guard: Option<&rustfs_lock::NamespaceLockGuard>,
|
||||
) -> Result<ObjectInfo> {
|
||||
// The caller owns the bucket namespace and movement guards. Acquire
|
||||
// the fixed object domain, then every physical domain in pool order.
|
||||
let guards = self
|
||||
.acquire_all_physical_object_write_locks("usage_namespace_cleanup", RUSTFS_META_BUCKET, object)
|
||||
.await?;
|
||||
let mut opts = ObjectOptions {
|
||||
no_lock: true,
|
||||
max_parity: true,
|
||||
write_completion: crate::object_api::WriteCompletion::TailDrained,
|
||||
suppress_read_repair: true,
|
||||
..Default::default()
|
||||
};
|
||||
for guard in &guards {
|
||||
guard.add_namespace_lock_fence(&mut opts);
|
||||
}
|
||||
if let Some(guard) = namespace_guard {
|
||||
opts.add_namespace_lock_guard(guard);
|
||||
}
|
||||
let source = match self
|
||||
.get_latest_object_info_for_conditional_write(RUSTFS_META_BUCKET, object, &opts)
|
||||
.await
|
||||
.and_then(|(info, idx)| resolve_latest_object_access(RUSTFS_META_BUCKET, object, info, idx, &opts))
|
||||
{
|
||||
Ok((info, _)) => Some(info),
|
||||
Err(err) if is_err_object_not_found(&err) || is_err_version_not_found(&err) => None,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
if source.as_ref().and_then(|info| info.etag.as_deref()) != expected_revision {
|
||||
return Err(StorageError::PreconditionFailed);
|
||||
}
|
||||
if source.as_ref().is_some_and(|info| {
|
||||
info.version_id.is_some_and(|version| !version.is_nil()) || info.etag.as_ref().is_none_or(String::is_empty)
|
||||
}) {
|
||||
return Err(StorageError::other("usage cleanup requires an unversioned snapshot with a revision"));
|
||||
}
|
||||
let target = self.get_pool_idx_no_lock(RUSTFS_META_BUCKET, object, data.size()).await?;
|
||||
let target_info = match self.pools[target].get_object_info(RUSTFS_META_BUCKET, object, &opts).await {
|
||||
Ok(info) => Some(info),
|
||||
Err(err) if is_err_object_not_found(&err) || is_err_version_not_found(&err) => None,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
opts.http_preconditions = Some(match target_info.as_ref() {
|
||||
Some(info) => crate::storage_api_contracts::object::HTTPPreconditions {
|
||||
if_match: Some(
|
||||
info.etag
|
||||
.as_ref()
|
||||
.filter(|etag| !etag.is_empty())
|
||||
.cloned()
|
||||
.ok_or_else(|| StorageError::other("usage cleanup target has no revision"))?,
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
None => crate::storage_api_contracts::object::HTTPPreconditions {
|
||||
if_none_match: Some("*".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
let latest = source
|
||||
.iter()
|
||||
.chain(target_info.iter())
|
||||
.filter_map(|info| info.mod_time)
|
||||
.fold(OffsetDateTime::now_utc(), std::cmp::max);
|
||||
opts.mod_time = Some(
|
||||
latest
|
||||
.checked_add(time::Duration::nanoseconds(1))
|
||||
.ok_or_else(|| StorageError::other("usage cleanup snapshot timestamp is exhausted"))?,
|
||||
);
|
||||
opts.decommission_capacity_admission = crate::bucket::metadata_sys::object_store_if_initialized_in(&self.ctx).await;
|
||||
self.pools[target].put_object(RUSTFS_META_BUCKET, object, data, &opts).await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, data))]
|
||||
#[hotpath::measure(impl_type = "ECStore")]
|
||||
pub(super) async fn handle_put_object(
|
||||
@@ -7005,7 +7086,7 @@ mod tests {
|
||||
};
|
||||
use crate::set_disk::same_distributed_lock_domain;
|
||||
use crate::set_disk::{SetDisks, disk_call_counters};
|
||||
use crate::storage_api_contracts::bucket::MakeBucketOptions;
|
||||
use crate::storage_api_contracts::bucket::{BucketOptions, MakeBucketOptions};
|
||||
use crate::storage_api_contracts::lifecycle::TransitionedObject;
|
||||
use crate::storage_api_contracts::list::ListOperations as _;
|
||||
use bytes::Bytes;
|
||||
@@ -10347,6 +10428,296 @@ mod tests {
|
||||
multipool_delete_test_store(bucket, true).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_fresh_bucket_after_canceled_decommission() {
|
||||
let (_dirs, store) = multipool_version_test_store("canceled-decommission-existing").await;
|
||||
let mut canceled = prepared_pool_test_status(1, false);
|
||||
canceled.decommission = Some(PoolDecommissionInfo {
|
||||
canceled: true,
|
||||
..Default::default()
|
||||
});
|
||||
*store.pool_meta.write().await = PoolMeta {
|
||||
pools: vec![prepared_pool_test_status(0, false), canceled],
|
||||
..Default::default()
|
||||
};
|
||||
store
|
||||
.handle_make_bucket("canceled-decommission-fresh", &MakeBucketOptions::default())
|
||||
.await
|
||||
.expect("canceled decommission must not block creation of a new bucket");
|
||||
assert!(
|
||||
store.scanner_data_usage_publication_blocked().await,
|
||||
"scanner publication must remain fenced"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_bucket_after_failed_decommission() {
|
||||
let (_dirs, store) = multipool_version_test_store("decommission-existing-bucket").await;
|
||||
let bucket = "decommission-new-bucket";
|
||||
let snapshot_time = std::time::SystemTime::now() - Duration::from_secs(60);
|
||||
let mut snapshot = rustfs_data_usage::DataUsageInfo {
|
||||
last_update: Some(snapshot_time),
|
||||
scanner_cycle: Some(37),
|
||||
scanner_epoch: Some(2),
|
||||
buckets_count: 1,
|
||||
usage_snapshot_complete: true,
|
||||
..Default::default()
|
||||
};
|
||||
snapshot.buckets_usage.insert(
|
||||
bucket.to_string(),
|
||||
rustfs_data_usage::BucketUsageInfo {
|
||||
size: 42,
|
||||
objects_count: 1,
|
||||
versions_count: 1,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
snapshot.bucket_sizes.insert(bucket.to_string(), 42);
|
||||
snapshot.calculate_totals();
|
||||
crate::data_usage::store_data_usage_in_backend(snapshot, store.clone())
|
||||
.await
|
||||
.expect("store a stale bucket-usage baseline before the failed decommission");
|
||||
let (old_snapshot, usage_owner) = store
|
||||
.get_latest_accessible_object_info_with_idx(
|
||||
RUSTFS_META_BUCKET,
|
||||
crate::data_usage::DATA_USAGE_OBJ_NAME_PATH.as_str(),
|
||||
&ObjectOptions::default(),
|
||||
)
|
||||
.await
|
||||
.expect("identify the pool holding the old usage baseline");
|
||||
let mut pool_statuses = vec![prepared_pool_test_status(0, false), prepared_pool_test_status(1, false)];
|
||||
pool_statuses[usage_owner].decommission = Some(PoolDecommissionInfo {
|
||||
failed: true,
|
||||
..Default::default()
|
||||
});
|
||||
*store.pool_meta.write().await = PoolMeta {
|
||||
pools: pool_statuses,
|
||||
..Default::default()
|
||||
};
|
||||
store
|
||||
.handle_make_bucket(bucket, &MakeBucketOptions::default())
|
||||
.await
|
||||
.expect("a failed decommission must not block creation of an unrelated bucket");
|
||||
store
|
||||
.handle_get_bucket_info(bucket, &BucketOptions::default())
|
||||
.await
|
||||
.expect("the created bucket must be visible");
|
||||
let usage = crate::data_usage::load_data_usage_from_backend(store.clone())
|
||||
.await
|
||||
.expect("load the cleaned usage baseline");
|
||||
assert!(!usage.buckets_usage.contains_key(bucket), "the new bucket must not inherit old usage");
|
||||
assert_eq!(usage.scanner_cycle, Some(37), "namespace cleanup must not certify a new scan");
|
||||
assert_eq!(usage.scanner_epoch, Some(2), "namespace cleanup must preserve the scanner authority");
|
||||
let object = crate::data_usage::DATA_USAGE_OBJ_NAME_PATH.as_str();
|
||||
let (_, current_owner) = store
|
||||
.get_latest_accessible_object_info_with_idx(RUSTFS_META_BUCKET, object, &ObjectOptions::default())
|
||||
.await
|
||||
.expect("read the new snapshot authority");
|
||||
assert_ne!(current_owner, usage_owner, "cleanup must publish to a writable pool");
|
||||
let retained_source = store.pools[usage_owner]
|
||||
.get_object_info(RUSTFS_META_BUCKET, object, &ObjectOptions::default())
|
||||
.await
|
||||
.expect("decommission retains its original source for reconciliation");
|
||||
assert_eq!(retained_source.etag, old_snapshot.etag);
|
||||
let stale_update = store
|
||||
.put_usage_snapshot_for_namespace_cleanup(
|
||||
object,
|
||||
old_snapshot.etag.as_deref(),
|
||||
&mut PutObjReader::from_vec(b"stale snapshot".to_vec()),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert!(matches!(stale_update, Err(StorageError::PreconditionFailed)));
|
||||
let usage = crate::data_usage::load_data_usage_from_backend(store.clone())
|
||||
.await
|
||||
.expect("a stale source CAS must not replace the writable target");
|
||||
assert!(!usage.buckets_usage.contains_key(bucket));
|
||||
assert!(
|
||||
store.scanner_data_usage_publication_blocked().await,
|
||||
"scanner publication must remain fenced"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn canceled_create_retains_usage_cleanup_namespace_owner() {
|
||||
use crate::set_disk::{PutObjectCommitBarrier, PutObjectCommitPause};
|
||||
|
||||
let (_dirs, store) = multipool_version_test_store("cleanup-cancellation-existing").await;
|
||||
let bucket = "cleanup-cancellation-new";
|
||||
let mut snapshot = rustfs_data_usage::DataUsageInfo {
|
||||
last_update: Some(std::time::SystemTime::now()),
|
||||
usage_snapshot_complete: true,
|
||||
..Default::default()
|
||||
};
|
||||
snapshot.buckets_usage.insert(
|
||||
bucket.to_owned(),
|
||||
rustfs_data_usage::BucketUsageInfo {
|
||||
size: 42,
|
||||
objects_count: 1,
|
||||
versions_count: 1,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
snapshot.bucket_sizes.insert(bucket.to_owned(), 42);
|
||||
snapshot.calculate_totals();
|
||||
crate::data_usage::store_data_usage_in_backend(snapshot, store.clone())
|
||||
.await
|
||||
.expect("seed the stale namespace usage");
|
||||
let object = crate::data_usage::DATA_USAGE_OBJ_NAME_PATH.as_str();
|
||||
let (_, source) = store
|
||||
.get_latest_accessible_object_info_with_idx(RUSTFS_META_BUCKET, object, &ObjectOptions::default())
|
||||
.await
|
||||
.expect("find the source snapshot");
|
||||
let mut pools = vec![prepared_pool_test_status(0, false), prepared_pool_test_status(1, false)];
|
||||
pools[source].decommission = Some(PoolDecommissionInfo {
|
||||
canceled: true,
|
||||
..Default::default()
|
||||
});
|
||||
*store.pool_meta.write().await = PoolMeta {
|
||||
pools,
|
||||
..Default::default()
|
||||
};
|
||||
let barrier = PutObjectCommitBarrier::install(RUSTFS_META_BUCKET, object, PutObjectCommitPause::AfterNamespace);
|
||||
let create_store = store.clone();
|
||||
let create = tokio::spawn(async move { create_store.handle_make_bucket(bucket, &MakeBucketOptions::default()).await });
|
||||
tokio::time::timeout(Duration::from_secs(10), barrier.wait_until_paused())
|
||||
.await
|
||||
.expect("cleanup reaches its physical commit with the namespace held");
|
||||
create.abort();
|
||||
assert!(create.await.expect_err("the request waiter was canceled").is_cancelled());
|
||||
let namespace = store
|
||||
.new_ns_lock(bucket, bucket)
|
||||
.await
|
||||
.expect("open the bucket namespace lock");
|
||||
assert!(
|
||||
tokio::time::timeout(Duration::from_millis(50), namespace.get_write_lock(Duration::from_secs(1)))
|
||||
.await
|
||||
.is_err(),
|
||||
"cancellation must not release the namespace before its cleanup finishes"
|
||||
);
|
||||
barrier.release();
|
||||
drop(barrier);
|
||||
tokio::time::timeout(Duration::from_secs(10), store.handle_make_bucket(bucket, &MakeBucketOptions::default()))
|
||||
.await
|
||||
.expect("the resumed create must not remain blocked")
|
||||
.expect("a new request resumes after the old cleanup releases its namespace");
|
||||
let usage = crate::data_usage::load_data_usage_from_backend(store.clone())
|
||||
.await
|
||||
.expect("read the cleaned baseline");
|
||||
assert!(!usage.buckets_usage.contains_key(bucket));
|
||||
assert!(store.scanner_data_usage_publication_blocked().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn usage_cleanup_rejects_an_unreadable_pool() {
|
||||
let (_dirs, store) = multipool_version_test_store("cleanup-unreadable-pool").await;
|
||||
let snapshot = rustfs_data_usage::DataUsageInfo {
|
||||
last_update: Some(std::time::SystemTime::now()),
|
||||
usage_snapshot_complete: true,
|
||||
..Default::default()
|
||||
};
|
||||
crate::data_usage::store_data_usage_in_backend(snapshot, store.clone())
|
||||
.await
|
||||
.expect("seed the usage baseline");
|
||||
let object = crate::data_usage::DATA_USAGE_OBJ_NAME_PATH.as_str();
|
||||
let (before, source) = store
|
||||
.get_latest_accessible_object_info_with_idx(RUSTFS_META_BUCKET, object, &ObjectOptions::default())
|
||||
.await
|
||||
.expect("find the baseline owner");
|
||||
let unavailable = 1 - source;
|
||||
for disk in store.pools[unavailable]
|
||||
.get_disks_by_key(object)
|
||||
.disks
|
||||
.read()
|
||||
.await
|
||||
.iter()
|
||||
.flatten()
|
||||
{
|
||||
disk.close().await.expect("stop disk health monitoring");
|
||||
disk.force_offline_for_test();
|
||||
}
|
||||
let update = store
|
||||
.put_usage_snapshot_for_namespace_cleanup(
|
||||
object,
|
||||
before.etag.as_deref(),
|
||||
&mut PutObjReader::from_vec(b"unproven authority".to_vec()),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert!(update.is_err(), "an unreadable pool may hold a newer snapshot");
|
||||
let after = store.pools[source]
|
||||
.get_object_info(RUSTFS_META_BUCKET, object, &ObjectOptions::default())
|
||||
.await
|
||||
.expect("the readable baseline remains intact");
|
||||
assert_eq!(after.etag, before.etag);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn usage_cleanup_rejects_namespace_loss_before_physical_commit() {
|
||||
use crate::set_disk::{PutObjectCommitBarrier, PutObjectCommitPause};
|
||||
use rustfs_lock::{LocalClient, LockRequest, LockType, NamespaceLock, ObjectKey};
|
||||
|
||||
let (_dirs, store) = multipool_version_test_store("cleanup-fence-existing").await;
|
||||
let snapshot = rustfs_data_usage::DataUsageInfo {
|
||||
last_update: Some(std::time::SystemTime::now()),
|
||||
usage_snapshot_complete: true,
|
||||
..Default::default()
|
||||
};
|
||||
crate::data_usage::store_data_usage_in_backend(snapshot, store.clone())
|
||||
.await
|
||||
.expect("seed the protected baseline");
|
||||
let object = crate::data_usage::DATA_USAGE_OBJ_NAME_PATH.as_str();
|
||||
let (before, _) = store
|
||||
.get_latest_accessible_object_info_with_idx(RUSTFS_META_BUCKET, object, &ObjectOptions::default())
|
||||
.await
|
||||
.expect("read the baseline revision");
|
||||
let ttl = Duration::from_secs(60);
|
||||
let namespace = NamespaceLock::new("usage-cleanup-commit-fence".to_owned(), Arc::new(LocalClient::new()));
|
||||
let request = LockRequest::new(ObjectKey::new("cleanup-fence-existing", ""), LockType::Exclusive, "usage-cleanup")
|
||||
.with_acquire_timeout(Duration::from_secs(1))
|
||||
.with_ttl(ttl)
|
||||
.with_refresh_interval(ttl);
|
||||
let guard = Arc::new(
|
||||
namespace
|
||||
.acquire_guard(&request)
|
||||
.await
|
||||
.expect("acquire the namespace lease")
|
||||
.expect("namespace lease is available"),
|
||||
);
|
||||
let barrier = PutObjectCommitBarrier::install(RUSTFS_META_BUCKET, object, PutObjectCommitPause::AfterNamespace);
|
||||
let update_store = store.clone();
|
||||
let update_guard = guard.clone();
|
||||
let revision = before.etag.clone();
|
||||
let update = tokio::spawn(async move {
|
||||
let _movement = update_store
|
||||
.data_usage_namespace_cleanup_guard()
|
||||
.await
|
||||
.expect("admit the namespace-only update");
|
||||
update_store
|
||||
.put_usage_snapshot_for_namespace_cleanup(
|
||||
object,
|
||||
revision.as_deref(),
|
||||
&mut PutObjReader::from_vec(b"must not commit".to_vec()),
|
||||
Some(update_guard.as_ref()),
|
||||
)
|
||||
.await
|
||||
});
|
||||
tokio::time::timeout(Duration::from_secs(10), barrier.wait_until_paused())
|
||||
.await
|
||||
.expect("the update reaches its commit barrier");
|
||||
tokio::time::pause();
|
||||
tokio::time::advance(ttl + Duration::from_millis(1)).await;
|
||||
assert!(guard.is_lock_lost(), "the parent namespace lease has expired");
|
||||
tokio::time::resume();
|
||||
barrier.release();
|
||||
assert!(update.await.expect("the cleanup task completes").is_err());
|
||||
let (after, _) = store
|
||||
.get_latest_accessible_object_info_with_idx(RUSTFS_META_BUCKET, object, &ObjectOptions::default())
|
||||
.await
|
||||
.expect("the original baseline remains readable");
|
||||
assert_eq!(after.etag, before.etag, "a lost namespace lease must fence the physical write");
|
||||
}
|
||||
|
||||
async fn multipool_delete_test_store(bucket: &str, versioned: bool) -> (Vec<tempfile::TempDir>, Arc<ECStore>) {
|
||||
let ctx = Arc::new(crate::runtime::instance::InstanceContext::new());
|
||||
let (mut dirs, first_set) = make_local_set_disks_with_ctx(4, 2, Arc::clone(&ctx)).await;
|
||||
|
||||
@@ -690,6 +690,19 @@ pub(super) enum ScannerPauseBacklogAttemptDecision {
|
||||
pub struct ScannerPauseBacklogReplicaId {
|
||||
pub pool_index: usize,
|
||||
pub set_index: usize,
|
||||
/// Stable format identity; indices are only the current routing location.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub set_id: Option<uuid::Uuid>,
|
||||
}
|
||||
|
||||
impl ScannerPauseBacklogReplicaId {
|
||||
fn from_set(set: &SetDisks) -> Result<Self, EcstoreError> {
|
||||
Ok(Self {
|
||||
pool_index: set.pool_index,
|
||||
set_index: set.set_index,
|
||||
set_id: Some(set.persistent_set_id()?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
@@ -706,9 +719,23 @@ impl ScannerPauseBacklogCommitRecord {
|
||||
|
||||
fn validate(&self) -> Result<(), String> {
|
||||
self.ledger.validate()?;
|
||||
if self.replicas.is_empty() || !self.replicas.windows(2).all(|pair| pair[0] < pair[1]) {
|
||||
if self.replicas.is_empty()
|
||||
|| !self
|
||||
.replicas
|
||||
.windows(2)
|
||||
.all(|pair| (pair[0].pool_index, pair[0].set_index) < (pair[1].pool_index, pair[1].set_index))
|
||||
{
|
||||
return Err("scanner pause backlog commit has invalid replica membership".to_string());
|
||||
}
|
||||
let mut identities = BTreeSet::new();
|
||||
if self
|
||||
.replicas
|
||||
.iter()
|
||||
.filter_map(|id| id.set_id)
|
||||
.any(|id| id.is_nil() || !identities.insert(id))
|
||||
{
|
||||
return Err("scanner pause backlog commit has invalid persistent set identities".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -747,6 +774,10 @@ impl ScannerPauseBacklogReplicaRecord {
|
||||
}
|
||||
if let Some(committed) = &self.committed {
|
||||
committed.validate()?;
|
||||
if committed.replicas.iter().any(|id| id.set_id.is_some()) && committed.replicas.iter().any(|id| id.set_id.is_none())
|
||||
{
|
||||
return Err("scanner pause backlog membership mixes positional and persistent identities".to_string());
|
||||
}
|
||||
if let Some(stable) = &self.stable {
|
||||
let stable_key = (stable.writer_epoch, stable.generation);
|
||||
let committed_key = (committed.ledger.writer_epoch, committed.ledger.generation);
|
||||
@@ -907,9 +938,19 @@ fn unix_now() -> u64 {
|
||||
}
|
||||
|
||||
async fn read_scanner_pause_backlog_replica(store: Arc<SetDisks>) -> ScannerPauseBacklogReplica {
|
||||
let id = ScannerPauseBacklogReplicaId {
|
||||
pool_index: store.pool_index,
|
||||
set_index: store.set_index,
|
||||
let id = match ScannerPauseBacklogReplicaId::from_set(&store) {
|
||||
Ok(id) => id,
|
||||
Err(error) => {
|
||||
return ScannerPauseBacklogReplica {
|
||||
id: ScannerPauseBacklogReplicaId {
|
||||
pool_index: store.pool_index,
|
||||
set_index: store.set_index,
|
||||
set_id: None,
|
||||
},
|
||||
revision: None,
|
||||
state: ScannerPauseBacklogReplicaState::Unavailable(error.to_string()),
|
||||
};
|
||||
}
|
||||
};
|
||||
let reader = match store
|
||||
.get_object_reader(
|
||||
@@ -1333,8 +1374,48 @@ fn select_scanner_pause_backlog_roll_forward(replicas: &[ScannerPauseBacklogRepl
|
||||
.then(|| newest.clone())
|
||||
}
|
||||
|
||||
/// Resolve stored membership against immutable set identities before checking
|
||||
/// commit proofs. A missing identity is never rebound to a new set at its old
|
||||
/// index. Legacy records retain their positional interpretation until an
|
||||
/// existing commit or rollback proof permits publishing the bound membership.
|
||||
fn normalize_scanner_pause_backlog_membership(
|
||||
replicas: &mut [ScannerPauseBacklogReplica],
|
||||
) -> Result<(), ScannerPauseBacklogSelectionError> {
|
||||
let mut by_location = HashMap::with_capacity(replicas.len());
|
||||
let mut by_identity = HashMap::with_capacity(replicas.len());
|
||||
for replica in replicas.iter() {
|
||||
let id = replica.id;
|
||||
if by_location.insert((id.pool_index, id.set_index), id).is_some()
|
||||
|| id
|
||||
.set_id
|
||||
.is_some_and(|identity| identity.is_nil() || by_identity.insert(identity, id).is_some())
|
||||
{
|
||||
return Err(ScannerPauseBacklogSelectionError::AuthorityConflict(
|
||||
"scanner pause backlog has duplicate or invalid storage identities".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
for replica in replicas {
|
||||
if let ScannerPauseBacklogReplicaState::Valid(record) = &mut replica.state
|
||||
&& let Some(commit) = record.committed.as_mut()
|
||||
{
|
||||
for member in &mut commit.replicas {
|
||||
let current = match member.set_id {
|
||||
Some(identity) => by_identity.get(&identity),
|
||||
None => by_location.get(&(member.pool_index, member.set_index)),
|
||||
};
|
||||
if let Some(current) = current {
|
||||
*member = *current;
|
||||
}
|
||||
}
|
||||
commit.replicas.sort_unstable();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn select_scanner_pause_backlog_replicas(
|
||||
replicas: Vec<ScannerPauseBacklogReplica>,
|
||||
mut replicas: Vec<ScannerPauseBacklogReplica>,
|
||||
) -> Result<LoadedScannerPauseBacklog, ScannerPauseBacklogSelectionFailure> {
|
||||
if replicas.is_empty() {
|
||||
return Err(scanner_pause_backlog_selection_failure(
|
||||
@@ -1354,6 +1435,9 @@ fn select_scanner_pause_backlog_replicas(
|
||||
)),
|
||||
));
|
||||
}
|
||||
if let Err(error) = normalize_scanner_pause_backlog_membership(&mut replicas) {
|
||||
return Err(scanner_pause_backlog_selection_failure(replicas, error));
|
||||
}
|
||||
|
||||
let replica_ids = scanner_pause_backlog_replica_ids(&replicas);
|
||||
let authoritative_commit = match select_scanner_pause_backlog_commit(&replicas, &replica_ids) {
|
||||
@@ -1512,12 +1596,13 @@ fn decode_scanner_pause_backlog_retirement_replicas(
|
||||
) -> Result<(Vec<ScannerPauseBacklogReplica>, Vec<ScannerPauseBacklogReplica>), ScannerPauseBacklogRetirementError> {
|
||||
let mut ids = BTreeSet::new();
|
||||
let mut decoded = Vec::with_capacity(replicas.len());
|
||||
let mut surviving = Vec::new();
|
||||
let mut writable_ids = BTreeSet::new();
|
||||
let mut has_source = false;
|
||||
for replica in replicas {
|
||||
let id = ScannerPauseBacklogReplicaId {
|
||||
pool_index: replica.pool_index,
|
||||
set_index: replica.set_index,
|
||||
set_id: replica.set_id,
|
||||
};
|
||||
if !ids.insert(id) {
|
||||
return Err(scanner_pause_backlog_retirement_invalid_record(
|
||||
@@ -1548,7 +1633,7 @@ fn decode_scanner_pause_backlog_retirement_replicas(
|
||||
state,
|
||||
};
|
||||
if replica.pool_index != source_pool_index && replica.writable {
|
||||
surviving.push(decoded_replica.clone());
|
||||
writable_ids.insert(id);
|
||||
}
|
||||
decoded.push(decoded_replica);
|
||||
}
|
||||
@@ -1557,6 +1642,12 @@ fn decode_scanner_pause_backlog_retirement_replicas(
|
||||
"scanner pause backlog retirement has no native source record",
|
||||
));
|
||||
}
|
||||
normalize_scanner_pause_backlog_membership(&mut decoded).map_err(scanner_pause_backlog_retirement_selection_error)?;
|
||||
let surviving = decoded
|
||||
.iter()
|
||||
.filter(|replica| writable_ids.contains(&replica.id))
|
||||
.cloned()
|
||||
.collect();
|
||||
Ok((decoded, surviving))
|
||||
}
|
||||
|
||||
@@ -1874,11 +1965,9 @@ where
|
||||
let loaded_ids = loaded.replicas.iter().map(|replica| replica.id).collect::<BTreeSet<_>>();
|
||||
let writable_ids = writable
|
||||
.iter()
|
||||
.map(|set| ScannerPauseBacklogReplicaId {
|
||||
pool_index: set.pool_index,
|
||||
set_index: set.set_index,
|
||||
})
|
||||
.collect::<BTreeSet<_>>();
|
||||
.map(|set| ScannerPauseBacklogReplicaId::from_set(set))
|
||||
.collect::<Result<BTreeSet<_>, _>>()
|
||||
.map_err(|error| error.to_string())?;
|
||||
if writable_ids != loaded_ids {
|
||||
return Err("scanner pause backlog replica topology changed during commit".to_string());
|
||||
}
|
||||
@@ -1901,10 +1990,7 @@ where
|
||||
let results = tokio::spawn(async move {
|
||||
let mut results = Vec::with_capacity(writable.len());
|
||||
for set in writable {
|
||||
let id = ScannerPauseBacklogReplicaId {
|
||||
pool_index: set.pool_index,
|
||||
set_index: set.set_index,
|
||||
};
|
||||
let id = ScannerPauseBacklogReplicaId::from_set(&set).map_err(|error| error.to_string())?;
|
||||
if only.as_ref().is_some_and(|only| !only.contains(&id)) {
|
||||
continue;
|
||||
}
|
||||
@@ -1928,10 +2014,10 @@ where
|
||||
break;
|
||||
}
|
||||
}
|
||||
results
|
||||
Ok::<_, String>(results)
|
||||
})
|
||||
.await
|
||||
.map_err(|err| format!("scanner pause backlog publication owner failed: {err}"))?;
|
||||
.map_err(|err| format!("scanner pause backlog publication owner failed: {err}"))??;
|
||||
|
||||
let failures = results
|
||||
.iter()
|
||||
@@ -2444,6 +2530,142 @@ mod tests {
|
||||
(root, store)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn native_legacy_upgrade_and_pool_reindex_preserve_backlog_authority() {
|
||||
assert_native_legacy_upgrade_and_pool_reindex_preserve_backlog_authority(false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn native_pool_reindex_with_failed_membership_publication_preserves_authority() {
|
||||
assert_native_legacy_upgrade_and_pool_reindex_preserve_backlog_authority(true);
|
||||
}
|
||||
|
||||
fn assert_native_legacy_upgrade_and_pool_reindex_preserve_backlog_authority(fail_publication: bool) {
|
||||
run_native_retirement_test(async move || {
|
||||
use crate::storage_api::owner::NativeScannerPauseBacklogWriteFault;
|
||||
|
||||
let (root, store) = native_pause_backlog_store(3, 1).await;
|
||||
let now = unix_now();
|
||||
let original = seed_native_expansion_ledger(&store, now).await;
|
||||
// Model bytes written by the previous positional-membership writer.
|
||||
for id in native_replica_ids(&store) {
|
||||
let (bytes, _) = native_replica_bytes(&store.pools[id.pool_index].disk_set[0]).await;
|
||||
let ScannerPauseBacklogReplicaState::Valid(mut record) = decode_scanner_pause_backlog_ledger(&bytes) else {
|
||||
panic!("native seed is valid");
|
||||
};
|
||||
record.replica_schema_version = 1;
|
||||
for member in &mut record.committed.as_mut().expect("seed commit").replicas {
|
||||
member.set_id = None;
|
||||
}
|
||||
record.validate().expect("previous schema fixture");
|
||||
replace_native_pause_backlog_replica(&store, id, *record).await;
|
||||
}
|
||||
let fault =
|
||||
NativeScannerPauseBacklogWriteFault::fail_before_write(Arc::clone(&store.pools[1].disk_set[0]), "publish", 1);
|
||||
assert!(
|
||||
ScannerPauseBacklogController::claim(Arc::clone(&store), now + 60)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
drop(fault);
|
||||
let interrupted = load_scanner_pause_backlog(Arc::clone(&store))
|
||||
.await
|
||||
.expect("mixed legacy and bound records retain the rollback proof");
|
||||
assert_eq!(interrupted.ledger, original);
|
||||
let upgraded = ScannerPauseBacklogController::claim(Arc::clone(&store), now + 61)
|
||||
.await
|
||||
.expect("retry finishes the format upgrade");
|
||||
let expected = upgraded.loaded.ledger.clone();
|
||||
drop(upgraded);
|
||||
for pool in &store.pools {
|
||||
let (bytes, _) = native_replica_bytes(&pool.disk_set[0]).await;
|
||||
let ScannerPauseBacklogReplicaState::Valid(record) = decode_scanner_pause_backlog_ledger(&bytes) else {
|
||||
panic!("upgraded native record is valid");
|
||||
};
|
||||
assert_eq!(record.replica_schema_version, 1);
|
||||
assert!(
|
||||
record
|
||||
.committed
|
||||
.expect("upgraded membership")
|
||||
.replicas
|
||||
.iter()
|
||||
.all(|id| id.set_id.is_some())
|
||||
);
|
||||
}
|
||||
let surviving_id = native_replica_id(&store, 2, 0).set_id;
|
||||
let (source, _) = native_replica_bytes(&store.pools[1].disk_set[0]).await;
|
||||
store
|
||||
.prepare_scanner_pause_backlog_retirement_for_test(1, source.len() * 2)
|
||||
.await
|
||||
.expect("start retiring the middle pool");
|
||||
store
|
||||
.retire_scanner_pause_backlog_for_test(1, 0)
|
||||
.await
|
||||
.expect("hand off the native backlog");
|
||||
store
|
||||
.complete_scanner_pause_backlog_retirement_for_test(1)
|
||||
.await
|
||||
.expect("persist the completed middle pool through the ILM receipt checks");
|
||||
shutdown_native_retirement_store(store).await;
|
||||
|
||||
let reordered = super::super::tests::setup_scanner_cycle_store_with_pool_order(
|
||||
root.path(),
|
||||
false,
|
||||
&[0, 2, 3],
|
||||
1,
|
||||
NATIVE_RETIREMENT_DRIVES_PER_SET,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(native_replica_id(&reordered, 1, 0).set_id, surviving_id);
|
||||
assert_ne!(native_replica_id(&reordered, 2, 0).set_id, surviving_id);
|
||||
let loaded = load_scanner_pause_backlog(Arc::clone(&reordered))
|
||||
.await
|
||||
.expect("removing a middle pool and appending a new one preserves native authority");
|
||||
assert_eq!(loaded.ledger, expected);
|
||||
assert_eq!(loaded.persistence_state, "membership_repair_pending");
|
||||
if fail_publication {
|
||||
// Match the reported partial publication: the two existing
|
||||
// pools reject the new commit while the replacement accepts it.
|
||||
let faults = [0, 1].map(|pool| {
|
||||
NativeScannerPauseBacklogWriteFault::fail_before_write(
|
||||
Arc::clone(&reordered.pools[pool].disk_set[0]),
|
||||
"publish",
|
||||
2,
|
||||
)
|
||||
});
|
||||
let error = ScannerPauseBacklogController::claim(Arc::clone(&reordered), now + 120)
|
||||
.await
|
||||
.err()
|
||||
.expect("the incomplete publication must fail");
|
||||
assert!(error.contains("pool 0 set 0") && error.contains("pool 1 set 0"), "{error}");
|
||||
drop(faults);
|
||||
let recovered = load_scanner_pause_backlog(Arc::clone(&reordered))
|
||||
.await
|
||||
.expect("partial publication after reindexing retains proven authority");
|
||||
assert_eq!(recovered.ledger, expected);
|
||||
assert!(recovered.durable);
|
||||
}
|
||||
let (source, _) = native_replica_bytes(&reordered.pools[1].disk_set[0]).await;
|
||||
reordered
|
||||
.prepare_scanner_pause_backlog_retirement_for_test(1, source.len() * 2)
|
||||
.await
|
||||
.expect("retire the reindexed member before a scanner writer completes the membership change");
|
||||
reordered
|
||||
.retire_scanner_pause_backlog_for_test(1, 0)
|
||||
.await
|
||||
.expect("retirement uses the same bound authority");
|
||||
let surviving = load_scanner_pause_backlog(Arc::clone(&reordered))
|
||||
.await
|
||||
.expect("remaining members retain the complete ledger");
|
||||
assert_eq!(surviving.ledger, expected);
|
||||
assert!(surviving.all_current_replicas_stable());
|
||||
shutdown_native_retirement_store(reordered).await;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn native_single_pool_partial_commit_converges_across_24_sets() {
|
||||
@@ -2592,9 +2814,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let stable = durable_ledger(unix_now());
|
||||
let members = (0..3)
|
||||
.flat_map(|pool_index| (0..2).map(move |set_index| replica_id(pool_index, set_index)))
|
||||
.collect::<Vec<_>>();
|
||||
let members = native_replica_ids(&store);
|
||||
let committed = ScannerPauseBacklogCommitRecord::new(stable.clone(), members);
|
||||
let record = ScannerPauseBacklogReplicaRecord::new(Some(stable), Some(committed));
|
||||
let bytes = encode_scanner_pause_backlog_record(&record).expect("stable replica should encode");
|
||||
@@ -3021,10 +3241,7 @@ mod tests {
|
||||
let now = unix_now().saturating_add(60);
|
||||
let replacement = claim_scanner_pause_backlog_writer(&original, now).expect("next writer generation");
|
||||
let new_commit = ScannerPauseBacklogCommitRecord::new(replacement.clone(), current_ids.clone());
|
||||
let failed_id = ScannerPauseBacklogReplicaId {
|
||||
pool_index: failed_pool,
|
||||
set_index: 0,
|
||||
};
|
||||
let failed_id = native_replica_id(&store, failed_pool, 0);
|
||||
let fault = NativeScannerPauseBacklogWriteFault::fail_before_write(
|
||||
Arc::clone(&store.pools[failed_pool].disk_set[0]),
|
||||
"publish",
|
||||
@@ -3553,10 +3770,8 @@ mod tests {
|
||||
.authoritative_commit
|
||||
.expect("old cohort commit");
|
||||
let now = unix_now().saturating_add(60);
|
||||
let ids = (0..3)
|
||||
.flat_map(|pool_index| (0..2).map(move |set_index| replica_id(pool_index, set_index)))
|
||||
.collect::<Vec<_>>();
|
||||
let newer_member = replica_id(2, 1);
|
||||
let ids = native_replica_ids(&store);
|
||||
let newer_member = native_replica_id(&store, 2, 1);
|
||||
let newer = claim_scanner_pause_backlog_writer(&original, now).expect("newer generation");
|
||||
let newer_commit = ScannerPauseBacklogCommitRecord::new(newer.clone(), ids.clone());
|
||||
for id in &ids {
|
||||
@@ -3617,14 +3832,12 @@ mod tests {
|
||||
let (_root, store, original) = native_expanded_backlog_store(1, false).await;
|
||||
let now = unix_now().saturating_add(60);
|
||||
let wedge = native_interleaved_expansion_wedge(&store, &original, now).await;
|
||||
let ids = (0..3)
|
||||
.flat_map(|pool_index| (0..2).map(move |set_index| replica_id(pool_index, set_index)))
|
||||
.collect::<Vec<_>>();
|
||||
let ids = native_replica_ids(&store);
|
||||
// The retiring source holds the newest stable ledger.
|
||||
let newest = claim_scanner_pause_backlog_writer(&wedge.acknowledged, now + 1).expect("newest generation");
|
||||
replace_native_pause_backlog_replica(
|
||||
&store,
|
||||
replica_id(0, 0),
|
||||
native_replica_id(&store, 0, 0),
|
||||
ScannerPauseBacklogReplicaRecord::new(
|
||||
Some(newest.clone()),
|
||||
Some(ScannerPauseBacklogCommitRecord::new(newest.clone(), ids)),
|
||||
@@ -3675,10 +3888,8 @@ mod tests {
|
||||
set_runtime_error(None);
|
||||
let (_root, store, original) = native_expanded_backlog_store(1, false).await;
|
||||
let now = unix_now().saturating_add(60);
|
||||
let ids = (0..3)
|
||||
.flat_map(|pool_index| (0..2).map(move |set_index| replica_id(pool_index, set_index)))
|
||||
.collect::<Vec<_>>();
|
||||
let newer_member = replica_id(2, 1);
|
||||
let ids = native_replica_ids(&store);
|
||||
let newer_member = native_replica_id(&store, 2, 1);
|
||||
let others = ids.iter().copied().filter(|id| *id != newer_member).collect::<Vec<_>>();
|
||||
let middle = claim_scanner_pause_backlog_writer(&original, now).expect("middle generation");
|
||||
let newest = claim_scanner_pause_backlog_writer(&middle, now + 1).expect("newest generation");
|
||||
@@ -3837,10 +4048,8 @@ mod tests {
|
||||
let (_root, store, original) = native_expanded_backlog_store(1, false).await;
|
||||
let now = unix_now().saturating_add(60);
|
||||
let wedge = native_interleaved_expansion_wedge(&store, &original, now).await;
|
||||
let ids = (0..3)
|
||||
.flat_map(|pool_index| (0..2).map(move |set_index| replica_id(pool_index, set_index)))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(wedge.last, replica_id(2, 1));
|
||||
let ids = native_replica_ids(&store);
|
||||
assert_eq!(wedge.last, native_replica_id(&store, 2, 1));
|
||||
|
||||
// A different stable ledger at the newest key is a fork.
|
||||
let fork = claim_scanner_pause_backlog_writer(&original, now + 7).expect("forked writer generation");
|
||||
@@ -3851,7 +4060,7 @@ mod tests {
|
||||
assert_ne!(fork, wedge.acknowledged);
|
||||
replace_native_pause_backlog_replica(
|
||||
&store,
|
||||
replica_id(1, 0),
|
||||
native_replica_id(&store, 1, 0),
|
||||
ScannerPauseBacklogReplicaRecord::new(
|
||||
Some(fork.clone()),
|
||||
Some(ScannerPauseBacklogCommitRecord::new(fork, ids.clone())),
|
||||
@@ -3868,7 +4077,7 @@ mod tests {
|
||||
"{error}"
|
||||
);
|
||||
assert_eq!(native_replica_snapshot(&store).await, forked, "a refused claim keeps every record");
|
||||
replace_native_pause_backlog_replica(&store, replica_id(1, 0), wedge.rollback.clone()).await;
|
||||
replace_native_pause_backlog_replica(&store, native_replica_id(&store, 1, 0), wedge.rollback.clone()).await;
|
||||
|
||||
// A missing member that a stored commit names may have lost a
|
||||
// newer stable ledger.
|
||||
@@ -3903,7 +4112,7 @@ mod tests {
|
||||
.expect_err("retirement must refuse an unproven authority");
|
||||
assert!(refused.to_string().contains("unsafe authority conflict"), "{refused}");
|
||||
assert_eq!(native_replica_bytes(&store.pools[0].disk_set[0]).await.0, source_bytes);
|
||||
replace_native_pause_backlog_replica(&store, replica_id(1, 1), wedge.rollback.clone()).await;
|
||||
replace_native_pause_backlog_replica(&store, native_replica_id(&store, 1, 1), wedge.rollback.clone()).await;
|
||||
|
||||
// The same records without the conflict converge and retire.
|
||||
for set in 0..2 {
|
||||
@@ -4020,7 +4229,7 @@ mod tests {
|
||||
.authoritative_commit
|
||||
.as_ref()
|
||||
.expect("seed retains the old cohort proof");
|
||||
assert_eq!(committed.replicas, vec![replica_id(0, 0), replica_id(0, 1)]);
|
||||
assert_eq!(committed.replicas, vec![native_replica_id(&store, 0, 0), native_replica_id(&store, 0, 1)]);
|
||||
assert_eq!(loaded.replica_count, 6);
|
||||
if loaded.healthy_replicas == loaded.replica_count {
|
||||
break loaded;
|
||||
@@ -4037,13 +4246,13 @@ mod tests {
|
||||
.authoritative_commit
|
||||
.as_ref()
|
||||
.expect("seed retains the old cohort proof");
|
||||
assert_eq!(committed.replicas, vec![replica_id(0, 0), replica_id(0, 1)]);
|
||||
assert_eq!(committed.replicas, vec![native_replica_id(&store, 0, 0), native_replica_id(&store, 0, 1)]);
|
||||
assert_eq!(loaded.replica_count, 6);
|
||||
assert_eq!(loaded.healthy_replicas, 6);
|
||||
let seeded = loaded
|
||||
.replicas
|
||||
.iter()
|
||||
.find(|replica| replica.id == replica_id(2, 0))
|
||||
.find(|replica| replica.id == native_replica_id(&store, 2, 0))
|
||||
.expect("the canceled caller's admitted seed replica");
|
||||
assert!(matches!(
|
||||
&seeded.state,
|
||||
@@ -4055,7 +4264,7 @@ mod tests {
|
||||
let remaining = loaded
|
||||
.replicas
|
||||
.iter()
|
||||
.find(|replica| replica.id == replica_id(2, 1))
|
||||
.find(|replica| replica.id == native_replica_id(&store, 2, 1))
|
||||
.expect("the remaining seed replica");
|
||||
assert!(matches!(
|
||||
&remaining.state,
|
||||
@@ -4268,7 +4477,12 @@ mod tests {
|
||||
.prepare_scanner_pause_backlog_retirement_for_test(0, source_before.len() * 2)
|
||||
.await
|
||||
.expect("retire the old cohort's first pool");
|
||||
let surviving = [replica_id(1, 0), replica_id(1, 1), replica_id(2, 0), replica_id(2, 1)];
|
||||
let surviving = [
|
||||
native_replica_id(&store, 1, 0),
|
||||
native_replica_id(&store, 1, 1),
|
||||
native_replica_id(&store, 2, 0),
|
||||
native_replica_id(&store, 2, 1),
|
||||
];
|
||||
let newer = claim_scanner_pause_backlog_writer(&old.ledger, now + 2).expect("surviving writer");
|
||||
replace_native_pause_backlog_replica(
|
||||
&store,
|
||||
@@ -4333,7 +4547,7 @@ mod tests {
|
||||
assert_eq!(loaded.ledger, original);
|
||||
assert!(loaded.durable && loaded.stable_matches_ledger);
|
||||
let committed = loaded.authoritative_commit.expect("survivor commit proof");
|
||||
assert_eq!(committed.replicas, vec![replica_id(2, 0), replica_id(2, 1)]);
|
||||
assert_eq!(committed.replicas, vec![native_replica_id(&store, 2, 0), native_replica_id(&store, 2, 1)]);
|
||||
}
|
||||
shutdown_native_retirement_store(store).await;
|
||||
});
|
||||
@@ -4911,7 +5125,135 @@ mod tests {
|
||||
}
|
||||
|
||||
fn replica_id(pool_index: usize, set_index: usize) -> ScannerPauseBacklogReplicaId {
|
||||
ScannerPauseBacklogReplicaId { pool_index, set_index }
|
||||
ScannerPauseBacklogReplicaId {
|
||||
pool_index,
|
||||
set_index,
|
||||
set_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn native_replica_id(store: &ECStore, pool_index: usize, set_index: usize) -> ScannerPauseBacklogReplicaId {
|
||||
ScannerPauseBacklogReplicaId::from_set(&store.pools[pool_index].disk_set[set_index])
|
||||
.expect("native replica has an immutable format identity")
|
||||
}
|
||||
|
||||
fn native_replica_ids(store: &ECStore) -> Vec<ScannerPauseBacklogReplicaId> {
|
||||
store
|
||||
.pools
|
||||
.iter()
|
||||
.flat_map(|pool| pool.disk_set.iter())
|
||||
.map(|set| ScannerPauseBacklogReplicaId::from_set(set).expect("native replica format identity"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn bound_replica_id(pool_index: usize, identity: u128) -> ScannerPauseBacklogReplicaId {
|
||||
ScannerPauseBacklogReplicaId {
|
||||
pool_index,
|
||||
set_index: 0,
|
||||
set_id: Some(uuid::Uuid::from_u128(identity)),
|
||||
}
|
||||
}
|
||||
|
||||
fn missing_replica(id: ScannerPauseBacklogReplicaId) -> ScannerPauseBacklogReplica {
|
||||
ScannerPauseBacklogReplica {
|
||||
id,
|
||||
revision: Some(DataUsageCacheRevision::Missing),
|
||||
state: ScannerPauseBacklogReplicaState::Missing,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bound_membership_survives_pool_reindexing() {
|
||||
let ledger = durable_ledger(50);
|
||||
let previous = bound_replica_id(2, 22);
|
||||
let current = bound_replica_id(1, 22);
|
||||
let added = bound_replica_id(2, 33);
|
||||
let record = replica_record_for_members(&ledger, &ledger, &[previous]);
|
||||
assert_eq!(record.replica_schema_version, 1);
|
||||
let loaded = select_scanner_pause_backlog_replicas(vec![
|
||||
missing_replica(bound_replica_id(0, 11)),
|
||||
decoded_replica(current, &record),
|
||||
missing_replica(added),
|
||||
])
|
||||
.expect("a surviving set keeps its commit proof after its routing index changes");
|
||||
assert_eq!(loaded.ledger, ledger);
|
||||
assert!(loaded.durable);
|
||||
assert_eq!(loaded.authoritative_commit.expect("surviving native proof").replicas, vec![current]);
|
||||
|
||||
let mut replicas = vec![retirement_replica(current, &record)];
|
||||
for id in [bound_replica_id(0, 11), added] {
|
||||
replicas.push(ScannerPauseBacklogRetirementReplica {
|
||||
pool_index: id.pool_index,
|
||||
set_index: id.set_index,
|
||||
set_id: id.set_id,
|
||||
writable: true,
|
||||
data: None,
|
||||
});
|
||||
}
|
||||
let plan = plan_scanner_pause_backlog_retirement(1, &replicas)
|
||||
.expect("reindexed native authority permits safe retirement")
|
||||
.expect("new survivors need the existing ledger");
|
||||
assert_eq!(planned_stable_ledger(&plan), ledger);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bound_membership_does_not_rebind_a_missing_set_to_its_reused_index() {
|
||||
let ledger = durable_ledger(50);
|
||||
let missing = bound_replica_id(2, 22);
|
||||
let replacement = bound_replica_id(2, 33);
|
||||
let record = replica_record_for_members(&ledger, &ledger, &[missing]);
|
||||
assert!(
|
||||
select_scanner_pause_backlog_replicas(vec![
|
||||
missing_replica(bound_replica_id(0, 11)),
|
||||
decoded_replica(replacement, &record),
|
||||
])
|
||||
.is_err(),
|
||||
"copied bytes on a new set cannot impersonate the missing commit member"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_membership_binds_only_after_its_existing_proof_is_verified() {
|
||||
let ledger = durable_ledger(50);
|
||||
let legacy = replica_record_for_members(&ledger, &ledger, &[replica_id(2, 0)]);
|
||||
assert_eq!(legacy.replica_schema_version, 1);
|
||||
let current = bound_replica_id(2, 22);
|
||||
let loaded = select_scanner_pause_backlog_replicas(vec![decoded_replica(current, &legacy)])
|
||||
.expect("unchanged legacy membership remains readable");
|
||||
let upgraded = stable_scanner_pause_backlog_record(
|
||||
&loaded.ledger,
|
||||
loaded.authoritative_commit.as_ref(),
|
||||
&scanner_pause_backlog_replica_ids(&loaded.replicas),
|
||||
);
|
||||
upgraded
|
||||
.validate()
|
||||
.expect("the replacement record binds every committed member");
|
||||
assert_eq!(upgraded.replica_schema_version, 1);
|
||||
assert_eq!(upgraded.committed.expect("bound membership").replicas, vec![current]);
|
||||
|
||||
assert!(
|
||||
select_scanner_pause_backlog_replicas(vec![
|
||||
decoded_replica(bound_replica_id(1, 22), &legacy),
|
||||
missing_replica(bound_replica_id(2, 33)),
|
||||
])
|
||||
.is_err(),
|
||||
"an already stranded legacy proof must not guess its old member's identity"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bound_membership_rejects_nil_duplicate_and_mixed_identities() {
|
||||
let ledger = durable_ledger(50);
|
||||
for members in [
|
||||
vec![bound_replica_id(0, 0)],
|
||||
vec![bound_replica_id(0, 11), bound_replica_id(1, 11)],
|
||||
vec![bound_replica_id(0, 11), bound_replica_id(0, 22)],
|
||||
vec![bound_replica_id(0, 11), replica_id(1, 0)],
|
||||
] {
|
||||
let record = replica_record_for_members(&ledger, &ledger, &members);
|
||||
assert_eq!(record.replica_schema_version, 1);
|
||||
assert!(record.validate().is_err());
|
||||
}
|
||||
}
|
||||
|
||||
fn decoded_replica(
|
||||
@@ -5155,6 +5497,7 @@ mod tests {
|
||||
.map(|set_index| ScannerPauseBacklogReplicaId {
|
||||
pool_index: 0,
|
||||
set_index,
|
||||
set_id: None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
replica_record_for_members(stable, committed, &replicas)
|
||||
@@ -5167,6 +5510,7 @@ mod tests {
|
||||
ScannerPauseBacklogRetirementReplica {
|
||||
pool_index: id.pool_index,
|
||||
set_index: id.set_index,
|
||||
set_id: id.set_id,
|
||||
writable: true,
|
||||
data: Some(serde_json::to_vec(record).expect("native record bytes")),
|
||||
}
|
||||
@@ -5294,6 +5638,7 @@ mod tests {
|
||||
replicas.extend(targets.iter().map(|id| ScannerPauseBacklogRetirementReplica {
|
||||
pool_index: id.pool_index,
|
||||
set_index: id.set_index,
|
||||
set_id: id.set_id,
|
||||
writable: true,
|
||||
data: None,
|
||||
}));
|
||||
|
||||
@@ -113,17 +113,37 @@ pub(super) async fn setup_scanner_cycle_store_at_path_with_layout_and_disk_prein
|
||||
sets_per_pool: usize,
|
||||
drives_per_set: usize,
|
||||
preinitialize_disks: bool,
|
||||
) -> Arc<ECStore> {
|
||||
setup_scanner_cycle_store_with_pool_order(
|
||||
root,
|
||||
seed_usage_baseline,
|
||||
&(0..pool_count).collect::<Vec<_>>(),
|
||||
sets_per_pool,
|
||||
drives_per_set,
|
||||
preinitialize_disks,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) async fn setup_scanner_cycle_store_with_pool_order(
|
||||
root: &Path,
|
||||
seed_usage_baseline: bool,
|
||||
pool_order: &[usize],
|
||||
sets_per_pool: usize,
|
||||
drives_per_set: usize,
|
||||
preinitialize_disks: bool,
|
||||
) -> Arc<ECStore> {
|
||||
init_ecstore_config_for_scanner_tests();
|
||||
let pool_count = pool_order.len();
|
||||
let mut pools = Vec::with_capacity(pool_count);
|
||||
for pool_index in 0..pool_count {
|
||||
for (pool_index, physical_pool) in pool_order.iter().enumerate() {
|
||||
let mut endpoints = Vec::new();
|
||||
for set_index in 0..sets_per_pool {
|
||||
for disk_index in 0..drives_per_set {
|
||||
let disk_path = if sets_per_pool == 1 {
|
||||
root.join(format!("pool{pool_index}/disk{disk_index}"))
|
||||
root.join(format!("pool{physical_pool}/disk{disk_index}"))
|
||||
} else {
|
||||
root.join(format!("pool{pool_index}/set{set_index}/disk{disk_index}"))
|
||||
root.join(format!("pool{physical_pool}/set{set_index}/disk{disk_index}"))
|
||||
};
|
||||
tokio::fs::create_dir_all(&disk_path)
|
||||
.await
|
||||
@@ -144,7 +164,7 @@ pub(super) async fn setup_scanner_cycle_store_at_path_with_layout_and_disk_prein
|
||||
cmd_line: if pool_count == 1 && sets_per_pool == 1 {
|
||||
"scanner-cycle-metrics".to_string()
|
||||
} else {
|
||||
format!("scanner-cycle-metrics-pool-{pool_index}")
|
||||
format!("scanner-cycle-metrics-pool-{physical_pool}")
|
||||
},
|
||||
platform: format!("OS: {} | Arch: {}", std::env::consts::OS, std::env::consts::ARCH),
|
||||
});
|
||||
|
||||
@@ -72,6 +72,8 @@ Ordinary publication rechecks the selected pool against the durable pool metadat
|
||||
|
||||
Running, queued, failed, canceled, and completed decommission states exclude the source from new ordinary publication, including new multipart uploads. Previously created multipart uploads retain their drain path while the source remains non-terminal; terminal source states reject further multipart publication. Failed and canceled entries become writable for new ordinary publication only after an allowed clear operation removes that state. This check does not change repair admission or the separate fence for operations that only release capacity.
|
||||
|
||||
Bucket creation and deletion remove stale bucket usage through namespace maintenance, independently of the scanner publication pause. Failed or canceled decommission state alone does not reject that cleanup. The update checks the logical snapshot revision while holding the fixed and physical object locks, then conditionally publishes to a writable pool; a suspended source remains available for decommission to reconcile against the newer target. Cleanup preserves the scanner epoch and cycle, and retains the bucket namespace and movement guards until its physical writes finish even if the request is canceled. Namespace lease loss still fences the write. Scanner publication continues to require its existing movement admission.
|
||||
|
||||
For mixed batch deletes, only the pools selected to receive new delete markers are publication targets. Exact-version deletions on other pools remain protected by the same pool metadata read fence, without treating the retiring source or an unrelated reserved target as a destination for those markers.
|
||||
|
||||
### Shared Capacity On Healthy Targets
|
||||
@@ -143,6 +145,10 @@ Existing capacity-owner and mutation checks reconcile the pending intent before
|
||||
source cleanup; the replica exception does not clear an unknown intent, rewrite
|
||||
the native target, or change the scanner's committed-membership selection.
|
||||
|
||||
Backlog membership also carries an optional `set_id`, the first disk-slot UUID from the erasure set's persisted format. This identity survives pool removal, reordering, and disk replacement; `pool_index` and `set_index` remain routing locations. Readers resolve bound members by that UUID before checking the existing commit and rollback proofs. A new pool at a removed member's old index cannot impersonate that member. New publications bind every member, while positional records remain readable and gain identities only after their existing authority is verified. Replica schema 1 is retained; 1.0.4 readers ignore the additive identity field and still use positional membership.
|
||||
|
||||
Finish upgrading all scanner-capable nodes before changing pool membership. An older writer can replace a bound record with a positional one. If a legacy record was already stranded by pool reindexing, the missing identity cannot be guessed safely: restore the known topology in which its proof was readable, then let the updated writer bind the membership before changing it again. Do not delete or reset the backlog ledger to bypass this check. Partial writes, canceled callers, missing members, and conflicting authority remain subject to the native CAS and recovery rules above.
|
||||
|
||||
## MinIO Divergence Decisions
|
||||
|
||||
Behavior that is close to MinIO but not byte-for-byte identical. Changing either decision requires an operator compatibility note and updated characterization tests.
|
||||
|
||||
@@ -116,40 +116,18 @@ Functional-chain health runs on a hosted runner independently of the lab fleet.
|
||||
|
||||
## Release validation
|
||||
|
||||
Post-merge and tag-driven; not a substitute for a PR gate. Every public publication exit consumes the release certification described here; this certification path is separate from the reusable CI artifact contract in [verification plans](ci-verification-pipeline.md), whose archives expire after one day and are never used as release certification.
|
||||
|
||||
The versioned plan is `.config/release-validation-plan.json` (`schema_version=1`): release types, target OS/arch/ABI, supported features, a fixed harness SHA per lane, required case IDs, environment capabilities, timeouts, applicability conditions, and an explicit `upgrade_sources` list carrying the real artifact digests of the supported N-1/N-2 sources plus a stated rollback basis — compatibility and rollback are never invented or defaulted. `scripts/release/release_gate.py validate` expands and freezes `plan_sha256` and the required case set before reading results, then binds that plan, a `candidate.json` (product 40-character SHA, version, release profile, features, immutable deliverable locations with SHA256 and wrapped-binary SHA256, image digest) and the shared G01 evidence-envelope set (rustfs/backlog#2658) of one parent candidate batch, and writes machine JSON, JUnit and a summary. Within a batch the unique key is `(lane, platform{os,arch,abi}, case_id)`; the top-level run/attempt is the parent batch and the nested workflow run/attempt is the child workflow identity, and both are validated separately (workflow SHA and product build SHA are bound independently and need not be equal). Missing, duplicate or extra cases, stale attempts, wrong digests, wrong platforms, wrong harness SHAs, zero tests, cancellation, timeout, untrusted sources and any non-PASS required item block with classified errors; only a complete legal success returns 0. `verify-promotion` re-checks the exact bytes about to be published against the passed list and fails on a swapped package or an edited plan after verification. preview, prerelease and stable all require an explicit plan; preview may use the plan's smaller pre-declared set but never bypasses artifact identity and startup checks. development artifacts are explicitly uncertified and never touch official or latest channels.
|
||||
Post-merge and tag-driven; not a substitute for a PR gate.
|
||||
|
||||
| Trigger | Workflow / job | Result |
|
||||
|---|---|---|
|
||||
| Nightly schedule, dispatch | `build.yml` `build-rustfs` (development build over the full platform matrix) | build artifacts and the uncertified dev download channel; no release publication |
|
||||
| Valid release or preview tag | `build.yml` `build-rustfs`, `release-certification`, `create-release`, `upload-release-assets`, `publish-release`, `promote-release-artifacts` | staged binaries, draft release, checksummed assets, then certified publish and promotion of the same bytes |
|
||||
| Successful non-preview release-tag build (`workflow_run`) | `docker.yml` `release-certification`, `build-docker`, `scan-docker-image` | multi-architecture images staged, verified against the certification and promoted, plus vulnerability report |
|
||||
| Successful release-tag build (`workflow_run`) | `package.yml` `release-certification`, `package`, `promote-packages` | DEB/RPM packages with wrapped-binary verification, staged then promoted with checksums |
|
||||
| Successful non-preview release-tag build (`workflow_run`) | `helm-package.yml` `release-certification`, `build-helm-package`, `publish-helm-package` | versioned chart and repository index, only over a certified image promotion record |
|
||||
| Nightly schedule, dispatch | `build.yml` `build-rustfs` (development build over the full platform matrix) | build artifacts and the dev download channel; no release publication |
|
||||
| Valid release or preview tag | `build.yml` `build-rustfs`, `create-release`, `upload-release-assets`, `publish-release` | draft release, checksummed assets, publish |
|
||||
| Successful non-preview release-tag build (`workflow_run`) | `docker.yml` `build-docker`, `scan-docker-image` | multi-architecture images and vulnerability report |
|
||||
| Successful release-tag build (`workflow_run`) | `package.yml` `package` | DEB/RPM packages and checksums uploaded to the release |
|
||||
| Successful non-preview release-tag build (`workflow_run`) | `helm-package.yml` `build-helm-package`, `publish-helm-package` | versioned chart and repository index |
|
||||
| Final tag's release published | `build.yml` `cleanup-preview-releases` | deletes every `<target>-preview.<N>` Release for that target; the tags are kept |
|
||||
|
||||
Publication exits and their gate:
|
||||
|
||||
| Exit | Writer | Certification consumed |
|
||||
|---|---|---|
|
||||
| GitHub Release draft → public | `build.yml` `publish-release` | `release-certification` success and per-asset `verify-promotion` |
|
||||
| Official R2 binary path `artifacts/rustfs/release/` | `build.yml` `promote-release-artifacts` | `release-certification` plus `verify-promotion`; the build job itself only writes `artifacts/rustfs/staging/<run>/<attempt>/` |
|
||||
| `latest.json` version pointer | `build.yml` `update-latest-version` | transitively through `publish-release` |
|
||||
| Image channels (Docker Hub, GHCR, Quay tags) | `docker.yml` `build-docker` promotion step | certified input digests, staged image binary identity and `verify-promotion`, then promotion of the same digest with `imagetools create` |
|
||||
| Official R2 package paths `packages/release` and `packages/latest` | `package.yml` `promote-packages` | `release-certification` and wrapped-binary `verify-promotion`; the package job only stages |
|
||||
| GitHub release package assets | `package.yml` `package` | `release-certification` and wrapped-binary `verify-promotion` before upload |
|
||||
| Helm chart repository (`rustfs/helm` push) | `helm-package.yml` `publish-helm-package` | `release-certification` plus the Docker workflow's image-promotion record bound to the same candidate |
|
||||
|
||||
Downstream workflows fail closed when the trusted certification is absent: a manual dispatch without certification fails and pushes no official tag. Only results from the trusted verification sources listed in the plan (currently the `rustfs/s3chaos` `release-gate.yml` envelope runs) carry release authority; a PR-uploaded JSON set cannot certify a release. Retries reuse the same candidate, only the staged bytes are promoted (no rebuild-after-verify), and no downstream step re-resolves a mutable `latest`.
|
||||
|
||||
Use an exact preview tag for an end-to-end release rehearsal. Manual dispatches are backfill/debug paths and do not prove the automatic `workflow_run` chain. The committed fixtures under `testdata/release-gate/` and the commands below are contract rehearsals of this orchestration; they are explicitly not product release evidence and not live verifier results.
|
||||
|
||||
```bash
|
||||
python3 -m unittest discover -s scripts/release -p 'test_release_gate.py'
|
||||
python3 scripts/release/release_gate.py validate --plan testdata/release-gate/plan.json --candidate testdata/release-gate/candidate.json --results testdata/release-gate/pass --output target/release-gate-check
|
||||
bash scripts/security/check_preview_release_workflow.sh
|
||||
```
|
||||
Use an exact preview tag for an end-to-end release rehearsal. Manual dispatches are backfill/debug paths and do not prove the automatic `workflow_run` chain.
|
||||
|
||||
## Change checklist
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,376 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2024 RustFS Team
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Contract tests for scripts/release/release_gate.py (rustfs/backlog#2662).
|
||||
|
||||
Every fixture under testdata/release-gate/ is a contract rehearsal only: these
|
||||
tests prove the gate's fail-closed orchestration and must never be read as
|
||||
product release evidence or as R02 live verification results.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
GATE = ROOT / "scripts" / "release" / "release_gate.py"
|
||||
FIXTURES = ROOT / "testdata" / "release-gate"
|
||||
PLAN = FIXTURES / "plan.json"
|
||||
CANDIDATE = FIXTURES / "candidate.json"
|
||||
ARTIFACT = FIXTURES / "artifacts" / "rustfs-linux-x86_64-gnu-v9.9.9-fixture.zip"
|
||||
ARTIFACT_NAME = "rustfs-linux-x86_64-gnu-v9.9.9-fixture.zip"
|
||||
REAL_PLAN = ROOT / ".config" / "release-validation-plan.json"
|
||||
|
||||
|
||||
def run_gate(*arguments):
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(GATE), *map(str, arguments)],
|
||||
cwd=ROOT,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def run_validate(results, output, plan=PLAN, candidate=CANDIDATE):
|
||||
return run_gate(
|
||||
"validate",
|
||||
"--plan", plan,
|
||||
"--candidate", candidate,
|
||||
"--results", results,
|
||||
"--output", output,
|
||||
)
|
||||
|
||||
|
||||
def run_promote(output, plan=PLAN, candidate=CANDIDATE, summary=None, artifact=ARTIFACT,
|
||||
artifact_name=ARTIFACT_NAME, artifact_sha256=None):
|
||||
arguments = [
|
||||
"verify-promotion",
|
||||
"--plan", plan,
|
||||
"--candidate", candidate,
|
||||
"--summary", summary or output,
|
||||
]
|
||||
if artifact is not None:
|
||||
arguments += ["--artifact", artifact]
|
||||
if artifact_name is not None:
|
||||
arguments += ["--artifact-name", artifact_name]
|
||||
if artifact_sha256 is not None:
|
||||
arguments += ["--artifact-sha256", artifact_sha256]
|
||||
if output is not None:
|
||||
arguments += ["--output", output]
|
||||
return run_gate(*arguments)
|
||||
|
||||
|
||||
def load_summary(output):
|
||||
return json.loads((Path(output) / "release-gate-summary.json").read_text())
|
||||
|
||||
|
||||
def finding_codes(summary):
|
||||
return {finding["code"] for finding in summary["findings"]}
|
||||
|
||||
|
||||
class GateFixtureTests(unittest.TestCase):
|
||||
"""Assertion matrix rows from rustfs/backlog#2662 exercised on the fixtures."""
|
||||
|
||||
def setUp(self):
|
||||
self._temporary = tempfile.TemporaryDirectory(prefix="release-gate-test.")
|
||||
self.addCleanup(self._temporary.cleanup)
|
||||
self.output = Path(self._temporary.name)
|
||||
|
||||
def validate_fixture(self, name, expect):
|
||||
output = self.output / name
|
||||
result = run_validate(FIXTURES / name, output)
|
||||
self.assertEqual(result.returncode, expect, result.stderr)
|
||||
return result, load_summary(output)
|
||||
|
||||
def test_all_required_pass_and_identity_consistent(self):
|
||||
# Matrix: all required PASS and identity consistent -> validate and
|
||||
# verify-promotion return 0.
|
||||
result, summary = self.validate_fixture("pass", 0)
|
||||
self.assertEqual(summary["state"], "pass")
|
||||
self.assertTrue(summary["publish_allowed"])
|
||||
self.assertEqual(len(summary["required_cases"]), 5)
|
||||
self.assertEqual(len(summary["observed"]), 5)
|
||||
self.assertEqual(summary["findings"], [])
|
||||
expansion = json.loads((self.output / "pass" / "plan-expansion.json").read_text())
|
||||
self.assertEqual(len(expansion["required_cases"]), 5)
|
||||
promote = run_promote(self.output / "pass")
|
||||
self.assertEqual(promote.returncode, 0, promote.stderr)
|
||||
|
||||
def test_cross_platform_same_case_and_split_shas_accepted(self):
|
||||
# Matrix: the same case name on two platforms, and a workflow SHA that
|
||||
# differs from the (legally bound) product SHA, must not be rejected.
|
||||
result, summary = self.validate_fixture("cross-platform", 0)
|
||||
self.assertEqual(summary["state"], "pass")
|
||||
product_commit = json.loads(CANDIDATE.read_text())["product_commit"]
|
||||
workflows = []
|
||||
for name in sorted((FIXTURES / "cross-platform").glob("*.json")):
|
||||
if name.name == "batch.json":
|
||||
continue
|
||||
envelope = json.loads(name.read_text())
|
||||
workflows.append(envelope["workflow"]["workflow_sha"])
|
||||
self.assertNotEqual(envelope["workflow"]["workflow_sha"], product_commit)
|
||||
self.assertEqual(len(workflows), 5)
|
||||
case_names = [entry["case_id"] for entry in summary["observed"]]
|
||||
self.assertEqual(case_names.count("startup"), 2)
|
||||
self.assertEqual(case_names.count("artifact-identity"), 2)
|
||||
|
||||
def test_missing_case_names_the_case(self):
|
||||
result, summary = self.validate_fixture("missing-case", 1)
|
||||
self.assertIn("missing_case", finding_codes(summary))
|
||||
missing = [f for f in summary["findings"] if f["code"] == "missing_case"]
|
||||
self.assertTrue(any(f.get("case_id") == "startup" for f in missing), missing)
|
||||
self.assertFalse(summary["publish_allowed"])
|
||||
|
||||
def test_duplicate_key_names_the_case(self):
|
||||
result, summary = self.validate_fixture("duplicate-key", 1)
|
||||
self.assertIn("duplicate_key", finding_codes(summary))
|
||||
duplicates = [f for f in summary["findings"] if f["code"] == "duplicate_key"]
|
||||
self.assertTrue(any(f.get("case_id") == "startup" for f in duplicates), duplicates)
|
||||
self.assertFalse(summary["publish_allowed"])
|
||||
|
||||
def test_extra_unplanned_case_names_the_case(self):
|
||||
result, summary = self.validate_fixture("extra-case", 1)
|
||||
self.assertIn("extra_case", finding_codes(summary))
|
||||
extra = [f for f in summary["findings"] if f["code"] == "extra_case"]
|
||||
self.assertTrue(any(f.get("case_id") == "surprise-case" for f in extra), extra)
|
||||
self.assertFalse(summary["publish_allowed"])
|
||||
|
||||
def test_non_pass_required_items_block(self):
|
||||
# Matrix: any skipped / infra_error / quarantined required item blocks
|
||||
# and no official publish write is allowed.
|
||||
for name in ("non-pass-skipped", "non-pass-infra-error", "non-pass-quarantined"):
|
||||
with self.subTest(name=name):
|
||||
result, summary = self.validate_fixture(name, 1)
|
||||
self.assertIn("non_pass", finding_codes(summary))
|
||||
self.assertFalse(summary["publish_allowed"])
|
||||
|
||||
def test_child_workflow_cancellation_blocks(self):
|
||||
result, summary = self.validate_fixture("child-cancelled", 1)
|
||||
self.assertIn("cancelled", finding_codes(summary))
|
||||
self.assertFalse(summary["publish_allowed"])
|
||||
|
||||
def test_batch_timeout_blocks(self):
|
||||
result, summary = self.validate_fixture("timeout", 1)
|
||||
self.assertIn("timeout", finding_codes(summary))
|
||||
self.assertFalse(summary["publish_allowed"])
|
||||
|
||||
def test_same_tag_different_bytes_is_rejected(self):
|
||||
result, summary = self.validate_fixture("same-tag-different-bytes", 1)
|
||||
self.assertIn("digest_mismatch", finding_codes(summary))
|
||||
self.assertFalse(summary["publish_allowed"])
|
||||
|
||||
def test_stale_attempt_cannot_be_reused(self):
|
||||
result, summary = self.validate_fixture("stale-attempt", 1)
|
||||
self.assertIn("stale_attempt", finding_codes(summary))
|
||||
self.assertFalse(summary["publish_allowed"])
|
||||
|
||||
def test_wrong_platform_is_rejected(self):
|
||||
result, summary = self.validate_fixture("wrong-platform", 1)
|
||||
self.assertIn("platform_mismatch", finding_codes(summary))
|
||||
self.assertFalse(summary["publish_allowed"])
|
||||
|
||||
def test_wrong_harness_sha_cannot_be_reused(self):
|
||||
result, summary = self.validate_fixture("wrong-harness", 1)
|
||||
self.assertIn("harness_mismatch", finding_codes(summary))
|
||||
self.assertFalse(summary["publish_allowed"])
|
||||
|
||||
def test_zero_tests_block(self):
|
||||
result, summary = self.validate_fixture("zero-tests", 1)
|
||||
self.assertIn("zero_tests", finding_codes(summary))
|
||||
self.assertFalse(summary["publish_allowed"])
|
||||
|
||||
def test_untrusted_source_blocks(self):
|
||||
# A PR-uploaded or foreign JSON set must never gain release authority.
|
||||
result, summary = self.validate_fixture("untrusted-source", 1)
|
||||
self.assertIn("untrusted_source", finding_codes(summary))
|
||||
self.assertFalse(summary["publish_allowed"])
|
||||
|
||||
def test_swapped_package_after_verification_fails_promotion(self):
|
||||
# Matrix: swapping a published package after verification must fail
|
||||
# verify-promotion.
|
||||
output = self.output / "certified"
|
||||
self.assertEqual(run_validate(FIXTURES / "pass", output).returncode, 0)
|
||||
promote = run_promote(
|
||||
output,
|
||||
artifact=FIXTURES / "promotion-swap" / ARTIFACT_NAME,
|
||||
summary=output,
|
||||
)
|
||||
self.assertEqual(promote.returncode, 1, promote.stdout)
|
||||
self.assertIn("artifact_mismatch", promote.stderr)
|
||||
report = json.loads((output / "promotion-check.json").read_text())
|
||||
self.assertEqual(report["state"], "blocked")
|
||||
self.assertEqual(finding_codes(report), {"artifact_mismatch"})
|
||||
|
||||
def test_edited_plan_after_verification_fails_promotion(self):
|
||||
# Matrix: editing the plan after verification must fail
|
||||
# verify-promotion; the old certification cannot be reused.
|
||||
output = self.output / "certified"
|
||||
self.assertEqual(run_validate(FIXTURES / "pass", output).returncode, 0)
|
||||
promote = run_promote(
|
||||
output,
|
||||
plan=FIXTURES / "promotion-edit" / "plan.json",
|
||||
summary=output,
|
||||
)
|
||||
self.assertEqual(promote.returncode, 1, promote.stdout)
|
||||
self.assertIn("plan_edited", promote.stderr)
|
||||
|
||||
def test_promotion_without_certification_fails(self):
|
||||
output = self.output / "blocked"
|
||||
self.assertEqual(run_validate(FIXTURES / "missing-case", output).returncode, 1)
|
||||
promote = run_promote(output, summary=output)
|
||||
self.assertEqual(promote.returncode, 1, promote.stdout)
|
||||
self.assertIn("certification_missing", promote.stderr)
|
||||
|
||||
def test_summary_junit_and_expansion_are_written(self):
|
||||
output = self.output / "pass"
|
||||
self.assertEqual(run_validate(FIXTURES / "pass", output).returncode, 0)
|
||||
summary = load_summary(output)
|
||||
self.assertEqual(summary["plan_sha256"], json.loads(CANDIDATE.read_text())["plan_sha256"])
|
||||
junit = (output / "release-gate.junit.xml").read_text()
|
||||
self.assertIn("release-gate", junit)
|
||||
self.assertIn("identity/linux-x86_64-gnu/startup", junit)
|
||||
markdown = (output / "release-gate-summary.md").read_text()
|
||||
self.assertIn("state: pass", markdown)
|
||||
|
||||
def test_error_messages_carry_no_credentials(self):
|
||||
for name in ("missing-case", "stale-attempt", "untrusted-source", "zero-tests"):
|
||||
output = self.output / name
|
||||
run_validate(FIXTURES / name, output)
|
||||
text = output.joinpath("release-gate-summary.json").read_text()
|
||||
text += (output / "release-gate-summary.md").read_text()
|
||||
lowered = text.lower()
|
||||
for marker in ("secret", "password", "access_key", "akia", "bearer "):
|
||||
self.assertNotIn(marker, lowered, f"{name} leaked a credential-shaped string")
|
||||
|
||||
|
||||
class PlanContractTests(unittest.TestCase):
|
||||
def test_real_plan_is_explicit_about_sources_and_rollback(self):
|
||||
sys.path.insert(0, str(GATE.parent))
|
||||
import release_gate
|
||||
|
||||
plan = json.loads(REAL_PLAN.read_text())
|
||||
info = release_gate.validate_plan(plan)
|
||||
sources = info["upgrade_sources"]
|
||||
self.assertGreaterEqual(len(sources["sources"]), 1)
|
||||
self.assertFalse(sources["rollback_supported"])
|
||||
self.assertTrue(sources["basis"].strip())
|
||||
self.assertTrue(sources["rollback_basis"].strip())
|
||||
for source in sources["sources"]:
|
||||
self.assertEqual(len(source["artifact_sha256"]), 64)
|
||||
|
||||
def test_plan_requires_trusted_workflows_and_frozen_harness(self):
|
||||
sys.path.insert(0, str(GATE.parent))
|
||||
import release_gate
|
||||
|
||||
plan = json.loads(REAL_PLAN.read_text())
|
||||
plan["trusted_workflows"] = []
|
||||
with self.assertRaises(release_gate.GateError):
|
||||
release_gate.validate_plan(plan)
|
||||
plan = json.loads(REAL_PLAN.read_text())
|
||||
plan["lanes"][0]["harness_sha"] = "not-a-sha"
|
||||
with self.assertRaises(release_gate.GateError):
|
||||
release_gate.validate_plan(plan)
|
||||
plan = json.loads(REAL_PLAN.read_text())
|
||||
plan["upgrade_sources"].pop("rollback_supported")
|
||||
with self.assertRaises(release_gate.GateError):
|
||||
release_gate.validate_plan(plan)
|
||||
|
||||
def test_plan_sha256_is_semantic_and_detects_required_set_edits(self):
|
||||
sys.path.insert(0, str(GATE.parent))
|
||||
import release_gate
|
||||
|
||||
plan = json.loads(PLAN.read_text())
|
||||
baseline = release_gate.canonical_sha256(plan)
|
||||
self.assertEqual(release_gate.canonical_sha256(json.loads(json.dumps(plan))), baseline)
|
||||
edited = json.loads(PLAN.read_text())
|
||||
edited["lanes"][0]["cases"] = ["artifact-identity"]
|
||||
self.assertNotEqual(release_gate.canonical_sha256(edited), baseline)
|
||||
|
||||
|
||||
class PublicationWiringTests(unittest.TestCase):
|
||||
"""Static checks that the real publication exits consume the gate."""
|
||||
|
||||
def read(self, relative):
|
||||
return (ROOT / relative).read_text()
|
||||
|
||||
def job_block(self, workflow, job):
|
||||
match = re.search(
|
||||
rf"^ {re.escape(job)}:\n(.*?)(?=^ [A-Za-z#])",
|
||||
workflow,
|
||||
re.S | re.M,
|
||||
)
|
||||
self.assertIsNotNone(match, f"job {job} not found")
|
||||
return match.group(1)
|
||||
|
||||
def test_build_publish_requires_certification(self):
|
||||
workflow = self.read(".github/workflows/build.yml")
|
||||
self.assertIn("\n release-certification:", workflow)
|
||||
publish_block = self.job_block(workflow, "publish-release")
|
||||
self.assertIn("release-certification", publish_block)
|
||||
self.assertIn("release_gate.py validate", workflow)
|
||||
self.assertIn("release_gate.py verify-promotion", workflow)
|
||||
|
||||
def test_build_time_official_r2_writes_are_staged(self):
|
||||
workflow = self.read(".github/workflows/build.yml")
|
||||
self.assertIn("artifacts/rustfs/staging/", workflow)
|
||||
release_writes = [
|
||||
line for line in workflow.splitlines()
|
||||
if "artifacts/rustfs/release/" in line
|
||||
]
|
||||
# Only the certification-gated promotion job may write the official path.
|
||||
self.assertTrue(release_writes, "the promotion job must write the official R2 path")
|
||||
promotion_block = self.job_block(workflow, "promote-release-artifacts")
|
||||
self.assertIn("verify-promotion", promotion_block)
|
||||
self.assertIn("artifacts/rustfs/release/", promotion_block)
|
||||
build_block = self.job_block(workflow, "build-rustfs")
|
||||
self.assertNotIn('R2_PATH="s3://${R2_BUCKET}/artifacts/rustfs/release/"', build_block)
|
||||
|
||||
def test_downstream_workflows_verify_certification(self):
|
||||
docker = self.read(".github/workflows/docker.yml")
|
||||
self.assertIn("\n release-certification:", docker)
|
||||
build_docker_block = self.job_block(docker, "build-docker")
|
||||
self.assertIn("release-certification", build_docker_block)
|
||||
self.assertIn("verify-promotion", docker)
|
||||
package = self.read(".github/workflows/package.yml")
|
||||
self.assertIn("\n release-certification:", package)
|
||||
package_block = self.job_block(package, "package")
|
||||
self.assertIn("release-certification", package_block)
|
||||
helm = self.read(".github/workflows/helm-package.yml")
|
||||
self.assertIn("\n release-certification:", helm)
|
||||
helm_block = self.job_block(helm, "build-helm-package")
|
||||
self.assertIn("release-certification", helm_block)
|
||||
|
||||
def test_manual_downstream_dispatch_without_certification_fails(self):
|
||||
# Every downstream certification job must fail closed when the trusted
|
||||
# certification artifact is absent (e.g. a manual dispatch without one).
|
||||
for relative in (
|
||||
".github/workflows/docker.yml",
|
||||
".github/workflows/package.yml",
|
||||
".github/workflows/helm-package.yml",
|
||||
):
|
||||
workflow = self.read(relative)
|
||||
certification_block = self.job_block(workflow, "release-certification")
|
||||
self.assertIn("fail", certification_block.lower(), relative)
|
||||
self.assertIn("release-certification", certification_block, relative)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Vendored
-29
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"version": "9.9.9-fixture",
|
||||
"release_profile": "stable",
|
||||
"features": [
|
||||
"pyroscope"
|
||||
],
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"validation_batch": {
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2
|
||||
},
|
||||
"deliverables": [
|
||||
{
|
||||
"kind": "zip",
|
||||
"name": "rustfs-linux-x86_64-gnu-v9.9.9-fixture.zip",
|
||||
"location": "https://example.invalid/artifacts/runs/fixture-batch-1/2/rustfs-linux-x86_64-gnu-v9.9.9-fixture.zip",
|
||||
"sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"size": 36,
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa"
|
||||
}
|
||||
],
|
||||
"image": {
|
||||
"ref": "example.invalid/rustfs:9.9.9-fixture",
|
||||
"digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"status": "completed"
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:02:00Z",
|
||||
"finished_at": "2026-09-26T10:02:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-aarch64-gnu.json",
|
||||
"sha256": "fa138d5891e5098b97137b0edbb4779c4350285fdc76cec7e6214809b34b3075",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:03:00Z",
|
||||
"finished_at": "2026-09-26T10:03:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-aarch64-gnu.json",
|
||||
"sha256": "314b19c4fee5f406cbf7ee0d6a436059115f1ff06f6864830045f9d452ab91c9",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:00:00Z",
|
||||
"finished_at": "2026-09-26T10:00:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-x86_64-gnu.json",
|
||||
"sha256": "37e58ec3fc6fe3ac7ff2fcbeebdb62f6a6aaaa34ec771a0f3306c821da0336f7",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:01:00Z",
|
||||
"finished_at": "2026-09-26T10:01:30Z",
|
||||
"state": "infra_error",
|
||||
"reason_code": "child-cancelled",
|
||||
"reason_class": "ENV",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-x86_64-gnu.json",
|
||||
"sha256": "8b8774175c55c400b337610a89fe725ad2637af8fcc4973a82ab218f9182b80d",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111",
|
||||
"status": "cancelled"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "upgrade",
|
||||
"case_id": "upgrade-smoke",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "2222222222222222222222222222222222222222",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:04:00Z",
|
||||
"finished_at": "2026-09-26T10:04:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/upgrade/upgrade-smoke-linux-x86_64-gnu.json",
|
||||
"sha256": "c1ce56808b52859d2590b34b5990aed7bf560b134e44bd0c09f1873c9f764b38",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-upgrade-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "2222222222222222222222222222222222222222"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"status": "completed"
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:02:00Z",
|
||||
"finished_at": "2026-09-26T10:02:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-aarch64-gnu.json",
|
||||
"sha256": "fa138d5891e5098b97137b0edbb4779c4350285fdc76cec7e6214809b34b3075",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-cross-identity-linux-aarch64-gnu-artifact-identity",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "4444444444444444444444444444444444444444"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:03:00Z",
|
||||
"finished_at": "2026-09-26T10:03:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-aarch64-gnu.json",
|
||||
"sha256": "314b19c4fee5f406cbf7ee0d6a436059115f1ff06f6864830045f9d452ab91c9",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-cross-identity-linux-aarch64-gnu-startup",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "4444444444444444444444444444444444444444"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:00:00Z",
|
||||
"finished_at": "2026-09-26T10:00:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-x86_64-gnu.json",
|
||||
"sha256": "37e58ec3fc6fe3ac7ff2fcbeebdb62f6a6aaaa34ec771a0f3306c821da0336f7",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-cross-identity-linux-x86_64-gnu-artifact-identity",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "4444444444444444444444444444444444444444"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:01:00Z",
|
||||
"finished_at": "2026-09-26T10:01:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-x86_64-gnu.json",
|
||||
"sha256": "8b8774175c55c400b337610a89fe725ad2637af8fcc4973a82ab218f9182b80d",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-cross-identity-linux-x86_64-gnu-startup",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "4444444444444444444444444444444444444444"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "upgrade",
|
||||
"case_id": "upgrade-smoke",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "2222222222222222222222222222222222222222",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:04:00Z",
|
||||
"finished_at": "2026-09-26T10:04:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/upgrade/upgrade-smoke-linux-x86_64-gnu.json",
|
||||
"sha256": "c1ce56808b52859d2590b34b5990aed7bf560b134e44bd0c09f1873c9f764b38",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-cross-upgrade-linux-x86_64-gnu-upgrade-smoke",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "4444444444444444444444444444444444444444"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"status": "completed"
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:02:00Z",
|
||||
"finished_at": "2026-09-26T10:02:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-aarch64-gnu.json",
|
||||
"sha256": "fa138d5891e5098b97137b0edbb4779c4350285fdc76cec7e6214809b34b3075",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:03:00Z",
|
||||
"finished_at": "2026-09-26T10:03:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-aarch64-gnu.json",
|
||||
"sha256": "314b19c4fee5f406cbf7ee0d6a436059115f1ff06f6864830045f9d452ab91c9",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:00:00Z",
|
||||
"finished_at": "2026-09-26T10:00:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-x86_64-gnu.json",
|
||||
"sha256": "37e58ec3fc6fe3ac7ff2fcbeebdb62f6a6aaaa34ec771a0f3306c821da0336f7",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:01:00Z",
|
||||
"finished_at": "2026-09-26T10:01:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-x86_64-gnu.json",
|
||||
"sha256": "8b8774175c55c400b337610a89fe725ad2637af8fcc4973a82ab218f9182b80d",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:01:00Z",
|
||||
"finished_at": "2026-09-26T10:01:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-x86_64-gnu.json",
|
||||
"sha256": "8b8774175c55c400b337610a89fe725ad2637af8fcc4973a82ab218f9182b80d",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "upgrade",
|
||||
"case_id": "upgrade-smoke",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "2222222222222222222222222222222222222222",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:04:00Z",
|
||||
"finished_at": "2026-09-26T10:04:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/upgrade/upgrade-smoke-linux-x86_64-gnu.json",
|
||||
"sha256": "c1ce56808b52859d2590b34b5990aed7bf560b134e44bd0c09f1873c9f764b38",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-upgrade-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "2222222222222222222222222222222222222222"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"status": "completed"
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:02:00Z",
|
||||
"finished_at": "2026-09-26T10:02:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-aarch64-gnu.json",
|
||||
"sha256": "fa138d5891e5098b97137b0edbb4779c4350285fdc76cec7e6214809b34b3075",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:03:00Z",
|
||||
"finished_at": "2026-09-26T10:03:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-aarch64-gnu.json",
|
||||
"sha256": "314b19c4fee5f406cbf7ee0d6a436059115f1ff06f6864830045f9d452ab91c9",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:00:00Z",
|
||||
"finished_at": "2026-09-26T10:00:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-x86_64-gnu.json",
|
||||
"sha256": "37e58ec3fc6fe3ac7ff2fcbeebdb62f6a6aaaa34ec771a0f3306c821da0336f7",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:01:00Z",
|
||||
"finished_at": "2026-09-26T10:01:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-x86_64-gnu.json",
|
||||
"sha256": "8b8774175c55c400b337610a89fe725ad2637af8fcc4973a82ab218f9182b80d",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "surprise-case",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:05:00Z",
|
||||
"finished_at": "2026-09-26T10:05:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/surprise-case-linux-x86_64-gnu.json",
|
||||
"sha256": "f82eedac16c983bd426ca24a6cbf3acfa032dc722e9c4a5ed7ef21c7eff0bcb1",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "upgrade",
|
||||
"case_id": "upgrade-smoke",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "2222222222222222222222222222222222222222",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:04:00Z",
|
||||
"finished_at": "2026-09-26T10:04:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/upgrade/upgrade-smoke-linux-x86_64-gnu.json",
|
||||
"sha256": "c1ce56808b52859d2590b34b5990aed7bf560b134e44bd0c09f1873c9f764b38",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-upgrade-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "2222222222222222222222222222222222222222"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"status": "completed"
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:02:00Z",
|
||||
"finished_at": "2026-09-26T10:02:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-aarch64-gnu.json",
|
||||
"sha256": "fa138d5891e5098b97137b0edbb4779c4350285fdc76cec7e6214809b34b3075",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:00:00Z",
|
||||
"finished_at": "2026-09-26T10:00:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-x86_64-gnu.json",
|
||||
"sha256": "37e58ec3fc6fe3ac7ff2fcbeebdb62f6a6aaaa34ec771a0f3306c821da0336f7",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:01:00Z",
|
||||
"finished_at": "2026-09-26T10:01:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-x86_64-gnu.json",
|
||||
"sha256": "8b8774175c55c400b337610a89fe725ad2637af8fcc4973a82ab218f9182b80d",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "upgrade",
|
||||
"case_id": "upgrade-smoke",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "2222222222222222222222222222222222222222",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:04:00Z",
|
||||
"finished_at": "2026-09-26T10:04:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/upgrade/upgrade-smoke-linux-x86_64-gnu.json",
|
||||
"sha256": "c1ce56808b52859d2590b34b5990aed7bf560b134e44bd0c09f1873c9f764b38",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-upgrade-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "2222222222222222222222222222222222222222"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"status": "completed"
|
||||
}
|
||||
Vendored
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:02:00Z",
|
||||
"finished_at": "2026-09-26T10:02:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-aarch64-gnu.json",
|
||||
"sha256": "fa138d5891e5098b97137b0edbb4779c4350285fdc76cec7e6214809b34b3075",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:03:00Z",
|
||||
"finished_at": "2026-09-26T10:03:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-aarch64-gnu.json",
|
||||
"sha256": "314b19c4fee5f406cbf7ee0d6a436059115f1ff06f6864830045f9d452ab91c9",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
Vendored
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:00:00Z",
|
||||
"finished_at": "2026-09-26T10:00:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-x86_64-gnu.json",
|
||||
"sha256": "37e58ec3fc6fe3ac7ff2fcbeebdb62f6a6aaaa34ec771a0f3306c821da0336f7",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:01:00Z",
|
||||
"finished_at": "2026-09-26T10:01:30Z",
|
||||
"state": "infra_error",
|
||||
"reason_code": "fixture-infra_error",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-x86_64-gnu.json",
|
||||
"sha256": "8b8774175c55c400b337610a89fe725ad2637af8fcc4973a82ab218f9182b80d",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "upgrade",
|
||||
"case_id": "upgrade-smoke",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "2222222222222222222222222222222222222222",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:04:00Z",
|
||||
"finished_at": "2026-09-26T10:04:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/upgrade/upgrade-smoke-linux-x86_64-gnu.json",
|
||||
"sha256": "c1ce56808b52859d2590b34b5990aed7bf560b134e44bd0c09f1873c9f764b38",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-upgrade-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "2222222222222222222222222222222222222222"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"status": "completed"
|
||||
}
|
||||
Vendored
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:02:00Z",
|
||||
"finished_at": "2026-09-26T10:02:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-aarch64-gnu.json",
|
||||
"sha256": "fa138d5891e5098b97137b0edbb4779c4350285fdc76cec7e6214809b34b3075",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:03:00Z",
|
||||
"finished_at": "2026-09-26T10:03:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-aarch64-gnu.json",
|
||||
"sha256": "314b19c4fee5f406cbf7ee0d6a436059115f1ff06f6864830045f9d452ab91c9",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
Vendored
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:00:00Z",
|
||||
"finished_at": "2026-09-26T10:00:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-x86_64-gnu.json",
|
||||
"sha256": "37e58ec3fc6fe3ac7ff2fcbeebdb62f6a6aaaa34ec771a0f3306c821da0336f7",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:01:00Z",
|
||||
"finished_at": "2026-09-26T10:01:30Z",
|
||||
"state": "quarantined",
|
||||
"reason_code": "fixture-quarantined",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-x86_64-gnu.json",
|
||||
"sha256": "8b8774175c55c400b337610a89fe725ad2637af8fcc4973a82ab218f9182b80d",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "upgrade",
|
||||
"case_id": "upgrade-smoke",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "2222222222222222222222222222222222222222",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:04:00Z",
|
||||
"finished_at": "2026-09-26T10:04:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/upgrade/upgrade-smoke-linux-x86_64-gnu.json",
|
||||
"sha256": "c1ce56808b52859d2590b34b5990aed7bf560b134e44bd0c09f1873c9f764b38",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-upgrade-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "2222222222222222222222222222222222222222"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"status": "completed"
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:02:00Z",
|
||||
"finished_at": "2026-09-26T10:02:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-aarch64-gnu.json",
|
||||
"sha256": "fa138d5891e5098b97137b0edbb4779c4350285fdc76cec7e6214809b34b3075",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:03:00Z",
|
||||
"finished_at": "2026-09-26T10:03:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-aarch64-gnu.json",
|
||||
"sha256": "314b19c4fee5f406cbf7ee0d6a436059115f1ff06f6864830045f9d452ab91c9",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:00:00Z",
|
||||
"finished_at": "2026-09-26T10:00:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-x86_64-gnu.json",
|
||||
"sha256": "37e58ec3fc6fe3ac7ff2fcbeebdb62f6a6aaaa34ec771a0f3306c821da0336f7",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:01:00Z",
|
||||
"finished_at": "2026-09-26T10:01:30Z",
|
||||
"state": "skipped",
|
||||
"reason_code": "fixture-skipped",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-x86_64-gnu.json",
|
||||
"sha256": "8b8774175c55c400b337610a89fe725ad2637af8fcc4973a82ab218f9182b80d",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "upgrade",
|
||||
"case_id": "upgrade-smoke",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "2222222222222222222222222222222222222222",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:04:00Z",
|
||||
"finished_at": "2026-09-26T10:04:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/upgrade/upgrade-smoke-linux-x86_64-gnu.json",
|
||||
"sha256": "c1ce56808b52859d2590b34b5990aed7bf560b134e44bd0c09f1873c9f764b38",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-upgrade-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "2222222222222222222222222222222222222222"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"status": "completed"
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:02:00Z",
|
||||
"finished_at": "2026-09-26T10:02:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-aarch64-gnu.json",
|
||||
"sha256": "fa138d5891e5098b97137b0edbb4779c4350285fdc76cec7e6214809b34b3075",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:03:00Z",
|
||||
"finished_at": "2026-09-26T10:03:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-aarch64-gnu.json",
|
||||
"sha256": "314b19c4fee5f406cbf7ee0d6a436059115f1ff06f6864830045f9d452ab91c9",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:00:00Z",
|
||||
"finished_at": "2026-09-26T10:00:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-x86_64-gnu.json",
|
||||
"sha256": "37e58ec3fc6fe3ac7ff2fcbeebdb62f6a6aaaa34ec771a0f3306c821da0336f7",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:01:00Z",
|
||||
"finished_at": "2026-09-26T10:01:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-x86_64-gnu.json",
|
||||
"sha256": "8b8774175c55c400b337610a89fe725ad2637af8fcc4973a82ab218f9182b80d",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "upgrade",
|
||||
"case_id": "upgrade-smoke",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "2222222222222222222222222222222222222222",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:04:00Z",
|
||||
"finished_at": "2026-09-26T10:04:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/upgrade/upgrade-smoke-linux-x86_64-gnu.json",
|
||||
"sha256": "c1ce56808b52859d2590b34b5990aed7bf560b134e44bd0c09f1873c9f764b38",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-upgrade-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "2222222222222222222222222222222222222222"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
Vendored
-53
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"plan_id": "testdata-release-gate",
|
||||
"description": "Contract fixtures for scripts/release/release_gate.py (rustfs/backlog#2662). Rehearsal data only: these files are never product release evidence and cannot certify a real candidate.",
|
||||
"release_types": ["preview", "prerelease", "stable"],
|
||||
"targets": [
|
||||
{"os": "linux", "arch": "x86_64", "abi": "gnu"},
|
||||
{"os": "linux", "arch": "aarch64", "abi": "gnu"}
|
||||
],
|
||||
"features": ["pyroscope"],
|
||||
"trusted_workflows": [
|
||||
{"repository": "rustfs/s3chaos", "workflow_id": "release-gate.yml"}
|
||||
],
|
||||
"lanes": [
|
||||
{
|
||||
"lane": "identity",
|
||||
"harness_repository": "rustfs/s3chaos",
|
||||
"harness_sha": "1111111111111111111111111111111111111111",
|
||||
"timeout_seconds": 1800,
|
||||
"environment_capabilities": ["process-exec"],
|
||||
"cases": ["artifact-identity", "startup"],
|
||||
"applies_to": {
|
||||
"release_types": ["preview", "prerelease", "stable"],
|
||||
"platforms": "all"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lane": "upgrade",
|
||||
"harness_repository": "rustfs/s3chaos",
|
||||
"harness_sha": "2222222222222222222222222222222222222222",
|
||||
"timeout_seconds": 3600,
|
||||
"environment_capabilities": ["process-exec", "previous-release-artifact"],
|
||||
"cases": ["upgrade-smoke"],
|
||||
"applies_to": {
|
||||
"release_types": ["prerelease", "stable"],
|
||||
"platforms": [{"os": "linux", "arch": "x86_64", "abi": "gnu"}]
|
||||
}
|
||||
}
|
||||
],
|
||||
"upgrade_sources": {
|
||||
"basis": "Fixture-only synthetic source. Product plans must cite the real pinned digests of the exercised upgrade sources and must not invent compatibility.",
|
||||
"sources": [
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"role": "N-1",
|
||||
"artifact_name": "fixture-upgrade-source.zip",
|
||||
"artifact_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
}
|
||||
],
|
||||
"rollback_supported": false,
|
||||
"rollback_basis": "The fixture certifies no rollback support; rollback is never a default claim."
|
||||
}
|
||||
}
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"plan_id": "testdata-release-gate",
|
||||
"description": "Contract fixtures for scripts/release/release_gate.py (rustfs/backlog#2662). Rehearsal data only: these files are never product release evidence and cannot certify a real candidate.",
|
||||
"release_types": [
|
||||
"preview",
|
||||
"prerelease",
|
||||
"stable"
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
{
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
}
|
||||
],
|
||||
"features": [
|
||||
"pyroscope"
|
||||
],
|
||||
"trusted_workflows": [
|
||||
{
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml"
|
||||
}
|
||||
],
|
||||
"lanes": [
|
||||
{
|
||||
"lane": "identity",
|
||||
"harness_repository": "rustfs/s3chaos",
|
||||
"harness_sha": "1111111111111111111111111111111111111111",
|
||||
"timeout_seconds": 1800,
|
||||
"environment_capabilities": [
|
||||
"process-exec"
|
||||
],
|
||||
"cases": [
|
||||
"artifact-identity"
|
||||
],
|
||||
"applies_to": {
|
||||
"release_types": [
|
||||
"preview",
|
||||
"prerelease",
|
||||
"stable"
|
||||
],
|
||||
"platforms": "all"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lane": "upgrade",
|
||||
"harness_repository": "rustfs/s3chaos",
|
||||
"harness_sha": "2222222222222222222222222222222222222222",
|
||||
"timeout_seconds": 3600,
|
||||
"environment_capabilities": [
|
||||
"process-exec",
|
||||
"previous-release-artifact"
|
||||
],
|
||||
"cases": [
|
||||
"upgrade-smoke"
|
||||
],
|
||||
"applies_to": {
|
||||
"release_types": [
|
||||
"prerelease",
|
||||
"stable"
|
||||
],
|
||||
"platforms": [
|
||||
{
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"upgrade_sources": {
|
||||
"basis": "Fixture-only synthetic source. Product plans must cite the real pinned digests of the exercised upgrade sources and must not invent compatibility.",
|
||||
"sources": [
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"role": "N-1",
|
||||
"artifact_name": "fixture-upgrade-source.zip",
|
||||
"artifact_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
}
|
||||
],
|
||||
"rollback_supported": false,
|
||||
"rollback_basis": "The fixture certifies no rollback support; rollback is never a default claim."
|
||||
}
|
||||
}
|
||||
-1
@@ -1 +0,0 @@
|
||||
swapped package bytes
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"status": "completed"
|
||||
}
|
||||
Vendored
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:02:00Z",
|
||||
"finished_at": "2026-09-26T10:02:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-aarch64-gnu.json",
|
||||
"sha256": "fa138d5891e5098b97137b0edbb4779c4350285fdc76cec7e6214809b34b3075",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:03:00Z",
|
||||
"finished_at": "2026-09-26T10:03:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-aarch64-gnu.json",
|
||||
"sha256": "314b19c4fee5f406cbf7ee0d6a436059115f1ff06f6864830045f9d452ab91c9",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
Vendored
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:00:00Z",
|
||||
"finished_at": "2026-09-26T10:00:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-x86_64-gnu.json",
|
||||
"sha256": "37e58ec3fc6fe3ac7ff2fcbeebdb62f6a6aaaa34ec771a0f3306c821da0336f7",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:01:00Z",
|
||||
"finished_at": "2026-09-26T10:01:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-x86_64-gnu.json",
|
||||
"sha256": "8b8774175c55c400b337610a89fe725ad2637af8fcc4973a82ab218f9182b80d",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "upgrade",
|
||||
"case_id": "upgrade-smoke",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "2222222222222222222222222222222222222222",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:04:00Z",
|
||||
"finished_at": "2026-09-26T10:04:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/upgrade/upgrade-smoke-linux-x86_64-gnu.json",
|
||||
"sha256": "c1ce56808b52859d2590b34b5990aed7bf560b134e44bd0c09f1873c9f764b38",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-upgrade-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "2222222222222222222222222222222222222222"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"status": "completed"
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 1,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:02:00Z",
|
||||
"finished_at": "2026-09-26T10:02:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-aarch64-gnu.json",
|
||||
"sha256": "fa138d5891e5098b97137b0edbb4779c4350285fdc76cec7e6214809b34b3075",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 1,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:03:00Z",
|
||||
"finished_at": "2026-09-26T10:03:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-aarch64-gnu.json",
|
||||
"sha256": "314b19c4fee5f406cbf7ee0d6a436059115f1ff06f6864830045f9d452ab91c9",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 1,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:00:00Z",
|
||||
"finished_at": "2026-09-26T10:00:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-x86_64-gnu.json",
|
||||
"sha256": "37e58ec3fc6fe3ac7ff2fcbeebdb62f6a6aaaa34ec771a0f3306c821da0336f7",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 1,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:01:00Z",
|
||||
"finished_at": "2026-09-26T10:01:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-x86_64-gnu.json",
|
||||
"sha256": "8b8774175c55c400b337610a89fe725ad2637af8fcc4973a82ab218f9182b80d",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 1,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "upgrade",
|
||||
"case_id": "upgrade-smoke",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "2222222222222222222222222222222222222222",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:04:00Z",
|
||||
"finished_at": "2026-09-26T10:04:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/upgrade/upgrade-smoke-linux-x86_64-gnu.json",
|
||||
"sha256": "c1ce56808b52859d2590b34b5990aed7bf560b134e44bd0c09f1873c9f764b38",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-upgrade-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "2222222222222222222222222222222222222222"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"status": "timed_out"
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:02:00Z",
|
||||
"finished_at": "2026-09-26T10:02:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-aarch64-gnu.json",
|
||||
"sha256": "fa138d5891e5098b97137b0edbb4779c4350285fdc76cec7e6214809b34b3075",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:03:00Z",
|
||||
"finished_at": "2026-09-26T10:03:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-aarch64-gnu.json",
|
||||
"sha256": "314b19c4fee5f406cbf7ee0d6a436059115f1ff06f6864830045f9d452ab91c9",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:00:00Z",
|
||||
"finished_at": "2026-09-26T10:00:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-x86_64-gnu.json",
|
||||
"sha256": "37e58ec3fc6fe3ac7ff2fcbeebdb62f6a6aaaa34ec771a0f3306c821da0336f7",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "startup",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:01:00Z",
|
||||
"finished_at": "2026-09-26T10:01:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/startup-linux-x86_64-gnu.json",
|
||||
"sha256": "8b8774175c55c400b337610a89fe725ad2637af8fcc4973a82ab218f9182b80d",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "upgrade",
|
||||
"case_id": "upgrade-smoke",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "2222222222222222222222222222222222222222",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-x86_64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:04:00Z",
|
||||
"finished_at": "2026-09-26T10:04:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/upgrade/upgrade-smoke-linux-x86_64-gnu.json",
|
||||
"sha256": "c1ce56808b52859d2590b34b5990aed7bf560b134e44bd0c09f1873c9f764b38",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-upgrade-linux-x86_64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "2222222222222222222222222222222222222222"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"status": "completed"
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"evidence_profile": "release-artifact",
|
||||
"run_id": "fixture-batch-1",
|
||||
"run_attempt": 2,
|
||||
"plan_sha256": "578bb57e65df8ba7a63e40aafc254fa644163270b7ec288fe3368bcd7c10ff86",
|
||||
"tier": "release-certification",
|
||||
"lane": "identity",
|
||||
"case_id": "artifact-identity",
|
||||
"product_commit": "3333333333333333333333333333333333333333",
|
||||
"harness_commit": "1111111111111111111111111111111111111111",
|
||||
"target_fingerprint": "rustfs-9.9.9-fixture-linux-aarch64-gnu",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"abi": "gnu"
|
||||
},
|
||||
"started_at": "2026-09-26T10:02:00Z",
|
||||
"finished_at": "2026-09-26T10:02:30Z",
|
||||
"state": "pass",
|
||||
"reason_code": "ok",
|
||||
"reason_class": "NOT_APPLICABLE",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "cases/identity/artifact-identity-linux-aarch64-gnu.json",
|
||||
"sha256": "fa138d5891e5098b97137b0edbb4779c4350285fdc76cec7e6214809b34b3075",
|
||||
"size": 64
|
||||
}
|
||||
],
|
||||
"workflow": {
|
||||
"repository": "rustfs/s3chaos",
|
||||
"workflow_id": "release-gate.yml",
|
||||
"run_id": "child-identity-linux-aarch64-gnu",
|
||||
"run_attempt": 1,
|
||||
"workflow_sha": "1111111111111111111111111111111111111111"
|
||||
},
|
||||
"artifact": {
|
||||
"archive_sha256": "ea3ac1fb0ec4aa8fbec650f64ccdbc2543843c8d5734c3915d1b57d7cbb92ec8",
|
||||
"binary_sha256": "7b77e78cc7b8f63a60f8a7101bebed6502f82e03016f4d4e11450f927e0a52fa",
|
||||
"container_image_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user