feat(modernization): full architecture blueprint with hardening follow-up

Implement phased modernization infrastructure for transitioning from
multi-page legacy routing to SPA portal-shell architecture, plus
post-delivery hardening fixes for policy loading, fallback consistency,
and governance drift detection.

Key changes:
- Add route contract enrichment with scope/visibility/compatibility policies
- Canonical 302 redirects from legacy direct-entry to /portal-shell/ routes
- Asset readiness enforcement and runtime fallback retirement for in-scope routes
- Shared feature-flag helpers (env > config > default) replacing duplicated _to_bool
- Defensive copy for lru_cached policy payloads preventing mutation corruption
- Unified retired-fallback response helper across app and blueprint routes
- Frontend/backend route-contract cross-validation in governance gates
- Shell CSS token fallback values for routes rendered outside shell scope
- Local-safe .env.example defaults with production recommendation comments
- Legacy contract fallback warning logging and single-hop redirect optimization

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
egg
2026-02-12 11:26:02 +08:00
parent 2c8d80afe6
commit 7cb0985b12
113 changed files with 4577 additions and 582 deletions

View File

@@ -211,10 +211,37 @@ with engine.connect() as conn:
}
build_frontend_assets() {
if [ "${FRONTEND_BUILD_ON_START:-true}" != "true" ]; then
log_info "Skip frontend build (FRONTEND_BUILD_ON_START=${FRONTEND_BUILD_ON_START})"
local mode="${FRONTEND_BUILD_MODE:-}"
local fail_on_error="${FRONTEND_BUILD_FAIL_ON_ERROR:-}"
# Backward compatibility:
# - FRONTEND_BUILD_MODE takes precedence when set.
# - Otherwise, retain FRONTEND_BUILD_ON_START behavior.
if [ -z "${mode}" ]; then
if [ "${FRONTEND_BUILD_ON_START:-true}" = "true" ]; then
mode="auto"
else
mode="never"
fi
fi
mode="$(echo "${mode}" | tr '[:upper:]' '[:lower:]')"
if [ -z "${fail_on_error}" ]; then
if [ "$(echo "${FLASK_ENV:-development}" | tr '[:upper:]' '[:lower:]')" = "production" ]; then
fail_on_error="true"
else
fail_on_error="false"
fi
fi
if [ "${mode}" = "never" ]; then
log_info "Skip frontend build (FRONTEND_BUILD_MODE=${mode})"
return 0
fi
if [ "${mode}" != "auto" ] && [ "${mode}" != "always" ]; then
log_warn "Invalid FRONTEND_BUILD_MODE='${mode}', fallback to auto"
mode="auto"
fi
if [ ! -f "${ROOT}/frontend/package.json" ]; then
return 0
@@ -225,54 +252,61 @@ build_frontend_assets() {
return 0
fi
local required_entries=(
"portal.js"
"wip-overview.js"
"wip-detail.js"
"hold-detail.js"
"hold-overview.js"
"hold-history.js"
"resource-status.js"
"resource-history.js"
"job-query.js"
"excel-query.js"
"tables.js"
"query-tool.js"
"tmtt-defect.js"
"qc-gate.js"
"mid-section-defect.js"
)
local needs_build=false
local newest_entry=""
local needs_build=true
if [ "${mode}" = "auto" ]; then
local required_entries=(
"portal.js"
"wip-overview.js"
"wip-detail.js"
"hold-detail.js"
"hold-overview.js"
"hold-history.js"
"resource-status.js"
"resource-history.js"
"job-query.js"
"excel-query.js"
"tables.js"
"query-tool.js"
"tmtt-defect.js"
"qc-gate.js"
"mid-section-defect.js"
)
needs_build=false
local newest_entry=""
for entry in "${required_entries[@]}"; do
local entry_path="${ROOT}/src/mes_dashboard/static/dist/${entry}"
if [ ! -f "${entry_path}" ]; then
for entry in "${required_entries[@]}"; do
local entry_path="${ROOT}/src/mes_dashboard/static/dist/${entry}"
if [ ! -f "${entry_path}" ]; then
needs_build=true
break
fi
if [ -z "${newest_entry}" ] || [ "${entry_path}" -nt "${newest_entry}" ]; then
newest_entry="${entry_path}"
fi
done
if [ "$needs_build" = false ] && find "${ROOT}/frontend/src" -type f -newer "${newest_entry}" | grep -q .; then
needs_build=true
break
fi
if [ -z "${newest_entry}" ] || [ "${entry_path}" -nt "${newest_entry}" ]; then
newest_entry="${entry_path}"
if [ "$needs_build" = false ] && ([ "${ROOT}/frontend/package.json" -nt "${newest_entry}" ] || [ "${ROOT}/frontend/vite.config.js" -nt "${newest_entry}" ]); then
needs_build=true
fi
done
if [ "$needs_build" = false ] && find "${ROOT}/frontend/src" -type f -newer "${newest_entry}" | grep -q .; then
needs_build=true
fi
if [ "$needs_build" = false ] && ([ "${ROOT}/frontend/package.json" -nt "${newest_entry}" ] || [ "${ROOT}/frontend/vite.config.js" -nt "${newest_entry}" ]); then
needs_build=true
if [ "$needs_build" = false ]; then
log_success "Frontend assets are up to date (FRONTEND_BUILD_MODE=auto)"
return 0
fi
fi
if [ "$needs_build" = false ]; then
log_success "Frontend assets are up to date"
return 0
fi
log_info "Building frontend assets with Vite..."
log_info "Building frontend assets with Vite (FRONTEND_BUILD_MODE=${mode})..."
if npm --prefix "${ROOT}/frontend" run build >/dev/null 2>&1; then
log_success "Frontend assets built"
else
log_warn "Frontend build failed; continuing with fallback inline scripts"
if is_enabled "${fail_on_error}"; then
log_error "Frontend build failed; aborting startup (FRONTEND_BUILD_FAIL_ON_ERROR=${fail_on_error})"
return 1
fi
log_warn "Frontend build failed; continuing startup (FRONTEND_BUILD_FAIL_ON_ERROR=${fail_on_error})"
fi
}
@@ -594,7 +628,7 @@ do_start() {
PORT=$(echo "${GUNICORN_BIND:-0.0.0.0:8080}" | cut -d: -f2)
export PYTHONPATH="${ROOT}/src:${PYTHONPATH:-}"
cd "$ROOT"
build_frontend_assets
build_frontend_assets || return 1
# Log startup
echo "[$(timestamp)] Starting server" >> "$STARTUP_LOG"