14 KiB
Yuqei V2 Infrastructure
This directory contains the local and deployment baseline for the V2 rebuild.
Local Compose Startup
From repository root:
Copy-Item yuqei-infra\compose\.env.example yuqei-infra\compose\.env
docker compose --env-file yuqei-infra\compose\.env -f yuqei-infra\compose\docker-compose.yml up --build
Services:
- Contract Web:
http://localhost:3000 - Contract API:
http://localhost:8100 - AI Platform API:
http://localhost:8101 - MinIO S3 API:
http://localhost:9000 - MinIO Console:
http://localhost:9001
Migration Order
The compose file initializes databases and runs explicit Alembic migrations before starting the APIs:
postgres-init-databasesai-platform-api-migratecontract-api-migrate
Application containers keep *_DATABASE_AUTO_CREATE=false so schema changes are owned by migrations instead of implicit table creation.
The local compose baseline uses one PostgreSQL instance, but separates V2 data into dedicated databases:
yuqei_contract: Contract Platform business data.yuqei_ai: AI Platform runtime, RAG, Prompt, Trace, and quality data.yuqei_ops: infrastructure and operations data reserved for later services.
postgres-init-databases runs before migrations and creates missing databases idempotently. The two active platforms also keep separate Alembic histories:
ai_platform_alembic_versioncontract_api_alembic_version
Contract API migrations must only target yuqei_contract; AI Platform and worker migrations/runtime data must only target yuqei_ai. Cross-platform references should use IDs such as ai_trace_id, never direct cross-database joins.
Runtime Verification
After services are up:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File yuqei-infra\scripts\verify-v2-runtime.ps1
The script checks:
- Contract API
/health. - Contract API
/api/v1/runtime/config-check. - AI Platform
/health. - AI Platform worker runtime health.
- AI Platform object storage read/write/delete probe.
- Contract Web
/healthpage availability.
Remote Smoke Deploy
Use this before replacing the old contact.yuqei.com deployment. It deploys V2 to an independent directory and independent ports on 192.168.3.90, so it does not overwrite the V1 backend or the Aliyun frontend.
Default remote path:
/opt/apps/ai-contract-platform-v2-smoke
Default smoke URLs:
- Contract Web:
http://192.168.3.90:13000 - Contract API:
http://192.168.3.90:18100 - AI Platform API:
http://192.168.3.90:18101 - MinIO Console:
http://192.168.3.90:19001
Run from repository root:
$env:SSHPASS='YOUR_192_168_3_90_ROOT_PASSWORD'
powershell.exe -NoProfile -ExecutionPolicy Bypass -File yuqei-infra\scripts\deploy-v2-smoke-docker.ps1 `
-UseSshpass `
-AllowDirtyWorktree
The script:
- Packages only V2 source and infrastructure directories.
- Excludes local
.env*runtime files from the release archive. - Uploads the archive to
192.168.3.90. - Starts compose with
yuqei_contract,yuqei_ai, andyuqei_ops. - Runs health checks for Contract Web, Contract API, and AI Platform API.
- Runs the AI golden sample asset gate inside the
ai-platform-apicontainer. - Checks that the three V2 databases exist.
- Runs the contract review worker
--run-oncesmoke command. - Writes deploy artifacts to
project-docs\qa-artifacts\v2-smoke-docker-deploy.
Latest passing smoke artifact:
project-docs\qa-artifacts\v2-smoke-docker-deploy\2026-07-16T11-12-47- AI golden sample asset gate passed with
90samples,6/6suites, and16/16checks. - Contract review worker smoke completed.
- Remote database baseline passed for
yuqei_contract,yuqei_ai, andyuqei_ops. - Contract API runs with one Uvicorn worker and a 2-second read cache; all mutating requests invalidate the cache before and after execution.
After smoke passes, the old production deployment can be replaced deliberately: backend/AI/worker/database/MinIO on 192.168.3.90, frontend on Aliyun, and contact.yuqei.com kept as the public entry.
Production is now a separate, guarded path. Use compose/docker-compose.production.yml together with the shared compose file, then follow deploy/README.md. The production path requires digest-pinned images, loopback-only internal ports, TLS edge configuration, an off-host backup mount, protected provider secret files, and a passing deploy/preflight-production.py. It intentionally has no automatic traffic-cutover command.
For shared-staging real-Provider acceptance, keep the Provider key in an
owner-only host file outside the repository and add
compose/docker-compose.staging-provider.yml to the Compose command. Set
AI_PROVIDER_API_KEY_FILE to that absolute host path and configure the enabled
default database Provider with
api_key_secret_ref=file:///run/secrets/ai_provider_api_key. The staging
override clears the direct Provider-key environment variable and mounts the
file read-only; never place the key in .env.smoke, API payloads, or artifacts.
Real-provider acceptance uses scripts/run-ai-provider-acceptance.py. It exercises 10 legal QA and 8 contract-review Phase13 samples and separates technical Provider success from named legal approval. The collect step produces a deterministic, redacted technical review package. The protected shared-staging registration/finalize step requires a named legal Ed25519 approval envelope, a trusted legal public-key ring, and server-side content-hash verification of the shared evidence object. The old HMAC attestation is rejected by the protected API. The existing asset-count golden gate does not replace this runtime evaluation.
After a named legal reviewer signs the exact collect output, the finalize
step validates that package, registers it in shared staging, fetches the
server-normalized receipt payload, and signs receipt v2 with a CI-held Ed25519 key:
python yuqei-infra\scripts\run-ai-provider-acceptance.py `
--mode finalize `
--base-url https://staging-api.example.com/ai `
--service-key-file C:\protected\staging-service-key `
--review-package-file C:\protected\provider-technical-review-package.json `
--legal-approval-file C:\protected\named-legal-approval.json `
--legal-keyring-file C:\protected\legal-trusted-keyring.json `
--receipt-private-key-file C:\protected\provider-acceptance-ed25519.pem `
--receipt-key-id provider-acceptance-2026q3 `
--target-release-id v2.0.0-rc.1 `
--target-image-digest sha256:<64-hex-image-digest> `
--receipt-output C:\release-evidence\provider-acceptance-receipt.json `
--promotion-bundle-output C:\release-evidence\provider-promotion-bundle.json
The receipt signing private key and legal approval private key stay in staging/CI.
Production receives only the signed receipt, a public-key ring, the receipt file
SHA-256 pin, and the matching immutable RELEASE_ID and image digest.
compose/docker-compose.production.yml mounts
both JSON files read-only, and deploy/preflight-production.py rejects missing
or mismatched evidence, signing material, legal approval material, and staging database
credentials. Production readiness verifies the receipt offline and never reads
the shared-staging acceptance or audit tables.
Contract generation and clause recommendation are intentionally outside the Provider acceptance path because those product flows are deterministic Contract API operations. Run their independent golden gate from the repository root with a Python environment that has the Contract API dependencies installed:
python yuqei-infra\scripts\run-contract-product-golden.py
This gate invokes the real Contract API template renderer for 23 generation cases and the real review-rule/clause recommendation logic for 8 cases. It preserves unresolved required business fields as explicit template placeholders. A technical pass reports needs_legal_review until the inventory contains verifiable named legal and business approvals; it never creates or persists an acceptance_run_id.
Release Quality Gate
Use this after a smoke deploy and interface benchmark to turn the release checks into one pass/fail gate:
When service authentication is enabled, load the protected smoke values into process environment variables first. Do not pass the values on the command line or write them to the artifact directory.
$env:CONTRACT_API_WEB_PROXY_SHARED_SECRET = "<load from protected smoke environment>"
$env:AI_PLATFORM_SERVICE_SHARED_SECRET = "<load from protected smoke environment>"
python yuqei-infra\scripts\run-release-quality-gate.py `
--web-base-url http://192.168.3.90:13000 `
--contract-api-base http://192.168.3.90:18100 `
--ai-api-base http://192.168.3.90:18101 `
--allow-ai-provider-warning
The gate verifies:
- Contract API
/api/v1/runtime/schema-check. - AI Platform API
/api/v1/runtime/schema-check. - Latest Docker smoke summary under
project-docs\qa-artifacts\v2-smoke-docker-deploy. - Latest interface benchmark summary under
project-docs\qa-artifacts\v2-interface-benchmark.
To refresh the benchmark during the same gate run:
python yuqei-infra\scripts\run-release-quality-gate.py `
--web-base-url http://192.168.3.90:13000 `
--contract-api-base http://192.168.3.90:18100 `
--ai-api-base http://192.168.3.90:18101 `
--run-benchmark `
--allow-ai-provider-warning
When --run-benchmark is enabled, the release gate defaults to 15 measured
requests per scenario, concurrency 3, and 2 warmup requests. Including the
benchmark probes, this remains below the strictest enforced read limit of 20
requests per user per minute. The standalone interface benchmark retains its
larger load-test defaults; only raise the release-gate values in an isolated
performance environment where the intended anti-abuse response is evaluated
separately.
To require fresh summaries without rerunning every check:
python yuqei-infra\scripts\run-release-quality-gate.py `
--web-base-url http://192.168.3.90:13000 `
--contract-api-base http://192.168.3.90:18100 `
--ai-api-base http://192.168.3.90:18101 `
--max-smoke-summary-age-hours 24 `
--max-benchmark-summary-age-hours 24
Artifacts are written to project-docs\qa-artifacts\v2-release-quality-gate. GitHub Actions also exposes a manual Release Quality Gate workflow for a self-hosted runner with access to 192.168.3.90.
--allow-ai-provider-warning is only for a smoke environment that intentionally disables real Provider calls and enables fallback. Omit it for production readiness; any provider_runtime warning then fails the gate. Both the gate and benchmark read service keys from environment variables and record only whether authentication was configured, never the values.
Latest passing release gate artifact:
project-docs\qa-artifacts\v2-release-quality-gate\2026-07-13T14-03-15- Run mode:
--run-benchmark --allow-ai-provider-warning, with protected service keys loaded from environment variables. - Contract API schema-check, the expected smoke-only AI Provider warning, Docker smoke summary, and 7 interface benchmark scenarios passed.
- Each scenario ran 60 measured requests at concurrency 6 with 0% errors. Contract list p95 was 199.31 ms and knowledge chunk hybrid search p95 was 66.74 ms.
Latest supporting artifacts:
- Interface benchmark:
project-docs\qa-artifacts\v2-release-quality-gate\2026-07-13T14-03-15\interface-benchmark. - Security scan:
project-docs\qa-artifacts\v2-security-scan\2026-07-13-auth-hardening-final-2. - Source secret scan: PR CI for commit
6abca4ebpassed npm audit, Bandit, pip-audit, and Linux detect-secrets with no unreviewed source secret.
Contract Review Worker Flow Verification
Use this after the APIs and worker are running to verify the T7 contract review path:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File yuqei-infra\scripts\verify-contract-review-worker-flow.ps1 `
-ContractApiBaseUrl http://localhost:8100 `
-WorkerExecutionMode docker-compose `
-RequireConfidence
The script creates a temporary contract, uploads a readable source text file, starts a review task, runs or waits for the contract review worker, then checks that the review result was written back with an AI Trace id. Add -RequirePrimarySources in staging/production when the AI Platform knowledge base is expected to return legal or policy sources.
-WorkerExecutionMode supports:
api: callPOST /api/v1/review-worker/run-once.docker-compose: runpython -m yuqei_contract_api.review_worker --run-onceinside thecontract-review-workerservice.none: do not trigger manually; wait for a background worker loop to consume the task.
Production Baseline
Production must override the local demo defaults:
CONTRACT_API_ENVIRONMENT=production
CONTRACT_API_DATABASE_URL=postgresql+psycopg://.../yuqei_contract
CONTRACT_API_DATABASE_AUTO_CREATE=false
CONTRACT_API_SEED_DEMO_DATA=false
CONTRACT_API_AI_PLATFORM_BASE_URL=http://ai-platform-api:8101
CONTRACT_API_AI_PLATFORM_TIMEOUT_SECONDS=60
CONTRACT_API_UVICORN_WORKERS=1
CONTRACT_API_READ_CACHE_TTL_SECONDS=2
CONTRACT_API_REVIEW_WORKER_NAME=contract-review-worker-1
CONTRACT_API_REVIEW_WORKER_POLL_INTERVAL_SECONDS=5
CONTRACT_API_REVIEW_WORKER_BATCH_LIMIT=5
AI_PLATFORM_ENVIRONMENT=production
AI_PLATFORM_DATABASE_URL=postgresql+psycopg://.../yuqei_ai
AI_PLATFORM_DATABASE_AUTO_CREATE=false
AI_PLATFORM_UVICORN_WORKERS=1
YUQEI_WORKER_DATABASE_URL=postgresql+psycopg://.../yuqei_ai
YUQEI_WORKER_DATABASE_AUTO_CREATE=false
Keep CONTRACT_API_AI_PLATFORM_TIMEOUT_SECONDS at least five seconds above AI_PLATFORM_PROVIDER_REQUEST_TIMEOUT_SECONDS; production preflight rejects missing, non-finite, out-of-range, or inverted timeout budgets.
Start from yuqei-infra\compose\.env.production.example and replace every CHANGE_ME value before deployment.
Run migrations explicitly before replacing application containers.
The 2-second read cache is process-local and is automatically disabled when CONTRACT_API_UVICORN_WORKERS is greater than 1. Use a shared cache with cross-process invalidation before increasing the production worker count if the same list/detail p95 target must be retained.