297 lines
15 KiB
Bash
Executable File
297 lines
15 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Applies the full migration chain to a fresh PostgreSQL database and runs the
|
|
# real restricted-role isolation suite. This gate must never fall back to a
|
|
# skipped Vitest suite: it owns the database and always exports the test URL.
|
|
|
|
set -euo pipefail
|
|
|
|
RLS_NAME="hivegrid-rls-live-$$"
|
|
RLS_USER="rls_admin"
|
|
RLS_PASSWORD="rls_admin"
|
|
RLS_DB="rls_live"
|
|
RLS_NEGATIVE_DB="rls_missing_membership"
|
|
RLS_APP_ROLE="hivegrid_app"
|
|
RLS_APP_TEST_PASSWORD="hivegrid_rls_test_only"
|
|
RLS_MIGRATOR_PASSWORD="migrator_rls_live_0123456789abcdef"
|
|
RLS_APP_PASSWORD="app_rls_live_0123456789abcdef012345"
|
|
RLS_PORTAL_PASSWORD="portal_rls_live_0123456789abcdef01"
|
|
RLS_WORKER_PASSWORD="worker_rls_live_0123456789abcdef01"
|
|
RLS_AUTHENTIK_JIT_PASSWORD="authentik_jit_rls_live_0123456789abcdef"
|
|
PG_IMAGE="${RLS_PG_IMAGE:-postgres:16-alpine}"
|
|
REQUIRE_MIGRATION_CHAIN_PARITY="${HIVEGRID_RLS_REQUIRE_MIGRATION_CHAIN_PARITY:-0}"
|
|
|
|
if [[ "$REQUIRE_MIGRATION_CHAIN_PARITY" != "0" && "$REQUIRE_MIGRATION_CHAIN_PARITY" != "1" ]]; then
|
|
echo "FAIL HIVEGRID_RLS_REQUIRE_MIGRATION_CHAIN_PARITY must be exactly 0 or 1" >&2
|
|
exit 1
|
|
fi
|
|
|
|
NEGATIVE_MIGRATION_LOG="$(mktemp -t hivegrid-rls-negative.XXXXXX)"
|
|
POSITIVE_MIGRATION_LOG="$(mktemp -t hivegrid-rls-positive.XXXXXX)"
|
|
NEGATIVE_RECUT_LOG="$(mktemp -t hivegrid-rls-recut-negative.XXXXXX)"
|
|
MIGRATION_CHAIN_SCHEMA_LOG="$(mktemp -t hivegrid-rls-chain-schema.XXXXXX)"
|
|
MIGRATION_CHAIN_POLICY_LOG="$(mktemp -t hivegrid-rls-chain-policy.XXXXXX)"
|
|
|
|
require_cmd() {
|
|
if ! command -v "$1" >/dev/null 2>&1; then
|
|
echo "MISSING $1: the RLS live gate requires it" >&2
|
|
exit 127
|
|
fi
|
|
}
|
|
|
|
require_cmd docker
|
|
require_cmd pnpm
|
|
|
|
cleanup() {
|
|
docker rm -f "$RLS_NAME" >/dev/null 2>&1 || true
|
|
rm -f \
|
|
"$NEGATIVE_MIGRATION_LOG" \
|
|
"$POSITIVE_MIGRATION_LOG" \
|
|
"$NEGATIVE_RECUT_LOG" \
|
|
"$MIGRATION_CHAIN_SCHEMA_LOG" \
|
|
"$MIGRATION_CHAIN_POLICY_LOG"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
echo "PHASE 1/8 starting ephemeral PostgreSQL"
|
|
docker run -d \
|
|
--name "$RLS_NAME" \
|
|
-p 127.0.0.1::5432 \
|
|
-e POSTGRES_USER="$RLS_USER" \
|
|
-e POSTGRES_PASSWORD="$RLS_PASSWORD" \
|
|
-e POSTGRES_DB="$RLS_DB" \
|
|
"$PG_IMAGE" >/dev/null
|
|
|
|
RLS_PORT="$(docker port "$RLS_NAME" 5432/tcp | awk -F: 'NR == 1 { print $NF }')"
|
|
RLS_ADMIN_DATABASE_URL="postgresql://${RLS_USER}:${RLS_PASSWORD}@127.0.0.1:${RLS_PORT}/${RLS_DB}?schema=public"
|
|
RLS_NEGATIVE_ADMIN_DATABASE_URL="postgresql://${RLS_USER}:${RLS_PASSWORD}@127.0.0.1:${RLS_PORT}/${RLS_NEGATIVE_DB}?schema=public"
|
|
RLS_MIGRATOR_DATABASE_URL="postgresql://hivegrid_migrator:${RLS_MIGRATOR_PASSWORD}@127.0.0.1:${RLS_PORT}/${RLS_DB}?schema=public"
|
|
RLS_NEGATIVE_MIGRATOR_DATABASE_URL="postgresql://hivegrid_migrator:${RLS_MIGRATOR_PASSWORD}@127.0.0.1:${RLS_PORT}/${RLS_NEGATIVE_DB}?schema=public"
|
|
RLS_APP_DATABASE_URL="postgresql://hivegrid_app:${RLS_APP_PASSWORD}@127.0.0.1:${RLS_PORT}/${RLS_DB}?schema=public"
|
|
RLS_PORTAL_DATABASE_URL="postgresql://hivegrid_portal:${RLS_PORTAL_PASSWORD}@127.0.0.1:${RLS_PORT}/${RLS_DB}?schema=public"
|
|
|
|
for attempt in $(seq 1 30); do
|
|
if docker exec "$RLS_NAME" psql -At -U "$RLS_USER" -d "$RLS_DB" -c "SELECT 1" >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
if [[ "$attempt" == "30" ]]; then
|
|
echo "FAIL RLS live PostgreSQL did not become ready" >&2
|
|
docker logs "$RLS_NAME" >&2 || true
|
|
exit 1
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
echo "PHASE 2/8 creating the negative-test database"
|
|
docker exec "$RLS_NAME" \
|
|
psql -v ON_ERROR_STOP=1 -U "$RLS_USER" -d "$RLS_DB" \
|
|
-c "CREATE DATABASE ${RLS_NEGATIVE_DB}" >/dev/null
|
|
|
|
echo "PHASE 3/8 proving missing definer membership blocks a non-superuser migration"
|
|
HIVEGRID_RLS_PROVISION_DATABASE_URL="$RLS_NEGATIVE_ADMIN_DATABASE_URL" \
|
|
HIVEGRID_RLS_MIGRATOR_PASSWORD="$RLS_MIGRATOR_PASSWORD" \
|
|
HIVEGRID_RLS_APP_PASSWORD="$RLS_APP_PASSWORD" \
|
|
HIVEGRID_RLS_PORTAL_PASSWORD="$RLS_PORTAL_PASSWORD" \
|
|
HIVEGRID_RLS_WORKER_PASSWORD="$RLS_WORKER_PASSWORD" \
|
|
HIVEGRID_RLS_AUTHENTIK_JIT_PASSWORD="$RLS_AUTHENTIK_JIT_PASSWORD" \
|
|
node scripts/provision-rls-roles.mjs >/dev/null
|
|
|
|
docker exec "$RLS_NAME" \
|
|
psql -v ON_ERROR_STOP=1 -U "$RLS_USER" -d "$RLS_NEGATIVE_DB" \
|
|
-c "REVOKE hivegrid_rls_definer FROM hivegrid_migrator" >/dev/null
|
|
|
|
if [[ "$(docker exec "$RLS_NAME" psql -At -U "$RLS_USER" -d "$RLS_NEGATIVE_DB" -c "SELECT pg_has_role('hivegrid_migrator', 'hivegrid_rls_definer', 'MEMBER')")" != "f" ]]; then
|
|
echo "FAIL negative-test setup did not remove the definer membership" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if DATABASE_URL="$RLS_NEGATIVE_MIGRATOR_DATABASE_URL" DIRECT_URL="$RLS_NEGATIVE_MIGRATOR_DATABASE_URL" \
|
|
pnpm exec prisma migrate deploy --config prisma.config.ts >"$NEGATIVE_MIGRATION_LOG" 2>&1; then
|
|
echo "FAIL migration unexpectedly succeeded without the definer membership" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# The second idempotent provisioning pass must also remove this deliberately
|
|
# dangerous external membership before the successful migration starts.
|
|
docker exec "$RLS_NAME" \
|
|
psql -v ON_ERROR_STOP=1 -U "$RLS_USER" -d "$RLS_NEGATIVE_DB" \
|
|
-c "GRANT ${RLS_USER} TO hivegrid_app" >/dev/null
|
|
|
|
echo "PHASE 4/8 provisioning exact roles and applying a fresh migration as hivegrid_migrator"
|
|
HIVEGRID_RLS_PROVISION_DATABASE_URL="$RLS_ADMIN_DATABASE_URL" \
|
|
HIVEGRID_RLS_MIGRATOR_PASSWORD="$RLS_MIGRATOR_PASSWORD" \
|
|
HIVEGRID_RLS_APP_PASSWORD="$RLS_APP_PASSWORD" \
|
|
HIVEGRID_RLS_PORTAL_PASSWORD="$RLS_PORTAL_PASSWORD" \
|
|
HIVEGRID_RLS_WORKER_PASSWORD="$RLS_WORKER_PASSWORD" \
|
|
HIVEGRID_RLS_AUTHENTIK_JIT_PASSWORD="$RLS_AUTHENTIK_JIT_PASSWORD" \
|
|
node scripts/provision-rls-roles.mjs >/dev/null
|
|
|
|
if ! DATABASE_URL="$RLS_MIGRATOR_DATABASE_URL" DIRECT_URL="$RLS_MIGRATOR_DATABASE_URL" \
|
|
pnpm exec prisma migrate deploy --config prisma.config.ts >"$POSITIVE_MIGRATION_LOG" 2>&1; then
|
|
echo "FAIL fresh migration as hivegrid_migrator failed" >&2
|
|
tail -n 30 "$POSITIVE_MIGRATION_LOG" \
|
|
| sed -E 's#(postgres(ql)?://)[^/@[:space:]]+@#\1<redacted>@#g' >&2
|
|
docker logs "$RLS_NAME" 2>&1 \
|
|
| grep -E 'ERROR:|DETAIL:|HINT:|CONTEXT:' \
|
|
| tail -n 30 >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "PHASE 5/8 checking fresh migration-chain schema and portal-policy parity"
|
|
set +e
|
|
DATABASE_URL="$RLS_MIGRATOR_DATABASE_URL" DIRECT_URL="$RLS_MIGRATOR_DATABASE_URL" \
|
|
pnpm exec prisma migrate diff \
|
|
--from-config-datasource \
|
|
--to-schema prisma/schema \
|
|
--exit-code \
|
|
--config prisma.config.ts \
|
|
>"$MIGRATION_CHAIN_SCHEMA_LOG" 2>&1
|
|
MIGRATION_CHAIN_SCHEMA_EXIT=$?
|
|
set -e
|
|
|
|
if [[ "$MIGRATION_CHAIN_SCHEMA_EXIT" != "0" && "$MIGRATION_CHAIN_SCHEMA_EXIT" != "2" ]]; then
|
|
echo "FAIL migration-chain schema parity probe errored" >&2
|
|
tail -n 30 "$MIGRATION_CHAIN_SCHEMA_LOG" \
|
|
| sed -E 's#(postgres(ql)?://)[^/@[:space:]]+@#\1<redacted>@#g' >&2
|
|
exit 1
|
|
fi
|
|
|
|
MIGRATION_CHAIN_POLICY_READY=0
|
|
if [[ "$MIGRATION_CHAIN_SCHEMA_EXIT" == "0" ]]; then
|
|
if DATABASE_URL="$RLS_PORTAL_DATABASE_URL" DATABASE_READ_URL='' DIRECT_URL='' RLS_RUNTIME_MODE=required \
|
|
pnpm exec tsx scripts/verify-portal-migration-chain-policy-state.ts \
|
|
>"$MIGRATION_CHAIN_POLICY_LOG" 2>&1; then
|
|
MIGRATION_CHAIN_POLICY_READY=1
|
|
fi
|
|
fi
|
|
|
|
USE_FEATURE_BRANCH_OVERLAY=0
|
|
if [[ "$MIGRATION_CHAIN_SCHEMA_EXIT" == "0" && "$MIGRATION_CHAIN_POLICY_READY" == "1" ]]; then
|
|
echo "MODE migration-chain: fresh migrations provide the current schema and exact portal policy contract; db push and manual policy overlays are disabled"
|
|
elif [[ "$REQUIRE_MIGRATION_CHAIN_PARITY" == "1" ]]; then
|
|
if [[ "$MIGRATION_CHAIN_SCHEMA_EXIT" == "2" ]]; then
|
|
echo "FAIL strict migration-chain mode: fresh migrations do not match prisma/schema" >&2
|
|
tail -n 40 "$MIGRATION_CHAIN_SCHEMA_LOG" >&2
|
|
else
|
|
echo "FAIL strict migration-chain mode: fresh migrations do not provide the exact portal policy contract" >&2
|
|
tail -n 30 "$MIGRATION_CHAIN_POLICY_LOG" >&2
|
|
fi
|
|
echo "Create the reviewed forward-only merge migration; strict mode never runs db push or a policy overlay" >&2
|
|
exit 1
|
|
else
|
|
USE_FEATURE_BRANCH_OVERLAY=1
|
|
echo "MODE feature-branch-overlay: migration-chain parity is not available yet; applying the explicit local/CI-only recut, db push, and policy artifacts"
|
|
fi
|
|
|
|
if [[ "$USE_FEATURE_BRANCH_OVERLAY" == "1" ]]; then
|
|
docker exec -e PGPASSWORD="$RLS_MIGRATOR_PASSWORD" "$RLS_NAME" \
|
|
psql -v ON_ERROR_STOP=1 -h 127.0.0.1 -U hivegrid_migrator -d "$RLS_DB" \
|
|
-c 'CREATE POLICY "va_p26a_unreviewed_probe" ON "PortalCheckoutSessionStatus" USING (true)' \
|
|
>/dev/null
|
|
|
|
if HIVEGRID_ALLOW_PORTAL_LEGACY_RECUT_DB_PUSH=1 \
|
|
DATABASE_URL="$RLS_MIGRATOR_DATABASE_URL" DIRECT_URL="$RLS_MIGRATOR_DATABASE_URL" \
|
|
pnpm run db:prepare:portal-legacy-commerce-recut >"$NEGATIVE_RECUT_LOG" 2>&1; then
|
|
echo "FAIL legacy recut accepted a non-allowlisted policy" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! grep -Fq "found non-allowlisted policies" "$NEGATIVE_RECUT_LOG"; then
|
|
echo "FAIL legacy recut rejected the probe without the expected dependency diagnostic" >&2
|
|
tail -n 20 "$NEGATIVE_RECUT_LOG" \
|
|
| sed -E 's#(postgres(ql)?://)[^/@[:space:]]+@#\1<redacted>@#g' >&2
|
|
exit 1
|
|
fi
|
|
|
|
RECUT_ROLLBACK_POLICY_COUNT="$(docker exec -e PGPASSWORD="$RLS_MIGRATOR_PASSWORD" "$RLS_NAME" \
|
|
psql -At -v ON_ERROR_STOP=1 -h 127.0.0.1 -U hivegrid_migrator -d "$RLS_DB" \
|
|
-c "SELECT count(*) FROM pg_policy WHERE polrelid = '\"PortalCheckoutSessionStatus\"'::regclass AND polname IN ('hg_PortalCheckoutSessionStatus_all', 'va_p26a_unreviewed_probe')")"
|
|
if [[ "$RECUT_ROLLBACK_POLICY_COUNT" != "2" ]]; then
|
|
echo "FAIL rejected legacy recut did not roll back its allowlisted policy drop" >&2
|
|
exit 1
|
|
fi
|
|
|
|
docker exec -e PGPASSWORD="$RLS_MIGRATOR_PASSWORD" "$RLS_NAME" \
|
|
psql -v ON_ERROR_STOP=1 -h 127.0.0.1 -U hivegrid_migrator -d "$RLS_DB" \
|
|
-c 'DROP POLICY "va_p26a_unreviewed_probe" ON "PortalCheckoutSessionStatus"' \
|
|
>/dev/null
|
|
|
|
HIVEGRID_ALLOW_PORTAL_LEGACY_RECUT_DB_PUSH=1 \
|
|
DATABASE_URL="$RLS_MIGRATOR_DATABASE_URL" DIRECT_URL="$RLS_MIGRATOR_DATABASE_URL" \
|
|
pnpm run db:prepare:portal-legacy-commerce-recut >/dev/null
|
|
|
|
DATABASE_URL="$RLS_MIGRATOR_DATABASE_URL" DIRECT_URL="$RLS_MIGRATOR_DATABASE_URL" \
|
|
pnpm exec prisma db push --accept-data-loss --config prisma.config.ts >/dev/null
|
|
|
|
docker exec -e PGPASSWORD="$RLS_MIGRATOR_PASSWORD" -i "$RLS_NAME" \
|
|
psql -v ON_ERROR_STOP=1 -h 127.0.0.1 -U hivegrid_migrator -d "$RLS_DB" \
|
|
< prisma/rls/tenant-policy-contract-isolation.sql >/dev/null
|
|
|
|
fi
|
|
|
|
echo "PHASE 6/8 running real app/worker/chat restricted-role isolation tests"
|
|
DATABASE_URL="$RLS_ADMIN_DATABASE_URL" DIRECT_URL="$RLS_ADMIN_DATABASE_URL" RLS_TEST_DATABASE_URL="$RLS_ADMIN_DATABASE_URL" \
|
|
pnpm exec vitest run packages/db/src/rls-live.integration.test.ts
|
|
|
|
RLS_APP_TEST_DATABASE_URL="postgresql://${RLS_APP_ROLE}:${RLS_APP_TEST_PASSWORD}@127.0.0.1:${RLS_PORT}/${RLS_DB}?schema=public"
|
|
DATABASE_URL="$RLS_APP_TEST_DATABASE_URL" DIRECT_URL="$RLS_APP_TEST_DATABASE_URL" RLS_TEST_DATABASE_URL="$RLS_ADMIN_DATABASE_URL" \
|
|
pnpm exec vitest run packages/chat/src/link-preview.rls.integration.test.ts
|
|
DATABASE_URL="$RLS_APP_TEST_DATABASE_URL" DIRECT_URL="$RLS_APP_TEST_DATABASE_URL" RLS_TEST_DATABASE_URL="$RLS_ADMIN_DATABASE_URL" \
|
|
pnpm exec vitest run packages/chat/src/repository.rls.integration.test.ts
|
|
DATABASE_URL="$RLS_APP_TEST_DATABASE_URL" DIRECT_URL="$RLS_APP_TEST_DATABASE_URL" RLS_TEST_DATABASE_URL="$RLS_ADMIN_DATABASE_URL" \
|
|
pnpm exec vitest run packages/chat/src/message-discovery.rls.integration.test.ts
|
|
DATABASE_URL="$RLS_APP_TEST_DATABASE_URL" DIRECT_URL="$RLS_APP_TEST_DATABASE_URL" RLS_TEST_DATABASE_URL="$RLS_ADMIN_DATABASE_URL" \
|
|
pnpm exec vitest run packages/chat/src/message-mutations.rls.integration.test.ts
|
|
DATABASE_URL="$RLS_APP_TEST_DATABASE_URL" DIRECT_URL="$RLS_APP_TEST_DATABASE_URL" RLS_TEST_DATABASE_URL="$RLS_ADMIN_DATABASE_URL" \
|
|
pnpm exec vitest run packages/chat/src/project-write-revocation.rls.integration.test.ts
|
|
DATABASE_URL="$RLS_APP_TEST_DATABASE_URL" DIRECT_URL="$RLS_APP_TEST_DATABASE_URL" RLS_TEST_DATABASE_URL="$RLS_ADMIN_DATABASE_URL" \
|
|
pnpm exec vitest run apps/web/app/utils/db/permissions/userGroups.rls.integration.test.ts
|
|
DATABASE_URL="$RLS_APP_TEST_DATABASE_URL" DIRECT_URL="$RLS_APP_TEST_DATABASE_URL" RLS_TEST_DATABASE_URL="$RLS_ADMIN_DATABASE_URL" \
|
|
pnpm exec vitest run packages/chat/src/system-ownership.rls.integration.test.ts
|
|
DATABASE_URL="$RLS_APP_TEST_DATABASE_URL" DIRECT_URL="$RLS_APP_TEST_DATABASE_URL" RLS_TEST_DATABASE_URL="$RLS_ADMIN_DATABASE_URL" \
|
|
pnpm exec vitest run packages/chat/src/message-features.rls.integration.test.ts
|
|
DATABASE_URL="$RLS_APP_TEST_DATABASE_URL" DIRECT_URL="$RLS_APP_TEST_DATABASE_URL" RLS_TEST_DATABASE_URL="$RLS_ADMIN_DATABASE_URL" \
|
|
pnpm exec vitest run packages/chat/src/audit.rls.integration.test.ts
|
|
|
|
echo "PHASE 7/8 installing and testing the customer-portal RLS artifact"
|
|
if [[ "$USE_FEATURE_BRANCH_OVERLAY" == "1" ]]; then
|
|
# The generated app/worker artifact owns an exact definer-function inventory.
|
|
# Install this additive portal actor helper only in the portal overlay phase;
|
|
# the approved merge migration will fold both contracts into one review.
|
|
docker exec -e PGPASSWORD="$RLS_MIGRATOR_PASSWORD" -i "$RLS_NAME" \
|
|
psql -v ON_ERROR_STOP=1 -h 127.0.0.1 -U hivegrid_migrator -d "$RLS_DB" \
|
|
< prisma/rls/portal-actor-invariants.sql >/dev/null
|
|
|
|
docker exec -e PGPASSWORD="$RLS_MIGRATOR_PASSWORD" -i "$RLS_NAME" \
|
|
psql -v ON_ERROR_STOP=1 -h 127.0.0.1 -U hivegrid_migrator -d "$RLS_DB" \
|
|
< prisma/rls/portal-policy-contract-isolation.sql >/dev/null
|
|
|
|
docker exec -e PGPASSWORD="$RLS_MIGRATOR_PASSWORD" -i "$RLS_NAME" \
|
|
psql -v ON_ERROR_STOP=1 -h 127.0.0.1 -U hivegrid_migrator -d "$RLS_DB" \
|
|
< prisma/rls/portal-domain-capabilities.sql >/dev/null
|
|
else
|
|
echo "MODE migration-chain: using portal policies installed by migrations; manual portal policy overlay is disabled"
|
|
fi
|
|
|
|
DATABASE_URL="$RLS_PORTAL_DATABASE_URL" DATABASE_READ_URL='' DIRECT_URL='' RLS_RUNTIME_MODE=required \
|
|
pnpm exec tsx scripts/verify-portal-migration-chain-policy-state.ts
|
|
|
|
DATABASE_URL="$RLS_ADMIN_DATABASE_URL" DIRECT_URL="$RLS_ADMIN_DATABASE_URL" \
|
|
RLS_TEST_DATABASE_URL="$RLS_ADMIN_DATABASE_URL" RLS_PORTAL_TEST_DATABASE_URL="$RLS_PORTAL_DATABASE_URL" \
|
|
pnpm exec vitest run \
|
|
packages/db/src/portal-domain-capabilities.rls.integration.test.ts \
|
|
packages/db/src/portal-rls-live.integration.test.ts \
|
|
packages/db/src/portal-p26b-rls-live.integration.test.ts
|
|
|
|
echo "PHASE 8/8 running the product tenant bootstrap as hivegrid_app"
|
|
# The package-level suite intentionally rotates the restricted-role passwords.
|
|
# Restore the provisioned app password before the separate product process.
|
|
docker exec "$RLS_NAME" \
|
|
psql -v ON_ERROR_STOP=1 -U "$RLS_USER" -d "$RLS_DB" \
|
|
-c "ALTER ROLE hivegrid_app PASSWORD '${RLS_APP_PASSWORD}'" >/dev/null
|
|
|
|
DATABASE_URL="$RLS_APP_DATABASE_URL" DATABASE_READ_URL="$RLS_APP_DATABASE_URL" DIRECT_URL='' \
|
|
RLS_TEST_DATABASE_URL="$RLS_ADMIN_DATABASE_URL" REDIS_URL='' CACHE_INVALIDATION_ENABLED=0 \
|
|
pnpm exec vitest run apps/web/app/domain/tenant-bootstrap/tenantBootstrapService.live.integration.test.ts
|
|
|
|
echo "PASS non-superuser migration, membership denial, app/worker/chat/portal live RLS isolation, and product tenant bootstrap gate"
|