onboarding-harness: combined gate+buyer-pays brief; probe mints .live-env

run-stage2.sh: rewrite AGENT_BRIEF to the four-step operator-order journey
(define a paid product + entitlement, integrate the SDK and verify the gate
is BLOCKED, connect BTCPay regtest and have a buyer pay, then the PURCHASED
license unlocks the gate) and add the sandbox-app section the SDK-gating half
needs. Header comment updated to match.

probe.sh: do what the README/brief already claim it does. In addition to the
de-risk payload dump, create both stores (wallet + no-wallet), generate the
on-chain regtest wallet, mint store-scoped tokens with the five documented
connect permissions, and write .live-env for run-stage2.sh / validate-gate.sh
to source. Previously .live-env had to be hand-built and went stale on down -v.
This commit is contained in:
Grant
2026-06-17 12:03:35 -05:00
parent b6758cf30a
commit 8c5cdb6468
2 changed files with 141 additions and 56 deletions
@@ -1,25 +1,55 @@
#!/usr/bin/env bash
# De-risk probe for agent-payment-connect network detection (spec §6.1).
# Stands up a store + on-chain regtest wallet on the local BTCPay regtest stack,
# then dumps the exact Greenfield responses the slice-3 gate would consult:
# - GET /api/v1/stores/{id}/payment-methods (paymentMethodId form? derivationScheme exposed?)
# - GET /api/v1/stores/{id}/payment-methods/{pmid}/wallet/address (bcrt1… prefix?)
# De-risk probe + .live-env minter for the Stage 2 / combined onboarding harness.
# Run once after `docker compose -p keysat-btcpay up -d`.
#
# Two jobs:
# A. Mint .live-env — create the two stores the harness needs (one with an
# on-chain regtest wallet, one without) plus store-scoped BTCPay API tokens
# carrying the five permissions the Connect-BTCPay flow documents
# (install.html#connect-btcpay), and write them to .live-env for
# run-stage2.sh / validate-gate.sh to source.
# B. De-risk (spec §6.1) — dump the exact Greenfield responses the slice-3
# network gate consults (payment-methods, wallet/address) into probe-out/
# and classify the receive-address HRP.
#
# Idempotency: assumes a FRESH instance (compose `up -d` after `down -v`).
# Re-running against a live instance creates duplicate stores — tear down first.
# Read-only against Keysat; only mutates the throwaway BTCPay instance.
set -uo pipefail
BASE="${BTCPAY_BASE:-http://127.0.0.1:49392}"
ADMIN_EMAIL="admin@keysat.local"
ADMIN_PW="keysatregtest1!"
OUT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/probe-out"
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT_DIR="$HERE/probe-out"
LIVE_ENV="$HERE/.live-env"
mkdir -p "$OUT_DIR"
# Permissions the documented Connect-BTCPay flow grants (install.html#connect-btcpay).
STORE_PERMS='canviewstoresettings canmodifystoresettings canviewinvoices cancreateinvoice canmodifyinvoices'
BTND=keysat-btcpay-bitcoind-1
hr(){ printf '\n\033[1;36m=== %s ===\033[0m\n' "$*"; }
jqp(){ jq . 2>/dev/null || cat; }
AUTH=(-u "$ADMIN_EMAIL:$ADMIN_PW")
cli(){ docker exec "$BTND" bitcoin-cli -regtest -rpcuser=keysat -rpcpassword=keysat -rpcport=43782 "$@"; }
create_store(){ # NAME -> store id
curl -sS "${AUTH[@]}" -X POST "$BASE/api/v1/stores" \
-H 'Content-Type: application/json' -d "{\"name\":\"$1\"}" | jq -r '.id'
}
store_token(){ # STORE_ID -> store-scoped API key with the 5 documented perms
local sid="$1" perms="" p
for p in $STORE_PERMS; do perms="$perms\"btcpay.store.$p:$sid\","; done
curl -sS "${AUTH[@]}" -X POST "$BASE/api/v1/api-keys" \
-H 'Content-Type: application/json' \
-d "{\"label\":\"keysat-$sid\",\"permissions\":[${perms%,}]}" | jq -r '.apiKey'
}
# --- 0. wait for BTCPay --------------------------------------------------------
hr "0. waiting for BTCPay health at $BASE"
for i in $(seq 1 120); do
if curl -fsS "$BASE/api/v1/health" >/dev/null 2>&1; then break; fi
curl -fsS "$BASE/api/v1/health" >/dev/null 2>&1 && break
sleep 2
[[ $i == 120 ]] && { echo "BTCPay never became healthy"; exit 1; }
done
@@ -31,52 +61,64 @@ curl -sS -X POST "$BASE/api/v1/users" \
-H 'Content-Type: application/json' \
-d "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PW\",\"isAdministrator\":true}" | jqp
# Basic-auth header for subsequent Greenfield calls.
AUTH=(-u "$ADMIN_EMAIL:$ADMIN_PW")
# --- 2. admin user API key (KEYSAT_LIVE_BTCPAY_KEY; broad, for ad-hoc admin use) -
hr "2. mint admin user API key"
ADMIN_KEY="$(curl -sS "${AUTH[@]}" -X POST "$BASE/api/v1/api-keys" \
-H 'Content-Type: application/json' \
-d '{"label":"keysat-admin","permissions":["btcpay.server.canmodifyserversettings","btcpay.store.canmodifystoresettings","btcpay.store.canmodifyinvoices"]}' \
| jq -r '.apiKey')"
echo "ADMIN_KEY=${ADMIN_KEY:0:8}"
# --- 2. create a store ---------------------------------------------------------
hr "2. create store"
STORE_JSON="$(curl -sS "${AUTH[@]}" -X POST "$BASE/api/v1/stores" \
-H 'Content-Type: application/json' -d '{"name":"Keysat Regtest Co"}')"
echo "$STORE_JSON" | jqp
STORE_ID="$(echo "$STORE_JSON" | jq -r '.id')"
echo "STORE_ID=$STORE_ID"
[[ -z "$STORE_ID" || "$STORE_ID" == null ]] && { echo "no store id"; exit 1; }
# --- 3. regtest store WITH an on-chain wallet ----------------------------------
hr "3. create regtest store (with on-chain wallet)"
STORE_REGTEST="$(create_store 'Keysat Regtest Co')"
echo "STORE_REGTEST=$STORE_REGTEST"
[[ -z "$STORE_REGTEST" || "$STORE_REGTEST" == null ]] && { echo "no regtest store id"; exit 1; }
# --- 3. generate an on-chain wallet; try BTC-CHAIN then BTC --------------------
gen_body='{"savePrivateKeys":false,"importKeysToRPC":false,"wordList":"English","wordCount":12,"scriptPubKeyType":"Segwit"}'
PMID=""
for cand in BTC-CHAIN BTC; do
hr "3. generate wallet on pmid=$cand"
hr "3b. generate wallet on pmid=$cand"
code="$(curl -sS -o "$OUT_DIR/gen-$cand.json" -w '%{http_code}' "${AUTH[@]}" \
-X POST "$BASE/api/v1/stores/$STORE_ID/payment-methods/$cand/wallet/generate" \
-X POST "$BASE/api/v1/stores/$STORE_REGTEST/payment-methods/$cand/wallet/generate" \
-H 'Content-Type: application/json' -d "$gen_body")"
echo "HTTP $code"; cat "$OUT_DIR/gen-$cand.json" | jqp
if [[ "$code" == 2* ]]; then PMID="$cand"; break; fi
[[ "$code" == 2* ]] && { PMID="$cand"; break; }
done
[[ -z "$PMID" ]] && echo "!! wallet generate failed for both pmid forms (see above)"
[[ -z "$PMID" ]] && { echo "!! wallet generate failed for both pmid forms"; exit 1; }
# --- 4. mine some regtest blocks so the wallet has a usable address ------------
# --- 4. mine regtest blocks so the wallet has a usable address -----------------
hr "4. mine regtest blocks"
ADDR_FOR_MINE="$(docker exec keysat-btcpay-bitcoind-1 bitcoin-cli -regtest -rpcuser=keysat -rpcpassword=keysat -rpcport=43782 getnewaddress 2>/dev/null || true)"
ADDR_FOR_MINE="$(cli getnewaddress 2>/dev/null || true)"
echo "miner address: ${ADDR_FOR_MINE:-<none>}"
if [[ -n "$ADDR_FOR_MINE" ]]; then
docker exec keysat-btcpay-bitcoind-1 bitcoin-cli -regtest -rpcuser=keysat -rpcpassword=keysat -rpcport=43782 generatetoaddress 101 "$ADDR_FOR_MINE" >/dev/null 2>&1 \
&& echo "mined 101 blocks" || echo "mine failed (non-fatal for detection probe)"
fi
[[ -n "$ADDR_FOR_MINE" ]] && { cli generatetoaddress 101 "$ADDR_FOR_MINE" >/dev/null 2>&1 \
&& echo "mined 101 blocks" || echo "mine failed (non-fatal for detection probe)"; }
# --- 5. THE PAYLOADS the slice-3 gate consults --------------------------------
hr "5a. GET payment-methods (does it expose derivationScheme? what pmid?)"
curl -sS "${AUTH[@]}" "$BASE/api/v1/stores/$STORE_ID/payment-methods?includeConfig=true" \
# --- 5. no-wallet store (fail-closed arm of the gate) --------------------------
hr "5. create no-wallet store"
STORE_NOWALLET="$(create_store 'Keysat NoWallet Co')"
echo "STORE_NOWALLET=$STORE_NOWALLET"
[[ -z "$STORE_NOWALLET" || "$STORE_NOWALLET" == null ]] && { echo "no nowallet store id"; exit 1; }
# --- 6. store-scoped tokens (what the agent/harness hand Keysat at connect) -----
hr "6. mint store-scoped tokens"
GATE_TOK_REGTEST="$(store_token "$STORE_REGTEST")"
GATE_TOK_NOWALLET="$(store_token "$STORE_NOWALLET")"
echo "GATE_TOK_REGTEST=${GATE_TOK_REGTEST:0:8}… GATE_TOK_NOWALLET=${GATE_TOK_NOWALLET:0:8}"
[[ "$GATE_TOK_REGTEST" == null || -z "$GATE_TOK_REGTEST" ]] && { echo "regtest token mint failed"; exit 1; }
# --- 7. THE PAYLOADS the slice-3 gate consults --------------------------------
hr "7a. GET payment-methods (does it expose derivationScheme? what pmid?)"
curl -sS "${AUTH[@]}" "$BASE/api/v1/stores/$STORE_REGTEST/payment-methods?includeConfig=true" \
| tee "$OUT_DIR/payment-methods.json" | jqp
hr "5b. GET wallet/address (THE network artifact — expect bcrt1…)"
ADDR_JSON="$(curl -sS "${AUTH[@]}" "$BASE/api/v1/stores/$STORE_ID/payment-methods/${PMID:-BTC-CHAIN}/wallet/address")"
hr "7b. GET wallet/address (THE network artifact — expect bcrt1…)"
ADDR_JSON="$(curl -sS "${AUTH[@]}" "$BASE/api/v1/stores/$STORE_REGTEST/payment-methods/${PMID:-BTC-CHAIN}/wallet/address")"
echo "$ADDR_JSON" | tee "$OUT_DIR/wallet-address.json" | jqp
ADDR="$(echo "$ADDR_JSON" | jq -r '.address // empty')"
# --- 6. classify --------------------------------------------------------------
hr "6. network classification"
# --- 8. classify --------------------------------------------------------------
hr "8. network classification"
echo "pmid used : ${PMID:-BTC-CHAIN}"
echo "receive address: ${ADDR:-<none>}"
case "$ADDR" in
@@ -89,4 +131,16 @@ case "$ADDR" in
*) echo "=> UNRECOGNIZED prefix => FAIL-CLOSED → mainnet → master-only";;
esac
hr "done — raw payloads under $OUT_DIR/"
# --- 9. write .live-env -------------------------------------------------------
hr "9. write .live-env"
cat > "$LIVE_ENV" <<EOF
export KEYSAT_LIVE_BTCPAY_URL=$BASE
export KEYSAT_LIVE_BTCPAY_KEY=$ADMIN_KEY
export KEYSAT_LIVE_BTCPAY_STORE_REGTEST=$STORE_REGTEST
export KEYSAT_LIVE_BTCPAY_STORE_NOWALLET=$STORE_NOWALLET
export GATE_TOK_REGTEST=$GATE_TOK_REGTEST
export GATE_TOK_NOWALLET=$GATE_TOK_NOWALLET
EOF
echo "wrote $LIVE_ENV"
hr "done — raw payloads under $OUT_DIR/, credentials in $LIVE_ENV"