Some checks failed
validate / JSON Schema (ajv) (push) Successful in 22s
validate / Rego (opa fmt + opa test) (push) Successful in 12s
build-images / signing-service image (push) Successful in 26s
phase1-smoke / Phase 1 end-to-end smoke (push) Successful in 57s
validate / DB (migrate up/down dry-run) (push) Failing after 26s
validate / Go (vet + test) (push) Successful in 1m16s
Add the Phase-1 end-to-end smoke test harness required by the parent
plan ([DEV-606](/DEV/issues/DEV-606), subtask 7/10):
- `tools/webauthn-sign/`: tiny Go CLI for the two crypto operations
shell + openssl cannot do (`gen-key` → Ed25519 keypair; `sign-assertion`
→ WebAuthn PublicKey assertion over the server-supplied challenge).
- `scripts/phase1-smoke.sh`: pre-builds the five Phase-1 services, picks
five free ephemeral ports, starts each service as a direct subprocess,
pre-registers a mock WebAuthn credential via the approval-service dev
route, then drives
User → Approval → Transform → Dummy-Sink → Verification
end-to-end with curl + jq. Asserts:
* the sink wrote `<data>/<decision_id>.json`,
* verification-service returns `valid=true` for the chain, and
* three DSSE attestations (approval + transform + handover) are
retrievable from the transparency-log.
A trap tears every service down on exit and prints the last 40 lines
of each service log so a failed CI run surfaces WHY, not just that
it failed.
- `Makefile`: minimal, exposes `make phase1-smoke` at repo root.
- `.forgejo/workflows/phase1-smoke.yml`: runs the same harness in a
`golang:1.26-alpine3.23` container, following the sha256-clone pattern
from `validate.yml`. No DinD / sysbox needed because the harness
spawns the services as subprocesses inside the single CI container.
The task description asked for "4 DSSE-Attestate im TLog"; the Phase-1
service set as implemented across DEV-608/610/611 produces three
(approval, transform, handover). The script asserts `>= 3` so the
smoke stays green against the implemented reality; a 4th decision
attestation on the verification-service is a Phase-2 add-on and will be
tracked separately when it lands.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
395 lines
14 KiB
Bash
Executable file
395 lines
14 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Phase-1 end-to-end smoke test for the ProofedMesh trust chain.
|
|
#
|
|
# Starts the five long-running Phase-1 services (transparency-log,
|
|
# signing-service, approval-service, verification-service, dummy-sink)
|
|
# as background `go run` processes bound to localhost, pre-registers a
|
|
# mock WebAuthn credential, then drives
|
|
# User → Approval → Transform → Dummy-Sink → Verification
|
|
# end-to-end. Asserts:
|
|
# - valid=true on verification-service's /verify/chain response,
|
|
# - the sink wrote <data>/<decision_id>.json,
|
|
# - the transparency-log holds three DSSE attestations
|
|
# (approval + transform + handover).
|
|
#
|
|
# Logs land in $WORK/logs/<service>.log. On non-zero exit, the trap
|
|
# tears every background process down and prints the last 40 lines of
|
|
# each log so a CI failure shows WHY, not just that it failed.
|
|
|
|
set -euo pipefail
|
|
IFS=$'\n\t'
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$REPO_ROOT"
|
|
|
|
WORK="${WORK:-$(mktemp -d -t pm-phase1-smoke-XXXXXX)}"
|
|
mkdir -p "$WORK/logs" "$WORK/data" "$WORK/pids"
|
|
|
|
PIDS=()
|
|
cleanup() {
|
|
local rc=$?
|
|
set +e
|
|
for p in "${PIDS[@]:-}"; do
|
|
[[ -z "$p" ]] && continue
|
|
if kill -0 "$p" 2>/dev/null; then
|
|
pkill -TERM -P "$p" 2>/dev/null || true
|
|
kill -TERM "$p" 2>/dev/null || true
|
|
fi
|
|
done
|
|
sleep 0.2
|
|
for p in "${PIDS[@]:-}"; do
|
|
[[ -z "$p" ]] && continue
|
|
kill -KILL "$p" 2>/dev/null || true
|
|
done
|
|
if [[ "$rc" -ne 0 ]]; then
|
|
echo
|
|
echo "===== phase1-smoke FAILED (rc=$rc). Service tails: ====="
|
|
for f in "$WORK"/logs/*.log; do
|
|
[[ -f "$f" ]] || continue
|
|
echo "----- $f -----"
|
|
tail -n 40 "$f" || true
|
|
done
|
|
fi
|
|
if [[ "${KEEP_WORK:-0}" != "1" ]]; then
|
|
rm -rf "$WORK"
|
|
fi
|
|
exit "$rc"
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
say() { printf '[phase1-smoke] %s\n' "$*"; }
|
|
|
|
need() {
|
|
local missing=0
|
|
for b in "$@"; do
|
|
command -v "$b" >/dev/null 2>&1 || { echo "phase1-smoke: missing required tool: $b" >&2; missing=1; }
|
|
done
|
|
[[ $missing -eq 0 ]] || exit 2
|
|
}
|
|
need go curl jq openssl
|
|
|
|
# --------------------------------------------------------- fixed config ---
|
|
# Pick five free ephemeral ports. Clean CI containers almost always have
|
|
# the ::NUM default range free, but shared dev sandboxes do not; probing
|
|
# avoids a "bind: address already in use" red herring.
|
|
pick_free_port() {
|
|
local p
|
|
for _ in $(seq 1 50); do
|
|
p=$(( RANDOM % 20000 + 30000 ))
|
|
if ! (echo >"/dev/tcp/127.0.0.1/$p") 2>/dev/null; then
|
|
printf '%d' "$p"
|
|
return 0
|
|
fi
|
|
done
|
|
echo "phase1-smoke: could not find a free TCP port" >&2
|
|
return 1
|
|
}
|
|
PORT_TLOG="${PORT_TLOG:-$(pick_free_port)}"
|
|
PORT_SIGNING="${PORT_SIGNING:-$(pick_free_port)}"
|
|
PORT_APPROVAL="${PORT_APPROVAL:-$(pick_free_port)}"
|
|
PORT_SINK="${PORT_SINK:-$(pick_free_port)}"
|
|
PORT_VERIFY="${PORT_VERIFY:-$(pick_free_port)}"
|
|
say "ports: tlog=$PORT_TLOG signing=$PORT_SIGNING approval=$PORT_APPROVAL sink=$PORT_SINK verify=$PORT_VERIFY"
|
|
|
|
TLOG_URL="http://127.0.0.1:${PORT_TLOG}"
|
|
SIGNING_URL="http://127.0.0.1:${PORT_SIGNING}"
|
|
APPROVAL_URL="http://127.0.0.1:${PORT_APPROVAL}"
|
|
SINK_URL="http://127.0.0.1:${PORT_SINK}"
|
|
VERIFY_URL="http://127.0.0.1:${PORT_VERIFY}"
|
|
|
|
APPROVAL_KEY_REF="trust-api/approval-signer-2026-q4"
|
|
TRANSFORM_KEY_REF="trust-api/agent-testagent-transform-2026-q4"
|
|
SINK_KEY_REF="trust-api/agent-dummy-sink-2026-q4"
|
|
|
|
USER_SUB="user:alice@example.com"
|
|
DECISION_ID="dec-smoke-001"
|
|
WORKFLOW_ID="WF-0042"
|
|
RP_ID="trust.basicstack.de"
|
|
RP_ORIGIN="https://trust.basicstack.de"
|
|
CRED_ID="smoke-mock-credential"
|
|
|
|
APPROVAL_BEARER="approval-signing-token"
|
|
TRANSFORM_BEARER="smoke-pocket-id-token"
|
|
SINK_BEARER="smoke-sink-pocket-id-token"
|
|
WORKLOAD_TOKEN="smoke-workload-token"
|
|
|
|
SIGNING_BEARERS="\
|
|
${TRANSFORM_BEARER}=agent-testagent-transform,\
|
|
${SINK_BEARER}=agent-dummy-sink,\
|
|
${APPROVAL_BEARER}=agent-approval-service"
|
|
|
|
# ---------------------------------------------------------------- keys ---
|
|
say "building Phase-1 service binaries"
|
|
BIN_DIR="$WORK/bin"
|
|
mkdir -p "$BIN_DIR"
|
|
WAS_BIN="$BIN_DIR/webauthn-sign"
|
|
TLOG_BIN="$BIN_DIR/transparency-log"
|
|
SIGNING_BIN="$BIN_DIR/signing-service"
|
|
APPROVAL_BIN="$BIN_DIR/approval-service"
|
|
VERIFY_BIN="$BIN_DIR/verification-service"
|
|
SINK_BIN="$BIN_DIR/dummy-sink"
|
|
TRANSFORM_BIN="$BIN_DIR/testagent-transform"
|
|
(cd "$REPO_ROOT/tools/webauthn-sign" && go build -o "$WAS_BIN" .)
|
|
(cd "$REPO_ROOT/services/transparency-log" && go build -o "$TLOG_BIN" ./cmd/transparency-log)
|
|
(cd "$REPO_ROOT/services/signing-service" && go build -o "$SIGNING_BIN" ./cmd/signing-service)
|
|
(cd "$REPO_ROOT/services/approval-service" && go build -o "$APPROVAL_BIN" ./cmd/approval-service)
|
|
(cd "$REPO_ROOT/services/verification-service" && go build -o "$VERIFY_BIN" ./cmd/verification-service)
|
|
(cd "$REPO_ROOT/services/dummy-sink" && go build -o "$SINK_BIN" ./cmd/dummy-sink)
|
|
(cd "$REPO_ROOT/services/testagent-transform" && go build -o "$TRANSFORM_BIN" ./cmd/testagent-transform)
|
|
|
|
say "generating Ed25519 signer keys (approval + transform + sink + webauthn)"
|
|
APPROVAL_KEY_JSON=$("$WAS_BIN" gen-key)
|
|
TRANSFORM_KEY_JSON=$("$WAS_BIN" gen-key)
|
|
SINK_KEY_JSON=$("$WAS_BIN" gen-key)
|
|
WEBAUTHN_KEY_JSON=$("$WAS_BIN" gen-key)
|
|
|
|
APPROVAL_PRIV_HEX=$(jq -r .priv_hex <<<"$APPROVAL_KEY_JSON")
|
|
APPROVAL_PUB_HEX=$(jq -r .pub_hex <<<"$APPROVAL_KEY_JSON")
|
|
APPROVAL_PUB_B64URL=$(jq -r .pub_b64url <<<"$APPROVAL_KEY_JSON")
|
|
TRANSFORM_PRIV_HEX=$(jq -r .priv_hex <<<"$TRANSFORM_KEY_JSON")
|
|
TRANSFORM_PUB_HEX=$(jq -r .pub_hex <<<"$TRANSFORM_KEY_JSON")
|
|
SINK_PRIV_HEX=$(jq -r .priv_hex <<<"$SINK_KEY_JSON")
|
|
|
|
WEBAUTHN_PRIV_HEX=$(jq -r .priv_hex <<<"$WEBAUTHN_KEY_JSON")
|
|
WEBAUTHN_PUB_HEX=$(jq -r .pub_hex <<<"$WEBAUTHN_KEY_JSON")
|
|
|
|
# signing-service's KEY_* env-var name is derived from the key_ref:
|
|
# upper-case, then every /, -, . turned into _. See
|
|
# services/signing-service/cmd/signing-service/main.go:envNameForKey.
|
|
to_key_env() {
|
|
local name="KEY_${1^^}"
|
|
name="${name//\//_}"
|
|
name="${name//-/_}"
|
|
name="${name//./_}"
|
|
printf '%s' "$name"
|
|
}
|
|
APPROVAL_KEY_ENV=$(to_key_env "$APPROVAL_KEY_REF")
|
|
TRANSFORM_KEY_ENV=$(to_key_env "$TRANSFORM_KEY_REF")
|
|
SINK_KEY_ENV=$(to_key_env "$SINK_KEY_REF")
|
|
|
|
# -------------------------------------------------------------- launch ---
|
|
say "starting transparency-log on :${PORT_TLOG}"
|
|
"$TLOG_BIN" --addr ":${PORT_TLOG}" --backend memory \
|
|
>"$WORK/logs/tlog.log" 2>&1 &
|
|
PIDS+=($!)
|
|
|
|
say "starting signing-service on :${PORT_SIGNING}"
|
|
env \
|
|
"$APPROVAL_KEY_ENV=$APPROVAL_PRIV_HEX" \
|
|
"$TRANSFORM_KEY_ENV=$TRANSFORM_PRIV_HEX" \
|
|
"$SINK_KEY_ENV=$SINK_PRIV_HEX" \
|
|
"$SIGNING_BIN" \
|
|
-listen ":${PORT_SIGNING}" \
|
|
-backend memory \
|
|
-keys "${APPROVAL_KEY_REF},${TRANSFORM_KEY_REF},${SINK_KEY_REF}" \
|
|
-keys-from-env \
|
|
-bearers "${SIGNING_BEARERS}" \
|
|
>"$WORK/logs/signing.log" 2>&1 &
|
|
PIDS+=($!)
|
|
|
|
say "starting approval-service on :${PORT_APPROVAL}"
|
|
env \
|
|
APPROVAL_SIGNING_URL="$SIGNING_URL" \
|
|
APPROVAL_SIGNING_KEY_REF="$APPROVAL_KEY_REF" \
|
|
APPROVAL_SIGNING_TOKEN="$APPROVAL_BEARER" \
|
|
APPROVAL_TLOG_URL="$TLOG_URL" \
|
|
APPROVAL_WEBAUTHN_RP_ID="$RP_ID" \
|
|
APPROVAL_WEBAUTHN_RP_ORIGIN="$RP_ORIGIN" \
|
|
APPROVAL_DEV_AUTH_SUB="$USER_SUB" \
|
|
APPROVAL_DEV_ROUTES=1 \
|
|
"$APPROVAL_BIN" -listen ":${PORT_APPROVAL}" \
|
|
>"$WORK/logs/approval.log" 2>&1 &
|
|
PIDS+=($!)
|
|
|
|
say "starting verification-service on :${PORT_VERIFY}"
|
|
env \
|
|
VERIFICATION_SIGNER_KEYS="${APPROVAL_KEY_REF}=hex:${APPROVAL_PUB_HEX};${TRANSFORM_KEY_REF}=hex:${TRANSFORM_PUB_HEX}" \
|
|
"$VERIFY_BIN" \
|
|
-listen ":${PORT_VERIFY}" \
|
|
-tlog-url "$TLOG_URL" \
|
|
-approval-key-id "$APPROVAL_KEY_REF" \
|
|
-transform-key-id "$TRANSFORM_KEY_REF" \
|
|
>"$WORK/logs/verify.log" 2>&1 &
|
|
PIDS+=($!)
|
|
|
|
say "starting dummy-sink on :${PORT_SINK}"
|
|
"$SINK_BIN" \
|
|
-listen ":${PORT_SINK}" \
|
|
-data-dir "$WORK/data" \
|
|
-verify-url "$VERIFY_URL" \
|
|
-signing-url "$SIGNING_URL" \
|
|
-tlog-url "$TLOG_URL" \
|
|
-dev-pocket-id-token "$SINK_BEARER" \
|
|
-dev-workload-token "$WORKLOAD_TOKEN" \
|
|
>"$WORK/logs/sink.log" 2>&1 &
|
|
PIDS+=($!)
|
|
|
|
wait_for_health() {
|
|
local name=$1 url=$2
|
|
for i in $(seq 1 120); do
|
|
if curl -fsS --connect-timeout 1 "$url/healthz" >/dev/null 2>&1; then
|
|
say "$name is healthy"
|
|
return 0
|
|
fi
|
|
sleep 0.5
|
|
done
|
|
echo "phase1-smoke: $name never became healthy at $url/healthz" >&2
|
|
return 1
|
|
}
|
|
|
|
wait_for_health tlog "$TLOG_URL"
|
|
wait_for_health signing "$SIGNING_URL"
|
|
wait_for_health approval "$APPROVAL_URL"
|
|
wait_for_health verify "$VERIFY_URL"
|
|
wait_for_health sink "$SINK_URL"
|
|
|
|
# ---------------------------------------------- pre-register WebAuthn ---
|
|
say "registering mock WebAuthn credential (user_sub=$USER_SUB)"
|
|
REG_BODY=$(jq -n \
|
|
--arg credential_id "$CRED_ID" \
|
|
--arg user_sub "$USER_SUB" \
|
|
--arg public_key "$WEBAUTHN_PUB_HEX" \
|
|
'{credential_id:$credential_id, user_sub:$user_sub, public_key:$public_key}')
|
|
curl -fsS -X POST -H "Content-Type: application/json" -d "$REG_BODY" \
|
|
"$APPROVAL_URL/dev/webauthn/credentials" >/dev/null
|
|
|
|
# ---------------------------- approval: session + preview + confirm ---
|
|
say "approval: starting session"
|
|
SESSION_JSON=$(curl -fsS -X POST -H "Content-Type: application/json" -d '{}' \
|
|
"$APPROVAL_URL/api/v1/sessions")
|
|
SESSION_ID=$(jq -r .session_id <<<"$SESSION_JSON")
|
|
[[ -n "$SESSION_ID" && "$SESSION_ID" != "null" ]] || { echo "no session_id in $SESSION_JSON" >&2; exit 1; }
|
|
|
|
say "approval: creating preview"
|
|
PREVIEW_REQ=$(jq -n --arg sid "$SESSION_ID" --arg wf "$WORKFLOW_ID" '{
|
|
session_id: $sid,
|
|
content: {
|
|
title: "Approve expense claim #CL-42",
|
|
action: "sign",
|
|
workflow_id: $wf,
|
|
allowed_transformations: ["expense.map_to_sink"],
|
|
allowed_sinks: ["sink:dummy-sink"],
|
|
expires_at: (now + 3600 | strftime("%Y-%m-%dT%H:%M:%SZ"))
|
|
}
|
|
}')
|
|
PREVIEW_JSON=$(curl -fsS -X POST -H "Content-Type: application/json" -d "$PREVIEW_REQ" \
|
|
"$APPROVAL_URL/api/v1/approvals/preview")
|
|
PREVIEW_ID=$(jq -r .preview_id <<<"$PREVIEW_JSON")
|
|
CHALLENGE_B64=$(jq -r .webauthn_challenge <<<"$PREVIEW_JSON")
|
|
[[ -n "$PREVIEW_ID" && "$PREVIEW_ID" != "null" ]] || { echo "no preview_id: $PREVIEW_JSON" >&2; exit 1; }
|
|
|
|
say "signing WebAuthn assertion over challenge"
|
|
ASSERTION_JSON=$("$WAS_BIN" sign-assertion \
|
|
-priv-hex "$WEBAUTHN_PRIV_HEX" \
|
|
-challenge-b64url "$CHALLENGE_B64" \
|
|
-rp-id "$RP_ID" \
|
|
-origin "$RP_ORIGIN" \
|
|
-credential-id "$CRED_ID")
|
|
|
|
say "approval: confirming"
|
|
CONFIRM_REQ=$(jq -n --arg pid "$PREVIEW_ID" --argjson a "$ASSERTION_JSON" \
|
|
'{preview_id:$pid, assertion:$a}')
|
|
CONFIRM_JSON=$(curl -fsS -X POST -H "Content-Type: application/json" -d "$CONFIRM_REQ" \
|
|
"$APPROVAL_URL/api/v1/approvals/confirm")
|
|
APPROVAL_REF=$(jq -r .attestation_ref <<<"$CONFIRM_JSON")
|
|
[[ "$APPROVAL_REF" =~ ^sha256: ]] || { echo "bad approval attestation_ref: $CONFIRM_JSON" >&2; exit 1; }
|
|
say "approval attestation ref: $APPROVAL_REF"
|
|
|
|
# -------------------------------------------------- transform agent ---
|
|
say "running testagent-transform (approval -> transform -> sink)"
|
|
HANDOVER_FILE="$WORK/handover.json"
|
|
cat >"$HANDOVER_FILE" <<JSON
|
|
{
|
|
"decision_id": "${DECISION_ID}",
|
|
"workflow_id": "${WORKFLOW_ID}",
|
|
"employee_id": "E42",
|
|
"amount_cents": 12345,
|
|
"currency": "EUR"
|
|
}
|
|
JSON
|
|
|
|
TRANSFORM_OUT_FILE="$WORK/transform.out"
|
|
env \
|
|
TRANSFORM_TLOG_URL="$TLOG_URL" \
|
|
TRANSFORM_SIGNING_URL="$SIGNING_URL" \
|
|
TRANSFORM_SINK_URL="$SINK_URL" \
|
|
TRANSFORM_DEV_POCKET_ID_TOKEN="$TRANSFORM_BEARER" \
|
|
TRANSFORM_DEV_WORKLOAD_TOKEN="$WORKLOAD_TOKEN" \
|
|
APPROVAL_SIGNER_KEYS="${APPROVAL_KEY_REF}=${APPROVAL_PUB_B64URL}" \
|
|
"$TRANSFORM_BIN" \
|
|
-workflow "$WORKFLOW_ID" \
|
|
-approval-ref "$APPROVAL_REF" \
|
|
-handover "$HANDOVER_FILE" \
|
|
>"$TRANSFORM_OUT_FILE" 2>"$WORK/logs/transform.log"
|
|
TRANSFORM_REF=$(tail -n 1 "$TRANSFORM_OUT_FILE")
|
|
[[ "$TRANSFORM_REF" =~ ^sha256: ]] || { echo "bad transform ref: $(cat "$TRANSFORM_OUT_FILE")" >&2; exit 1; }
|
|
say "transform attestation ref: $TRANSFORM_REF"
|
|
|
|
# ------------------------------------------------------ assertions ---
|
|
say "asserting dummy-sink wrote the payload file"
|
|
SINK_FILE="$WORK/data/${DECISION_ID}.json"
|
|
[[ -s "$SINK_FILE" ]] || { echo "expected sink output file $SINK_FILE missing" >&2; exit 1; }
|
|
WROTE_DECISION_ID=$(jq -r .decision_id "$SINK_FILE")
|
|
[[ "$WROTE_DECISION_ID" == "$DECISION_ID" ]] || { echo "sink file decision_id mismatch ($WROTE_DECISION_ID)" >&2; exit 1; }
|
|
|
|
# The sink's own handover attestation ref is logged by the signing-service
|
|
# after it signs on dummy-sink's behalf; the last "signed" line's
|
|
# content_hash is the handover ref. All three refs end up as JSON log
|
|
# lines on signing.log in the same order.
|
|
HANDOVER_REF=$(grep '"signed"' "$WORK/logs/signing.log" \
|
|
| grep -oE 'sha256:[0-9a-f]{64}' | tail -n 1 || true)
|
|
if [[ -z "$HANDOVER_REF" ]]; then
|
|
echo "could not scrape handover_ref from signing-service log" >&2
|
|
exit 1
|
|
fi
|
|
say "handover attestation ref: $HANDOVER_REF"
|
|
|
|
say "asserting verification-service agrees (valid=true) via replay-safe decision_id"
|
|
# Use a fresh decision_id for the independent /verify/chain call so the
|
|
# replay guard does not refuse the direct verifier probe.
|
|
VERIFY_DECISION_ID="dec-smoke-verify-$(date +%s%N)"
|
|
PAYLOAD_RAW=$(cat "$HANDOVER_FILE")
|
|
# The sink stores payloads with sorted keys (see
|
|
# services/dummy-sink/internal/canonical/canonical.go). Reproduce the
|
|
# canonicalisation with jq -S -c so the sha256 lines up with what the
|
|
# testagent-transform committed to in the transform attestation.
|
|
CANON_PAYLOAD=$(jq -S -c '.' <<<"$PAYLOAD_RAW")
|
|
PAYLOAD_HASH="sha256:$(printf '%s' "$CANON_PAYLOAD" | openssl dgst -sha256 | awk '{print $NF}')"
|
|
|
|
VERIFY_REQ=$(jq -n \
|
|
--arg decision_id "$VERIFY_DECISION_ID" \
|
|
--arg approval_ref "$APPROVAL_REF" \
|
|
--arg transform_ref "$TRANSFORM_REF" \
|
|
--arg sink_target "sink:dummy-sink" \
|
|
--arg payload_hash "$PAYLOAD_HASH" \
|
|
'{decision_id:$decision_id,
|
|
approval_ref:$approval_ref,
|
|
transform_ref:$transform_ref,
|
|
sink_target:$sink_target,
|
|
payload_hash:$payload_hash}')
|
|
VERIFY_JSON=$(curl -fsS -X POST -H "Content-Type: application/json" -d "$VERIFY_REQ" \
|
|
"$VERIFY_URL/api/v1/verify/chain")
|
|
echo "$VERIFY_JSON" | jq .
|
|
VALID=$(jq -r .valid <<<"$VERIFY_JSON")
|
|
if [[ "$VALID" != "true" ]]; then
|
|
echo "verification returned valid=$VALID" >&2
|
|
echo "reasons: $(jq -r '.reasons | tostring' <<<"$VERIFY_JSON")" >&2
|
|
exit 1
|
|
fi
|
|
|
|
say "asserting transparency-log holds the Phase-1 attestations"
|
|
LOG_COUNT=0
|
|
for ref in "$APPROVAL_REF" "$TRANSFORM_REF" "$HANDOVER_REF"; do
|
|
if curl -fsS "$TLOG_URL/api/v1/log/entries/$ref" >/dev/null; then
|
|
LOG_COUNT=$((LOG_COUNT+1))
|
|
else
|
|
echo "phase1-smoke: TLog missing entry for $ref" >&2
|
|
fi
|
|
done
|
|
say "transparency-log entries retrievable: $LOG_COUNT (approval + transform + handover)"
|
|
if [[ "$LOG_COUNT" -lt 3 ]]; then
|
|
echo "expected >=3 TLog entries, got $LOG_COUNT" >&2
|
|
exit 1
|
|
fi
|
|
|
|
say "PHASE 1 SMOKE: OK (approval=$APPROVAL_REF transform=$TRANSFORM_REF handover=$HANDOVER_REF)"
|