Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions 04-backend/scripts/phase8-certify-from-evidence.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "${REPO_ROOT}"

FINAL_EVIDENCE="${ARCHITOKEN_FINAL_EVIDENCE:-${ARCHITOKEN_LOAD_EVIDENCE:-}}"
OUT_DIR="${ARCHITOKEN_EVIDENCE_DIR:-}"

if [[ -n "${FINAL_EVIDENCE}" && -f "${FINAL_EVIDENCE}" ]]; then
ARCHITOKEN_TARGET_CONCURRENCY=100000 \
04-backend/scripts/validate-phase8-load-evidence.sh "${FINAL_EVIDENCE}"
printf 'verdict=certified evidence=%s\n' "${FINAL_EVIDENCE}"
exit 0
fi

if [[ -z "${OUT_DIR}" ]]; then
printf 'verdict=blocked reason=missing_ARCHITOKEN_FINAL_EVIDENCE_or_ARCHITOKEN_EVIDENCE_DIR\n' >&2
exit 2
fi

required_vars=(
ARCHITOKEN_RUN_ID
ARCHITOKEN_ENVIRONMENT
ARCHITOKEN_PROMETHEUS_SNAPSHOT
ARCHITOKEN_RUNTIME_CLUSTER_SNAPSHOT
ARCHITOKEN_DOCKER_IMAGE_DIGEST
ARCHITOKEN_GRAFANA_SNAPSHOT
ARCHITOKEN_OTEL_SNAPSHOT
)

for var in "${required_vars[@]}"; do
if [[ -z "${!var:-}" ]]; then
printf 'verdict=blocked reason=missing_%s\n' "${var}" >&2
exit 2
fi
done

for stage in smoke 1k 10k 25k 50k 100k; do
if [[ ! -f "${OUT_DIR}/${stage}-k6-summary.json" ]]; then
printf 'verdict=blocked reason=missing_k6_summary stage=%s path=%s\n' "${stage}" "${OUT_DIR}/${stage}-k6-summary.json" >&2
exit 2
fi
if [[ ! -f "${OUT_DIR}/${stage}-stage-meta.json" ]]; then
printf 'verdict=blocked reason=missing_stage_metadata stage=%s path=%s\n' "${stage}" "${OUT_DIR}/${stage}-stage-meta.json" >&2
exit 2
fi
done

if [[ -z "${ARCHITOKEN_K8S_MANIFEST_HASH:-}" && -z "${ARCHITOKEN_K8S_MANIFEST_FILE:-}" ]]; then
printf 'verdict=blocked reason=missing_k8s_manifest_hash_or_file\n' >&2
exit 2
fi

FINAL_EVIDENCE="${FINAL_EVIDENCE:-${OUT_DIR}/phase8-final-load-evidence.json}"
merge_args=(
--run-id "${ARCHITOKEN_RUN_ID}"
--environment "${ARCHITOKEN_ENVIRONMENT}"
--prometheus-snapshot "${ARCHITOKEN_PROMETHEUS_SNAPSHOT}"
--runtime-cluster "${ARCHITOKEN_RUNTIME_CLUSTER_SNAPSHOT}"
--docker-image-digest "${ARCHITOKEN_DOCKER_IMAGE_DIGEST}"
--grafana-snapshot "${ARCHITOKEN_GRAFANA_SNAPSHOT}"
--otel-snapshot "${ARCHITOKEN_OTEL_SNAPSHOT}"
--out "${FINAL_EVIDENCE}"
)

for stage in smoke 1k 10k 25k 50k 100k; do
merge_args+=(--stage "${stage}=${OUT_DIR}/${stage}-k6-summary.json")
merge_args+=(--stage-meta "${stage}=${OUT_DIR}/${stage}-stage-meta.json")
done

if [[ -n "${ARCHITOKEN_K8S_MANIFEST_HASH:-}" ]]; then
merge_args+=(--k8s-manifest-hash "${ARCHITOKEN_K8S_MANIFEST_HASH}")
else
merge_args+=(--k8s-manifest "${ARCHITOKEN_K8S_MANIFEST_FILE}")
fi

if [[ -n "${ARCHITOKEN_K6_SCRIPT_HASH:-}" ]]; then
merge_args+=(--k6-script-hash "${ARCHITOKEN_K6_SCRIPT_HASH}")
fi

if [[ -n "${ARCHITOKEN_CERT_GIT_SHA:-}" ]]; then
merge_args+=(--git-sha "${ARCHITOKEN_CERT_GIT_SHA}")
fi

python3 tools/phase8_merge_load_evidence.py "${merge_args[@]}"
ARCHITOKEN_TARGET_CONCURRENCY=100000 \
04-backend/scripts/validate-phase8-load-evidence.sh "${FINAL_EVIDENCE}"
printf 'verdict=certified evidence=%s\n' "${FINAL_EVIDENCE}"
43 changes: 43 additions & 0 deletions 04-backend/scripts/phase8-run-100k.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -euo pipefail

PROFILE="100k"
TARGET_VU="100000"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
cd "${REPO_ROOT}"

if [[ "${ARCHITOKEN_ALLOW_LOCAL_100K:-0}" != "1" ]]; then
printf 'verdict=blocked reason=100k_requires_external_dedicated_load_workers set ARCHITOKEN_ALLOW_LOCAL_100K=1 only on that infrastructure\n' >&2
exit 2
fi

if ! command -v k6 >/dev/null 2>&1; then
printf 'verdict=blocked reason=k6_required profile=%s\n' "${PROFILE}" >&2
exit 2
fi

RUN_ID="${ARCHITOKEN_LOAD_RUN_ID:-phase8-$(date -u +%Y%m%dT%H%M%SZ)}"
OUT_DIR="${ARCHITOKEN_EVIDENCE_DIR:-/tmp/architoken-phase8-evidence/${RUN_ID}}"
mkdir -p "${OUT_DIR}"
SUMMARY="${OUT_DIR}/${PROFILE}-k6-summary.json"
META="${OUT_DIR}/${PROFILE}-stage-meta.json"
START_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

ARCHITOKEN_LOAD_PROFILE="${PROFILE}" k6 run --summary-export "${SUMMARY}" tools/k6/phase8_100k_ramp.js

END_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
STAGE="${PROFILE}" START_TIME="${START_TIME}" END_TIME="${END_TIME}" TARGET_VU="${TARGET_VU}" \
python3 - <<'PY' > "${META}"
import json
import os

print(json.dumps({
"stage": os.environ["STAGE"],
"start_time": os.environ["START_TIME"],
"end_time": os.environ["END_TIME"],
"vu": int(os.environ["TARGET_VU"]),
}, indent=2, sort_keys=True))
PY

printf 'verdict=stage_completed profile=%s summary=%s metadata=%s\n' "${PROFILE}" "${SUMMARY}" "${META}"
38 changes: 38 additions & 0 deletions 04-backend/scripts/phase8-run-10k.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail

PROFILE="10k"
TARGET_VU="10000"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
cd "${REPO_ROOT}"

if ! command -v k6 >/dev/null 2>&1; then
printf 'verdict=blocked reason=k6_required profile=%s\n' "${PROFILE}" >&2
exit 2
fi

RUN_ID="${ARCHITOKEN_LOAD_RUN_ID:-phase8-$(date -u +%Y%m%dT%H%M%SZ)}"
OUT_DIR="${ARCHITOKEN_EVIDENCE_DIR:-/tmp/architoken-phase8-evidence/${RUN_ID}}"
mkdir -p "${OUT_DIR}"
SUMMARY="${OUT_DIR}/${PROFILE}-k6-summary.json"
META="${OUT_DIR}/${PROFILE}-stage-meta.json"
START_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

ARCHITOKEN_LOAD_PROFILE="${PROFILE}" k6 run --summary-export "${SUMMARY}" tools/k6/phase8_100k_ramp.js

END_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
STAGE="${PROFILE}" START_TIME="${START_TIME}" END_TIME="${END_TIME}" TARGET_VU="${TARGET_VU}" \
python3 - <<'PY' > "${META}"
import json
import os

print(json.dumps({
"stage": os.environ["STAGE"],
"start_time": os.environ["START_TIME"],
"end_time": os.environ["END_TIME"],
"vu": int(os.environ["TARGET_VU"]),
}, indent=2, sort_keys=True))
PY

printf 'verdict=stage_completed profile=%s summary=%s metadata=%s\n' "${PROFILE}" "${SUMMARY}" "${META}"
38 changes: 38 additions & 0 deletions 04-backend/scripts/phase8-run-1k.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail

PROFILE="1k"
TARGET_VU="1000"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
cd "${REPO_ROOT}"

if ! command -v k6 >/dev/null 2>&1; then
printf 'verdict=blocked reason=k6_required profile=%s\n' "${PROFILE}" >&2
exit 2
fi

RUN_ID="${ARCHITOKEN_LOAD_RUN_ID:-phase8-$(date -u +%Y%m%dT%H%M%SZ)}"
OUT_DIR="${ARCHITOKEN_EVIDENCE_DIR:-/tmp/architoken-phase8-evidence/${RUN_ID}}"
mkdir -p "${OUT_DIR}"
SUMMARY="${OUT_DIR}/${PROFILE}-k6-summary.json"
META="${OUT_DIR}/${PROFILE}-stage-meta.json"
START_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

ARCHITOKEN_LOAD_PROFILE="${PROFILE}" k6 run --summary-export "${SUMMARY}" tools/k6/phase8_100k_ramp.js

END_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
STAGE="${PROFILE}" START_TIME="${START_TIME}" END_TIME="${END_TIME}" TARGET_VU="${TARGET_VU}" \
python3 - <<'PY' > "${META}"
import json
import os

print(json.dumps({
"stage": os.environ["STAGE"],
"start_time": os.environ["START_TIME"],
"end_time": os.environ["END_TIME"],
"vu": int(os.environ["TARGET_VU"]),
}, indent=2, sort_keys=True))
PY

printf 'verdict=stage_completed profile=%s summary=%s metadata=%s\n' "${PROFILE}" "${SUMMARY}" "${META}"
38 changes: 38 additions & 0 deletions 04-backend/scripts/phase8-run-25k.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail

PROFILE="25k"
TARGET_VU="25000"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
cd "${REPO_ROOT}"

if ! command -v k6 >/dev/null 2>&1; then
printf 'verdict=blocked reason=k6_required profile=%s\n' "${PROFILE}" >&2
exit 2
fi

RUN_ID="${ARCHITOKEN_LOAD_RUN_ID:-phase8-$(date -u +%Y%m%dT%H%M%SZ)}"
OUT_DIR="${ARCHITOKEN_EVIDENCE_DIR:-/tmp/architoken-phase8-evidence/${RUN_ID}}"
mkdir -p "${OUT_DIR}"
SUMMARY="${OUT_DIR}/${PROFILE}-k6-summary.json"
META="${OUT_DIR}/${PROFILE}-stage-meta.json"
START_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

ARCHITOKEN_LOAD_PROFILE="${PROFILE}" k6 run --summary-export "${SUMMARY}" tools/k6/phase8_100k_ramp.js

END_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
STAGE="${PROFILE}" START_TIME="${START_TIME}" END_TIME="${END_TIME}" TARGET_VU="${TARGET_VU}" \
python3 - <<'PY' > "${META}"
import json
import os

print(json.dumps({
"stage": os.environ["STAGE"],
"start_time": os.environ["START_TIME"],
"end_time": os.environ["END_TIME"],
"vu": int(os.environ["TARGET_VU"]),
}, indent=2, sort_keys=True))
PY

printf 'verdict=stage_completed profile=%s summary=%s metadata=%s\n' "${PROFILE}" "${SUMMARY}" "${META}"
38 changes: 38 additions & 0 deletions 04-backend/scripts/phase8-run-50k.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail

PROFILE="50k"
TARGET_VU="50000"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
cd "${REPO_ROOT}"

if ! command -v k6 >/dev/null 2>&1; then
printf 'verdict=blocked reason=k6_required profile=%s\n' "${PROFILE}" >&2
exit 2
fi

RUN_ID="${ARCHITOKEN_LOAD_RUN_ID:-phase8-$(date -u +%Y%m%dT%H%M%SZ)}"
OUT_DIR="${ARCHITOKEN_EVIDENCE_DIR:-/tmp/architoken-phase8-evidence/${RUN_ID}}"
mkdir -p "${OUT_DIR}"
SUMMARY="${OUT_DIR}/${PROFILE}-k6-summary.json"
META="${OUT_DIR}/${PROFILE}-stage-meta.json"
START_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

ARCHITOKEN_LOAD_PROFILE="${PROFILE}" k6 run --summary-export "${SUMMARY}" tools/k6/phase8_100k_ramp.js

END_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
STAGE="${PROFILE}" START_TIME="${START_TIME}" END_TIME="${END_TIME}" TARGET_VU="${TARGET_VU}" \
python3 - <<'PY' > "${META}"
import json
import os

print(json.dumps({
"stage": os.environ["STAGE"],
"start_time": os.environ["START_TIME"],
"end_time": os.environ["END_TIME"],
"vu": int(os.environ["TARGET_VU"]),
}, indent=2, sort_keys=True))
PY

printf 'verdict=stage_completed profile=%s summary=%s metadata=%s\n' "${PROFILE}" "${SUMMARY}" "${META}"
38 changes: 38 additions & 0 deletions 04-backend/scripts/phase8-run-smoke.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail

PROFILE="smoke"
TARGET_VU="20"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
cd "${REPO_ROOT}"

if ! command -v k6 >/dev/null 2>&1; then
printf 'verdict=blocked reason=k6_required profile=%s\n' "${PROFILE}" >&2
exit 2
fi

RUN_ID="${ARCHITOKEN_LOAD_RUN_ID:-phase8-$(date -u +%Y%m%dT%H%M%SZ)}"
OUT_DIR="${ARCHITOKEN_EVIDENCE_DIR:-/tmp/architoken-phase8-evidence/${RUN_ID}}"
mkdir -p "${OUT_DIR}"
SUMMARY="${OUT_DIR}/${PROFILE}-k6-summary.json"
META="${OUT_DIR}/${PROFILE}-stage-meta.json"
START_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

ARCHITOKEN_LOAD_PROFILE="${PROFILE}" k6 run --summary-export "${SUMMARY}" tools/k6/phase8_100k_ramp.js

END_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
STAGE="${PROFILE}" START_TIME="${START_TIME}" END_TIME="${END_TIME}" TARGET_VU="${TARGET_VU}" \
python3 - <<'PY' > "${META}"
import json
import os

print(json.dumps({
"stage": os.environ["STAGE"],
"start_time": os.environ["START_TIME"],
"end_time": os.environ["END_TIME"],
"vu": int(os.environ["TARGET_VU"]),
}, indent=2, sort_keys=True))
PY

printf 'verdict=stage_completed profile=%s summary=%s metadata=%s\n' "${PROFILE}" "${SUMMARY}" "${META}"
16 changes: 12 additions & 4 deletions docs/29_PHASE8_REAL_100K_CERTIFICATION.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Phase 8.1 Real 100k Load Certification
# Phase 8.1 Real 100k Load Certification Gates

Date: 2026-05-01

This document defines the evidence-based certification process for ArchIToken first-day 100,000 concurrent online sessions. It is not a planning claim: the platform is **not certified** until a real external k6/cloud/distributed load run produces validated evidence that passes the gates below.
This document defines the evidence-based certification gates for ArchIToken first-day 100,000 concurrent online sessions. PR #21 implemented the certification gates, schema, scripts, and fail-closed validation. It did **not** certify real 100,000 concurrent production traffic by itself.

Phase 8.2 is the real execution evidence phase. The platform is **not certified** until a real external k6/cloud/distributed load run produces validated evidence that passes the gates below and the Phase 8.2 evidence contract in `docs/30_PHASE8_REAL_LOAD_EXECUTION_EVIDENCE.md`.

Without a real final evidence JSON, do not use public wording such as “已支持 100k 同时在线,” “100k certified,” or “100k passed.” Allowed wording is limited to “Phase 8.1 certification gates are implemented” or “Phase 8.2 certification execution is pending.”

## Certification Target

Expand Down Expand Up @@ -36,15 +40,19 @@ ARCHITOKEN_TARGET_CONCURRENCY=100000 \
04-backend/scripts/validate-phase8-load-evidence.sh /path/to/load-evidence.json
```

The required JSON fields are:
The Phase 8.2 required JSON fields include:

- `run_id`, `git_sha`, `environment`, `start_time`, `end_time`, `duration`.
- `k8s_manifest_hash`, `docker_image_digest`, `k6_script_hash`.
- `target_concurrency`, `achieved_concurrency`, `checks_passed`, `verdict`.
- `thresholds`, `p50`, `p95`, `p99`, `http_req_failed`.
- `ws_connected`, `dropped_connections`, `gateway_restarts`.
- `db_pool_saturation`, `object_store_errors`, `nats_lag`, `qdrant_consistency`.
- `stage_results` for `smoke`, `1k`, `10k`, `25k`, `50k`, and `100k`.
- Prometheus, Grafana, and OpenTelemetry evidence.
- Live Kubernetes runtime-cluster validation evidence.

The validator rejects incomplete evidence, failed evidence, mismatched target concurrency, missing realtime evidence, failed Qdrant consistency, excessive gateway restarts, excessive database saturation, object-store errors, and NATS lag above threshold.
The validator rejects incomplete evidence, failed evidence, mismatched target concurrency, missing per-stage evidence, missing Prometheus/Grafana/OTel evidence, missing realtime evidence, failed Qdrant consistency, failed live K8s validation, excessive gateway restarts, excessive database saturation, object-store errors, and NATS lag above threshold.

## Certification Sequence

Expand Down
Loading
Loading