Skip to content

Manual Test Runbook — B6: Client Self-Service Bootstrap

Owner: Sagar  |  Time: ~5 min (Part A + B offline) / +3 min (optional Part C live)  |  Sandbox: none required (read-only)

Overview

B6 is the prerequisite checker a prospective client runs in their own Azure tenant before a SnowOps engagement. It probes tooling (az, terraform) and the signed-in principal's Azure permissions, then prints a READY / NOT READY verdict with clear remediation for anything that blocks onboarding. It is read-only — it never creates or changes anything.

The asset is a TS app (apps/client-bootstrap/): a pure evaluator over an EnvironmentSnapshot, behind a collector seam (FixtureCollector for tests/offline, AzureCliCollector for the live az path). The single client-facing entrypoint is bootstrap.sh.

The acceptance contract (asset catalogue): passes against a correctly-permissioned subscription, and fails with clear remediation output on a restricted principal.

Parts A + B verify both required scenarios offline via fixtures. Part C runs the live check against a real signed-in session.

Part A — Offline (no cloud, ~4 min)

A1. Build + typecheck + unit tests

cd apps/client-bootstrap
npm ci
npm run typecheck
npm run build
npm test

Expect: 3 suites, 25 tests pass — version/tooling checks, the permission checks (Reader/Contributor cannot assign roles; missing app-management role; unregistered providers named in remediation; permission checks skip when not signed in), the readiness rule, and the markdown/status renderers.

A2. Scenario 1 — correctly-permissioned subscription → READY

node dist/index.js --snapshot examples/snapshot.ok.json \
  --out-dir /tmp/b6ok --now 2026-06-01T00:00:00.000Z --fail-on-not-ready true
echo "exit=$?"
cat /tmp/b6ok/status.json

Expect: stderr ... READY — 8/8 passed, 0 failed, 0 warning(s); exit 0; status.json has "ready": true, "blockers": [], "warnings": [].

A3. Scenario 2 — restricted service principal → NOT READY + remediation

node dist/index.js --snapshot examples/snapshot.restricted.json \
  --out-dir /tmp/b6bad --now 2026-06-01T00:00:00.000Z --fail-on-not-ready true
echo "exit=$?"
cat /tmp/b6bad/status.json

Expect: exit 2; stderr lists three blockers each with a remediation line; status.json has "ready": false and "blockers": ["perm-role-assignment", "perm-entra-app-management", "perm-providers"], "warnings": ["license-entra-p2"].

A4. Remediation is actionable

sed -n '/## Remediation/,/^---/p' /tmp/b6bad/summary.md

Expect: each blocker carries a concrete fix — grant Owner/User Access Administrator, assign an app-management Entra role, and az provider register for each missing namespace. Recommended (P2 license) is flagged _[recommended]_, not a blocker.

Part B — Wrapper script (offline, ~1 min)

B1. bootstrap.sh runs the fixture path and gates on exit code

./bootstrap.sh --snapshot examples/snapshot.ok.json --out-dir /tmp/b6sh
echo "exit=$?"          # expect 0 (READY)
./bootstrap.sh --snapshot examples/snapshot.restricted.json --out-dir /tmp/b6sh2
echo "exit=$?"          # expect 2 (NOT READY)

Expect: the wrapper prints the verdict, the report locations, and propagates the CLI exit code (0 / 2).

Part C — Live check (optional, requires az login, ~3 min)

Run in any subscription you can sign into. Read-only — safe in prod.

C1. Live run against your session

az login
cd apps/client-bootstrap
./bootstrap.sh --out-dir /tmp/b6live
cat /tmp/b6live/status.json

Expect: the verdict reflects your real permissions. As an Owner with Application Administrator you should see READY (or a P2 warning only); a Reader-scoped session should report the same permission blockers as A3 with remediation. No resources are created (az group list is unchanged).


Sign-off

Field Value
Result ☐ PASS   ☐ FAIL
Run by
Date
Commit
Notes