docs: create ERROR_CONTRACT.md and MAINTENANCE_MODE.md
Unified API error envelope + full HTTP status catalogue (401/403/404/ 409/422/429/500/503, maintenance, validation, tenant-disabled, rate-limit, expired-token, invalid-signature) with JSON examples and current frontend reaction behavior, including two flagged pre-existing frontend bugs (expired-token/invalid-signature body-code handling is currently dead code - toAuthErrorShape() ignores fallbackCode for real HTTP errors). Maintenance-mode contract (global/per-tenant/per-module/read-only/ scheduled/feature-disable) with proposed 503 response shapes and an explicit split between "requires backend decision" and "no frontend UI exists yet, requires a future frontend task." These two agents wrote their files before hitting a session usage limit that killed the process before final report-back; content verified complete on disk before committing.
This commit is contained in:
398
docs/MAINTENANCE_MODE.md
Normal file
398
docs/MAINTENANCE_MODE.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# Maintenance Mode
|
||||
|
||||
Frontend contract for backend maintenance/availability signals: global, per-tenant,
|
||||
per-module, read-only, scheduled, and single-feature-disable scenarios. Written from
|
||||
the current source tree (branch `B2B`) — see `docs/context/BACKEND-AUDIT.md` for the
|
||||
full backend surface this builds on.
|
||||
|
||||
**Existing frontend handling today: none.** There is no maintenance concept anywhere
|
||||
in the frontend — no model field, no interceptor branch, no route, no component. This
|
||||
document proposes a contract and marks every open question explicitly as either
|
||||
"Requires backend decision" (the backend hasn't decided the signal shape) or "No
|
||||
frontend UI currently exists for this - requires a future frontend task" (the signal
|
||||
is plausible but no UI has been built to react to it).
|
||||
|
||||
The one adjacent, already-built pattern worth reusing is `AuthErrorPageComponent`
|
||||
(`src/app/core/auth/pages/auth-error-page.component.ts`): a single component keyed by
|
||||
an error-code route param, rendering `EmptyStateComponent` +
|
||||
`ButtonComponent`, with a `Record<Code, {title, description, actionLabel}>` copy table
|
||||
and a `retry()` handler. Section 7 proposes the maintenance screens follow this exact
|
||||
shape rather than inventing a new one.
|
||||
|
||||
---
|
||||
|
||||
## 1. Global maintenance
|
||||
|
||||
Whole platform down for all tenants.
|
||||
|
||||
**What the backend should send:** `503 Service Unavailable` on every endpoint
|
||||
(including `GET /bootstrap`), with a `Retry-After` header (seconds) and a structured
|
||||
JSON body (see §7 for exact shape). `GET /bootstrap` is the critical path — it is the
|
||||
first call the frontend makes (`ApiBootstrapProvider.loadBootstrap()`,
|
||||
`src/app/core/bootstrap/providers/api-bootstrap.provider.ts`, `GET /bootstrap`) and
|
||||
every facade that renders anything (`UiRuntimeFacade`, `WebsiteRuntimeFacade`,
|
||||
`ProjectEditorFacade`, `ContentManagementFacade`, `DiagnosticsFacade`) depends on it
|
||||
resolving.
|
||||
|
||||
**What the frontend currently does:** nothing maintenance-specific. Tracing the call
|
||||
chain in `src/app/core/config/config.service.ts`:
|
||||
|
||||
```ts
|
||||
this.bootstrap$ = this.provider.loadBootstrap().pipe(
|
||||
tap(config => { this.bootstrapSnapshot = config; ... }),
|
||||
shareReplay(1),
|
||||
catchError(error => {
|
||||
this.bootstrap$ = undefined;
|
||||
this.bootstrapSnapshot = null;
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
Any bootstrap failure (503 or otherwise) just rethrows. Every one of the ~14 call
|
||||
sites of `configService.loadBootstrap()` (footer, theme engine, branding engine,
|
||||
platform-runtime, page-resolver, static-page-resolver, footer-resolver, diagnostics,
|
||||
etc. — see `Grep` results for `loadBootstrap()` across `src/app`) either does not
|
||||
subscribe to the error channel at all, or handles it locally and inconsistently.
|
||||
There is no global "the whole app is down" screen.
|
||||
|
||||
**No frontend UI currently exists for this — requires a future frontend task.** A
|
||||
clean contract would intercept a `503` on the bootstrap call specifically (distinct
|
||||
from a 503 on a leaf endpoint, which should degrade that one section instead — see
|
||||
§3) and route to a full-page takeover, structurally identical to
|
||||
`AuthErrorPageComponent`: a `maintenance-page.component.ts` using
|
||||
`EmptyStateComponent` + `ButtonComponent`, keyed off the response body's `reason`
|
||||
(§7), with a retry button that calls `configService.loadBootstrap(true)`.
|
||||
|
||||
**Requires backend decision:** whether maintenance state is signaled by response
|
||||
status alone (`503` on `/bootstrap`) or also via a dedicated
|
||||
`GET /status` / `GET /maintenance` probe the frontend could poll while showing the
|
||||
takeover screen, to auto-recover without the user manually retrying.
|
||||
|
||||
---
|
||||
|
||||
## 2. Per-tenant maintenance
|
||||
|
||||
Single tenant disabled while others operate normally.
|
||||
|
||||
This ties directly into tenant resolution: `TenantResolverService`
|
||||
(`src/app/core/config/tenant-resolver.service.ts`) determines the tenant key before
|
||||
`ApiConfigService.getBaseUrl()` resolves which base URL to call (`tenantApiBaseUrls`
|
||||
map, or `tenantApiTemplate` with `{tenant}` substituted — see
|
||||
`docs/context/BACKEND-AUDIT.md` §2). Because tenant resolution happens client-side
|
||||
before any network call, a per-tenant maintenance signal can only surface through the
|
||||
response to that tenant's own `GET /bootstrap` call — there is no separate
|
||||
"is this tenant up" check today.
|
||||
|
||||
**What the backend should send:** the *same* `503` + structured body as global
|
||||
maintenance (§7) on that tenant's `/bootstrap` response. The frontend has no way to
|
||||
distinguish "this tenant is down" from "the whole platform is down" except by the
|
||||
response body's content — so the body must carry enough to tell (e.g. a `scope` field:
|
||||
`"global" | "tenant"`).
|
||||
|
||||
**What the frontend currently does:** nothing. `ConfigService.loadBootstrap()` is
|
||||
tenant-agnostic from the frontend's point of view — it just calls whatever base URL
|
||||
`ApiConfigService` resolved and doesn't know if a 503 means "this tenant" vs.
|
||||
"everything."
|
||||
|
||||
**Requires backend decision:** the `scope` discriminator mentioned above, and whether
|
||||
a disabled tenant's static/marketing content (branding, footer) should still resolve
|
||||
from a cached/last-known bootstrap so the takeover page can show the tenant's own
|
||||
logo, or whether it's a fully generic (unbranded) page. Given
|
||||
`BootstrapConfig.branding`/`theme` are only available *after* a successful bootstrap
|
||||
load, a tenant-branded maintenance page is not achievable without a design decision
|
||||
here (e.g. serving branding via a separate lightweight endpoint that stays up even
|
||||
when the tenant is otherwise disabled).
|
||||
|
||||
**No frontend UI currently exists for this — requires a future frontend task.** Same
|
||||
takeover component as §1 can likely serve both scopes once the backend supplies
|
||||
`scope`, but nothing renders differently for tenant-vs-global today because nothing
|
||||
renders a maintenance screen at all yet.
|
||||
|
||||
---
|
||||
|
||||
## 3. Per-module maintenance
|
||||
|
||||
E.g. payments down but catalog still browsable.
|
||||
|
||||
**Existing granularity concept:** `BootstrapConfig.featureFlags`
|
||||
(`FeatureFlagsConfig`, `src/app/shared/models/config/feature-flags.model.ts`) —
|
||||
a flat `Record<string, boolean>` with known keys `wishlist, compare, reviews,
|
||||
questions, comments, recommendations, blog, chat, analytics, notifications, coupons,
|
||||
loyalty, giftCards, invoices` and an index signature for tenant-specific extras. This
|
||||
is a **static, bootstrap-time** on/off switch per feature — not a live "is this
|
||||
service currently degraded" signal, and it has no `payments` or `catalog` key today.
|
||||
It's read once at bootstrap load and doesn't change until the next bootstrap refresh.
|
||||
|
||||
There is no separate "module health" concept distinct from `featureFlags`. The admin
|
||||
dashboard's `healthChecks()` / `homeHealthChecks()` (`AdminDashboardFacade`,
|
||||
`src/app/features/admin/dashboard/facade/admin-dashboard.facade.ts`) are **not**
|
||||
module-availability checks — they validate the *local bootstrap document itself*
|
||||
(schema version present, no missing translations, no invalid colors/widget refs/
|
||||
layouts, draft-exists, etc.), entirely client-side, with no backend health probe
|
||||
behind any row except product/category counts (which reflect load success/failure of
|
||||
`ProductFacade`/`CategoryFacade`, not an explicit "payments module is down" signal).
|
||||
`AdminMonitoringPageComponent` reuses the same boolean-shaped `healthChecks()` — it is
|
||||
not a live service-status board either.
|
||||
|
||||
**What the backend should send:** each domain-specific endpoint (e.g. `POST /cart`,
|
||||
`POST /orders`, `{qrApiUrl}/qr`) should independently return `503` with the structured
|
||||
body (§7) with `scope: "module"` and a `module` field (e.g. `"payments"`) when that
|
||||
subsystem specifically is down, while unrelated endpoints (`GET /category`,
|
||||
`GET /items/{id}`) keep responding normally. This requires no new bootstrap field —
|
||||
it's a per-request response behavior, consistent with REST conventions (the resource
|
||||
itself is unavailable, not the whole API).
|
||||
|
||||
**What the frontend currently does:** nothing differentiates a per-module outage from
|
||||
any other request failure. `ApiService` (`src/app/services/api.service.ts`) has no
|
||||
per-endpoint error branching for 503; a failed `createCartPayment()`/`createOrder()`
|
||||
call surfaces through whatever generic error handling the checkout components already
|
||||
have for network failures (out of scope for this doc — see the sibling
|
||||
`ERROR_CONTRACT.md` task for the general error-response shape).
|
||||
|
||||
**No frontend UI currently exists for this — requires a future frontend task.** The
|
||||
checkout flow would need a "payments unavailable" inline state (banner or disabled
|
||||
submit + tooltip, per §7) distinct from a generic error toast, and catalog browsing
|
||||
would need to keep working untouched — which it structurally already would, since
|
||||
`ProductFacade`/`CategoryFacade` and the payment calls are fully independent code
|
||||
paths today (no shared failure state). That independence is a real asset: a payments
|
||||
outage cannot accidentally break catalog browsing given the current facade
|
||||
separation, but no UI exists yet to *tell the user* payments specifically are down
|
||||
rather than "something went wrong."
|
||||
|
||||
---
|
||||
|
||||
## 4. Read-only mode
|
||||
|
||||
Writes disabled, reads still work.
|
||||
|
||||
**Does the frontend already assume this is possible?** Partially, structurally, but
|
||||
not deliberately. Cart state is `LOCAL-ONLY` (`CartService`,
|
||||
`src/app/services/cart.service.ts`, signal-based, persisted to `localStorage` key
|
||||
`marketplace_cart`) — adding items to cart, changing quantities, and browsing the cart
|
||||
UI works entirely client-side with **no backend call at all** until checkout. The
|
||||
only writes that hit a backend are at the checkout boundary: `POST /cart`
|
||||
(`createCartPayment`), `POST /orders` (`createOrder`), `POST /purchase-email`, and the
|
||||
QR/card payment polling. So today, if the backend rejected writes only, catalog
|
||||
browsing, search, wishlist/compare (also `LOCAL-ONLY`,
|
||||
`LocalUserExperienceRepository`), and cart-building would all continue working simply
|
||||
because they never touch the backend — but reviews (`POST /items/{id}/callback`) and
|
||||
questions (`POST /items/{id}/questiion`) are also writes and would fail the same as
|
||||
checkout, since both are LIVE endpoints via `ProductDataProvider`.
|
||||
|
||||
There is no code today that *checks for* a read-only flag and proactively disables
|
||||
write UI (e.g. graying out "Add to cart" or the checkout button ahead of time). A
|
||||
write attempt would only be discovered to be blocked when the write call itself
|
||||
fails.
|
||||
|
||||
**What the backend should send:** `503` (or `403`, see note below) with the
|
||||
structured body (§7), `scope: "readonly"`, on write endpoints specifically —
|
||||
`POST /cart`, `POST /orders`, `POST /purchase-email`, `POST /items/{id}/callback`,
|
||||
`POST /items/{id}/questiion`, `POST /websession/{sessionId}` (cart sync) — while GET
|
||||
endpoints keep working. `403 Forbidden` is arguably more correct REST semantics for
|
||||
"this resource forbids this method during a maintenance window" than `503`, but `503`
|
||||
+ `Retry-After` communicates "temporary" more clearly to a client and is
|
||||
recommended so the frontend can offer a countdown/retry consistent with §5's pattern.
|
||||
**Requires backend decision:** which status code is authoritative — this should be
|
||||
pinned down jointly with whatever `ERROR_CONTRACT.md` settles on for its 5xx
|
||||
conventions, since read-only is really "a subset of write endpoints return
|
||||
maintenance-503."
|
||||
|
||||
**No frontend UI currently exists for this — requires a future frontend task.** No
|
||||
bootstrap flag exists to proactively disable checkout/review/question submission
|
||||
ahead of a failed request (e.g. `featureFlags.readOnly` or a dedicated
|
||||
`platformStatus.readOnly` field would need to be added to `BootstrapConfig` if the
|
||||
product wants a proactive banner instead of a reactive failure). Reactive handling
|
||||
(showing an error when the write call 503s) can reuse the same inline
|
||||
error-state pattern as §3/§6 once `ERROR_CONTRACT.md` defines the generic error body
|
||||
handling.
|
||||
|
||||
---
|
||||
|
||||
## 5. Scheduled maintenance
|
||||
|
||||
Advance notice pattern (banner / countdown) ahead of a maintenance window.
|
||||
|
||||
**What exists in the frontend today:** nothing. No banner component, no countdown
|
||||
component, no bootstrap field for an upcoming maintenance window.
|
||||
|
||||
**Requires backend decision — proposed minimal contract:** add an optional field to
|
||||
`BootstrapConfig` (loaded once per session/on refresh via `GET /bootstrap`), e.g.:
|
||||
|
||||
```ts
|
||||
interface ScheduledMaintenanceNotice {
|
||||
startsAt: string; // ISO 8601
|
||||
endsAt?: string; // ISO 8601, optional if duration is unknown
|
||||
scope: 'global' | 'tenant' | 'module';
|
||||
module?: string; // present when scope === 'module'
|
||||
messageKey?: string; // optional i18n key/translated string for custom copy
|
||||
}
|
||||
```
|
||||
|
||||
surfaced as `bootstrap.maintenanceNotice?: ScheduledMaintenanceNotice | null`. This
|
||||
keeps the mechanism consistent with how the platform already declares other
|
||||
runtime-configured, backend-authored state (feature flags, tenant config, API
|
||||
endpoint records all live in the bootstrap document per
|
||||
`docs/context/BACKEND-AUDIT.md` §6) rather than inventing a new polling endpoint. A
|
||||
polling `GET /maintenance-notice` endpoint is an alternative if the notice needs to
|
||||
appear/change without a full bootstrap refresh — that tradeoff is the backend
|
||||
decision.
|
||||
|
||||
**No frontend UI currently exists for this — requires a future frontend task.** A
|
||||
dismissible banner component reading `bootstrap.maintenanceNotice` and showing a
|
||||
localized "maintenance starts in Xh Ym" countdown would need to be built and mounted
|
||||
at a layout level (header or a global banner slot) — no such banner or countdown
|
||||
component exists in `src/app/shared/ui/` today.
|
||||
|
||||
---
|
||||
|
||||
## 6. Temporary feature disable
|
||||
|
||||
Single feature toggled off without full maintenance — e.g. reviews temporarily
|
||||
disabled while the rest of the product page works.
|
||||
|
||||
**This is the one scenario the frontend already has a real mechanism for**, via
|
||||
`BootstrapConfig.featureFlags` (§3). Setting `featureFlags.reviews = false` in the
|
||||
bootstrap document is exactly the existing, live mechanism for "reviews are off right
|
||||
now" — it's read by whatever consumes `FeatureConfigService`
|
||||
(`src/app/core/config/*`) and gates the relevant UI. This is a **deploy/config-time**
|
||||
toggle (changes on next bootstrap load), not a live incident-response toggle, but
|
||||
structurally it is the same shape a backend team would use to kill a misbehaving
|
||||
feature quickly: update the bootstrap document (or whatever backend-side config
|
||||
drives it), and the next bootstrap fetch picks it up.
|
||||
|
||||
**Recommendation:** reuse `featureFlags` for this scenario rather than introducing a
|
||||
parallel mechanism — it already exists, is already wired through to the UI in the
|
||||
relevant places, and matches the "temporary, single-feature, not a full outage"
|
||||
framing exactly. No backend decision needed for the *mechanism*; only for *process*
|
||||
(how fast a flag flip propagates — depends on bootstrap cache/refresh cadence, which
|
||||
is outside this doc's scope).
|
||||
|
||||
**Gap:** `featureFlags` has no `payments` or `catalog` key and is a boolean only — it
|
||||
can't express "reviews disabled with reason X, back at time Y" the way §5's proposed
|
||||
`maintenanceNotice` can. If product wants a "reviews are temporarily unavailable —
|
||||
back tomorrow" message rather than the feature silently disappearing, that needs the
|
||||
richer shape from §5, scoped to `module`, not a plain `featureFlags` boolean.
|
||||
|
||||
---
|
||||
|
||||
## 7. Recommended API responses
|
||||
|
||||
All maintenance-scenario responses use HTTP `503 Service Unavailable` (except the
|
||||
read-only debate in §4) with a `Retry-After` header (seconds, standard HTTP) and a
|
||||
JSON body. This is written to be consistent with, not contradict, whatever
|
||||
`ERROR_CONTRACT.md` (sibling task, in progress) settles on for its general
|
||||
structured-error envelope — if that doc defines a different top-level error
|
||||
shape (e.g. `{ error: { code, message, ... } }` vs. a flatter shape), this body
|
||||
should be nested under that envelope rather than duplicating a competing shape.
|
||||
Pending that reconciliation, the fields below are what the frontend needs regardless
|
||||
of the outer envelope:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 503,
|
||||
"code": "maintenance",
|
||||
"scope": "global",
|
||||
"module": null,
|
||||
"reason": "scheduled",
|
||||
"message": "The marketplace is temporarily unavailable for scheduled maintenance.",
|
||||
"retryAfter": 1800,
|
||||
"startedAt": "2026-07-26T02:00:00Z",
|
||||
"expectedEndAt": "2026-07-26T03:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
Field notes:
|
||||
- `scope`: `"global" | "tenant" | "module" | "readonly"` — lets the frontend pick the
|
||||
right UI (full takeover vs. inline banner vs. disabled control) without guessing
|
||||
from status code alone.
|
||||
- `module`: present only when `scope === "module"` (e.g. `"payments"`, `"reviews"`).
|
||||
- `reason`: `"scheduled" | "incident" | "disabled"` — free-form enough for the
|
||||
frontend to choose copy tone (planned vs. unplanned) without needing new fields
|
||||
per scenario.
|
||||
- `retryAfter`: mirrors the `Retry-After` header in the body too, so a client that
|
||||
only reads JSON (not headers) still gets it — useful since some HttpClient error
|
||||
paths surface the body more readily than headers depending on interceptor
|
||||
structure.
|
||||
- `startedAt` / `expectedEndAt`: optional, ISO 8601, for countdown/banner copy (§5).
|
||||
|
||||
Per-scenario summary:
|
||||
|
||||
| Scenario | Status | `scope` | Notes |
|
||||
|---|---|---|---|
|
||||
| Global | 503 | `"global"` | On every endpoint, especially `/bootstrap` |
|
||||
| Per-tenant | 503 | `"tenant"` | On that tenant's `/bootstrap` and all its endpoints |
|
||||
| Per-module | 503 | `"module"` | Only on that module's endpoints (e.g. `/cart`, `/orders`) |
|
||||
| Read-only | 503 or 403 | `"readonly"` | Only on write endpoints; GETs unaffected — pin down with `ERROR_CONTRACT.md` |
|
||||
| Scheduled (advance notice) | 200, via `bootstrap.maintenanceNotice` | n/a | Not an error response — a proactive field on the normal `/bootstrap` payload, see §5 |
|
||||
| Temporary feature disable | 200, via `bootstrap.featureFlags.<key> = false` | n/a | Not an error response — existing bootstrap mechanism, see §6 |
|
||||
|
||||
---
|
||||
|
||||
## 8. Frontend behavior
|
||||
|
||||
Grounded in the UI patterns that already exist (`EmptyStateComponent`
|
||||
(`src/app/shared/ui/empty-state/empty-state.component.ts`), the `errorTitle` /
|
||||
`error` / `retry` i18n-key convention used across catalog, product details, and
|
||||
generic list widgets (`src/app/i18n/en.ts`), and `AuthErrorPageComponent`'s
|
||||
code-keyed full-page pattern). No new UI concepts are invented below beyond composing
|
||||
these.
|
||||
|
||||
| Scenario | Recommended UI | Existing pattern reused | Status |
|
||||
|---|---|---|---|
|
||||
| Global maintenance | Full-page takeover, replaces the entire app shell (no header/footer, since branding may be unavailable — see §2) | `AuthErrorPageComponent` shape: `EmptyStateComponent` + `ButtonComponent`, code-keyed copy, `retry()` action | No frontend UI currently exists for this — requires a future frontend task |
|
||||
| Per-tenant maintenance | Same full-page takeover as global, ideally tenant-branded if the backend decision in §2 allows branding to still resolve | Same as above | No frontend UI currently exists for this — requires a future frontend task |
|
||||
| Per-module maintenance | Inline empty-state/banner scoped to the affected section only (e.g. checkout step shows `EmptyStateComponent` with `errorTitle`/`error`/`retry` copy; catalog pages untouched) | `EmptyStateComponent` + the `errorTitle`/`error`/`retry` i18n triple already used in `catalog`/`productDetails`/generic-list translations | No frontend UI currently exists for this — requires a future frontend task |
|
||||
| Read-only mode | Disabled write control (e.g. "Add to cart" / "Submit review" button) + tooltip explaining why, OR a reactive error state on submit if no proactive flag exists (§4) | Disabled-button-plus-tooltip is a common pattern in the design system but not wired to any maintenance signal today | No frontend UI currently exists for this — requires a future frontend task |
|
||||
| Scheduled maintenance | Dismissible banner at layout/header level with countdown copy | No banner/countdown component exists in `src/app/shared/ui/` today | No frontend UI currently exists for this — requires a future frontend task |
|
||||
| Temporary feature disable | Feature's own UI simply doesn't render (existing `featureFlags` gating), optionally with a short "temporarily unavailable" note if `messageKey` (§5) is present | Existing `featureFlags` boolean gating (already live) | Existing mechanism works; richer messaging is the only gap |
|
||||
|
||||
---
|
||||
|
||||
## Summary: what's proposed/new vs. what already exists
|
||||
|
||||
**Already exists and can be reused as-is:**
|
||||
- `BootstrapConfig.featureFlags` — static per-feature kill switch (§3, §6).
|
||||
- `EmptyStateComponent` + `errorTitle`/`error`/`retry` i18n convention — the inline
|
||||
error-state building block for any scenario.
|
||||
- `AuthErrorPageComponent` — the full-page-takeover shape (code-keyed copy record,
|
||||
`EmptyStateComponent` + `ButtonComponent`, `retry()` handler) to model a maintenance
|
||||
page after.
|
||||
- Cart's `LOCAL-ONLY` design already means most of "read-only browsing" works
|
||||
incidentally, since browsing/cart-building never call the backend.
|
||||
|
||||
**Proposed/new (this document introduces):**
|
||||
- The `scope`/`module`/`reason` structured 503 body (§7).
|
||||
- `bootstrap.maintenanceNotice` (§5) for scheduled-maintenance advance notice.
|
||||
- A dedicated `maintenance-page.component.ts` full-page takeover (§1/§2).
|
||||
- Inline per-module/read-only error and disabled-control states wired to the new 503
|
||||
shape (§3/§4).
|
||||
|
||||
---
|
||||
|
||||
## Requires backend decision (full list)
|
||||
|
||||
- §1: whether a dedicated `GET /status`/`GET /maintenance` probe should exist for
|
||||
auto-recovery polling, beyond a plain 503 on `/bootstrap`.
|
||||
- §2: the `scope` discriminator (`"global"` vs `"tenant"`) so the frontend can tell
|
||||
the two apart from a single tenant's bootstrap response; and whether a
|
||||
disabled tenant's branding can still resolve for a branded takeover page.
|
||||
- §3: none beyond adopting the §7 response shape per-endpoint — this one is mostly
|
||||
frontend-gap, not backend-undecided.
|
||||
- §4: which status code is authoritative for read-only (`503` vs `403`) — to be
|
||||
pinned down jointly with `ERROR_CONTRACT.md`.
|
||||
- §5: whether scheduled-maintenance notice ships via a `bootstrap.maintenanceNotice`
|
||||
field (proposed) or a separate polling endpoint.
|
||||
- §7: how this document's 503 body nests inside whatever outer envelope
|
||||
`ERROR_CONTRACT.md` defines.
|
||||
|
||||
## No frontend UI currently exists for this — requires a future frontend task (full list)
|
||||
|
||||
- Global maintenance full-page takeover component.
|
||||
- Per-tenant maintenance takeover (branded or not, pending §2's backend decision).
|
||||
- Per-module inline maintenance banner/empty-state wiring on checkout/payment flows.
|
||||
- Proactive read-only disabling of write controls (Add to cart / Submit review /
|
||||
Submit question / Checkout) ahead of a failed request.
|
||||
- Scheduled-maintenance banner + countdown component at the layout/header level.
|
||||
- Richer "temporarily unavailable, back at X" messaging for `featureFlags`-gated
|
||||
features (today they just silently don't render — no explanatory copy).
|
||||
Reference in New Issue
Block a user