Programming & Technical May 18, 2026

Your First validate-packet.sh - Bash and PowerShell Guards for Q3 2026 Partner Packets

2026 Programming tutorial—write validate-packet.sh in Bash and PowerShell with jq and diff guards for MANIFEST.json and SHA256SUMS before Q3 partner packets.

By GamineAI Team

Your First validate-packet.sh - Bash and PowerShell Guards for Q3 2026 Partner Packets

Generated pixel-art thumbnail for validate-packet.sh Bash and PowerShell tutorial

You ran sha256sum once. You eyeball-compared MANIFEST.json. You uploaded anyway. Q3 2026 partner intake returned yellow on checksum parity while your Discord still says “green CI.”

Scripts do not replace judgment—they remove the steps you skip when tired. This Programming & Technical tutorial builds validate-packet.sh (Bash) and a PowerShell twin with the same gates: receipt present, manifest parses, sums match manifest paths, optional cold diff empty. Pair with BUILD_RECEIPT evening, SHA256 cold drill, and the validator listicle.

Who this is for and what you get

Audience You will be able to…
Solo dev with Git Bash or WSL Run one command before every zip upload
Windows-primary team Use Validate-Packet.ps1 with identical exit codes
Lead preparing mock audit Demo dimension 2 checks in the room

Time: ~90 minutes first implementation; under ten seconds per run after that.
Prerequisites: jq, sha256sum or Get-FileHash, release-evidence/ layout from folder taxonomy.

Why this matters now (May 2026)

  1. Intake compression — You get 72-hour recovery windows, not six-week polish buffers.
  2. Cold-hash challenge day 7 expects a script exit 0.
  3. Hash mismatch case study — Recovery started when teams had no script—only hope.
  4. Weekly patch opinion — Evidence cycles need automation, not hero uploads.

Direct answer: Commit release-evidence/scripts/validate-packet.sh; require exit 0 before portal upload and log output to validation/.

Script contract (both platforms)

Exit code Meaning
0 All gates pass
1 Missing file or bad arguments
2 JSON parse failure
3 Manifest vs sums mismatch
4 Receipt build_id mismatch
5 Non-empty diff (cold sums)

Document codes in release-evidence/README.md. CI and humans read the same legend.

Folder assumptions

release-evidence/
  BUILD_RECEIPT.json          # or path passed as flag
  SHA256SUMS.txt
  MANIFEST.json
  files_to_hash.txt
  01-build/game/              # hash cwd
  scripts/
    validate-packet.sh
    Validate-Packet.ps1
  validation/                 # logs written here

Adjust paths via flags—defaults match partner ZIP naming examples.

Gate 1 — Files exist

Bash:

need() { test -f "$1" || { echo "missing $1"; exit 1; }; }
need "$ROOT/BUILD_RECEIPT.json"
need "$ROOT/SHA256SUMS.txt"
need "$ROOT/MANIFEST.json"
need "$ROOT/files_to_hash.txt"

PowerShell:

function Need-File($p) {
  if (-not (Test-Path $p)) { Write-Error "missing $p"; exit 1 }
}

Fail fast before jq runs on empty paths.

Gate 2 — Receipt build_id

EXPECTED_BUILD_ID="${1:?pass expected build_id}"
ACTUAL=$(jq -r '.build_id' "$ROOT/BUILD_RECEIPT.json")
test "$ACTUAL" = "$EXPECTED_BUILD_ID" || { echo "build_id mismatch"; exit 4; }

Pass build_id as first argument matching planned zip filename—stops README/receipt/zip drift.

Gate 3 — Manifest vs SHA256SUMS (detailed)

Partners treat manifest and sidecar as one truth surface. Your script should verify:

  1. Every files[].path in MANIFEST.json has a sums line.
  2. Every files[].sha256 matches that line’s hex.
  3. No required gameplay file is missing from manifest (optional fourth check against files_to_hash.txt).

Bash loop (readable):

while IFS= read -r line; do
  path=$(jq -r --arg p "$line" '.files[] | select(.path==$p) | .path' "$ROOT/MANIFEST.json")
  hash=$(jq -r --arg p "$line" '.files[] | select(.path==$p) | .sha256' "$ROOT/MANIFEST.json")
  if [[ -z "$path" ]]; then echo "manifest missing $line"; exit 3; fi
  grep -q "^${hash}  ${path}$" "$ROOT/SHA256SUMS.txt" || { echo "sums mismatch $path"; exit 3; }
done < "$ROOT/files_to_hash.txt"

Why read files_to_hash.txt? It is the frozen list from cold-hash day 1—manifest cannot omit files you committed to hash.

CRLF pitfall: If sums were generated on Windows with CRLF line endings in the file list, normalize lists before hashing—see hash case study.

Gate 4 — Regenerate sums and diff

GAME_DIR="$ROOT/01-build/game"
(
  cd "$GAME_DIR"
  sha256sum $(cat "$ROOT/files_to_hash.txt") > "$ROOT/SHA256SUMS.recomputed.txt"
)
diff -u "$ROOT/SHA256SUMS.txt" "$ROOT/SHA256SUMS.recomputed.txt" || exit 5

Empty diff means export machine matches list. Run again on cold laptop before upload—not only on dev PC.

Gate 5 — Log success

LOG="$ROOT/validation/$(date -u +%Y-%m-%dT%H%M%SZ)_validate_packet_pass.log"
{
  echo "validate-packet pass"
  echo "build_id=$EXPECTED_BUILD_ID"
  echo "host=$(hostname)"
} > "$LOG"
exit 0

Friday Block 5 can grep latest *_pass.log.

Full Bash skeleton (copy-paste starter)

#!/usr/bin/env bash
set -euo pipefail
ROOT="${2:-.}"
EXPECTED_BUILD_ID="${1:?usage: validate-packet.sh BUILD_ID [ROOT]}"
need() { test -f "$1" || { echo "missing $1"; exit 1; }; }
need "$ROOT/BUILD_RECEIPT.json"
need "$ROOT/SHA256SUMS.txt"
need "$ROOT/MANIFEST.json"
need "$ROOT/files_to_hash.txt"
ACTUAL=$(jq -r '.build_id' "$ROOT/BUILD_RECEIPT.json")
[[ "$ACTUAL" == "$EXPECTED_BUILD_ID" ]] || { echo "build_id mismatch"; exit 4; }
while read -r path hash; do
  grep -q "^${hash}  ${path}$" "$ROOT/SHA256SUMS.txt" || { echo "manifest/sums mismatch $path"; exit 3; }
done < <(jq -r '.files[] | "\(.path) \(.sha256)"' "$ROOT/MANIFEST.json")
GAME_DIR="$ROOT/01-build/game"
( cd "$GAME_DIR"; sha256sum $(cat "$ROOT/files_to_hash.txt") > "$ROOT/SHA256SUMS.recomputed.txt" )
diff -u "$ROOT/SHA256SUMS.txt" "$ROOT/SHA256SUMS.recomputed.txt"
LOG_DIR="$ROOT/validation"; mkdir -p "$LOG_DIR"
echo "pass build_id=$EXPECTED_BUILD_ID utc=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$LOG_DIR/latest_validate_packet_pass.log"
echo "validate-packet: OK"

Run shellcheck validate-packet.sh per listicle tool 15.

PowerShell skeleton (exit code parity)

param(
  [Parameter(Mandatory=$true)][string]$BuildId,
  [string]$Root = "."
)
$ErrorActionPreference = "Stop"
function Need($p) { if (-not (Test-Path $p)) { exit 1 } }
Need (Join-Path $Root "BUILD_RECEIPT.json")
Need (Join-Path $Root "SHA256SUMS.txt")
Need (Join-Path $Root "MANIFEST.json")
Need (Join-Path $Root "files_to_hash.txt")
$receipt = Get-Content (Join-Path $Root "BUILD_RECEIPT.json") -Raw | ConvertFrom-Json
if ($receipt.build_id -ne $BuildId) { exit 4 }
$manifest = Get-Content (Join-Path $Root "MANIFEST.json") -Raw | ConvertFrom-Json
foreach ($f in $manifest.files) {
  $line = Select-String -Path (Join-Path $Root "SHA256SUMS.txt") -Pattern "^$($f.sha256)  $($f.path)$"
  if (-not $line) { exit 3 }
}
$game = Join-Path $Root "01-build\game"
$list = Get-Content (Join-Path $Root "files_to_hash.txt")
$out = foreach ($rel in $list) {
  $h = (Get-FileHash -Path (Join-Path $game $rel) -Algorithm SHA256).Hash.ToLower()
  "$h  $rel"
}
$recomputed = Join-Path $Root "SHA256SUMS.recomputed.txt"
$out | Set-Content $recomputed -Encoding utf8NoBOM
$diff = Compare-Object (Get-Content (Join-Path $Root "SHA256SUMS.txt")) (Get-Content $recomputed)
if ($diff) { exit 5 }
$logDir = Join-Path $Root "validation"
New-Item -ItemType Directory -Force -Path $logDir | Out-Null
"pass build_id=$BuildId utc=$((Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ'))" |
  Set-Content (Join-Path $logDir "latest_validate_packet_pass.log")
Write-Host "validate-packet: OK"
exit 0

Save as Validate-Packet.ps1. Invoke:

.\release-evidence\scripts\Validate-Packet.ps1 -BuildId "northlake_game_steam_abc123_cert_20260518" -Root ".\release-evidence"

Ninety-minute implementation sprint

Block Minutes Task
A 15 Install jq; verify sha256sum
B 25 Paste Bash skeleton; fix paths
C 20 Port to PowerShell; match exit codes
D 15 shellcheck + test on sample packet
E 15 Wire pre-upload checklist in README

Stop when both platforms exit 0 on frozen build_id.

Testing the script locally (before real packet)

Create a toy packet in release-evidence/_test/:

  1. Copy minimal MANIFEST.json with one fake file entry.
  2. Place one small binary in 01-build/game/.
  3. Generate real sums from files_to_hash.txt.
  4. Run script expecting pass.
  5. Change one manifest hash digit—expect exit 3.
  6. Change receipt build_id—expect exit 4.

Toy tests take twenty minutes and prevent shipping a script that always exits 0 because paths are wrong.

Flags and configuration (production hardening)

Extend skeleton with optional flags:

Flag Purpose
--root PATH Evidence root
--build-id ID Expected id
--skip-diff Manifest-only quick check
--cold Require SHA256SUMS.recomputed.txt from env var path
--log-dir Override validation output

Document flags in release-evidence/scripts/README.md. Micro-studios should not need twelve flags—defaults first, flags when two storefront zips share scripts.

Two-storefront invocation

Under two-storefront rule:

./validate-packet.sh northlake_game_steam_abc_cert_20260518 ./release-evidence/steam
./validate-packet.sh northlake_game_epic_abc_cert_20260518 ./release-evidence/epic

Separate roots or separate build_id args—never one script run pretending two channels share hashes.

Receipt extensions the script can assert

Optional jq checks when extensions exists:

jq -e '.extensions.validator_script_commit | length > 0' "$ROOT/BUILD_RECEIPT.json" >/dev/null

During RC freeze, pin validator commit in receipt—script can git rev-parse compare when run inside repo.

Asia-EU handoff integration

Handoff notes should cite:

validate_packet_log: validation/latest_validate_packet_pass.log

Overnight exporter runs script before dropping zip in 00-handoff/outbox/. Morning reviewer uploads only if log timestamp is after export.

AI annex files

When 04_ai/disclosure_matrix.csv ships, append paths to files_to_hash.txt before gate 4. Script does not know AI semantics—it knows paths. Run AI disclosure challenge before adding annex rows or gate 3 fails legitimately.

Debugging exit codes (quick reference)

Exit First look
1 Path typo; file not exported
2 Invalid JSON; trailing comma
3 Manifest path or hash typo
4 Receipt not updated after export
5 Bytes changed since sums written

Fix root cause—do not bump exit code meanings mid-quarter.

WSL vs native Windows

Windows teams often use Git Bash for sha256sum parity and PowerShell for daily work. Pick one cold-machine toolchain and document in portal_notes.md. Mixing tools without utf8NoBOM discipline recreates case study failures.

Versioning validate-packet.sh

Tag script in receipt:

"extensions": {
  "validate_packet_version": "1.0.0"
}

Bump minor version when adding flags; bump major when exit codes change. Publisher diligence reviewers notice silent semantic drift.

Operator checklist (print)

[ ] build_id argument matches zip filename
[ ] jq --version recorded in log
[ ] validate-packet exit 0 on export machine
[ ] validate-packet exit 0 on cold machine
[ ] latest_validate_packet_pass.log committed or archived
[ ] upload_log.csv row pending until after portal click

CI integration (optional, advisory)

GitHub Actions example—advisory until receipts stable:

- name: Validate partner packet
  run: ./release-evidence/scripts/validate-packet.sh "${{ vars.BUILD_ID }}" ./release-evidence

Do not block merges on day one—block upload manually until logs look boring.

Pre-commit hook (narrow)

From listicle tool 16:

# .git/hooks/pre-commit snippet
if git diff --cached --name-only | grep -q '^release-evidence/01-build/'; then
  ./release-evidence/scripts/validate-packet.sh "$(jq -r .build_id release-evidence/BUILD_RECEIPT.json)" release-evidence || exit 1
fi

Only when receipt build_id is current—avoid blocking unrelated commits.

Common mistakes

  1. Running from wrong cwd01-build/game paths break.
  2. Uppercase SHA256 on Windows — lowercase before compare.
  3. Skipping cold machine run — script passes on dev, fails partner laptop.
  4. No build_id argument — zip filename drifts.
  5. jq missing on cold PC — install before upload trip.
  6. Trailing commas in JSON — gate 2 fails; fix manifest.
  7. Empty files_to_hash.txt — passes trivially; useless.

Pairing with cold-hash challenge day 7

Day 7 requires script exit 0 on cold hardware. This tutorial is the script content for that gate.

Pairing with resubmission

After 72-hour recovery, bump build_id, regen files, run script, attach log to RESUBMISSION_NOTE.md reference line.

Security notes

  • Script must not echo secrets or API keys from receipt JSON.
  • Do not curl | bash partner templates—read scripts you commit.
  • Log files may contain hostnames—fine for internal evidence.

Key takeaways

  • validate-packet.sh encodes gates partners simulate—receipt, manifest, sums, diff.
  • Match exit codes across Bash and PowerShell for one runbook.
  • Pass build_id as argument aligned with zip name.
  • Write logs under validation/ for mock audit and diligence.
  • Run on cold machine before upload, not only CI.
  • shellcheck before trusting Bash; utf8NoBOM on Windows sums.
  • Script supports evidence cycles—not weekly patch theater.

FAQ

Must we use both Bash and PowerShell?

No—pick export-machine platform; cold machine needs same tooling or WSL.

Can Python replace jq?

Yes—keep exit code contract identical.

Does this upload to portal?

No—local gate only. Upload remains human click with log proof.

What if manifest has fifty files?

Script time stays seconds—optimize only if lists exceed thousands.

How does this relate to validate-packet in listicle?

Listicle names tools; this tutorial ships the script body.

Should the script run inside the zip?

No—run against release-evidence/ tree before zipping. Optionally run again on unpacked zip on cold machine per cold drill.

Can we call this from Unity or Godot?

Yes—Process.Start invoking Bash or PowerShell with captured exit code—keep game engine out of hash logic; call script as external tool.

Extended manifest jq recipes

Assert primary_executable exists:

jq -e '.primary_executable | length > 0' "$ROOT/MANIFEST.json"

Assert hash_sidecar filename:

jq -r '.hash_sidecar' "$ROOT/MANIFEST.json" | grep -qx 'SHA256SUMS.txt'

Count files matches list:

test "$(jq '.files | length' "$ROOT/MANIFEST.json")" -eq "$(wc -l < "$ROOT/files_to_hash.txt")"

Small assertions catch README lies before partners do.

Performance and scale

Indie packets rarely exceed hundreds of files. Script runtime dominated by disk IO, not jq. If lists exceed ~2k files, switch grep loop to awk associative arrays—optimization only when measured slow.

Maintenance cadence

Event Script action
New file type in zip Update files_to_hash.txt
Path convention change Update GAME_DIR variable
New exit code Major version bump + README
RC freeze week Pin commit in receipt

Align with operating review Block 3.

Honest limits

Script does not:

  • Replace legal review
  • Validate gameplay fun
  • Upload to portal
  • Fix bad art or store copy

It validates artifact graph consistency—the Lane A foundation in intake compression analysis.

Gate-by-gate failure messages (user-facing)

Write stderr messages partners never see but your team will:

Gate Message example
1 ERROR: missing MANIFEST.json at $ROOT
3 ERROR: sums line missing for path game/foo.dll
4 ERROR: receipt build_id northlake_a != arg northlake_b
5 ERROR: SHA256SUMS diff non-empty; see SHA256SUMS.recomputed.txt

Clear messages shorten 72-hour recovery debates.

Integrating with 7z test

After script pass, run:

7z t partner_packet.zip || exit 1

Log zip test in same validation folder. Corrupt zip passes hash on source tree but fails partner open—cheap addition before drive to upload café.

Upload_log hook (pseudo-code)

After exit 0:

echo "$(date -u +%Y-%m-%dT%H:%M:%SZ),$EXPECTED_BUILD_ID,partner_cert,${ZIP_NAME},PENDING,submitted,validate-packet exit 0" \
  >> "$ROOT/../05-operations/upload_log.csv"

Adjust path to your taxonomy—BUILD_RECEIPT tutorial defines CSV columns.

Mock audit demo script

In mock audit dimension 2, run:

./validate-packet.sh "$BUILD_ID" ./release-evidence; echo exit=$?
cat ./release-evidence/validation/latest_validate_packet_pass.log

Tabletop participants should see exit 0 and fresh UTC timestamp—evidence, not slides.

Symbols zip variant

When shipping _symbols.zip, duplicate script with GAME_DIR pointing at symbols root or pass --game-dir flag. Never hash gameplay and symbols in one manifest without documenting roles—see SHA256 drill symbols section.

Engine export hooks

Unity / Godot / Unreal should not embed hash logic in builds. Standard pattern:

  1. CI or local export job writes 01-build/.
  2. Release owner runs validate script.
  3. Only then copy into partner zip staging.

Keeps engine projects free of partner-specific path assumptions.

Script anti-patterns

  • Hard-coded C:\ paths
  • Ignoring exit codes in CI
  • || true after diff
  • Skipping shellcheck to “save time”
  • One manifest for two channel zips
  • Running script only after yellow flag

Each pattern appeared in composite recovery stories on this blog—avoid becoming the next composite.

Future: jsonschema gate

When ready, add Python gate reading BUILD_RECEIPT.schema.json—exit 2 on schema fail. Bash+jq remains the 2026 floor; schema is ceiling for mature teams.

Team onboarding blurb

Paste in repo CONTRIBUTING:

Before any partner upload, validate-packet.sh must exit 0 on export and cold machines. Attach validation/latest_validate_packet_pass.log to handoff notes. No exceptions during Q3 cert prep.

Ten lines beat forty-minute oral tradition.

Read order for cert cluster

  1. Folder taxonomy
  2. BUILD_RECEIPT evening
  3. SHA256 drill
  4. This script tutorial
  5. Partner ZIP naming
  6. Cold-hash challenge

Script is step four—after you know what files mean, before you name the zip.

Line-by-line walkthrough of the Bash skeleton

  1. set -euo pipefail — stop on first failed command; essential for diff gate.
  2. EXPECTED_BUILD_ID argument — ties CLI to zip filename convention.
  3. need checks — cheap failures before jq parses garbage.
  4. build_id jq extract — catches receipt typos.
  5. Manifest loop — enforces sums lines match structured JSON.
  6. Subshell cd — guarantees hash cwd matches zip interior layout.
  7. diff -u — empty output is the pass signal; non-empty is exit 5.
  8. Log file — proves when and where pass happened for audits.

Remove any step and you recreate a class of hash mismatch failures.

Dry-run mode (recommended flag)

Add --dry-run that prints gates without exit 5 recompute—useful when iterating paths:

echo "DRY: would hash $(wc -l < "$ROOT/files_to_hash.txt") files"
echo "DRY: would diff SHA256SUMS.txt"

Dry-run helps beginners learn layout without waiting on large binaries.

Q3 template cross-reference

Q3 submission templates supply folder names; this script supplies pass/fail. Install both in the same sprint—folders without gates are decoration.

Success checks for this tutorial

You finished when:

  • Bash and PowerShell both exit 0 on same frozen packet
  • Cold machine log exists with different hostname
  • You can force exit 3 by editing one manifest hash and see stderr message
  • Pre-upload checklist in repo mentions script
  • Mock audit dimension 2 marked pass with log path cited

Troubleshooting workshop (30 minutes)

Pair with a teammate: one person breaks manifest hash, one runs script, swap. Breaking and fixing in thirty minutes teaches more than reading three thousand words alone. Common break types: uppercase hex, wrong path prefix, stale receipt commit, missing annex file in list. Each maps to exit codes you now document in release-evidence/README.md. Schedule the workshop before your first real portal upload, not after the first yellow email.

Conclusion

validate-packet.sh is how micro-studios make SHA256 cold validation repeatable on upload night—not a one-time hero effort. May 2026 is the right week to commit the script beside BUILD_RECEIPT templates and refuse portal clicks until exit 0 prints on cold metal.

Ninety minutes today buys fewer 72-hour weekends later. Run the skeleton, fix your paths, log the pass, and upload with a receipt you can defend.

If the script feels pedantic, remember: partners run pedantic checks on cold laptops. Your job is to run them first on hardware you control, log the result, and treat exit 0 as the real definition of “ready to upload”—not the moment you feel done. Save the log beside the ZIP every time. No exceptions on any release branches.