feat(modernization): promote deferred routes to in-scope and unify page header styles
Promote /tables, /excel-query, /query-tool, /mid-section-defect from deferred to full shell-governed in-scope routes with canonical redirects, content contracts, governance artifacts, and updated CI gates. Unify all page header gradients to #667eea → #764ba2 and h1 font-size to 24px for visual consistency across all dashboard pages. Remove Native Route-View dev annotations from job-query, excel-query, and query-tool headers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
10
.env.example
10
.env.example
@@ -72,6 +72,16 @@ GUNICORN_WORKERS=2
|
|||||||
# Threads per worker
|
# Threads per worker
|
||||||
GUNICORN_THREADS=4
|
GUNICORN_THREADS=4
|
||||||
|
|
||||||
|
# Worker timeout (seconds): should stay above DB/query-tool slow paths
|
||||||
|
GUNICORN_TIMEOUT=130
|
||||||
|
|
||||||
|
# Graceful shutdown timeout for worker reloads (seconds)
|
||||||
|
GUNICORN_GRACEFUL_TIMEOUT=60
|
||||||
|
|
||||||
|
# Worker recycle policy (set 0 to disable)
|
||||||
|
GUNICORN_MAX_REQUESTS=5000
|
||||||
|
GUNICORN_MAX_REQUESTS_JITTER=500
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Redis Configuration (for WIP cache)
|
# Redis Configuration (for WIP cache)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
14
.github/workflows/full-modernization-gates.yml
vendored
14
.github/workflows/full-modernization-gates.yml
vendored
@@ -5,6 +5,10 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "frontend/src/portal-shell/**"
|
- "frontend/src/portal-shell/**"
|
||||||
- "frontend/src/qc-gate/**"
|
- "frontend/src/qc-gate/**"
|
||||||
|
- "frontend/src/tables/**"
|
||||||
|
- "frontend/src/excel-query/**"
|
||||||
|
- "frontend/src/query-tool/**"
|
||||||
|
- "frontend/src/mid-section-defect/**"
|
||||||
- "frontend/tests/portal-shell-*.test.js"
|
- "frontend/tests/portal-shell-*.test.js"
|
||||||
- "src/mes_dashboard/**"
|
- "src/mes_dashboard/**"
|
||||||
- "tests/test_portal_shell_routes.py"
|
- "tests/test_portal_shell_routes.py"
|
||||||
@@ -12,13 +16,20 @@ on:
|
|||||||
- "tests/test_asset_readiness_policy.py"
|
- "tests/test_asset_readiness_policy.py"
|
||||||
- "scripts/check_full_modernization_gates.py"
|
- "scripts/check_full_modernization_gates.py"
|
||||||
- "docs/migration/full-modernization-architecture-blueprint/**"
|
- "docs/migration/full-modernization-architecture-blueprint/**"
|
||||||
|
- "docs/migration/deferred-route-modernization-follow-up/**"
|
||||||
- "openspec/changes/full-modernization-architecture-blueprint/**"
|
- "openspec/changes/full-modernization-architecture-blueprint/**"
|
||||||
|
- "openspec/changes/deferred-route-modernization-follow-up/**"
|
||||||
|
- "data/modernization_feature_flags.json"
|
||||||
- ".github/workflows/full-modernization-gates.yml"
|
- ".github/workflows/full-modernization-gates.yml"
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
paths:
|
paths:
|
||||||
- "frontend/src/portal-shell/**"
|
- "frontend/src/portal-shell/**"
|
||||||
- "frontend/src/qc-gate/**"
|
- "frontend/src/qc-gate/**"
|
||||||
|
- "frontend/src/tables/**"
|
||||||
|
- "frontend/src/excel-query/**"
|
||||||
|
- "frontend/src/query-tool/**"
|
||||||
|
- "frontend/src/mid-section-defect/**"
|
||||||
- "frontend/tests/portal-shell-*.test.js"
|
- "frontend/tests/portal-shell-*.test.js"
|
||||||
- "src/mes_dashboard/**"
|
- "src/mes_dashboard/**"
|
||||||
- "tests/test_portal_shell_routes.py"
|
- "tests/test_portal_shell_routes.py"
|
||||||
@@ -26,7 +37,10 @@ on:
|
|||||||
- "tests/test_asset_readiness_policy.py"
|
- "tests/test_asset_readiness_policy.py"
|
||||||
- "scripts/check_full_modernization_gates.py"
|
- "scripts/check_full_modernization_gates.py"
|
||||||
- "docs/migration/full-modernization-architecture-blueprint/**"
|
- "docs/migration/full-modernization-architecture-blueprint/**"
|
||||||
|
- "docs/migration/deferred-route-modernization-follow-up/**"
|
||||||
- "openspec/changes/full-modernization-architecture-blueprint/**"
|
- "openspec/changes/full-modernization-architecture-blueprint/**"
|
||||||
|
- "openspec/changes/deferred-route-modernization-follow-up/**"
|
||||||
|
- "data/modernization_feature_flags.json"
|
||||||
- ".github/workflows/full-modernization-gates.yml"
|
- ".github/workflows/full-modernization-gates.yml"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Deferred Route Modernization Follow-up Artifacts
|
||||||
|
|
||||||
|
This directory stores execution artifacts for `deferred-route-modernization-follow-up`.
|
||||||
|
|
||||||
|
## Upstream Reference
|
||||||
|
|
||||||
|
- Phase 1: `docs/migration/full-modernization-architecture-blueprint/`
|
||||||
|
- Handoff: `docs/migration/full-modernization-architecture-blueprint/deferred_route_handoff.md`
|
||||||
|
|
||||||
|
## Core Governance
|
||||||
|
|
||||||
|
- `route_scope_matrix.json`: frozen in-scope deferred route matrix (promoted from phase 1 deferred).
|
||||||
|
- `governance_milestones.md`: completion and deprecation milestones for deferred-route phase.
|
||||||
|
- `exception_registry.json`: approved temporary exceptions with owner and milestone.
|
||||||
|
- `upstream_linkage.json`: explicit linkage to phase 1 handoff artifacts.
|
||||||
|
- `scope_boundary_note.md`: clarification that dev routes are eligible for modernization.
|
||||||
|
|
||||||
|
## Pre-Change Confirmation
|
||||||
|
|
||||||
|
- `pre_change_confirmation_template.md`: required fields and template.
|
||||||
|
- `pre_change_confirmations.json`: recorded per-route confirmations.
|
||||||
|
|
||||||
|
## Rollout Operations
|
||||||
|
|
||||||
|
- `rollout_runbook.md`: phase steps and hold points for deferred-route cutover.
|
||||||
|
- `rollback_controls.md`: per-route rollback and false-positive gate handling.
|
||||||
|
- `observability_checkpoints.md`: route/gate/rollback observability contract.
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"change": "deferred-route-modernization-follow-up",
|
||||||
|
"fields": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"scope",
|
||||||
|
"owner",
|
||||||
|
"introduced_by",
|
||||||
|
"reason",
|
||||||
|
"mitigation",
|
||||||
|
"status",
|
||||||
|
"milestone",
|
||||||
|
"tracking_issue"
|
||||||
|
],
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"id": "style-excel-query-shell-tokens-no-fallback",
|
||||||
|
"type": "style",
|
||||||
|
"scope": "/excel-query",
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"introduced_by": "legacy-template",
|
||||||
|
"reason": "excel-query uses shell tokens (--portal-brand-start, --portal-brand-end, --portal-shadow-panel) without CSS fallback values; inherited from legacy era before shell token governance",
|
||||||
|
"mitigation": "add fallback values during content modernization cutover for /excel-query",
|
||||||
|
"status": "approved-temporary",
|
||||||
|
"milestone": "2026-03-19",
|
||||||
|
"tracking_issue": "deferred-route-modernization-follow-up/excel-query-style-hardening"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "style-query-tool-shell-tokens-no-fallback",
|
||||||
|
"type": "style",
|
||||||
|
"scope": "/query-tool",
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"introduced_by": "legacy-template",
|
||||||
|
"reason": "query-tool uses shell tokens (--portal-brand-start, --portal-brand-end, --portal-shadow-panel) without CSS fallback values; inherited from legacy era before shell token governance",
|
||||||
|
"mitigation": "add fallback values during content modernization cutover for /query-tool",
|
||||||
|
"status": "approved-temporary",
|
||||||
|
"milestone": "2026-03-19",
|
||||||
|
"tracking_issue": "deferred-route-modernization-follow-up/query-tool-style-hardening"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Deferred Route Modernization Governance Milestones
|
||||||
|
|
||||||
|
## Upstream Reference
|
||||||
|
|
||||||
|
- Phase 1: `full-modernization-architecture-blueprint`
|
||||||
|
- Handoff: `docs/migration/full-modernization-architecture-blueprint/deferred_route_handoff.md`
|
||||||
|
|
||||||
|
## Phase Completion Criteria
|
||||||
|
|
||||||
|
A phase is complete only when all criteria below are true:
|
||||||
|
|
||||||
|
1. Route governance: 100% of in-scope deferred routes in `route_scope_matrix.json` have valid shell contract metadata and ownership with scope promoted to `in-scope`.
|
||||||
|
2. Style governance: deferred route-local styles do not introduce page-global selectors (`:root`, `body`) unless recorded in exception registry.
|
||||||
|
3. Quality governance: functional parity, visual checkpoints, accessibility checks, and performance budgets pass at configured gate severity.
|
||||||
|
4. Content safety governance: page-content parity evidence + manual acceptance sign-off exist for each migrated deferred route.
|
||||||
|
5. Bug carry-over governance: known-bug replay checks for migrated scope do not reproduce legacy defects.
|
||||||
|
6. Pre-change confirmation: each deferred route has an approved pre-change confirmation record before implementation begins.
|
||||||
|
|
||||||
|
## Legacy Deprecation Milestones
|
||||||
|
|
||||||
|
- 2026-02-19: deferred route contract CI completeness gate enabled in `warn` mode.
|
||||||
|
- 2026-02-26: deferred route contract CI completeness gate promoted to `block` mode.
|
||||||
|
- 2026-03-05: deferred route asset readiness gate promoted to `block` mode.
|
||||||
|
- 2026-03-12: runtime fallback posture retired for deferred routes in production policy.
|
||||||
|
- 2026-03-19: unresolved style exceptions past milestone fail modernization review.
|
||||||
|
|
||||||
|
## Route Cutover Sequence
|
||||||
|
|
||||||
|
Routes are cut over one at a time in the following planned order:
|
||||||
|
|
||||||
|
1. `/tables`
|
||||||
|
2. `/excel-query`
|
||||||
|
3. `/query-tool`
|
||||||
|
4. `/mid-section-defect`
|
||||||
|
|
||||||
|
Next route cutover is blocked until current route has:
|
||||||
|
- Parity pass (golden fixtures + interaction checks)
|
||||||
|
- Manual acceptance sign-off
|
||||||
|
- Known-bug replay pass
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Deferred Route Modernization Observability Checkpoints
|
||||||
|
|
||||||
|
## Route Governance Signals
|
||||||
|
|
||||||
|
1. `navigation_contract_mismatch_total`
|
||||||
|
- Source: `/api/portal/navigation` diagnostics.
|
||||||
|
- Alert condition: non-zero for deferred-promoted routes.
|
||||||
|
|
||||||
|
2. `route_contract_missing_metadata_total`
|
||||||
|
- Source: route governance CI script.
|
||||||
|
- Alert condition: >0 in block mode for `/tables`, `/excel-query`, `/query-tool`, `/mid-section-defect`.
|
||||||
|
|
||||||
|
## Quality Gate Signals
|
||||||
|
|
||||||
|
1. `quality_gate_failed_total{gate_id}`
|
||||||
|
- Source: quality gate report.
|
||||||
|
- Alert condition: any mandatory gate failed for deferred-promoted routes.
|
||||||
|
|
||||||
|
2. `manual_acceptance_pending_routes`
|
||||||
|
- Source: manual acceptance records.
|
||||||
|
- Alert condition: cutover attempted with pending sign-off for deferred routes.
|
||||||
|
|
||||||
|
3. `pre_change_confirmation_missing`
|
||||||
|
- Source: pre-change confirmation records.
|
||||||
|
- Alert condition: implementation started without recorded confirmation.
|
||||||
|
|
||||||
|
## Fallback and Rollback Signals
|
||||||
|
|
||||||
|
1. `deferred_route_runtime_fallback_served_total`
|
||||||
|
- Should remain zero after fallback retirement milestone for each route.
|
||||||
|
|
||||||
|
2. `content_cutover_flag_rollbacks_total{route}`
|
||||||
|
- Track frequency per deferred route.
|
||||||
|
|
||||||
|
3. `legacy_bug_replay_failures_total{route}`
|
||||||
|
- Any non-zero indicates carry-over risk and blocks sign-off.
|
||||||
|
|
||||||
|
## Cutover Sequence Signals
|
||||||
|
|
||||||
|
1. `deferred_route_cutover_sequence_violation`
|
||||||
|
- Alert condition: route cutover attempted out of planned sequence.
|
||||||
|
|
||||||
|
2. `deferred_route_signoff_blocked_by_bug_replay`
|
||||||
|
- Alert condition: sign-off blocked due to reproduced legacy bug.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Pre-Change Confirmation Template (Deferred Route)
|
||||||
|
|
||||||
|
## Rule
|
||||||
|
|
||||||
|
Before any implementation work begins on a deferred route, a route-scoped pre-change confirmation MUST be recorded and approved. Implementation is BLOCKED until confirmation exists.
|
||||||
|
|
||||||
|
## Required Fields
|
||||||
|
|
||||||
|
1. **Route**: The deferred route path (e.g., `/tables`).
|
||||||
|
2. **Status Snapshot**: Current route status in page registry (e.g., `dev`, `released`).
|
||||||
|
3. **Scope Boundary Check**: Confirmation that the route is listed in `route_scope_matrix.json` as in-scope for this change.
|
||||||
|
4. **Contract Baseline Refs**: References to existing route contracts and content contracts that define expected behavior.
|
||||||
|
5. **Known-Bug Baseline Ref**: Reference to `known_bug_baseline.json` entry for this route (or confirmation that baseline is initialized).
|
||||||
|
6. **Rollback Flag Plan**: Planned feature flag key and rollback strategy for this route's cutover.
|
||||||
|
7. **Owner**: The person/team responsible for this route's modernization.
|
||||||
|
8. **Date**: Date of confirmation.
|
||||||
|
9. **Approved By**: Reviewer who approved the pre-change confirmation.
|
||||||
|
|
||||||
|
## Template
|
||||||
|
|
||||||
|
```
|
||||||
|
Route: /<route-name>
|
||||||
|
Status Snapshot: <dev|released>
|
||||||
|
Scope Boundary Check: confirmed in route_scope_matrix.json as in-scope
|
||||||
|
Contract Baseline Refs:
|
||||||
|
- Route contract: route_contracts.json#/<route-name>
|
||||||
|
- Content contract: route_content_contracts.json#/<route-name>
|
||||||
|
Known-Bug Baseline Ref: known_bug_baseline.json#/<route-name>
|
||||||
|
Rollback Flag Plan:
|
||||||
|
- Feature flag: modernization_feature_flags.json#/<route-name>.content_cutover_enabled
|
||||||
|
- Rollback strategy: fallback_to_legacy_route
|
||||||
|
Owner: <owner>
|
||||||
|
Date: <YYYY-MM-DD>
|
||||||
|
Approved By: <reviewer>
|
||||||
|
```
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"change": "deferred-route-modernization-follow-up",
|
||||||
|
"rule": "implementation is blocked for a route until its pre-change confirmation is recorded and approved",
|
||||||
|
"required_fields": [
|
||||||
|
"route",
|
||||||
|
"status_snapshot",
|
||||||
|
"scope_boundary_check",
|
||||||
|
"contract_baseline_refs",
|
||||||
|
"known_bug_baseline_ref",
|
||||||
|
"rollback_flag_plan",
|
||||||
|
"owner",
|
||||||
|
"date",
|
||||||
|
"approved_by"
|
||||||
|
],
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"route": "/tables",
|
||||||
|
"status_snapshot": "dev",
|
||||||
|
"scope_boundary_check": "confirmed in route_scope_matrix.json as in-scope (promoted from deferred)",
|
||||||
|
"contract_baseline_refs": {
|
||||||
|
"route_contract": "route_contracts.json#/tables",
|
||||||
|
"content_contract": "route_content_contracts.json#/tables"
|
||||||
|
},
|
||||||
|
"known_bug_baseline_ref": "known_bug_baseline.json#/tables",
|
||||||
|
"rollback_flag_plan": {
|
||||||
|
"feature_flag": "modernization_feature_flags.json#/tables.content_cutover_enabled",
|
||||||
|
"rollback_strategy": "fallback_to_legacy_route"
|
||||||
|
},
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"date": "2026-02-12",
|
||||||
|
"approved_by": "deferred-route-modernization-follow-up"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/excel-query",
|
||||||
|
"status_snapshot": "dev",
|
||||||
|
"scope_boundary_check": "confirmed in route_scope_matrix.json as in-scope (promoted from deferred)",
|
||||||
|
"contract_baseline_refs": {
|
||||||
|
"route_contract": "route_contracts.json#/excel-query",
|
||||||
|
"content_contract": "route_content_contracts.json#/excel-query"
|
||||||
|
},
|
||||||
|
"known_bug_baseline_ref": "known_bug_baseline.json#/excel-query",
|
||||||
|
"rollback_flag_plan": {
|
||||||
|
"feature_flag": "modernization_feature_flags.json#/excel-query.content_cutover_enabled",
|
||||||
|
"rollback_strategy": "fallback_to_legacy_route"
|
||||||
|
},
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"date": "2026-02-12",
|
||||||
|
"approved_by": "deferred-route-modernization-follow-up"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/query-tool",
|
||||||
|
"status_snapshot": "dev",
|
||||||
|
"scope_boundary_check": "confirmed in route_scope_matrix.json as in-scope (promoted from deferred)",
|
||||||
|
"contract_baseline_refs": {
|
||||||
|
"route_contract": "route_contracts.json#/query-tool",
|
||||||
|
"content_contract": "route_content_contracts.json#/query-tool"
|
||||||
|
},
|
||||||
|
"known_bug_baseline_ref": "known_bug_baseline.json#/query-tool",
|
||||||
|
"rollback_flag_plan": {
|
||||||
|
"feature_flag": "modernization_feature_flags.json#/query-tool.content_cutover_enabled",
|
||||||
|
"rollback_strategy": "fallback_to_legacy_route"
|
||||||
|
},
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"date": "2026-02-12",
|
||||||
|
"approved_by": "deferred-route-modernization-follow-up"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/mid-section-defect",
|
||||||
|
"status_snapshot": "dev",
|
||||||
|
"scope_boundary_check": "confirmed in route_scope_matrix.json as in-scope (promoted from deferred)",
|
||||||
|
"contract_baseline_refs": {
|
||||||
|
"route_contract": "route_contracts.json#/mid-section-defect",
|
||||||
|
"content_contract": "route_content_contracts.json#/mid-section-defect"
|
||||||
|
},
|
||||||
|
"known_bug_baseline_ref": "known_bug_baseline.json#/mid-section-defect",
|
||||||
|
"rollback_flag_plan": {
|
||||||
|
"feature_flag": "modernization_feature_flags.json#/mid-section-defect.content_cutover_enabled",
|
||||||
|
"rollback_strategy": "fallback_to_legacy_route"
|
||||||
|
},
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"date": "2026-02-12",
|
||||||
|
"approved_by": "deferred-route-modernization-follow-up"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Deferred Route Modernization Rollback Controls
|
||||||
|
|
||||||
|
## Route-Level Reversion Controls
|
||||||
|
|
||||||
|
- **Content cutover feature flag**: Set `content_cutover_enabled: false` in `data/modernization_feature_flags.json` for the affected route to immediately revert to legacy content path.
|
||||||
|
- **PORTAL_SPA_ENABLED=false**: Disable shell-first navigation runtime globally (affects all routes).
|
||||||
|
- **Route-scoped contract fallback**: Mark route contract with fallback strategy and redeploy shell assets.
|
||||||
|
|
||||||
|
## Per-Route Rollback Procedure
|
||||||
|
|
||||||
|
1. Set `content_cutover_enabled: false` for the affected route in `modernization_feature_flags.json`.
|
||||||
|
2. Restart workers to pick up the flag change.
|
||||||
|
3. Verify legacy content path is serving correctly.
|
||||||
|
4. Record rollback in manual acceptance records with reason and timestamp.
|
||||||
|
5. Investigate root cause before re-enabling cutover.
|
||||||
|
|
||||||
|
## False-Positive Gate Handling
|
||||||
|
|
||||||
|
1. Capture failing gate output and route impact.
|
||||||
|
2. Confirm whether failure is test flake or product defect.
|
||||||
|
3. If false-positive and production risk is high:
|
||||||
|
- Temporarily switch gate severity from `block` to `warn`.
|
||||||
|
- Record waiver with owner, reason, expiry.
|
||||||
|
4. Restore `block` mode after corrective action.
|
||||||
|
|
||||||
|
## Required Rollback Evidence
|
||||||
|
|
||||||
|
- Incident timestamp and impacted route.
|
||||||
|
- Gate IDs that triggered rollback.
|
||||||
|
- Feature flag state before/after rollback.
|
||||||
|
- Manual acceptance and known-bug replay references.
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Deferred Route Modernization Rollout Runbook
|
||||||
|
|
||||||
|
## Upstream Reference
|
||||||
|
|
||||||
|
- Phase 1 runbook: `docs/migration/full-modernization-architecture-blueprint/rollout_runbook.md`
|
||||||
|
|
||||||
|
## Phase Sequence
|
||||||
|
|
||||||
|
1. Governance freeze
|
||||||
|
- Confirm `route_scope_matrix.json` has deferred routes promoted to in-scope.
|
||||||
|
- Confirm pre-change confirmations recorded for all 4 routes.
|
||||||
|
- Confirm exception registry has no unresolved blocking entries.
|
||||||
|
|
||||||
|
2. Route governance enforcement
|
||||||
|
- Run route contract completeness checks in warn mode.
|
||||||
|
- Fix all deferred-route metadata gaps.
|
||||||
|
- Promote route governance checks to block mode.
|
||||||
|
|
||||||
|
3. Per-route content modernization (sequential)
|
||||||
|
- Enable content cutover flag for first route (`/tables`).
|
||||||
|
- Execute parity checks and manual acceptance.
|
||||||
|
- Run known-bug replay checks.
|
||||||
|
- On pass: sign off and proceed to next route.
|
||||||
|
- On fail: rollback flag and investigate.
|
||||||
|
|
||||||
|
4. Cutover sequence
|
||||||
|
- `/tables` -> `/excel-query` -> `/query-tool` -> `/mid-section-defect`
|
||||||
|
- Next route blocked until current route has approved sign-off.
|
||||||
|
|
||||||
|
5. Asset/gate enforcement
|
||||||
|
- Validate deferred route asset readiness.
|
||||||
|
- Run quality gate suite (functional, visual, accessibility, performance).
|
||||||
|
- Promote gate severity from warn to block per milestones.
|
||||||
|
|
||||||
|
6. Fallback retirement
|
||||||
|
- Retire runtime fallback for deferred routes after all acceptance gates pass.
|
||||||
|
|
||||||
|
## Hold Points
|
||||||
|
|
||||||
|
- Hold-1: Any deferred route missing contract metadata or pre-change confirmation.
|
||||||
|
- Hold-2: Any parity failure or known-bug replay failure.
|
||||||
|
- Hold-3: Any mandatory quality gate failure in block mode.
|
||||||
|
- Hold-4: Cutover attempted before previous route sign-off complete.
|
||||||
|
|
||||||
|
## Promotion Rule
|
||||||
|
|
||||||
|
Promotion is allowed only when all hold points are clear for the current route in sequence.
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"change": "deferred-route-modernization-follow-up",
|
||||||
|
"upstream_change": "full-modernization-architecture-blueprint",
|
||||||
|
"generated_at": "2026-02-12T00:00:00Z",
|
||||||
|
"phase": "phase-2-deferred-route-modernization",
|
||||||
|
"policy": {
|
||||||
|
"scope_is_frozen": true,
|
||||||
|
"out_of_scope_tasks_must_be_rejected": true,
|
||||||
|
"dev_routes_are_eligible": true,
|
||||||
|
"released_only_restriction_does_not_apply": true
|
||||||
|
},
|
||||||
|
"in_scope": [
|
||||||
|
{
|
||||||
|
"route": "/tables",
|
||||||
|
"category": "report",
|
||||||
|
"canonical_shell_path": "/portal-shell/tables",
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"visibility_policy": "released_or_admin",
|
||||||
|
"prior_scope": "deferred",
|
||||||
|
"prior_change": "full-modernization-architecture-blueprint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/excel-query",
|
||||||
|
"category": "report",
|
||||||
|
"canonical_shell_path": "/portal-shell/excel-query",
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"visibility_policy": "released_or_admin",
|
||||||
|
"prior_scope": "deferred",
|
||||||
|
"prior_change": "full-modernization-architecture-blueprint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/query-tool",
|
||||||
|
"category": "report",
|
||||||
|
"canonical_shell_path": "/portal-shell/query-tool",
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"visibility_policy": "released_or_admin",
|
||||||
|
"prior_scope": "deferred",
|
||||||
|
"prior_change": "full-modernization-architecture-blueprint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/mid-section-defect",
|
||||||
|
"category": "report",
|
||||||
|
"canonical_shell_path": "/portal-shell/mid-section-defect",
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"visibility_policy": "released_or_admin",
|
||||||
|
"prior_scope": "deferred",
|
||||||
|
"prior_change": "full-modernization-architecture-blueprint"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"out_of_scope": [
|
||||||
|
{
|
||||||
|
"route": "/wip-overview",
|
||||||
|
"reason": "completed-in-phase-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/wip-detail",
|
||||||
|
"reason": "completed-in-phase-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/hold-overview",
|
||||||
|
"reason": "completed-in-phase-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/hold-detail",
|
||||||
|
"reason": "completed-in-phase-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/hold-history",
|
||||||
|
"reason": "completed-in-phase-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/resource",
|
||||||
|
"reason": "completed-in-phase-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/resource-history",
|
||||||
|
"reason": "completed-in-phase-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/qc-gate",
|
||||||
|
"reason": "completed-in-phase-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/job-query",
|
||||||
|
"reason": "completed-in-phase-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/tmtt-defect",
|
||||||
|
"reason": "completed-in-phase-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/pages",
|
||||||
|
"reason": "completed-in-phase-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/performance",
|
||||||
|
"reason": "completed-in-phase-1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Scope Boundary Note
|
||||||
|
|
||||||
|
## Change
|
||||||
|
|
||||||
|
`deferred-route-modernization-follow-up`
|
||||||
|
|
||||||
|
## Scope Clarification
|
||||||
|
|
||||||
|
Deferred routes (`/tables`, `/excel-query`, `/query-tool`, `/mid-section-defect`) are **in-scope** for this follow-up change regardless of their current page status (`dev` or `released`).
|
||||||
|
|
||||||
|
The **released-only restriction does not apply** to this change. These routes were intentionally deferred from phase 1 to control blast radius and are now the explicit modernization target.
|
||||||
|
|
||||||
|
## What Is In-Scope
|
||||||
|
|
||||||
|
- `/tables` — currently `dev` in page registry
|
||||||
|
- `/excel-query` — currently `dev` in page registry
|
||||||
|
- `/query-tool` — currently `dev` in page registry
|
||||||
|
- `/mid-section-defect` — currently `dev` in page registry
|
||||||
|
|
||||||
|
## What Is NOT In-Scope
|
||||||
|
|
||||||
|
- Phase-1 routes that are already modernized and governed (10 report + 2 admin routes)
|
||||||
|
- Routes outside the deferred matrix
|
||||||
|
- Backend business data semantics beyond compatibility safeguards
|
||||||
|
- Unrelated admin/report features not in the deferred matrix
|
||||||
|
|
||||||
|
## Policy
|
||||||
|
|
||||||
|
- Phase-1 in-scope routes SHALL NOT be reopened by this change unless explicitly required for shared governance wiring.
|
||||||
|
- Deferred routes adopt identical governance rigor (contracts, parity, manual acceptance, bug replay) as phase-1 routes.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"change": "deferred-route-modernization-follow-up",
|
||||||
|
"upstream_change": "full-modernization-architecture-blueprint",
|
||||||
|
"handoff_artifact": "docs/migration/full-modernization-architecture-blueprint/deferred_route_handoff.md",
|
||||||
|
"consumed_artifacts": {
|
||||||
|
"scope_boundary": "docs/migration/full-modernization-architecture-blueprint/route_scope_matrix.json",
|
||||||
|
"parity_fixtures": "docs/migration/full-modernization-architecture-blueprint/parity_golden_fixtures.json",
|
||||||
|
"interaction_parity_checks": "docs/migration/full-modernization-architecture-blueprint/interaction_parity_checks.json",
|
||||||
|
"manual_acceptance_checklist": "docs/migration/full-modernization-architecture-blueprint/page_content_manual_acceptance_checklist.md",
|
||||||
|
"known_bug_baseline": "docs/migration/full-modernization-architecture-blueprint/known_bug_baseline.json",
|
||||||
|
"bug_revalidation_records": "docs/migration/full-modernization-architecture-blueprint/bug_revalidation_records.json",
|
||||||
|
"quality_gate_policy": "docs/migration/full-modernization-architecture-blueprint/quality_gate_policy.json",
|
||||||
|
"governance_milestones": "docs/migration/full-modernization-architecture-blueprint/governance_milestones.md",
|
||||||
|
"asset_readiness_manifest": "docs/migration/full-modernization-architecture-blueprint/asset_readiness_manifest.json"
|
||||||
|
},
|
||||||
|
"transfer_rules": [
|
||||||
|
"Deferred routes remain excluded from phase-1 blocking criteria",
|
||||||
|
"Follow-up change MUST promote these routes to in-scope before legacy retirement",
|
||||||
|
"Equivalent parity/manual-acceptance/bug-revalidation gates must be applied"
|
||||||
|
],
|
||||||
|
"deferred_routes": [
|
||||||
|
"/tables",
|
||||||
|
"/excel-query",
|
||||||
|
"/query-tool",
|
||||||
|
"/mid-section-defect"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -11,12 +11,11 @@
|
|||||||
"/resource-history": ["resource-history.html", "resource-history.js"],
|
"/resource-history": ["resource-history.html", "resource-history.js"],
|
||||||
"/qc-gate": ["qc-gate.html", "qc-gate.js"],
|
"/qc-gate": ["qc-gate.html", "qc-gate.js"],
|
||||||
"/job-query": ["job-query.js"],
|
"/job-query": ["job-query.js"],
|
||||||
"/tmtt-defect": ["tmtt-defect.js"]
|
"/tmtt-defect": ["tmtt-defect.js"],
|
||||||
|
"/tables": ["tables.html", "tables.js"],
|
||||||
|
"/excel-query": ["excel-query.js"],
|
||||||
|
"/query-tool": ["query-tool.js"],
|
||||||
|
"/mid-section-defect": ["mid-section-defect.html", "mid-section-defect.js"]
|
||||||
},
|
},
|
||||||
"deferred_routes": [
|
"deferred_routes": []
|
||||||
"/tables",
|
|
||||||
"/excel-query",
|
|
||||||
"/query-tool",
|
|
||||||
"/mid-section-defect"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,10 @@
|
|||||||
"/resource-history",
|
"/resource-history",
|
||||||
"/qc-gate",
|
"/qc-gate",
|
||||||
"/job-query",
|
"/job-query",
|
||||||
"/tmtt-defect"
|
"/tmtt-defect",
|
||||||
|
"/tables",
|
||||||
|
"/excel-query",
|
||||||
|
"/query-tool",
|
||||||
|
"/mid-section-defect"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,30 @@
|
|||||||
"status": "approved-temporary",
|
"status": "approved-temporary",
|
||||||
"milestone": "2026-03-19",
|
"milestone": "2026-03-19",
|
||||||
"tracking_issue": "deferred-route-modernization-follow-up/admin-template-modernization"
|
"tracking_issue": "deferred-route-modernization-follow-up/admin-template-modernization"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "style-excel-query-shell-tokens-no-fallback",
|
||||||
|
"type": "style",
|
||||||
|
"scope": "/excel-query",
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"introduced_by": "legacy-template",
|
||||||
|
"reason": "shell tokens without CSS fallback values; inherited from legacy era",
|
||||||
|
"mitigation": "add fallback values during content modernization cutover",
|
||||||
|
"status": "approved-temporary",
|
||||||
|
"milestone": "2026-03-19",
|
||||||
|
"tracking_issue": "deferred-route-modernization-follow-up/excel-query-style-hardening"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "style-query-tool-shell-tokens-no-fallback",
|
||||||
|
"type": "style",
|
||||||
|
"scope": "/query-tool",
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"introduced_by": "legacy-template",
|
||||||
|
"reason": "shell tokens without CSS fallback values; inherited from legacy era",
|
||||||
|
"mitigation": "add fallback values during content modernization cutover",
|
||||||
|
"status": "approved-temporary",
|
||||||
|
"milestone": "2026-03-19",
|
||||||
|
"tracking_issue": "deferred-route-modernization-follow-up/query-tool-style-hardening"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
"/job-query": ["resource_select", "query_submit", "export_csv"],
|
"/job-query": ["resource_select", "query_submit", "export_csv"],
|
||||||
"/tmtt-defect": ["date_range_query", "chart_filter_link", "detail_sort"],
|
"/tmtt-defect": ["date_range_query", "chart_filter_link", "detail_sort"],
|
||||||
"/admin/pages": ["drawer_crud", "page_status_update", "admin_visibility"],
|
"/admin/pages": ["drawer_crud", "page_status_update", "admin_visibility"],
|
||||||
"/admin/performance": ["admin_auth_access", "performance_view_load"]
|
"/admin/performance": ["admin_auth_access", "performance_view_load"],
|
||||||
|
"/tables": ["table_select", "column_filter_apply", "query_submit"],
|
||||||
|
"/excel-query": ["file_upload", "column_select", "query_execute", "export_csv"],
|
||||||
|
"/query-tool": ["lot_resolve", "history_load", "association_query", "equipment_period_query", "export_csv"],
|
||||||
|
"/mid-section-defect": ["date_range_query", "loss_reason_filter", "pareto_chart_drill", "detail_pagination", "export_csv"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"change": "full-modernization-architecture-blueprint",
|
"change": "full-modernization-architecture-blueprint",
|
||||||
"scope": "in-scope-routes-only",
|
"scope": "all-governed-routes",
|
||||||
"routes": {
|
"routes": {
|
||||||
"/wip-overview": {
|
"/wip-overview": {
|
||||||
"baseline_status": "initialized",
|
"baseline_status": "initialized",
|
||||||
@@ -49,6 +49,22 @@
|
|||||||
"/admin/performance": {
|
"/admin/performance": {
|
||||||
"baseline_status": "initialized",
|
"baseline_status": "initialized",
|
||||||
"known_bugs": []
|
"known_bugs": []
|
||||||
|
},
|
||||||
|
"/tables": {
|
||||||
|
"baseline_status": "initialized",
|
||||||
|
"known_bugs": []
|
||||||
|
},
|
||||||
|
"/excel-query": {
|
||||||
|
"baseline_status": "initialized",
|
||||||
|
"known_bugs": []
|
||||||
|
},
|
||||||
|
"/query-tool": {
|
||||||
|
"baseline_status": "initialized",
|
||||||
|
"known_bugs": []
|
||||||
|
},
|
||||||
|
"/mid-section-defect": {
|
||||||
|
"baseline_status": "initialized",
|
||||||
|
"known_bugs": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"revalidation_rule": {
|
"revalidation_rule": {
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
"/job-query": ["docs/migration/portal-shell-route-view-integration/wave-b-parity-evidence.json"],
|
"/job-query": ["docs/migration/portal-shell-route-view-integration/wave-b-parity-evidence.json"],
|
||||||
"/tmtt-defect": ["docs/migration/portal-shell-route-view-integration/wave-b-parity-evidence.json"],
|
"/tmtt-defect": ["docs/migration/portal-shell-route-view-integration/wave-b-parity-evidence.json"],
|
||||||
"/admin/pages": ["tests/test_portal_shell_routes.py"],
|
"/admin/pages": ["tests/test_portal_shell_routes.py"],
|
||||||
"/admin/performance": ["tests/test_performance_integration.py"]
|
"/admin/performance": ["tests/test_performance_integration.py"],
|
||||||
|
"/tables": ["tests/test_portal_shell_routes.py"],
|
||||||
|
"/excel-query": ["tests/test_portal_shell_routes.py"],
|
||||||
|
"/query-tool": ["tests/test_portal_shell_routes.py"],
|
||||||
|
"/mid-section-defect": ["tests/test_portal_shell_routes.py"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,5 @@
|
|||||||
{"id": "Q4", "name": "performance-budget", "required": true},
|
{"id": "Q4", "name": "performance-budget", "required": true},
|
||||||
{"id": "Q5", "name": "manual-acceptance-and-bug-revalidation", "required": true}
|
{"id": "Q5", "name": "manual-acceptance-and-bug-revalidation", "required": true}
|
||||||
],
|
],
|
||||||
"deferred_routes_excluded": [
|
"deferred_routes_excluded": []
|
||||||
"/tables",
|
|
||||||
"/excel-query",
|
|
||||||
"/query-tool",
|
|
||||||
"/mid-section-defect"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
{
|
{
|
||||||
"mode": "block",
|
"mode": "block",
|
||||||
"errors": [],
|
"errors": [],
|
||||||
"warnings": [],
|
"warnings": [
|
||||||
|
"/excel-query uses shell tokens without fallback ['--portal-brand-end', '--portal-brand-start', '--portal-shadow-panel'] in frontend/src/excel-query/style.css with approved exception",
|
||||||
|
"/query-tool uses shell tokens without fallback ['--portal-brand-end', '--portal-brand-start', '--portal-shadow-panel'] in frontend/src/query-tool/style.css with approved exception"
|
||||||
|
],
|
||||||
"info": [],
|
"info": [],
|
||||||
"passed": true
|
"passed": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,30 @@
|
|||||||
"query_payload_contract": ["performance_summary", "log_stream"],
|
"query_payload_contract": ["performance_summary", "log_stream"],
|
||||||
"chart_data_shape": ["timeline", "status_summary"],
|
"chart_data_shape": ["timeline", "status_summary"],
|
||||||
"state_contract": ["loading", "empty", "error", "success"]
|
"state_contract": ["loading", "empty", "error", "success"]
|
||||||
|
},
|
||||||
|
"/tables": {
|
||||||
|
"filter_input_semantics": ["table_category", "table_name", "dynamic_column_filters"],
|
||||||
|
"query_payload_contract": ["table_name", "limit", "time_field", "filters"],
|
||||||
|
"chart_data_shape": ["dynamic_data_table"],
|
||||||
|
"state_contract": ["loading", "empty", "error", "success"]
|
||||||
|
},
|
||||||
|
"/excel-query": {
|
||||||
|
"filter_input_semantics": ["file_upload", "excel_column", "table_name", "search_column", "query_type", "return_columns", "date_range"],
|
||||||
|
"query_payload_contract": ["table_name", "search_column", "return_columns", "search_values", "query_type", "date_column", "date_from", "date_to"],
|
||||||
|
"chart_data_shape": ["result_table", "csv_export"],
|
||||||
|
"state_contract": ["loading", "empty", "error", "success"]
|
||||||
|
},
|
||||||
|
"/query-tool": {
|
||||||
|
"filter_input_semantics": ["input_type", "workcenter_groups", "input_values", "association_type", "equipment_ids", "equipment_query_type", "start_date", "end_date"],
|
||||||
|
"query_payload_contract": ["input_type", "values", "container_id", "workcenter_groups", "type", "equipment_ids", "equipment_names", "start_date", "end_date", "query_type"],
|
||||||
|
"chart_data_shape": ["resolved_lots_table", "lot_history_table", "association_table", "equipment_table"],
|
||||||
|
"state_contract": ["loading", "empty", "error", "success"]
|
||||||
|
},
|
||||||
|
"/mid-section-defect": {
|
||||||
|
"filter_input_semantics": ["start_date", "end_date", "loss_reasons"],
|
||||||
|
"query_payload_contract": ["start_date", "end_date", "loss_reasons", "page", "page_size"],
|
||||||
|
"chart_data_shape": ["kpi_cards", "pareto_by_station", "pareto_by_loss_reason", "pareto_by_machine", "pareto_by_tmtt_machine", "pareto_by_workflow", "pareto_by_package", "daily_trend", "detail_table"],
|
||||||
|
"state_contract": ["loading", "empty", "error", "success"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,7 +125,7 @@
|
|||||||
{
|
{
|
||||||
"route": "/tables",
|
"route": "/tables",
|
||||||
"route_id": "tables",
|
"route_id": "tables",
|
||||||
"scope": "deferred",
|
"scope": "in-scope",
|
||||||
"render_mode": "native",
|
"render_mode": "native",
|
||||||
"owner": "frontend-mes-reporting",
|
"owner": "frontend-mes-reporting",
|
||||||
"visibility_policy": "released_or_admin",
|
"visibility_policy": "released_or_admin",
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
{
|
{
|
||||||
"route": "/excel-query",
|
"route": "/excel-query",
|
||||||
"route_id": "excel-query",
|
"route_id": "excel-query",
|
||||||
"scope": "deferred",
|
"scope": "in-scope",
|
||||||
"render_mode": "native",
|
"render_mode": "native",
|
||||||
"owner": "frontend-mes-reporting",
|
"owner": "frontend-mes-reporting",
|
||||||
"visibility_policy": "released_or_admin",
|
"visibility_policy": "released_or_admin",
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
{
|
{
|
||||||
"route": "/query-tool",
|
"route": "/query-tool",
|
||||||
"route_id": "query-tool",
|
"route_id": "query-tool",
|
||||||
"scope": "deferred",
|
"scope": "in-scope",
|
||||||
"render_mode": "native",
|
"render_mode": "native",
|
||||||
"owner": "frontend-mes-reporting",
|
"owner": "frontend-mes-reporting",
|
||||||
"visibility_policy": "released_or_admin",
|
"visibility_policy": "released_or_admin",
|
||||||
@@ -155,7 +155,7 @@
|
|||||||
{
|
{
|
||||||
"route": "/mid-section-defect",
|
"route": "/mid-section-defect",
|
||||||
"route_id": "mid-section-defect",
|
"route_id": "mid-section-defect",
|
||||||
"scope": "deferred",
|
"scope": "in-scope",
|
||||||
"render_mode": "native",
|
"render_mode": "native",
|
||||||
"owner": "frontend-mes-reporting",
|
"owner": "frontend-mes-reporting",
|
||||||
"visibility_policy": "released_or_admin",
|
"visibility_policy": "released_or_admin",
|
||||||
|
|||||||
@@ -90,24 +90,43 @@
|
|||||||
"canonical_shell_path": "/portal-shell/admin/performance",
|
"canonical_shell_path": "/portal-shell/admin/performance",
|
||||||
"owner": "frontend-platform-admin",
|
"owner": "frontend-platform-admin",
|
||||||
"visibility_policy": "admin_only"
|
"visibility_policy": "admin_only"
|
||||||
}
|
},
|
||||||
],
|
|
||||||
"deferred": [
|
|
||||||
{
|
{
|
||||||
"route": "/tables",
|
"route": "/tables",
|
||||||
"reason": "deferred-to-follow-up-change"
|
"category": "report",
|
||||||
|
"canonical_shell_path": "/portal-shell/tables",
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"visibility_policy": "released_or_admin",
|
||||||
|
"promoted_from": "deferred",
|
||||||
|
"promoted_by": "deferred-route-modernization-follow-up"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"route": "/excel-query",
|
"route": "/excel-query",
|
||||||
"reason": "deferred-to-follow-up-change"
|
"category": "report",
|
||||||
|
"canonical_shell_path": "/portal-shell/excel-query",
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"visibility_policy": "released_or_admin",
|
||||||
|
"promoted_from": "deferred",
|
||||||
|
"promoted_by": "deferred-route-modernization-follow-up"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"route": "/query-tool",
|
"route": "/query-tool",
|
||||||
"reason": "deferred-to-follow-up-change"
|
"category": "report",
|
||||||
|
"canonical_shell_path": "/portal-shell/query-tool",
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"visibility_policy": "released_or_admin",
|
||||||
|
"promoted_from": "deferred",
|
||||||
|
"promoted_by": "deferred-route-modernization-follow-up"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"route": "/mid-section-defect",
|
"route": "/mid-section-defect",
|
||||||
"reason": "deferred-to-follow-up-change"
|
"category": "report",
|
||||||
|
"canonical_shell_path": "/portal-shell/mid-section-defect",
|
||||||
|
"owner": "frontend-mes-reporting",
|
||||||
|
"visibility_policy": "released_or_admin",
|
||||||
|
"promoted_from": "deferred",
|
||||||
|
"promoted_by": "deferred-route-modernization-follow-up"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"deferred": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,11 @@
|
|||||||
"global_selectors": ["body"],
|
"global_selectors": ["body"],
|
||||||
"status": "exception-registered",
|
"status": "exception-registered",
|
||||||
"exception_id": "style-admin-performance-inline-css"
|
"exception_id": "style-admin-performance-inline-css"
|
||||||
}
|
},
|
||||||
|
"/tables": {"global_selectors": [], "status": "pending-audit"},
|
||||||
|
"/excel-query": {"global_selectors": [], "status": "pending-audit"},
|
||||||
|
"/query-tool": {"global_selectors": [], "status": "pending-audit"},
|
||||||
|
"/mid-section-defect": {"global_selectors": [], "status": "pending-audit"}
|
||||||
},
|
},
|
||||||
"shared_layers": {
|
"shared_layers": {
|
||||||
"frontend/src/styles/tailwind.css": [":root", "body"],
|
"frontend/src/styles/tailwind.css": [":root", "body"],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"generated_at": "2026-02-11T17:49:00+08:00",
|
"generated_at": "2026-02-12T12:40:00+08:00",
|
||||||
"description": "Critical visual-state snapshots for chart/table/matrix routes.",
|
"description": "Critical visual-state snapshots for chart/table/matrix routes.",
|
||||||
"critical_diff_policy": {
|
"critical_diff_policy": {
|
||||||
"block_release": true,
|
"block_release": true,
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"frontend/src/hold-overview/components/HoldMatrix.vue",
|
"frontend/src/hold-overview/components/HoldMatrix.vue",
|
||||||
"frontend/src/hold-overview/style.css"
|
"frontend/src/hold-overview/style.css"
|
||||||
],
|
],
|
||||||
"fingerprint": "5d42352bfb3de23e2ea5638285b69e2fc8adf6f69d61989f0280739b58fedf4d"
|
"fingerprint": "f2ca1666f50afb4f922b522cdf739685ce068911a17d6e6c285244770f451f2c"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "qc-gate-chart-table-linked",
|
"id": "qc-gate-chart-table-linked",
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"frontend/src/qc-gate/components/QcGateChart.vue",
|
"frontend/src/qc-gate/components/QcGateChart.vue",
|
||||||
"frontend/src/qc-gate/style.css"
|
"frontend/src/qc-gate/style.css"
|
||||||
],
|
],
|
||||||
"fingerprint": "2d283febab9142f042a7961aef93201a9d75f43c248cdd40b6b4530101b29619"
|
"fingerprint": "13e000938f5fc398a9abf2c62b3e64dd0c4742ba87b20d144c18befd57e2e1f4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "resource-history-chart-detail",
|
"id": "resource-history-chart-detail",
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"frontend/src/resource-history/components/DetailSection.vue",
|
"frontend/src/resource-history/components/DetailSection.vue",
|
||||||
"frontend/src/resource-history/style.css"
|
"frontend/src/resource-history/style.css"
|
||||||
],
|
],
|
||||||
"fingerprint": "ec5560c3fd233de9d3a31928965e2c71c2e878cb203076e4b45ef149c46a5387"
|
"fingerprint": "385910e89f10f016f7973e97be30a697a396a71ea470c1bcfe028a2c6daa4cc9"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "tmtt-defect-pareto-detail",
|
"id": "tmtt-defect-pareto-detail",
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
"frontend/src/tmtt-defect/components/TmttKpiCards.vue",
|
"frontend/src/tmtt-defect/components/TmttKpiCards.vue",
|
||||||
"frontend/src/tmtt-defect/style.css"
|
"frontend/src/tmtt-defect/style.css"
|
||||||
],
|
],
|
||||||
"fingerprint": "59059868a9f61a20160d2acc8602ee9aa1a494ec0fdb6a816ee98028517451e8"
|
"fingerprint": "141d712008d33887a103a5a5133543d527be208c2a5700d16f4c045ce13bb166"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ onMounted(async () => {
|
|||||||
<div class="excel-query-page u-content-shell">
|
<div class="excel-query-page u-content-shell">
|
||||||
<header class="excel-query-header">
|
<header class="excel-query-header">
|
||||||
<h1>Excel 批次查詢</h1>
|
<h1>Excel 批次查詢</h1>
|
||||||
<p>Native Route-View:Upload / Detect / Query / Export</p>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="u-panel-stack">
|
<div class="u-panel-stack">
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
padding: 22px 24px;
|
padding: 22px 24px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: linear-gradient(135deg, var(--portal-brand-start) 0%, var(--portal-brand-end) 100%);
|
background: linear-gradient(135deg, var(--portal-brand-start, #667eea) 0%, var(--portal-brand-end, #764ba2) 100%);
|
||||||
box-shadow: var(--portal-shadow-panel);
|
box-shadow: var(--portal-shadow-panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@import '../wip-shared/styles.css';
|
@import '../wip-shared/styles.css';
|
||||||
|
|
||||||
.hold-detail-page .header h1 {
|
.hold-detail-page .header h1 {
|
||||||
font-size: 20px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hold-type-badge {
|
.hold-type-badge {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
.hold-history-header {
|
.hold-history-header {
|
||||||
background: linear-gradient(135deg, #0f766e 0%, #0ea5e9 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hold-history-page .header h1 {
|
.hold-history-page .header h1 {
|
||||||
font-size: 22px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hold-type-badge {
|
.hold-type-badge {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.hold-overview-page .header h1 {
|
.hold-overview-page .header h1 {
|
||||||
font-size: 22px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hold-type-badge {
|
.hold-type-badge {
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ onMounted(async () => {
|
|||||||
<div class="job-query-page u-content-shell">
|
<div class="job-query-page u-content-shell">
|
||||||
<header class="job-query-header">
|
<header class="job-query-header">
|
||||||
<h1>設備維修查詢</h1>
|
<h1>設備維修查詢</h1>
|
||||||
<p>Native Route-View:查詢 / 交易歷程 / CSV 匯出</p>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="u-panel-stack">
|
<div class="u-panel-stack">
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
135deg,
|
135deg,
|
||||||
var(--portal-brand-start, #6366f1) 0%,
|
var(--portal-brand-start, #667eea) 0%,
|
||||||
var(--portal-brand-end, #7c3aed) 100%
|
var(--portal-brand-end, #764ba2) 100%
|
||||||
);
|
);
|
||||||
box-shadow: var(--portal-shadow-panel, 0 8px 24px rgba(79, 70, 229, 0.18));
|
box-shadow: var(--portal-shadow-panel, 0 8px 24px rgba(79, 70, 229, 0.18));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ body {
|
|||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
box-shadow: var(--msd-shadow-md);
|
box-shadow: var(--msd-shadow-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ const IN_SCOPE_REPORT_ROUTES = Object.freeze([
|
|||||||
'/qc-gate',
|
'/qc-gate',
|
||||||
'/job-query',
|
'/job-query',
|
||||||
'/tmtt-defect',
|
'/tmtt-defect',
|
||||||
|
'/tables',
|
||||||
|
'/excel-query',
|
||||||
|
'/query-tool',
|
||||||
|
'/mid-section-defect',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const IN_SCOPE_ADMIN_ROUTES = Object.freeze([
|
const IN_SCOPE_ADMIN_ROUTES = Object.freeze([
|
||||||
@@ -16,12 +20,7 @@ const IN_SCOPE_ADMIN_ROUTES = Object.freeze([
|
|||||||
'/admin/performance',
|
'/admin/performance',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const DEFERRED_ROUTES = Object.freeze([
|
const DEFERRED_ROUTES = Object.freeze([]);
|
||||||
'/tables',
|
|
||||||
'/excel-query',
|
|
||||||
'/query-tool',
|
|
||||||
'/mid-section-defect',
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ALL_KNOWN_ROUTES = Object.freeze([
|
const ALL_KNOWN_ROUTES = Object.freeze([
|
||||||
...IN_SCOPE_REPORT_ROUTES,
|
...IN_SCOPE_REPORT_ROUTES,
|
||||||
@@ -195,8 +194,8 @@ const ROUTE_CONTRACTS = Object.freeze({
|
|||||||
title: '表格總覽',
|
title: '表格總覽',
|
||||||
rollbackStrategy: 'fallback_to_legacy_route',
|
rollbackStrategy: 'fallback_to_legacy_route',
|
||||||
visibilityPolicy: 'released_or_admin',
|
visibilityPolicy: 'released_or_admin',
|
||||||
scope: 'deferred',
|
scope: 'in-scope',
|
||||||
compatibilityPolicy: 'legacy_direct_entry_allowed',
|
compatibilityPolicy: 'redirect_to_shell_when_spa_enabled',
|
||||||
}),
|
}),
|
||||||
'/excel-query': buildContract({
|
'/excel-query': buildContract({
|
||||||
route: '/excel-query',
|
route: '/excel-query',
|
||||||
@@ -206,8 +205,8 @@ const ROUTE_CONTRACTS = Object.freeze({
|
|||||||
title: 'Excel 查詢工具',
|
title: 'Excel 查詢工具',
|
||||||
rollbackStrategy: 'fallback_to_legacy_route',
|
rollbackStrategy: 'fallback_to_legacy_route',
|
||||||
visibilityPolicy: 'released_or_admin',
|
visibilityPolicy: 'released_or_admin',
|
||||||
scope: 'deferred',
|
scope: 'in-scope',
|
||||||
compatibilityPolicy: 'legacy_direct_entry_allowed',
|
compatibilityPolicy: 'redirect_to_shell_when_spa_enabled',
|
||||||
}),
|
}),
|
||||||
'/query-tool': buildContract({
|
'/query-tool': buildContract({
|
||||||
route: '/query-tool',
|
route: '/query-tool',
|
||||||
@@ -217,8 +216,8 @@ const ROUTE_CONTRACTS = Object.freeze({
|
|||||||
title: 'Query Tool',
|
title: 'Query Tool',
|
||||||
rollbackStrategy: 'fallback_to_legacy_route',
|
rollbackStrategy: 'fallback_to_legacy_route',
|
||||||
visibilityPolicy: 'released_or_admin',
|
visibilityPolicy: 'released_or_admin',
|
||||||
scope: 'deferred',
|
scope: 'in-scope',
|
||||||
compatibilityPolicy: 'legacy_direct_entry_allowed',
|
compatibilityPolicy: 'redirect_to_shell_when_spa_enabled',
|
||||||
}),
|
}),
|
||||||
'/mid-section-defect': buildContract({
|
'/mid-section-defect': buildContract({
|
||||||
route: '/mid-section-defect',
|
route: '/mid-section-defect',
|
||||||
@@ -228,8 +227,8 @@ const ROUTE_CONTRACTS = Object.freeze({
|
|||||||
title: '中段製程不良追溯',
|
title: '中段製程不良追溯',
|
||||||
rollbackStrategy: 'fallback_to_legacy_route',
|
rollbackStrategy: 'fallback_to_legacy_route',
|
||||||
visibilityPolicy: 'released_or_admin',
|
visibilityPolicy: 'released_or_admin',
|
||||||
scope: 'deferred',
|
scope: 'in-scope',
|
||||||
compatibilityPolicy: 'legacy_direct_entry_allowed',
|
compatibilityPolicy: 'redirect_to_shell_when_spa_enabled',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
--text: #1f2937;
|
--text: #1f2937;
|
||||||
--muted: #64748b;
|
--muted: #64748b;
|
||||||
--border: #dbe3ef;
|
--border: #dbe3ef;
|
||||||
--header-from: var(--portal-brand-start, #6366f1);
|
--header-from: var(--portal-brand-start, #667eea);
|
||||||
--header-to: var(--portal-brand-end, #7c3aed);
|
--header-to: var(--portal-brand-end, #764ba2);
|
||||||
--success: #22c55e;
|
--success: #22c55e;
|
||||||
--warning: #facc15;
|
--warning: #facc15;
|
||||||
--danger: #ef4444;
|
--danger: #ef4444;
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
.qc-gate-header h1 {
|
.qc-gate-header h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 28px;
|
font-size: 24px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ onMounted(async () => {
|
|||||||
<div class="query-tool-page u-content-shell">
|
<div class="query-tool-page u-content-shell">
|
||||||
<header class="query-tool-header">
|
<header class="query-tool-header">
|
||||||
<h1>批次追蹤工具</h1>
|
<h1>批次追蹤工具</h1>
|
||||||
<p>Native Route-View:Resolve / History / Association / Equipment Period</p>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="u-panel-stack">
|
<div class="u-panel-stack">
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
padding: 22px 24px;
|
padding: 22px 24px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: linear-gradient(135deg, var(--portal-brand-start) 0%, var(--portal-brand-end) 100%);
|
background: linear-gradient(135deg, var(--portal-brand-start, #667eea) 0%, var(--portal-brand-end, #764ba2) 100%);
|
||||||
box-shadow: var(--portal-shadow-panel);
|
box-shadow: var(--portal-shadow-panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.history-header {
|
.history-header {
|
||||||
background: linear-gradient(135deg, #4f46e5 0%, #0ea5e9 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-row {
|
.filter-row {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ body {
|
|||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
box-shadow: var(--resource-shadow-md);
|
box-shadow: var(--resource-shadow-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
padding: 24px 28px;
|
padding: 24px 28px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -58,7 +58,7 @@ body {
|
|||||||
|
|
||||||
.header h1 {
|
.header h1 {
|
||||||
margin: 0 0 8px;
|
margin: 0 0 8px;
|
||||||
font-size: 28px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header p {
|
.header p {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
135deg,
|
135deg,
|
||||||
var(--portal-brand-start, #6366f1) 0%,
|
var(--portal-brand-start, #667eea) 0%,
|
||||||
var(--portal-brand-end, #7c3aed) 100%
|
var(--portal-brand-end, #764ba2) 100%
|
||||||
);
|
);
|
||||||
box-shadow: var(--portal-shadow-panel, 0 8px 24px rgba(79, 70, 229, 0.18));
|
box-shadow: var(--portal-shadow-panel, 0 8px 24px rgba(79, 70, 229, 0.18));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ const backendContractPath = path.resolve(
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
test('in-scope route contracts satisfy governance metadata requirements', () => {
|
test('all route contracts satisfy governance metadata requirements', () => {
|
||||||
const errors = validateRouteContractMap({ inScopeOnly: true });
|
const errors = validateRouteContractMap();
|
||||||
assert.deepEqual(errors, []);
|
assert.deepEqual(errors, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -38,14 +38,18 @@ test('admin shell targets are governed and rendered as external targets', () =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('deferred routes stay discoverable but are separable from in-scope gates', () => {
|
test('formerly deferred routes are now promoted to in-scope', () => {
|
||||||
const inScope = new Set(getInScopeRoutes());
|
const inScope = new Set(getInScopeRoutes());
|
||||||
const deferred = getDeferredRoutes();
|
const deferred = getDeferredRoutes();
|
||||||
|
const promotedRoutes = ['/tables', '/excel-query', '/query-tool', '/mid-section-defect'];
|
||||||
|
|
||||||
deferred.forEach((route) => {
|
assert.equal(deferred.length, 0, 'all deferred routes should be promoted');
|
||||||
assert.equal(inScope.has(route), false, `deferred route leaked into in-scope: ${route}`);
|
|
||||||
|
promotedRoutes.forEach((route) => {
|
||||||
|
assert.equal(inScope.has(route), true, `promoted route missing from in-scope: ${route}`);
|
||||||
const contract = getRouteContract(route);
|
const contract = getRouteContract(route);
|
||||||
assert.equal(contract.scope, 'deferred');
|
assert.equal(contract.scope, 'in-scope');
|
||||||
|
assert.equal(contract.compatibilityPolicy, 'redirect_to_shell_when_spa_enabled');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ workers = int(os.getenv("GUNICORN_WORKERS", "2")) # 2 workers for redundancy
|
|||||||
threads = int(os.getenv("GUNICORN_THREADS", "4"))
|
threads = int(os.getenv("GUNICORN_THREADS", "4"))
|
||||||
worker_class = "gthread"
|
worker_class = "gthread"
|
||||||
|
|
||||||
# Timeout settings - critical for dashboard stability
|
# Timeout settings - critical for dashboard stability.
|
||||||
timeout = 65 # Worker timeout: must be > call_timeout (55s)
|
# Keep this above slow-query timeout paths (e.g. query-tool 120s) and DB pool timeout.
|
||||||
graceful_timeout = 30 # Graceful shutdown timeout (enough for thread cleanup)
|
timeout = int(os.getenv("GUNICORN_TIMEOUT", "130"))
|
||||||
|
graceful_timeout = int(os.getenv("GUNICORN_GRACEFUL_TIMEOUT", "60"))
|
||||||
keepalive = 5 # Keep-alive connections timeout
|
keepalive = 5 # Keep-alive connections timeout
|
||||||
|
|
||||||
# Worker lifecycle management - prevent state accumulation
|
# Worker lifecycle management - prevent state accumulation.
|
||||||
max_requests = 1000 # Restart worker after N requests
|
# Make these configurable so high-load test environments can raise the ceiling.
|
||||||
max_requests_jitter = 100 # Random jitter to prevent simultaneous restarts
|
max_requests = int(os.getenv("GUNICORN_MAX_REQUESTS", "5000"))
|
||||||
|
max_requests_jitter = int(os.getenv("GUNICORN_MAX_REQUESTS_JITTER", "500"))
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
@@ -15,11 +15,13 @@ Current deferred-route risks:
|
|||||||
- Adopt canonical shell routing and explicit compatibility policy.
|
- Adopt canonical shell routing and explicit compatibility policy.
|
||||||
- Execute contract-first content modernization with parity and rollback controls.
|
- Execute contract-first content modernization with parity and rollback controls.
|
||||||
- Enforce mandatory manual acceptance and BUG revalidation before sign-off.
|
- Enforce mandatory manual acceptance and BUG revalidation before sign-off.
|
||||||
|
- Enforce mandatory pre-change confirmation before each deferred-route implementation starts.
|
||||||
|
|
||||||
**Non-Goals**
|
**Non-Goals**
|
||||||
- Reworking already in-scope phase-1 routes again.
|
- Reworking already in-scope phase-1 routes again.
|
||||||
- Changing backend business data semantics beyond compatibility safeguards.
|
- Changing backend business data semantics beyond compatibility safeguards.
|
||||||
- Bundling unrelated admin/report features into this follow-up.
|
- Bundling unrelated admin/report features into this follow-up.
|
||||||
|
- Restricting this change to already-`released` routes only (deferred routes are the intended modernization scope even when currently marked `dev`).
|
||||||
|
|
||||||
## Decisions
|
## Decisions
|
||||||
|
|
||||||
@@ -38,6 +40,11 @@ Current deferred-route risks:
|
|||||||
- Known bug baseline must be recorded before implementation.
|
- Known bug baseline must be recorded before implementation.
|
||||||
- Reproduced known bugs on modernized path block sign-off and legacy retirement.
|
- Reproduced known bugs on modernized path block sign-off and legacy retirement.
|
||||||
|
|
||||||
|
### D5. Pre-change confirmation is required per route
|
||||||
|
- Before changing `/tables`, `/excel-query`, `/query-tool`, or `/mid-section-defect`, maintainers must record route-scoped pre-change confirmation.
|
||||||
|
- Pre-change confirmation must include: current route status snapshot, scope boundary check, baseline contract/bug references, and planned rollback flag.
|
||||||
|
- Implementation work for a route is blocked until its pre-change confirmation is recorded.
|
||||||
|
|
||||||
## Risks / Trade-offs
|
## Risks / Trade-offs
|
||||||
|
|
||||||
- Deferred routes may have heavier legacy coupling than phase-1 routes.
|
- Deferred routes may have heavier legacy coupling than phase-1 routes.
|
||||||
@@ -48,10 +55,11 @@ Current deferred-route risks:
|
|||||||
|
|
||||||
1. Freeze deferred-route scope matrix for this follow-up change.
|
1. Freeze deferred-route scope matrix for this follow-up change.
|
||||||
2. Extend shell route contracts and metadata coverage to full completeness.
|
2. Extend shell route contracts and metadata coverage to full completeness.
|
||||||
3. Define route content contracts + golden fixtures + interaction parity checks.
|
3. Record pre-change confirmation for the first deferred route to be modernized.
|
||||||
4. Execute per-route migration with feature-flagged cutover and manual acceptance.
|
4. Define route content contracts + golden fixtures + interaction parity checks.
|
||||||
5. Retire deferred-route runtime fallback posture after acceptance and readiness gates pass.
|
5. Execute per-route migration with feature-flagged cutover and manual acceptance.
|
||||||
6. Update runbook/rollback docs and close handoff linkage.
|
6. Retire deferred-route runtime fallback posture after acceptance and readiness gates pass.
|
||||||
|
7. Update runbook/rollback docs and close handoff linkage.
|
||||||
|
|
||||||
## Rollback Strategy
|
## Rollback Strategy
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ Those routes still run on legacy posture (direct-entry-first + fallback continui
|
|||||||
- Promote all deferred routes to first-class in-scope shell-governed targets.
|
- Promote all deferred routes to first-class in-scope shell-governed targets.
|
||||||
- Apply canonical shell routing policy and explicit direct-entry compatibility behavior for each deferred route.
|
- Apply canonical shell routing policy and explicit direct-entry compatibility behavior for each deferred route.
|
||||||
- Modernize deferred route page-content flow (filters/charts/interactions) with contract-first parity gates.
|
- Modernize deferred route page-content flow (filters/charts/interactions) with contract-first parity gates.
|
||||||
|
- Require route-by-route pre-change confirmation records before any implementation work starts on each deferred route.
|
||||||
- Apply the same mandatory manual acceptance + BUG revalidation blocking policy used in phase 1.
|
- Apply the same mandatory manual acceptance + BUG revalidation blocking policy used in phase 1.
|
||||||
- Move deferred routes from fallback-era runtime posture to asset-readiness + governed retirement posture.
|
- Move deferred routes from fallback-era runtime posture to asset-readiness + governed retirement posture.
|
||||||
|
|
||||||
@@ -31,3 +32,4 @@ Those routes still run on legacy posture (direct-entry-first + fallback continui
|
|||||||
- Shell contract and navigation governance in `frontend/src/portal-shell/**`.
|
- Shell contract and navigation governance in `frontend/src/portal-shell/**`.
|
||||||
- Backend route handlers serving deferred routes and compatibility behavior.
|
- Backend route handlers serving deferred routes and compatibility behavior.
|
||||||
- Quality gate artifacts, runbook updates, and rollout/rollback policy for deferred-route cutover.
|
- Quality gate artifacts, runbook updates, and rollout/rollback policy for deferred-route cutover.
|
||||||
|
- Scope boundary clarification: this follow-up explicitly targets deferred routes (currently `dev` in page status) and does not require routes to already be `released` before modernization.
|
||||||
@@ -7,6 +7,22 @@ Before deferred routes are cut over to modernized implementations, each route SH
|
|||||||
- **WHEN** `/tables`, `/excel-query`, `/query-tool`, or `/mid-section-defect` enters modernization
|
- **WHEN** `/tables`, `/excel-query`, `/query-tool`, or `/mid-section-defect` enters modernization
|
||||||
- **THEN** a route-level baseline SHALL capture filter input semantics, query payload shape, and critical state expectations
|
- **THEN** a route-level baseline SHALL capture filter input semantics, query payload shape, and critical state expectations
|
||||||
|
|
||||||
|
### Requirement: Deferred-route implementation SHALL require pre-change confirmation
|
||||||
|
Each deferred route SHALL complete a route-scoped pre-change confirmation before implementation begins.
|
||||||
|
|
||||||
|
#### Scenario: Route enters implementation queue
|
||||||
|
- **WHEN** a deferred route is selected for modernization implementation
|
||||||
|
- **THEN** a pre-change confirmation record SHALL exist before any route code changes proceed
|
||||||
|
- **THEN** the record SHALL include current route status snapshot, baseline contract references, known-bug baseline reference, and rollback flag plan
|
||||||
|
|
||||||
|
### Requirement: Deferred-route modernization scope SHALL NOT be limited to already-released routes
|
||||||
|
Deferred modernization scope SHALL follow the deferred route matrix, even if those routes are currently marked `dev`.
|
||||||
|
|
||||||
|
#### Scenario: Route status is dev in page registry
|
||||||
|
- **WHEN** `/tables`, `/excel-query`, `/query-tool`, or `/mid-section-defect` is currently `dev`
|
||||||
|
- **THEN** the route SHALL remain eligible for modernization in this follow-up change
|
||||||
|
- **THEN** already-`released` in-scope routes outside deferred scope SHALL not be reopened by this change unless explicitly required for shared governance wiring
|
||||||
|
|
||||||
### Requirement: Deferred-route cutover SHALL require parity + manual acceptance
|
### Requirement: Deferred-route cutover SHALL require parity + manual acceptance
|
||||||
Deferred routes SHALL NOT complete cutover without parity evidence and explicit manual sign-off.
|
Deferred routes SHALL NOT complete cutover without parity evidence and explicit manual sign-off.
|
||||||
|
|
||||||
@@ -23,4 +39,3 @@ Known legacy bugs in deferred-route migrated scope SHALL be replayed during acce
|
|||||||
- **WHEN** deferred-route manual acceptance executes
|
- **WHEN** deferred-route manual acceptance executes
|
||||||
- **THEN** known-bug replay checks SHALL run
|
- **THEN** known-bug replay checks SHALL run
|
||||||
- **THEN** reproduced known bugs SHALL fail route sign-off and block legacy retirement
|
- **THEN** reproduced known bugs SHALL fail route sign-off and block legacy retirement
|
||||||
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
## 1. Scope and Governance Freeze
|
||||||
|
|
||||||
|
- [x] 1.1 Publish frozen in-scope matrix for `/tables`, `/excel-query`, `/query-tool`, `/mid-section-defect`
|
||||||
|
- [x] 1.2 Define completion criteria, deprecation milestones, and exception registry updates for deferred-route phase
|
||||||
|
- [x] 1.3 Record explicit upstream linkage to `full-modernization-architecture-blueprint` handoff artifacts
|
||||||
|
- [x] 1.4 Add deferred-route pre-change confirmation template and required fields (status snapshot, contract baseline refs, known-bug baseline ref, rollback flag plan)
|
||||||
|
- [x] 1.5 Record scope boundary note that deferred/dev routes are in-scope for this change and released-only restriction does not apply
|
||||||
|
|
||||||
|
## 2. Shell Route Contract Completion
|
||||||
|
|
||||||
|
- [x] 2.1 Promote deferred routes to in-scope in shell route contracts with complete metadata
|
||||||
|
- [x] 2.2 Implement governed navigation targets and visibility policy validation for all deferred routes
|
||||||
|
- [x] 2.3 Add CI-blocking checks for missing deferred-route contract metadata in this phase
|
||||||
|
|
||||||
|
## 3. Canonical Routing and Compatibility
|
||||||
|
|
||||||
|
- [x] 3.1 Define canonical shell entry behavior for each deferred route
|
||||||
|
- [x] 3.2 Implement explicit compatibility policy for direct non-canonical entry with query continuity
|
||||||
|
- [x] 3.3 Add integration tests for canonical redirect and compatibility semantics
|
||||||
|
|
||||||
|
## 4. Page-Content Modernization Safety
|
||||||
|
|
||||||
|
- [x] 4.0 Before each route implementation starts, record and approve route-scoped pre-change confirmation
|
||||||
|
- [x] 4.1 Define per-route content contracts (filter semantics, payload, chart/table shape, state transitions)
|
||||||
|
- [x] 4.2 Build golden fixtures and interaction parity checks for each deferred route
|
||||||
|
- [x] 4.3 Add route-scoped feature flags and rollback controls for deferred-route cutover
|
||||||
|
- [x] 4.4 Define and enforce per-route manual acceptance checklist and sign-off records
|
||||||
|
- [x] 4.5 Record known-bug baselines before implementation and require bug replay during acceptance
|
||||||
|
- [x] 4.6 Block sign-off and legacy retirement when known bugs reproduce on modernized routes
|
||||||
|
|
||||||
|
## 5. Asset Readiness and Fallback Retirement
|
||||||
|
|
||||||
|
- [x] 5.1 Extend asset-readiness manifest/checks to deferred routes
|
||||||
|
- [x] 5.2 Enforce fail-fast release behavior when deferred-route assets are missing
|
||||||
|
- [x] 5.3 Retire deferred-route runtime fallback posture per governance milestones
|
||||||
|
|
||||||
|
## 6. Quality Gates, CI, and Rollout
|
||||||
|
|
||||||
|
- [x] 6.1 Extend functional/visual/accessibility/performance gates to deferred routes
|
||||||
|
- [x] 6.2 Wire CI jobs for route governance, quality gates, and readiness checks for deferred scope
|
||||||
|
- [x] 6.3 Update rollout runbook, rollback controls, and observability checkpoints for deferred-route cutover
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
## 1. Scope and Governance Freeze
|
|
||||||
|
|
||||||
- [ ] 1.1 Publish frozen in-scope matrix for `/tables`, `/excel-query`, `/query-tool`, `/mid-section-defect`
|
|
||||||
- [ ] 1.2 Define completion criteria, deprecation milestones, and exception registry updates for deferred-route phase
|
|
||||||
- [ ] 1.3 Record explicit upstream linkage to `full-modernization-architecture-blueprint` handoff artifacts
|
|
||||||
|
|
||||||
## 2. Shell Route Contract Completion
|
|
||||||
|
|
||||||
- [ ] 2.1 Promote deferred routes to in-scope in shell route contracts with complete metadata
|
|
||||||
- [ ] 2.2 Implement governed navigation targets and visibility policy validation for all deferred routes
|
|
||||||
- [ ] 2.3 Add CI-blocking checks for missing deferred-route contract metadata in this phase
|
|
||||||
|
|
||||||
## 3. Canonical Routing and Compatibility
|
|
||||||
|
|
||||||
- [ ] 3.1 Define canonical shell entry behavior for each deferred route
|
|
||||||
- [ ] 3.2 Implement explicit compatibility policy for direct non-canonical entry with query continuity
|
|
||||||
- [ ] 3.3 Add integration tests for canonical redirect and compatibility semantics
|
|
||||||
|
|
||||||
## 4. Page-Content Modernization Safety
|
|
||||||
|
|
||||||
- [ ] 4.1 Define per-route content contracts (filter semantics, payload, chart/table shape, state transitions)
|
|
||||||
- [ ] 4.2 Build golden fixtures and interaction parity checks for each deferred route
|
|
||||||
- [ ] 4.3 Add route-scoped feature flags and rollback controls for deferred-route cutover
|
|
||||||
- [ ] 4.4 Define and enforce per-route manual acceptance checklist and sign-off records
|
|
||||||
- [ ] 4.5 Record known-bug baselines before implementation and require bug replay during acceptance
|
|
||||||
- [ ] 4.6 Block sign-off and legacy retirement when known bugs reproduce on modernized routes
|
|
||||||
|
|
||||||
## 5. Asset Readiness and Fallback Retirement
|
|
||||||
|
|
||||||
- [ ] 5.1 Extend asset-readiness manifest/checks to deferred routes
|
|
||||||
- [ ] 5.2 Enforce fail-fast release behavior when deferred-route assets are missing
|
|
||||||
- [ ] 5.3 Retire deferred-route runtime fallback posture per governance milestones
|
|
||||||
|
|
||||||
## 6. Quality Gates, CI, and Rollout
|
|
||||||
|
|
||||||
- [ ] 6.1 Extend functional/visual/accessibility/performance gates to deferred routes
|
|
||||||
- [ ] 6.2 Wire CI jobs for route governance, quality gates, and readiness checks for deferred scope
|
|
||||||
- [ ] 6.3 Update rollout runbook, rollback controls, and observability checkpoints for deferred-route cutover
|
|
||||||
@@ -19,13 +19,21 @@ Runtime fallback behavior for in-scope modernization routes SHALL be retired und
|
|||||||
- **THEN** runtime fallback behavior for that route SHALL be removed or disabled by policy
|
- **THEN** runtime fallback behavior for that route SHALL be removed or disabled by policy
|
||||||
- **THEN** reliability for that route SHALL be guaranteed by release-time readiness gates
|
- **THEN** reliability for that route SHALL be guaranteed by release-time readiness gates
|
||||||
|
|
||||||
### Requirement: Deferred routes SHALL keep existing fallback posture in this phase
|
### Requirement: Deferred-route assets SHALL be release-ready before promotion
|
||||||
Routes deferred from this modernization phase SHALL retain their existing fallback posture until handled by a follow-up change.
|
Deferred follow-up routes SHALL adopt release-time asset-readiness checks and SHALL fail promotion when required assets are missing.
|
||||||
|
|
||||||
#### Scenario: Deferred fallback continuity
|
#### Scenario: Deferred-route readiness validation
|
||||||
- **WHEN** `/tables`, `/excel-query`, `/query-tool`, or `/mid-section-defect` is evaluated in this phase
|
- **WHEN** release artifacts are prepared for deferred-route promotion
|
||||||
- **THEN** fallback retirement SHALL NOT be required for phase completion
|
- **THEN** required assets for `/tables`, `/excel-query`, `/query-tool`, and `/mid-section-defect` SHALL be validated
|
||||||
- **THEN** fallback retirement decisions for those routes SHALL be addressed in a follow-up modernization change
|
- **THEN** missing required assets SHALL fail release gating
|
||||||
|
|
||||||
|
### Requirement: Deferred-route runtime fallback SHALL be retired by governed policy
|
||||||
|
Deferred follow-up routes SHALL not remain on runtime fallback posture after readiness, parity, and manual acceptance gates pass.
|
||||||
|
|
||||||
|
#### Scenario: Deferred-route fallback retirement
|
||||||
|
- **WHEN** a deferred route passes readiness, parity, and manual acceptance gates
|
||||||
|
- **THEN** runtime fallback posture for that route SHALL be retired according to milestone policy
|
||||||
|
- **THEN** rollback control SHALL remain available via explicit route-level governance switch
|
||||||
|
|
||||||
### Requirement: Fallback-retirement failure response SHALL be consistent across route hosts
|
### Requirement: Fallback-retirement failure response SHALL be consistent across route hosts
|
||||||
When in-scope runtime fallback retirement is enabled and route assets are unavailable, app-level and blueprint-level route handlers SHALL return a consistent retired-fallback response surface.
|
When in-scope runtime fallback retirement is enabled and route assets are unavailable, app-level and blueprint-level route handlers SHALL return a consistent retired-fallback response surface.
|
||||||
@@ -37,4 +45,3 @@ When in-scope runtime fallback retirement is enabled and route assets are unavai
|
|||||||
#### Scenario: Blueprint-level in-scope route enters retired fallback state
|
#### Scenario: Blueprint-level in-scope route enters retired fallback state
|
||||||
- **WHEN** an in-scope blueprint-level route cannot serve required dist assets and fallback retirement is enabled
|
- **WHEN** an in-scope blueprint-level route cannot serve required dist assets and fallback retirement is enabled
|
||||||
- **THEN** the route SHALL return the same standardized retired-fallback response contract used by app-level routes
|
- **THEN** the route SHALL return the same standardized retired-fallback response contract used by app-level routes
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ Before chart/filter/page interaction refactors are cut over, each in-scope route
|
|||||||
- **THEN** the route SHALL define filter input semantics, query payload expectations, and chart data-shape contracts
|
- **THEN** the route SHALL define filter input semantics, query payload expectations, and chart data-shape contracts
|
||||||
- **THEN** the route SHALL define critical state expectations for loading, empty, error, and success interactions
|
- **THEN** the route SHALL define critical state expectations for loading, empty, error, and success interactions
|
||||||
|
|
||||||
|
#### Scenario: Deferred-route contract baseline defined
|
||||||
|
- **WHEN** `/tables`, `/excel-query`, `/query-tool`, or `/mid-section-defect` enters modernization
|
||||||
|
- **THEN** a route-level baseline SHALL capture filter input semantics, query payload shape, and critical state expectations
|
||||||
|
|
||||||
### Requirement: Cutover SHALL require parity evidence against baseline behavior
|
### Requirement: Cutover SHALL require parity evidence against baseline behavior
|
||||||
In-scope chart/filter modernization cutover SHALL require parity evidence against baseline fixtures and critical interaction flows.
|
In-scope chart/filter modernization cutover SHALL require parity evidence against baseline fixtures and critical interaction flows.
|
||||||
|
|
||||||
@@ -35,6 +39,22 @@ In-scope chart/filter/page-content migration SHALL progress one route at a time
|
|||||||
- **THEN** that route SHALL be manually accepted using a defined checklist covering filter flows, chart interactions, empty/error behavior, and visual correctness
|
- **THEN** that route SHALL be manually accepted using a defined checklist covering filter flows, chart interactions, empty/error behavior, and visual correctness
|
||||||
- **THEN** the next route SHALL NOT begin cutover until manual acceptance for the current route is signed off
|
- **THEN** the next route SHALL NOT begin cutover until manual acceptance for the current route is signed off
|
||||||
|
|
||||||
|
### Requirement: Deferred-route implementation SHALL require pre-change confirmation
|
||||||
|
Each deferred route SHALL complete a route-scoped pre-change confirmation before implementation begins.
|
||||||
|
|
||||||
|
#### Scenario: Route enters implementation queue
|
||||||
|
- **WHEN** a deferred route is selected for modernization implementation
|
||||||
|
- **THEN** a pre-change confirmation record SHALL exist before any route code changes proceed
|
||||||
|
- **THEN** the record SHALL include current route status snapshot, baseline contract references, known-bug baseline reference, and rollback flag plan
|
||||||
|
|
||||||
|
### Requirement: Deferred-route modernization scope SHALL NOT be limited to already-released routes
|
||||||
|
Deferred modernization scope SHALL follow the deferred route matrix, even if those routes are currently marked `dev`.
|
||||||
|
|
||||||
|
#### Scenario: Route status is dev in page registry
|
||||||
|
- **WHEN** `/tables`, `/excel-query`, `/query-tool`, or `/mid-section-defect` is currently `dev`
|
||||||
|
- **THEN** the route SHALL remain eligible for modernization in this follow-up change
|
||||||
|
- **THEN** already-`released` in-scope routes outside deferred scope SHALL not be reopened by this change unless explicitly required for shared governance wiring
|
||||||
|
|
||||||
### Requirement: Known legacy bugs in migrated scope SHALL NOT be carried into modernized routes
|
### Requirement: Known legacy bugs in migrated scope SHALL NOT be carried into modernized routes
|
||||||
Modernized route acceptance SHALL include explicit revalidation of known legacy defects in migrated scope, and reproduced defects SHALL block sign-off.
|
Modernized route acceptance SHALL include explicit revalidation of known legacy defects in migrated scope, and reproduced defects SHALL block sign-off.
|
||||||
|
|
||||||
@@ -43,6 +63,11 @@ Modernized route acceptance SHALL include explicit revalidation of known legacy
|
|||||||
- **THEN** a route-level known-bug baseline (within migrated scope) SHALL be recorded before implementation
|
- **THEN** a route-level known-bug baseline (within migrated scope) SHALL be recorded before implementation
|
||||||
- **THEN** manual acceptance SHALL replay those known-bug checks on the modernized route
|
- **THEN** manual acceptance SHALL replay those known-bug checks on the modernized route
|
||||||
|
|
||||||
|
#### Scenario: Deferred-route bug replay gate
|
||||||
|
- **WHEN** deferred-route manual acceptance executes
|
||||||
|
- **THEN** known-bug replay checks SHALL run
|
||||||
|
- **THEN** reproduced known bugs SHALL fail route sign-off and block legacy retirement
|
||||||
|
|
||||||
#### Scenario: Legacy bug carry-over is blocked
|
#### Scenario: Legacy bug carry-over is blocked
|
||||||
- **WHEN** manual acceptance finds that a known legacy bug is still reproducible in the modernized route
|
- **WHEN** manual acceptance finds that a known legacy bug is still reproducible in the modernized route
|
||||||
- **THEN** route sign-off SHALL fail
|
- **THEN** route sign-off SHALL fail
|
||||||
@@ -55,4 +80,3 @@ Legacy chart/filter implementations SHALL be removed only after parity checks an
|
|||||||
- **WHEN** legacy chart/filter code is planned for removal on an in-scope route
|
- **WHEN** legacy chart/filter code is planned for removal on an in-scope route
|
||||||
- **THEN** the route SHALL provide parity pass evidence and manual acceptance sign-off records
|
- **THEN** the route SHALL provide parity pass evidence and manual acceptance sign-off records
|
||||||
- **THEN** unresolved parity failures or manual acceptance defects SHALL block legacy removal
|
- **THEN** unresolved parity failures or manual acceptance defects SHALL block legacy removal
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
TBD - created by archiving change full-modernization-architecture-blueprint. Update Purpose after archive.
|
TBD - created by archiving change full-modernization-architecture-blueprint. Update Purpose after archive.
|
||||||
## Requirements
|
## Requirements
|
||||||
### Requirement: In-scope routes SHALL be shell-contract governed
|
### Requirement: In-scope routes SHALL be shell-contract governed
|
||||||
All in-scope modernization routes SHALL be represented in shell route contracts, loader registration policy, and navigation visibility governance.
|
All in-scope modernization routes, including deferred follow-up routes, SHALL be represented in shell route contracts, loader registration policy, and navigation visibility governance with complete metadata.
|
||||||
|
|
||||||
#### Scenario: In-scope coverage validation
|
#### Scenario: In-scope coverage validation
|
||||||
- **WHEN** shell route contract validation is executed
|
- **WHEN** shell route contract validation is executed
|
||||||
@@ -15,13 +15,10 @@ All in-scope modernization routes SHALL be represented in shell route contracts,
|
|||||||
- **WHEN** shell navigation is built for admin users
|
- **WHEN** shell navigation is built for admin users
|
||||||
- **THEN** `/admin/pages` and `/admin/performance` SHALL be represented as governed navigation targets according to visibility/access policy
|
- **THEN** `/admin/pages` and `/admin/performance` SHALL be represented as governed navigation targets according to visibility/access policy
|
||||||
|
|
||||||
### Requirement: Out-of-scope routes SHALL not block this phase
|
#### Scenario: Deferred route contract promotion
|
||||||
Routes explicitly marked as out-of-scope for this modernization phase SHALL be excluded from required shell-coverage gates in this phase.
|
- **WHEN** follow-up route coverage validation is executed
|
||||||
|
- **THEN** `/tables`, `/excel-query`, `/query-tool`, and `/mid-section-defect` SHALL have route metadata, owner metadata, and visibility/access policy metadata
|
||||||
#### Scenario: Deferred route exclusion
|
- **THEN** missing metadata on those deferred routes SHALL fail route-governance validation
|
||||||
- **WHEN** modernization gates execute for this phase
|
|
||||||
- **THEN** `/tables`, `/excel-query`, `/query-tool`, and `/mid-section-defect` SHALL be treated as deferred routes
|
|
||||||
- **THEN** deferred route absence from new shell-governance gates SHALL NOT fail this phase
|
|
||||||
|
|
||||||
### Requirement: Route coverage governance SHALL be CI-enforced
|
### Requirement: Route coverage governance SHALL be CI-enforced
|
||||||
Route coverage and contract completeness checks for in-scope routes SHALL run as CI gates.
|
Route coverage and contract completeness checks for in-scope routes SHALL run as CI gates.
|
||||||
@@ -48,4 +45,3 @@ When contract loading falls back from the primary modernization contract artifac
|
|||||||
#### Scenario: Legacy contract fallback path selected
|
#### Scenario: Legacy contract fallback path selected
|
||||||
- **WHEN** the primary contract artifact is unavailable and a legacy contract file is loaded
|
- **WHEN** the primary contract artifact is unavailable and a legacy contract file is loaded
|
||||||
- **THEN** the system SHALL log a warning that includes the selected legacy source path
|
- **THEN** the system SHALL log a warning that includes the selected legacy source path
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,10 @@ def _route_css_targets() -> dict[str, list[Path]]:
|
|||||||
"/tmtt-defect": [ROOT / "frontend/src/tmtt-defect/style.css"],
|
"/tmtt-defect": [ROOT / "frontend/src/tmtt-defect/style.css"],
|
||||||
"/admin/pages": [ROOT / "src/mes_dashboard/templates/admin/pages.html"],
|
"/admin/pages": [ROOT / "src/mes_dashboard/templates/admin/pages.html"],
|
||||||
"/admin/performance": [ROOT / "src/mes_dashboard/templates/admin/performance.html"],
|
"/admin/performance": [ROOT / "src/mes_dashboard/templates/admin/performance.html"],
|
||||||
|
"/tables": [ROOT / "frontend/src/tables/style.css"],
|
||||||
|
"/excel-query": [ROOT / "frontend/src/excel-query/style.css"],
|
||||||
|
"/query-tool": [ROOT / "frontend/src/query-tool/style.css"],
|
||||||
|
"/mid-section-defect": [ROOT / "frontend/src/mid-section-defect/style.css"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -129,9 +133,9 @@ def _check_scope_matrix(scope_matrix: dict[str, Any], report: CheckReport) -> tu
|
|||||||
report.fail("scope matrix has no in-scope routes")
|
report.fail("scope matrix has no in-scope routes")
|
||||||
if "/admin/pages" not in in_scope or "/admin/performance" not in in_scope:
|
if "/admin/pages" not in in_scope or "/admin/performance" not in in_scope:
|
||||||
report.fail("scope matrix must include /admin/pages and /admin/performance")
|
report.fail("scope matrix must include /admin/pages and /admin/performance")
|
||||||
required_deferred = {"/tables", "/excel-query", "/query-tool", "/mid-section-defect"}
|
required_deferred: set[str] = set()
|
||||||
if deferred != required_deferred:
|
if deferred != required_deferred:
|
||||||
report.fail("scope matrix deferred routes mismatch expected policy")
|
report.fail("scope matrix deferred routes mismatch expected policy (all routes promoted to in-scope)")
|
||||||
return in_scope, deferred
|
return in_scope, deferred
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -676,6 +676,10 @@ def create_app(config_name: str | None = None) -> Flask:
|
|||||||
@app.route('/tables')
|
@app.route('/tables')
|
||||||
def tables_page():
|
def tables_page():
|
||||||
"""Table viewer page served as pure Vite HTML output."""
|
"""Table viewer page served as pure Vite HTML output."""
|
||||||
|
canonical_redirect = maybe_redirect_to_canonical_shell('/tables')
|
||||||
|
if canonical_redirect is not None:
|
||||||
|
return canonical_redirect
|
||||||
|
|
||||||
dist_dir = os.path.join(app.static_folder or "", "dist")
|
dist_dir = os.path.join(app.static_folder or "", "dist")
|
||||||
dist_html = os.path.join(dist_dir, "tables.html")
|
dist_html = os.path.join(dist_dir, "tables.html")
|
||||||
if os.path.exists(dist_html):
|
if os.path.exists(dist_html):
|
||||||
@@ -687,14 +691,14 @@ def create_app(config_name: str | None = None) -> Flask:
|
|||||||
return send_from_directory(nested_dist_dir, "index.html")
|
return send_from_directory(nested_dist_dir, "index.html")
|
||||||
|
|
||||||
# Test/local fallback when frontend build artifacts are absent.
|
# Test/local fallback when frontend build artifacts are absent.
|
||||||
return (
|
return missing_in_scope_asset_response('/tables', (
|
||||||
"<!doctype html><html lang=\"zh-Hant\"><head><meta charset=\"UTF-8\">"
|
"<!doctype html><html lang=\"zh-Hant\"><head><meta charset=\"UTF-8\">"
|
||||||
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
|
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
|
||||||
"<title>MES 數據表查詢工具</title>"
|
"<title>MES 數據表查詢工具</title>"
|
||||||
"<script type=\"module\" src=\"/static/dist/tables.js\"></script>"
|
"<script type=\"module\" src=\"/static/dist/tables.js\"></script>"
|
||||||
"</head><body><div id='app'></div></body></html>",
|
"</head><body><div id='app'></div></body></html>",
|
||||||
200,
|
200,
|
||||||
)
|
))
|
||||||
|
|
||||||
@app.route('/wip-overview')
|
@app.route('/wip-overview')
|
||||||
def wip_overview_page():
|
def wip_overview_page():
|
||||||
@@ -765,6 +769,9 @@ def create_app(config_name: str | None = None) -> Flask:
|
|||||||
@app.route('/excel-query')
|
@app.route('/excel-query')
|
||||||
def excel_query_page():
|
def excel_query_page():
|
||||||
"""Excel batch query tool page."""
|
"""Excel batch query tool page."""
|
||||||
|
canonical_redirect = maybe_redirect_to_canonical_shell('/excel-query')
|
||||||
|
if canonical_redirect is not None:
|
||||||
|
return canonical_redirect
|
||||||
return render_template('excel_query.html')
|
return render_template('excel_query.html')
|
||||||
|
|
||||||
@app.route('/resource-history')
|
@app.route('/resource-history')
|
||||||
@@ -821,8 +828,23 @@ def create_app(config_name: str | None = None) -> Flask:
|
|||||||
@app.route('/mid-section-defect')
|
@app.route('/mid-section-defect')
|
||||||
def mid_section_defect_page():
|
def mid_section_defect_page():
|
||||||
"""Mid-section defect traceability analysis page (pure Vite)."""
|
"""Mid-section defect traceability analysis page (pure Vite)."""
|
||||||
|
canonical_redirect = maybe_redirect_to_canonical_shell('/mid-section-defect')
|
||||||
|
if canonical_redirect is not None:
|
||||||
|
return canonical_redirect
|
||||||
|
|
||||||
dist_dir = os.path.join(app.static_folder or "", "dist")
|
dist_dir = os.path.join(app.static_folder or "", "dist")
|
||||||
return send_from_directory(dist_dir, 'mid-section-defect.html')
|
dist_html = os.path.join(dist_dir, "mid-section-defect.html")
|
||||||
|
if os.path.exists(dist_html):
|
||||||
|
return send_from_directory(dist_dir, 'mid-section-defect.html')
|
||||||
|
|
||||||
|
return missing_in_scope_asset_response('/mid-section-defect', (
|
||||||
|
"<!doctype html><html lang=\"zh-Hant\"><head><meta charset=\"UTF-8\">"
|
||||||
|
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
|
||||||
|
"<title>中段製程不良追溯</title>"
|
||||||
|
"<script type=\"module\" src=\"/static/dist/mid-section-defect.js\"></script>"
|
||||||
|
"</head><body><div id='app'></div></body></html>",
|
||||||
|
200,
|
||||||
|
))
|
||||||
|
|
||||||
# ========================================================
|
# ========================================================
|
||||||
# Table Query APIs (for table_data_viewer)
|
# Table Query APIs (for table_data_viewer)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Contains Flask Blueprint for batch tracing and equipment period query endpoints:
|
|||||||
|
|
||||||
from flask import Blueprint, jsonify, request, Response, render_template
|
from flask import Blueprint, jsonify, request, Response, render_template
|
||||||
|
|
||||||
|
from mes_dashboard.core.modernization_policy import maybe_redirect_to_canonical_shell
|
||||||
from mes_dashboard.services.query_tool_service import (
|
from mes_dashboard.services.query_tool_service import (
|
||||||
resolve_lots,
|
resolve_lots,
|
||||||
get_lot_history,
|
get_lot_history,
|
||||||
@@ -43,6 +44,9 @@ query_tool_bp = Blueprint('query_tool', __name__)
|
|||||||
@query_tool_bp.route('/query-tool')
|
@query_tool_bp.route('/query-tool')
|
||||||
def query_tool_page():
|
def query_tool_page():
|
||||||
"""Render the query tool page."""
|
"""Render the query tool page."""
|
||||||
|
canonical_redirect = maybe_redirect_to_canonical_shell('/query-tool')
|
||||||
|
if canonical_redirect is not None:
|
||||||
|
return canonical_redirect
|
||||||
return render_template('query_tool.html')
|
return render_template('query_tool.html')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -89,12 +89,17 @@ class TestFullLoginLogoutFlow:
|
|||||||
def test_complete_admin_login_workflow(self, mock_auth, _mock_is_admin, client):
|
def test_complete_admin_login_workflow(self, mock_auth, _mock_is_admin, client):
|
||||||
"""Test complete admin login workflow."""
|
"""Test complete admin login workflow."""
|
||||||
mock_auth.return_value = _mock_admin_user()
|
mock_auth.return_value = _mock_admin_user()
|
||||||
|
spa_enabled = bool(client.application.config.get("PORTAL_SPA_ENABLED", False))
|
||||||
|
|
||||||
# 1. Access portal - should see login link
|
# 1. Access portal entry.
|
||||||
response = client.get("/")
|
response = client.get("/", follow_redirects=False)
|
||||||
assert response.status_code == 200
|
if spa_enabled:
|
||||||
content = response.data.decode("utf-8")
|
assert response.status_code == 302
|
||||||
assert "管理員登入" in content
|
assert response.location.rstrip("/").endswith("/portal-shell")
|
||||||
|
else:
|
||||||
|
assert response.status_code == 200
|
||||||
|
content = response.data.decode("utf-8")
|
||||||
|
assert "管理員登入" in content
|
||||||
|
|
||||||
# 2. Go to login page
|
# 2. Go to login page
|
||||||
response = client.get("/admin/login")
|
response = client.get("/admin/login")
|
||||||
@@ -107,15 +112,21 @@ class TestFullLoginLogoutFlow:
|
|||||||
}, follow_redirects=True)
|
}, follow_redirects=True)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
content = response.data.decode("utf-8")
|
if not spa_enabled:
|
||||||
# Should see admin name and logout option
|
content = response.data.decode("utf-8")
|
||||||
assert "Test Admin" in content or "登出" in content
|
# Portal template should show admin identity in non-SPA mode.
|
||||||
|
assert "Test Admin" in content or "登出" in content
|
||||||
|
|
||||||
# 4. Verify session has admin
|
# 4. Verify session has admin
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
assert "admin" in sess
|
assert "admin" in sess
|
||||||
assert sess["admin"]["mail"] == "ymirliu@panjit.com.tw"
|
assert sess["admin"]["mail"] == "ymirliu@panjit.com.tw"
|
||||||
|
|
||||||
|
if spa_enabled:
|
||||||
|
nav = client.get("/api/portal/navigation")
|
||||||
|
assert nav.status_code == 200
|
||||||
|
assert nav.get_json()["is_admin"] is True
|
||||||
|
|
||||||
# 5. Access admin pages
|
# 5. Access admin pages
|
||||||
response = client.get("/admin/pages")
|
response = client.get("/admin/pages")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@@ -248,15 +259,17 @@ class TestPortalDynamicTabs:
|
|||||||
|
|
||||||
def test_portal_hides_dev_tabs_for_non_admin(self, client, temp_page_status):
|
def test_portal_hides_dev_tabs_for_non_admin(self, client, temp_page_status):
|
||||||
"""Test portal hides dev page tabs for non-admin users."""
|
"""Test portal hides dev page tabs for non-admin users."""
|
||||||
response = client.get("/")
|
response = client.get("/api/portal/navigation")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
content = response.data.decode("utf-8")
|
payload = response.get_json()
|
||||||
|
routes = {
|
||||||
# Released pages should show
|
page["route"]
|
||||||
assert "WIP 即時概況" in content
|
for drawer in payload.get("drawers", [])
|
||||||
|
for page in drawer.get("pages", [])
|
||||||
# Dev pages should NOT show (tables and resource are dev)
|
}
|
||||||
# Note: This depends on the can_view_page implementation in portal.html
|
assert "/wip-overview" in routes
|
||||||
|
assert "/tables" not in routes
|
||||||
|
assert payload.get("is_admin") is False
|
||||||
|
|
||||||
@patch('mes_dashboard.routes.auth_routes.is_admin', return_value=True)
|
@patch('mes_dashboard.routes.auth_routes.is_admin', return_value=True)
|
||||||
@patch('mes_dashboard.routes.auth_routes.authenticate')
|
@patch('mes_dashboard.routes.auth_routes.authenticate')
|
||||||
@@ -270,12 +283,17 @@ class TestPortalDynamicTabs:
|
|||||||
"password": "password123"
|
"password": "password123"
|
||||||
})
|
})
|
||||||
|
|
||||||
response = client.get("/")
|
response = client.get("/api/portal/navigation")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
content = response.data.decode("utf-8")
|
payload = response.get_json()
|
||||||
|
routes = {
|
||||||
# Admin should see all pages
|
page["route"]
|
||||||
assert "WIP 即時概況" in content
|
for drawer in payload.get("drawers", [])
|
||||||
|
for page in drawer.get("pages", [])
|
||||||
|
}
|
||||||
|
assert "/wip-overview" in routes
|
||||||
|
assert "/tables" in routes
|
||||||
|
assert payload.get("is_admin") is True
|
||||||
|
|
||||||
|
|
||||||
class TestSessionPersistence:
|
class TestSessionPersistence:
|
||||||
|
|||||||
@@ -37,27 +37,44 @@ def client(app):
|
|||||||
class TestResourceHistoryPageAccess:
|
class TestResourceHistoryPageAccess:
|
||||||
"""E2E tests for page access and navigation."""
|
"""E2E tests for page access and navigation."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _load_resource_history_entry(client):
|
||||||
|
spa_enabled = bool(client.application.config.get("PORTAL_SPA_ENABLED", False))
|
||||||
|
response = client.get('/resource-history', follow_redirects=False)
|
||||||
|
if spa_enabled:
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert response.location.endswith('/portal-shell/resource-history')
|
||||||
|
shell_response = client.get('/portal-shell/resource-history')
|
||||||
|
assert shell_response.status_code == 200
|
||||||
|
return shell_response, True
|
||||||
|
return response, False
|
||||||
|
|
||||||
def test_page_loads_successfully(self, client):
|
def test_page_loads_successfully(self, client):
|
||||||
"""Resource history page should load without errors."""
|
"""Resource history page should load without errors."""
|
||||||
response = client.get('/resource-history')
|
response, spa_enabled = self._load_resource_history_entry(client)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
content = response.data.decode('utf-8')
|
content = response.data.decode('utf-8')
|
||||||
assert '設備歷史績效' in content
|
if spa_enabled:
|
||||||
|
assert '/static/dist/portal-shell.js' in content
|
||||||
|
else:
|
||||||
|
assert '設備歷史績效' in content
|
||||||
|
|
||||||
def test_page_bootstrap_container_exists(self, client):
|
def test_page_bootstrap_container_exists(self, client):
|
||||||
"""Resource history page should expose the Vue mount container."""
|
"""Resource history page should expose the Vue mount container."""
|
||||||
response = client.get('/resource-history')
|
response, _spa_enabled = self._load_resource_history_entry(client)
|
||||||
content = response.data.decode('utf-8')
|
content = response.data.decode('utf-8')
|
||||||
|
|
||||||
assert "id='app'" in content or 'id="app"' in content
|
assert "id='app'" in content or 'id="app"' in content
|
||||||
|
|
||||||
def test_page_references_vite_module(self, client):
|
def test_page_references_vite_module(self, client):
|
||||||
"""Resource history page should load the Vite module bundle."""
|
"""Resource history page should load the Vite module bundle."""
|
||||||
response = client.get('/resource-history')
|
response, spa_enabled = self._load_resource_history_entry(client)
|
||||||
content = response.data.decode('utf-8')
|
content = response.data.decode('utf-8')
|
||||||
|
|
||||||
assert '/static/dist/resource-history.js' in content
|
if spa_enabled:
|
||||||
|
assert '/static/dist/portal-shell.js' in content
|
||||||
|
else:
|
||||||
|
assert '/static/dist/resource-history.js' in content
|
||||||
assert 'type="module"' in content
|
assert 'type="module"' in content
|
||||||
|
|
||||||
|
|
||||||
@@ -334,11 +351,23 @@ class TestResourceHistoryNavigation:
|
|||||||
|
|
||||||
def test_portal_includes_history_tab(self, client):
|
def test_portal_includes_history_tab(self, client):
|
||||||
"""Portal should include resource history tab."""
|
"""Portal should include resource history tab."""
|
||||||
response = client.get('/')
|
if bool(client.application.config.get("PORTAL_SPA_ENABLED", False)):
|
||||||
content = response.data.decode('utf-8')
|
response = client.get('/api/portal/navigation')
|
||||||
|
assert response.status_code == 200
|
||||||
assert '設備歷史績效' in content
|
payload = response.get_json()
|
||||||
assert 'resourceHistoryFrame' in content
|
pages = [
|
||||||
|
page
|
||||||
|
for drawer in payload.get("drawers", [])
|
||||||
|
for page in drawer.get("pages", [])
|
||||||
|
]
|
||||||
|
history_pages = [page for page in pages if page.get("route") == "/resource-history"]
|
||||||
|
assert history_pages, "resource-history route missing from portal navigation contract"
|
||||||
|
assert history_pages[0].get("name") == "設備歷史績效"
|
||||||
|
else:
|
||||||
|
response = client.get('/')
|
||||||
|
content = response.data.decode('utf-8')
|
||||||
|
assert '設備歷史績效' in content
|
||||||
|
assert 'resourceHistoryFrame' in content
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ from urllib.parse import quote
|
|||||||
class TestAPILoadConcurrent:
|
class TestAPILoadConcurrent:
|
||||||
"""Load tests with concurrent requests."""
|
"""Load tests with concurrent requests."""
|
||||||
|
|
||||||
def _make_request(self, url: str, timeout: float) -> Tuple[bool, float, str]:
|
def _make_request(self, url: str, timeout: float, headers: dict | None = None) -> Tuple[bool, float, str]:
|
||||||
"""Make a single request and return (success, duration, error)."""
|
"""Make a single request and return (success, duration, error)."""
|
||||||
start = time.time()
|
start = time.time()
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, timeout=timeout)
|
response = requests.get(url, timeout=timeout, headers=headers)
|
||||||
duration = time.time() - start
|
duration = time.time() - start
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
@@ -212,8 +212,13 @@ class TestAPILoadConcurrent:
|
|||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=concurrent_users) as executor:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=concurrent_users) as executor:
|
||||||
futures = [
|
futures = [
|
||||||
executor.submit(self._make_request, url, timeout)
|
executor.submit(
|
||||||
for _ in range(total_requests)
|
self._make_request,
|
||||||
|
url,
|
||||||
|
timeout,
|
||||||
|
{"X-Forwarded-For": f"10.0.0.{(idx % concurrent_users) + 1}"},
|
||||||
|
)
|
||||||
|
for idx in range(total_requests)
|
||||||
]
|
]
|
||||||
|
|
||||||
for future in concurrent.futures.as_completed(futures):
|
for future in concurrent.futures.as_completed(futures):
|
||||||
@@ -249,10 +254,11 @@ class TestAPILoadConcurrent:
|
|||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=concurrent_users) as executor:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=concurrent_users) as executor:
|
||||||
futures = []
|
futures = []
|
||||||
for _ in range(concurrent_users):
|
for user_idx in range(concurrent_users):
|
||||||
|
headers = {"X-Forwarded-For": f"10.0.1.{user_idx + 1}"}
|
||||||
for endpoint in endpoints:
|
for endpoint in endpoints:
|
||||||
for _ in range(requests_per_endpoint):
|
for _ in range(requests_per_endpoint):
|
||||||
futures.append(executor.submit(self._make_request, endpoint, timeout))
|
futures.append(executor.submit(self._make_request, endpoint, timeout, headers))
|
||||||
|
|
||||||
for future in concurrent.futures.as_completed(futures):
|
for future in concurrent.futures.as_completed(futures):
|
||||||
success, duration, error = future.result()
|
success, duration, error = future.result()
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -57,7 +59,7 @@ def test_hold_blueprints_share_retired_fallback_template(monkeypatch, route, exi
|
|||||||
assert "系統發生錯誤" in response.data.decode("utf-8")
|
assert "系統發生錯誤" in response.data.decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
def test_deferred_route_keeps_fallback_posture_when_in_scope_retirement_enabled(monkeypatch):
|
def test_promoted_deferred_route_enforces_asset_readiness_when_retirement_enabled(monkeypatch):
|
||||||
monkeypatch.setenv("PORTAL_SPA_ENABLED", "false")
|
monkeypatch.setenv("PORTAL_SPA_ENABLED", "false")
|
||||||
monkeypatch.setenv("MODERNIZATION_RETIRE_IN_SCOPE_RUNTIME_FALLBACK", "true")
|
monkeypatch.setenv("MODERNIZATION_RETIRE_IN_SCOPE_RUNTIME_FALLBACK", "true")
|
||||||
app = create_app("testing")
|
app = create_app("testing")
|
||||||
@@ -69,4 +71,20 @@ def test_deferred_route_keeps_fallback_posture_when_in_scope_retirement_enabled(
|
|||||||
with patch("mes_dashboard.app.os.path.exists", return_value=False):
|
with patch("mes_dashboard.app.os.path.exists", return_value=False):
|
||||||
response = client.get("/tables")
|
response = client.get("/tables")
|
||||||
|
|
||||||
assert response.status_code == 200
|
# Promoted deferred routes now enforce asset readiness (503 when missing).
|
||||||
|
assert response.status_code == 503
|
||||||
|
|
||||||
|
|
||||||
|
def test_template_backed_promoted_routes_do_not_require_html_assets():
|
||||||
|
manifest_path = (
|
||||||
|
Path(__file__).resolve().parents[1]
|
||||||
|
/ "docs"
|
||||||
|
/ "migration"
|
||||||
|
/ "full-modernization-architecture-blueprint"
|
||||||
|
/ "asset_readiness_manifest.json"
|
||||||
|
)
|
||||||
|
payload = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
required = payload.get("in_scope_required_assets", {})
|
||||||
|
|
||||||
|
assert required.get("/excel-query") == ["excel-query.js"]
|
||||||
|
assert required.get("/query-tool") == ["query-tool.js"]
|
||||||
|
|||||||
@@ -50,10 +50,13 @@ def test_gate_runner_block_mode_passes_current_baseline():
|
|||||||
assert payload["passed"] is True
|
assert payload["passed"] is True
|
||||||
|
|
||||||
|
|
||||||
def test_scope_matrix_keeps_deferred_routes_for_follow_up_only():
|
def test_scope_matrix_has_all_routes_promoted_to_in_scope():
|
||||||
matrix = json.loads(SCOPE_MATRIX.read_text(encoding="utf-8"))
|
matrix = json.loads(SCOPE_MATRIX.read_text(encoding="utf-8"))
|
||||||
deferred = {item["route"] for item in matrix["deferred"]}
|
deferred = {item["route"] for item in matrix["deferred"]}
|
||||||
assert deferred == {"/tables", "/excel-query", "/query-tool", "/mid-section-defect"}
|
assert deferred == set(), "all deferred routes should be promoted to in-scope"
|
||||||
|
in_scope_routes = {item["route"] for item in matrix["in_scope"]}
|
||||||
|
for route in ("/tables", "/excel-query", "/query-tool", "/mid-section-defect"):
|
||||||
|
assert route in in_scope_routes, f"{route} should be in-scope"
|
||||||
|
|
||||||
|
|
||||||
def test_route_contract_parity_check_detects_route_set_drift():
|
def test_route_contract_parity_check_detects_route_set_drift():
|
||||||
|
|||||||
@@ -419,14 +419,23 @@ def test_legacy_shell_contract_fallback_logs_warning(monkeypatch):
|
|||||||
app_module._SHELL_ROUTE_CONTRACT_MAP = None
|
app_module._SHELL_ROUTE_CONTRACT_MAP = None
|
||||||
|
|
||||||
|
|
||||||
def test_deferred_routes_keep_direct_entry_compatibility_when_spa_enabled(monkeypatch):
|
def test_promoted_deferred_routes_redirect_to_canonical_shell_when_spa_enabled(monkeypatch):
|
||||||
monkeypatch.setenv("PORTAL_SPA_ENABLED", "true")
|
monkeypatch.setenv("PORTAL_SPA_ENABLED", "true")
|
||||||
app = create_app("testing")
|
app = create_app("testing")
|
||||||
app.config["TESTING"] = True
|
app.config["TESTING"] = True
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
_login_as_admin(client)
|
_login_as_admin(client)
|
||||||
|
|
||||||
for route in ["/excel-query", "/query-tool", "/tables"]:
|
cases = {
|
||||||
response = client.get(route, follow_redirects=False)
|
"/tables?category=wip": "/portal-shell/tables?category=wip",
|
||||||
# Deferred routes stay on direct-entry posture in this phase.
|
"/excel-query": "/portal-shell/excel-query",
|
||||||
assert response.status_code == 200, route
|
"/query-tool": "/portal-shell/query-tool",
|
||||||
|
"/mid-section-defect": "/portal-shell/mid-section-defect",
|
||||||
|
}
|
||||||
|
|
||||||
|
for direct_url, canonical_url in cases.items():
|
||||||
|
response = client.get(direct_url, follow_redirects=False)
|
||||||
|
assert response.status_code == 302, f"{direct_url} should redirect"
|
||||||
|
assert response.location.endswith(canonical_url), (
|
||||||
|
f"{direct_url}: expected {canonical_url}, got {response.location}"
|
||||||
|
)
|
||||||
|
|||||||
@@ -50,8 +50,9 @@ def test_job_query_native_smoke_query_search_export(client):
|
|||||||
shell = client.get("/portal-shell/job-query?start_date=2026-02-01&end_date=2026-02-11")
|
shell = client.get("/portal-shell/job-query?start_date=2026-02-01&end_date=2026-02-11")
|
||||||
assert shell.status_code == 200
|
assert shell.status_code == 200
|
||||||
|
|
||||||
page = client.get("/job-query")
|
page = client.get("/job-query", follow_redirects=False)
|
||||||
assert page.status_code == 200
|
assert page.status_code == 302
|
||||||
|
assert page.location.endswith("/portal-shell/job-query")
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
@@ -111,8 +112,9 @@ def test_excel_query_native_smoke_upload_detect_query_export(client):
|
|||||||
shell = client.get("/portal-shell/excel-query?mode=upload")
|
shell = client.get("/portal-shell/excel-query?mode=upload")
|
||||||
assert shell.status_code == 200
|
assert shell.status_code == 200
|
||||||
|
|
||||||
page = client.get("/excel-query")
|
page = client.get("/excel-query", follow_redirects=False)
|
||||||
assert page.status_code == 200
|
assert page.status_code == 302
|
||||||
|
assert page.location.endswith("/portal-shell/excel-query")
|
||||||
|
|
||||||
from mes_dashboard.routes.excel_query_routes import _uploaded_excel_cache
|
from mes_dashboard.routes.excel_query_routes import _uploaded_excel_cache
|
||||||
|
|
||||||
@@ -183,8 +185,9 @@ def test_query_tool_native_smoke_resolve_history_association(client):
|
|||||||
shell = client.get("/portal-shell/query-tool?input_type=lot_id")
|
shell = client.get("/portal-shell/query-tool?input_type=lot_id")
|
||||||
assert shell.status_code == 200
|
assert shell.status_code == 200
|
||||||
|
|
||||||
page = client.get("/query-tool")
|
page = client.get("/query-tool", follow_redirects=False)
|
||||||
assert page.status_code == 200
|
assert page.status_code == 302
|
||||||
|
assert page.location.endswith("/portal-shell/query-tool")
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
@@ -224,11 +227,17 @@ def test_query_tool_native_smoke_resolve_history_association(client):
|
|||||||
|
|
||||||
|
|
||||||
def test_tmtt_defect_native_smoke_range_query_and_csv_export(client):
|
def test_tmtt_defect_native_smoke_range_query_and_csv_export(client):
|
||||||
|
_login_as_admin(client)
|
||||||
|
|
||||||
shell = client.get("/portal-shell/tmtt-defect?start_date=2026-02-01&end_date=2026-02-11")
|
shell = client.get("/portal-shell/tmtt-defect?start_date=2026-02-01&end_date=2026-02-11")
|
||||||
assert shell.status_code == 200
|
assert shell.status_code == 200
|
||||||
|
|
||||||
page = client.get("/tmtt-defect")
|
page = client.get("/tmtt-defect", follow_redirects=False)
|
||||||
assert page.status_code == 200
|
if client.application.config.get("PORTAL_SPA_ENABLED", False):
|
||||||
|
assert page.status_code == 302
|
||||||
|
assert page.location.endswith("/portal-shell/tmtt-defect")
|
||||||
|
else:
|
||||||
|
assert page.status_code == 200
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ def client(app):
|
|||||||
class TestQueryToolPage:
|
class TestQueryToolPage:
|
||||||
"""Tests for /query-tool page route."""
|
"""Tests for /query-tool page route."""
|
||||||
|
|
||||||
def test_page_returns_html(self, client):
|
def test_page_returns_html(self, client, monkeypatch):
|
||||||
"""Should return the query tool page."""
|
"""Should return the query tool page when SPA redirect is disabled."""
|
||||||
|
monkeypatch.setenv("PORTAL_SPA_ENABLED", "false")
|
||||||
|
client.application.config["PORTAL_SPA_ENABLED"] = False
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["admin"] = {"username": "admin", "displayName": "Admin"}
|
sess["admin"] = {"username": "admin", "displayName": "Admin"}
|
||||||
|
|
||||||
|
|||||||
@@ -303,10 +303,15 @@ class TestMesApiUsageInTemplates(unittest.TestCase):
|
|||||||
self.assertTrue('MesApi.get' in html or '/static/dist/wip-detail.js' in html)
|
self.assertTrue('MesApi.get' in html or '/static/dist/wip-detail.js' in html)
|
||||||
|
|
||||||
def test_tables_page_uses_mesapi_or_vite_module(self):
|
def test_tables_page_uses_mesapi_or_vite_module(self):
|
||||||
response = self.client.get('/tables')
|
response, final_response, html = _get_response_and_html(self.client, '/tables')
|
||||||
html = response.data.decode('utf-8')
|
|
||||||
|
|
||||||
self.assertTrue('MesApi.post' in html or '/static/dist/tables.js' in html)
|
if response.status_code == 302:
|
||||||
|
self.assertTrue(response.location.endswith('/portal-shell/tables'))
|
||||||
|
self.assertEqual(final_response.status_code, 200)
|
||||||
|
self.assertIn('/static/dist/portal-shell.js', html)
|
||||||
|
else:
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTrue('MesApi.post' in html or '/static/dist/tables.js' in html)
|
||||||
|
|
||||||
def test_resource_page_uses_mesapi_or_vite_module(self):
|
def test_resource_page_uses_mesapi_or_vite_module(self):
|
||||||
response, final_response, html = _get_response_and_html(self.client, '/resource')
|
response, final_response, html = _get_response_and_html(self.client, '/resource')
|
||||||
@@ -324,11 +329,16 @@ class TestMesApiUsageInTemplates(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_query_tool_page_uses_vite_module(self):
|
def test_query_tool_page_uses_vite_module(self):
|
||||||
response = self.client.get('/query-tool')
|
response, final_response, html = _get_response_and_html(self.client, '/query-tool')
|
||||||
html = response.data.decode('utf-8')
|
|
||||||
|
|
||||||
self.assertIn('/static/dist/query-tool.js', html)
|
if response.status_code == 302:
|
||||||
self.assertIn('type="module"', html)
|
self.assertTrue(response.location.endswith('/portal-shell/query-tool'))
|
||||||
|
self.assertEqual(final_response.status_code, 200)
|
||||||
|
self.assertIn('/static/dist/portal-shell.js', html)
|
||||||
|
else:
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertIn('/static/dist/query-tool.js', html)
|
||||||
|
self.assertIn('type="module"', html)
|
||||||
|
|
||||||
def test_tmtt_defect_page_uses_vite_module(self):
|
def test_tmtt_defect_page_uses_vite_module(self):
|
||||||
response, final_response, html = _get_response_and_html(self.client, '/tmtt-defect')
|
response, final_response, html = _get_response_and_html(self.client, '/tmtt-defect')
|
||||||
@@ -377,6 +387,9 @@ class TestViteModuleIntegration(unittest.TestCase):
|
|||||||
'/resource-history': '/portal-shell/resource-history',
|
'/resource-history': '/portal-shell/resource-history',
|
||||||
'/job-query': '/portal-shell/job-query',
|
'/job-query': '/portal-shell/job-query',
|
||||||
'/tmtt-defect': '/portal-shell/tmtt-defect',
|
'/tmtt-defect': '/portal-shell/tmtt-defect',
|
||||||
|
'/tables': '/portal-shell/tables',
|
||||||
|
'/excel-query': '/portal-shell/excel-query',
|
||||||
|
'/query-tool': '/portal-shell/query-tool',
|
||||||
}
|
}
|
||||||
|
|
||||||
for endpoint, asset in endpoints_and_assets:
|
for endpoint, asset in endpoints_and_assets:
|
||||||
|
|||||||
Reference in New Issue
Block a user