Introduction
A verified delivery is useful only if those verified bytes become the running release. Waymark needs a reusable receiver that refuses an unapproved parcel before changing its walking festival guide.
You will replace a short shell script, exercise its rejection decisions, and publish the organizer's requested update. This independent environment starts with the original guide and its own parcels. The previous guided lab taught signatures, independently trusted keys, requested metadata and archive digests. You will now combine them without requiring Python programming.
Understand the Working Guide and Receiver Contract
In this step, you will identify what the update must change and what it must preserve. Open Event Guide, then Old Mill Walk. The original event-guide edition is 2.0 and the walk meets at 11:00. Use All walks to return and download the route sheet. Both the detail page and download must work after the update.
The guide home page identifies the edition above the heading and shows each walk’s meeting time on its card:

Switch to Workspace. cd changes directory; this absolute path works from any location:
cd /home/labex/project/waymark
cat prints files. Read the independently supplied update request:
cat /opt/labex-waymark/trust/request.json
It requests product event-guide, version 2.1. Its approved update moves Old Mill Walk to 14:00. The other walk remains at 09:30. Read the starter script:
cat receive.sh
The starter takes a digest from the parcel itself and passes it to the publisher. A different publisher could provide a matching manifest and archive. This script does not yet verify a signature or the organizer's requested product/version. You will replace it before publishing.
The required interface is:
bash receive.sh CANDIDATE_DIRECTORY DESTINATION_DIRECTORY
The first argument is one supplier parcel. The second is a guide publication directory; in the live project it is published. Both paths may be absolute or relative, renamed, and contain spaces. Do not hardcode a parcel label or the destination. A successful update returns zero. Rejection returns nonzero and keeps all existing publication files unchanged. A legitimate update keeps previous releases and unrelated uploads.
You may edit receive.sh and create temporary files. Keep the original parcels, independent key/request, prepared publisher and other runtime files unchanged. The receiver must work from another directory when copied alone, with the same prepared tools and independent trust files available. Assessment exercises this contract on disposable directories as well as checking the real guide.
If a path is mistyped, run the absolute cd above, use ls candidates to check the labels, then retry the command. A failed check is a reason to inspect the relevant evidence, not to reset the environment.
Design a Receiver That Stops Before Publishing
In this step, you will understand the short script before writing it. Stay in Workspace.
The receiver will copy all three related files into a private temporary directory, verify that copied manifest, compare its product/version with the independent request, and pass the copied archive plus the signed digest to a prepared publisher. It will never verify one candidate and then reopen a different candidate path for delivery.
The publisher /opt/labex-waymark/publish.py is prepared archive-handling code. It reads the supplied archive into one byte buffer, checks that buffer against the supplied digest, safely extracts only site.json and routes.csv, and publishes those same bytes. It preserves earlier releases and uploads/. It does not decide whose signature to trust or which product/version the organizer requested; those decisions belong in your receiver.
You will use these shell features:
#!/bin/bashidentifies the script's shell. Runningbash receive.shalso selects Bash explicitly.set -euo pipefailmakes an unhandled failed command, an unset variable, or a failed pipeline stop the script. A failed signature must never fall through to publishing.$1and$2are the two arguments.candidate=$1saves a value;"$candidate"expands it as one argument, including spaces.$#counts arguments, and[ "$#" -ne 2 ]tests whether that count is not two.if,then, andfidelimit the conditional block.$(command)saves a command's output.mktemp -dcreates a unique temporary directory.trap '...' EXITruns cleanup when the script finishes. Hererm -rf -- "$stage"removes only that generated temporary directory; never substitute your project directory.--ends option parsing so a path is treated as data.cp -- SOURCE SOURCE SOURCE DIRECTORY/copies the three files. The final slash identifies the destination directory.jq -r .product FILEreads a JSON string without JSON quote marks.jq -ereturns a failing status for a false result.--arg product "$product"gives the filter a string variable named$product.andrequires every condition to hold. Single quotes preserve the filter text for jq.> /dev/nulldiscards the printed boolean while keeping its exit status.echo ... >&2writes an error to the error stream;exit 2ends with a nonzero status.
The signature command uses the same independently pinned public key as the previous lab. Its --bundle points to the copied signature bundle, and its last argument is the copied manifest. --insecure-ignore-tlog is the explicit limitation of these offline synthetic fixtures: it skips public log verification while still checking the pinned-key signature. This receiver is not a public identity or transparency-log verifier.
The final Python command invokes the prepared publisher; you are not writing Python. It receives the copied archive, the digest from the verified manifest, and the requested destination. If the digest does not match the archive bytes, it refuses the update before touching the existing guide.
Save the Reusable Receiver
In this step, you will write the receiver using the mechanisms you just reviewed. Start in Workspace, in /home/labex/project/waymark.
The following command replaces receive.sh. cat > receive.sh redirects the supplied text into the file. <<'RECEIVER' starts a here-document: everything until the line containing only RECEIVER becomes file content. Quoting that marker preserves $1, $stage, and the jq variables for the script's later execution. Paste the complete block, including its final marker:
cat > receive.sh <<'RECEIVER'
#!/bin/bash
set -euo pipefail
if [ "$#" -ne 2 ]; then
echo "Usage: bash receive.sh CANDIDATE_DIRECTORY DESTINATION_DIRECTORY" >&2
exit 2
fi
candidate=$1
destination=$2
stage=$(mktemp -d)
trap 'rm -rf -- "$stage"' EXIT
cp -- "$candidate/manifest.json" "$candidate/manifest.sigstore.json" "$candidate/guide.tar.gz" "$stage/"
/opt/labex-waymark/bin/cosign verify-blob --key /opt/labex-waymark/trust/publisher.pub --bundle "$stage/manifest.sigstore.json" --insecure-ignore-tlog "$stage/manifest.json"
product=$(jq -r .product /opt/labex-waymark/trust/request.json)
version=$(jq -r .version /opt/labex-waymark/trust/request.json)
jq -e --arg product "$product" --arg version "$version" '.product == $product and .version == $version and .archive == "guide.tar.gz"' "$stage/manifest.json" > /dev/null
digest=$(jq -r .sha256 "$stage/manifest.json")
python3 /opt/labex-waymark/publish.py "$stage/guide.tar.gz" "$digest" "$destination"
RECEIVER
Read it back:
cat receive.sh
Confirm the public key and request come from /opt/labex-waymark/trust/, while the manifest, bundle and archive all come from "$stage". Notice that the original candidate directory is not read again after copying. Changing or renaming the supplier drop after that copy cannot switch which bytes are handed to the publisher.
If the terminal shows a continuation prompt because the ending marker was omitted, type RECEIVER on its own line and press Enter. If the file was pasted incorrectly, repeat the entire here-document; this replaces only receive.sh. Do not run an incomplete receiver.
The prepared policy check runs your script unchanged as an ordinary account against disposable publication directories. It checks the actual published files, rejected cases and preservation behavior; it does not look for a particular command spelling:
sudo python3 /opt/labex-waymark/verify.py policy
sudo is needed by this prepared check to run the isolated ordinary account, not by your receiver. The check leaves the live guide alone. Expect its final line to say that the requested signed guide is accepted and the four unapproved cases are refused. If it fails, inspect the reported decision and correct receive.sh before proceeding.
Observe Rejections Before Updating the Live Guide
In this step, you will confirm the receiver stops on three different failures while the original guide remains available. Start in Workspace, in /home/labex/project/waymark.
First try the parcel signed by another publisher. Expect a signature-verification error and a nonzero status:
bash receive.sh candidates/parcel-102 published
$? holds the immediately preceding command's exit status. Read it before running anything else:
echo $?
Now try the genuine but earlier edition. Expect signature verification to pass, followed by a nonzero status because the requested product/version comparison is false:
bash receive.sh candidates/parcel-101 published
echo $?
The jq comparison is intentionally quiet; the nonzero status records its rejection. A valid signature did not override the independent request.
Finally try the changed archive. Expect the signature to pass but the publisher to report a digest mismatch and keep the existing guide:
bash receive.sh candidates/parcel-103 published
echo $?
Switch to Event Guide and refresh. The guide must still be edition 2.0; Old Mill Walk must still meet at 11:00. Use All walks to return, download the route sheet, and open Visitor notice. A receiver that refuses everything would preserve this old guide too, which is why a successful legitimate update is also required.
A missing file or a connection error is not the same as these expected policy rejections. For a misspelled parcel path, return to the absolute workspace path, list candidates, and retry. If the website is unavailable, inspect the reported error; do not call it a successful refusal.
Publish and Use the Approved New Guide
In this step, you will deliver the verified update and use its actual pages and download. Start in Workspace, in /home/labex/project/waymark.
Receive the approved parcel:
bash receive.sh candidates/parcel-104 published
Expect Verified OK and:
Published event-guide 2.1. Previous releases and uploads kept.
Check its status immediately:
echo $?
Expect 0. Switch to Event Guide and refresh. The home page now shows event-guide edition 2.1. Open Old Mill Walk and confirm 14:00, then use All walks and open Riverside Welcome to confirm its 09:30 meeting is preserved. Return with All walks and download the new route sheet. The two rows must agree with the detail pages. Open Visitor notice to confirm the original advice remains.
The approved home page shows edition 2.1; look at Old Mill Walk’s 14:00 meeting time:

Switch to Workspace. The live self-check reads the running detail page, downloaded route-sheet bytes, current guide files, preserved previous release and notice:
python3 /opt/labex-waymark/verify.py live
Expect:
The running guide and download match the approved release. Previous releases and visitor notice are preserved.
ls lists the retained release directories:
ls published/releases
There should be a previous event-guide-2.0-... release and the new event-guide-2.1-... release. The trailing characters come from the actual archive digest and are not values to memorize.
If the live check fails, use its message to distinguish a receiving decision from an unavailable website or changed original file. Correct the receiver if needed, then receive the original approved parcel again. Repeating the same valid update is safe: the publisher reuses the identical release and keeps the previous files. Repeat the live self-check to confirm the result.
Summary
You replaced a receiver that trusted its delivery with one that checks an independent publisher key and organizer request, then binds the signed archive digest to the bytes actually published. Wrong signers, genuine old releases and changed archives were refused while the original service stayed usable.
The approved update is now a real guide with working detail pages and a matching route download. The challenge applies these same tools to an accessible guide and includes a genuine signed parcel for the wrong product.


