diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml index a8e930ef4..f4835e1cc 100644 --- a/.github/workflows/site.yml +++ b/.github/workflows/site.yml @@ -61,7 +61,19 @@ jobs: test "$(wc -c < site/__sapper__/export/mighty-logo.png)" -gt 1000 test -f site/__sapper__/export/favicon.png test "$(wc -c < site/__sapper__/export/favicon.png)" -gt 1000 + test -s site/__sapper__/export/mighty-theme.css + test -s site/__sapper__/export/status-social-card.png + test -s site/__sapper__/export/robots.txt + test -s site/__sapper__/export/sitemap.xml + test -s site/__sapper__/export/llms.txt + grep -q 'Mighty Service Status' site/__sapper__/export/manifest.json + grep -q 'content=#fbf7ee' site/__sapper__/export/index.html grep -q 'href=/favicon.png' site/__sapper__/export/index.html + grep -q 'Mighty detects manipulated documents and media' site/__sapper__/export/index.html + grep -q 'application/ld+json' site/__sapper__/export/index.html + for component in website api scan-gateway; do + test -s "site/__sapper__/export/history/${component}/index.html" + done - name: Publish gh-pages branch uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4 with: diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 473f6cab4..e9b8d1e4d 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -72,4 +72,16 @@ jobs: test "$(wc -c < site/__sapper__/export/mighty-logo.png)" -gt 1000 test -f site/__sapper__/export/favicon.png test "$(wc -c < site/__sapper__/export/favicon.png)" -gt 1000 + test -s site/__sapper__/export/mighty-theme.css + test -s site/__sapper__/export/status-social-card.png + test -s site/__sapper__/export/robots.txt + test -s site/__sapper__/export/sitemap.xml + test -s site/__sapper__/export/llms.txt + grep -q 'Mighty Service Status' site/__sapper__/export/manifest.json + grep -q 'content=#fbf7ee' site/__sapper__/export/index.html grep -q 'href=/favicon.png' site/__sapper__/export/index.html + grep -q 'Mighty detects manipulated documents and media' site/__sapper__/export/index.html + grep -q 'application/ld+json' site/__sapper__/export/index.html + for component in website api scan-gateway; do + test -s "site/__sapper__/export/history/${component}/index.html" + done diff --git a/.upptimerc.yml b/.upptimerc.yml index 1e59f5cff..a64d33e98 100644 --- a/.upptimerc.yml +++ b/.upptimerc.yml @@ -46,13 +46,55 @@ status-website: cname: status.trymighty.ai logoUrl: /mighty-logo.png favicon: /favicon.png - name: Mighty Status - theme: night + name: Mighty Service Status + themeUrl: /mighty-theme.css + links: + - rel: canonical + href: https://status.trymighty.ai/ + metaTags: + - name: description + content: >- + Live uptime, incident, maintenance, and response-time updates for + Mighty’s AI document-fraud detection services for lending and insurance. + - name: robots + content: index, follow, max-image-preview:large, max-snippet:-1 + - name: color-scheme + content: light + - name: application-name + content: Mighty Status + - name: twitter:card + content: summary_large_image + - name: twitter:title + content: Mighty Service Status + - name: twitter:description + content: >- + Live uptime and incident updates for Mighty’s AI document-fraud + detection services. + - name: twitter:image + content: https://status.trymighty.ai/status-social-card.png + # Open Graph and JSON-LD need property/type attributes that Upptime's generic + # metaTags mapping cannot express, so keep this reviewed static HTML only. + customHeadHtml: >- + + + + + + + + + + + robotsText: |- + User-agent: * + Allow: / + Sitemap: https://status.trymighty.ai/sitemap.xml introTitle: "**Mighty service status**" introMessage: >- - Current availability and incident communications for Mighty production - services. Measurements are external observations and are not a contractual - service-level agreement. + Mighty detects manipulated documents and media before lenders fund loans or + insurers pay claims. This page reports current availability, incidents, and + scheduled maintenance for our production services. Measurements are + external observations, not a contractual service-level agreement. navbar: - title: Status href: / diff --git a/README.md b/README.md index 850f81d26..84106e368 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ the authoritative audit evidence. ## [Live status](https://status.trymighty.ai) -Availability measurements begin when the launch checklist enables production -automation. No historical sample data is carried over from the upstream template. +Production availability measurements and response-time history are collected +automatically. No historical sample data was carried over from the upstream template. ## Operations diff --git a/assets/llms.txt b/assets/llms.txt new file mode 100644 index 000000000..9f6d23a3f --- /dev/null +++ b/assets/llms.txt @@ -0,0 +1,21 @@ +# Mighty Service Status + +> Official public availability, incident, scheduled-maintenance, and response-time information for Mighty production services. + +Mighty detects manipulated documents and media before lenders fund loans or insurers pay claims. Mighty provides AI document-fraud detection for lending and insurance. + +## Current status + +- Status page: https://status.trymighty.ai/ +- Website component: https://status.trymighty.ai/history/website/ +- API component: https://status.trymighty.ai/history/api/ +- Scan Gateway component: https://status.trymighty.ai/history/scan-gateway/ +- Incident and maintenance records are published on the status page from the public status repository. +- Measurements are external observations, not a contractual service-level agreement. + +## Authoritative links + +- Mighty: https://trymighty.ai/ +- Documentation: https://trymighty.ai/docs +- Security: https://trymighty.ai/security +- Status source and history: https://github.com/TryMightyAI/status diff --git a/assets/logo-192.png b/assets/logo-192.png new file mode 100644 index 000000000..180babb79 Binary files /dev/null and b/assets/logo-192.png differ diff --git a/assets/logo-512.png b/assets/logo-512.png new file mode 100644 index 000000000..0529af99e Binary files /dev/null and b/assets/logo-512.png differ diff --git a/assets/manifest.json b/assets/manifest.json new file mode 100644 index 000000000..446d790b6 --- /dev/null +++ b/assets/manifest.json @@ -0,0 +1,21 @@ +{ + "background_color": "#fbf7ee", + "theme_color": "#fbf7ee", + "name": "Mighty Service Status", + "short_name": "Mighty Status", + "description": "Live availability and incident updates for Mighty production services.", + "display": "minimal-ui", + "start_url": "/", + "icons": [ + { + "src": "logo-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "logo-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/assets/mighty-theme.css b/assets/mighty-theme.css new file mode 100644 index 000000000..4c577e706 --- /dev/null +++ b/assets/mighty-theme.css @@ -0,0 +1,329 @@ +:root { + color-scheme: light; + --mighty-ink: #2a2622; + --mighty-ink-soft: #5c554b; + --mighty-yellow: #fedc25; + --mighty-yellow-deep: #e5c521; + --mighty-cream: #fbf7ee; + --mighty-paper: #fffdf8; + --mighty-line: #e7decc; + --mighty-green: #147d4c; + --mighty-green-soft: #e7f5ec; + --mighty-red: #cc2936; + --mighty-red-soft: #ffe8e9; + --mighty-amber: #b66b00; + --mighty-amber-soft: #fff3c4; + + --body-background-color: var(--mighty-cream); + --body-text-color: var(--mighty-ink); + --card-background-color: var(--mighty-paper); + --nav-background-color: rgba(251, 247, 238, 0.94); + --nav-border-bottom-color: var(--mighty-line); + --nav-current-border-bottom-color: var(--mighty-yellow-deep); + --card-border-color: var(--mighty-line); + --down-border-left-color: var(--mighty-red); + --down-background-color: var(--mighty-red-soft); + --degraded-border-left-color: var(--mighty-amber); + --up-border-left-color: var(--mighty-green); + --tag-color: var(--mighty-ink); + --tag-up-background-color: #bfe5cc; + --tag-down-background-color: #ffb8be; + --tag-degraded-background-color: #ffe285; + --change-background-color: var(--mighty-yellow); + --error-button-border-color: var(--mighty-ink); + --error-button-background-color: var(--mighty-yellow); + --error-button-color: var(--mighty-ink); + --submit-button-border-color: var(--mighty-ink); + --submit-button-background-color: var(--mighty-yellow); + --submit-button-color: var(--mighty-ink); + --graph-opacity: 1; +} + +html { + background: var(--mighty-cream); +} + +body { + min-height: 100vh; + padding-bottom: 3rem; + font-family: Manrope, Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + line-height: 1.6; + letter-spacing: -0.008em; + background: + radial-gradient(circle at 12% -8%, rgba(254, 220, 37, 0.28), transparent 29rem), + radial-gradient(circle at 92% 14%, rgba(18, 114, 148, 0.08), transparent 24rem), + var(--mighty-cream); +} + +::selection { + color: #000; + background: var(--mighty-yellow); +} + +.container { + max-width: 960px; + padding-left: 2rem; + padding-right: 2rem; +} + +nav { + position: sticky; + top: 0; + z-index: 20; + margin-bottom: 4.5rem !important; + border-bottom: 1px solid var(--mighty-line) !important; + background: rgba(251, 247, 238, 0.9) !important; + box-shadow: 0 1px 0 rgba(42, 38, 34, 0.03); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); +} + +nav .logo { + color: var(--mighty-ink); + font-size: 1.02rem; + font-weight: 800 !important; + letter-spacing: -0.025em; +} + +nav .logo img { + width: 3rem; + object-fit: contain; + border-radius: 0 !important; +} + +nav ul a { + color: var(--mighty-ink-soft); + font-size: 0.9rem; + font-weight: 650; + transition: color 160ms ease, background 160ms ease; +} + +nav ul a:hover { + color: var(--mighty-ink); + background: rgba(254, 220, 37, 0.16); +} + +nav [aria-current] { + color: var(--mighty-ink); + border-bottom-width: 3px; +} + + +a:focus-visible { + border-radius: 4px; + outline: 3px solid #127294; + outline-offset: 3px; +} + +main > header { + position: relative; + max-width: 760px; + margin-bottom: 2.4rem !important; +} + +main > header h1 { + margin-bottom: 0.7rem; + color: var(--mighty-ink); + font-size: clamp(2.2rem, 5vw, 3.65rem); + font-weight: 850; + line-height: 1.02; + letter-spacing: -0.055em; +} + +main > header .lead { + max-width: 720px; + margin: 0; + color: var(--mighty-ink-soft); + font-size: clamp(1rem, 2vw, 1.16rem); + line-height: 1.65; +} + +article { + overflow: hidden; + padding: 1.25rem 1.4rem; + border: 1px solid var(--mighty-line); + border-radius: 16px; + box-shadow: 0 8px 30px rgba(66, 52, 28, 0.06); + transition: transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease; +} + +article.link:hover { + z-index: 2; + transform: translateY(-2px); + border-color: #d8cbae; + box-shadow: 0 14px 34px rgba(66, 52, 28, 0.11); +} + +article + article { + margin-top: 0.85rem; +} + +article.up, +article.down, +article.degraded { + border-left-width: 5px !important; +} + +main > article.up { + position: relative; + padding: 1.25rem 1.35rem; + border-color: #b9dcc5; + color: #125f3b; + background: linear-gradient(105deg, #e7f5ec 0%, #f4fbf6 100%); + box-shadow: 0 10px 28px rgba(20, 125, 76, 0.08); + font-size: 1.02rem; + font-weight: 750; +} + +main > article.up::before { + display: none; +} + +main > article.up::after { + display: none; +} + +h2 { + margin-top: 2.5rem; + margin-bottom: 0.8rem; + color: var(--mighty-ink); + font-size: 1.3rem; + font-weight: 800; + letter-spacing: -0.03em; +} + +h3 { + color: var(--mighty-ink-soft); + font-size: 0.82rem; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +h4 { + color: var(--mighty-ink); + font-size: 1.03rem; + font-weight: 800; + letter-spacing: -0.02em; +} + +.live-status article > div > div:not(:first-child), +.live-status article > div:not(.f) { + color: var(--mighty-ink-soft); +} + +form label { + display: inline-block; + padding: 0.28rem 0.52rem; + border-radius: 999px; + color: var(--mighty-ink-soft); + cursor: pointer; + font-size: 0.79rem; + font-weight: 700; + transition: background 160ms ease, color 160ms ease; +} + +form input:checked + label { + color: var(--mighty-ink) !important; + background: var(--mighty-yellow); +} + +.data { + color: var(--mighty-ink); + font-variant-numeric: tabular-nums; + font-weight: 750; +} + +.tag { + border-radius: 999px; + font-weight: 750; + letter-spacing: 0.02em; +} + +footer { + max-width: 960px; + margin: 4.5rem auto 0 !important; + padding: 1.4rem 2rem 0; + border-top: 1px solid var(--mighty-line); + color: var(--mighty-ink-soft); + font-size: 0.82rem; +} + +footer a { + color: var(--mighty-ink); + text-decoration-color: var(--mighty-yellow-deep); + text-decoration-thickness: 2px; + text-underline-offset: 3px; +} + +@media (max-width: 700px) { + .container { + padding-left: 1.15rem; + padding-right: 1.15rem; + } + + nav { + position: relative; + margin-bottom: 2.7rem !important; + padding: 0 !important; + } + + nav .container { + align-items: flex-start !important; + flex-direction: column; + } + + nav .logo { + margin: 0 !important; + } + + nav ul { + width: 100%; + justify-content: flex-start !important; + overflow-x: auto; + } + + nav ul a { + padding: 0.85rem 1rem !important; + } + + nav ul li:first-child a { + padding-left: 0 !important; + } + + main > header h1 { + max-width: 100%; + font-size: 2rem; + letter-spacing: -0.055em; + line-height: 1.05; + } + + main > header h1 strong { + display: block; + } + + main > header .lead { + overflow-wrap: anywhere; + } + + .f { + gap: 0.6rem; + } + + form.f { + width: 100%; + justify-content: flex-start; + gap: 0; + } + + article { + padding: 1.1rem; + } +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + scroll-behavior: auto !important; + transition: none !important; + } +} diff --git a/assets/sitemap.xml b/assets/sitemap.xml new file mode 100644 index 000000000..e92f6dd4d --- /dev/null +++ b/assets/sitemap.xml @@ -0,0 +1,23 @@ + + + + https://status.trymighty.ai/ + hourly + 1.0 + + + https://status.trymighty.ai/history/website/ + daily + 0.8 + + + https://status.trymighty.ai/history/api/ + daily + 0.8 + + + https://status.trymighty.ai/history/scan-gateway/ + daily + 0.8 + + diff --git a/assets/status-social-card.png b/assets/status-social-card.png new file mode 100644 index 000000000..0dfbdab28 Binary files /dev/null and b/assets/status-social-card.png differ diff --git a/docs/DECISION.md b/docs/DECISION.md index d2a1486a4..1147b64bd 100644 --- a/docs/DECISION.md +++ b/docs/DECISION.md @@ -2,7 +2,8 @@ **Decision date:** 2026-08-11 **Decision:** Upptime in a dedicated public repository, with the generated site -served from Cloudflare Pages at `status.trymighty.ai`. +served from GitHub Pages at `status.trymighty.ai` and Cloudflare providing +DNS-only resolution. ## Requirements @@ -40,8 +41,8 @@ Upptime maps the public operating workflow onto tools Mighty already reviews: monitoring, and resolved updates. - Maintenance is an Issue with explicit UTC start/end metadata. - Git stores check history and reviewed configuration. -- A static site is generated onto `gh-pages` and can be served by Cloudflare - Pages without an application server or database. +- A static site is generated onto `gh-pages` and served by GitHub Pages without + an application server or database. This repository deliberately changes two upstream defaults. Short incidents are **not deleted** (`skipDeleteIssues: true`), and self-updating workflows are @@ -50,14 +51,13 @@ through review. ## Hosting decision -Cloudflare Pages should serve the already-generated `gh-pages` branch. It gives -Mighty a static global edge site and managed TLS outside the GCP production -stack. Use the Cloudflare Git integration restricted to this public repository; -do not put a broad Cloudflare API token in GitHub Actions. GitHub Pages is an -acceptable fallback host if the Pages integration cannot be approved. +GitHub Pages serves the already-generated `gh-pages` branch and manages TLS +outside the GCP production stack. Cloudflare provides a DNS-only CNAME from +`status.trymighty.ai` to `trymightyai.github.io`; there is no Cloudflare Pages +project, Worker, proxy, or API token in the publication path. The public GitHub Issues list is the alternate communications URL if the custom -domain or Cloudflare is unavailable. This is not complete independence—Upptime +domain or static page is unavailable. This is not complete independence—Upptime still uses GitHub for checks and incident data—so Mighty’s existing internal monitoring remains the source for paging and audit evidence. The page is a communication layer and an independent secondary observation. diff --git a/docs/HOSTING.md b/docs/HOSTING.md index 9df7c7c3b..cecf80df9 100644 --- a/docs/HOSTING.md +++ b/docs/HOSTING.md @@ -9,7 +9,9 @@ GitHub Actions (secondary public checks) └── gh-pages: generated static website │ ▼ - Cloudflare Pages + TLS + GitHub Pages + GitHub TLS + │ + Cloudflare DNS-only CNAME │ status.trymighty.ai @@ -53,34 +55,22 @@ and the static shell has its own schedule. 8. Confirm that `history/` and the `gh-pages` branch contain only Mighty data. Never import the upstream demo measurements. -## Cloudflare Pages (recommended) - -Wait until `Static Site CI` creates `gh-pages`, then: - -1. In Cloudflare Pages, connect the public `TryMightyAI/status` repository. -2. Select `gh-pages` as the production branch. -3. Use no framework preset, no build command, and `.` as the output directory; - the branch already contains generated static files. -4. Expose no build secrets. Restrict the Cloudflare GitHub integration to this - repository rather than reusing a broad application API token. -5. Attach the custom domain `status.trymighty.ai`. When the zone is in the same - Cloudflare account, let Pages create the required DNS record and certificate. -6. Leave Cloudflare Access/password protection **off** for the public page. -7. Keep the default `*.pages.dev` hostname enabled as an operator diagnostic URL, - but publish the GitHub Issues list as the Cloudflare-independent fallback. +## Production hosting: GitHub Pages with Cloudflare DNS -Cloudflare must deploy after every force/update to `gh-pages`. Validate this with -a harmless branding pull request before launch. The static shell is rebuilt for -configuration/branding changes, but live component status, incident comments, -and scheduled maintenance are fetched from GitHub at page load; an outage update -does not wait for a Cloudflare rebuild. +`Static Site CI` publishes the generated site from `gh-pages`. GitHub Pages +serves that branch, owns the certificate, and redirects HTTP to HTTPS. Cloudflare +is DNS-only: `status.trymighty.ai` is a CNAME to `trymightyai.github.io` and is +not proxied through Cloudflare. -## GitHub Pages alternative +In repository Pages settings, keep the source set to the `gh-pages` branch at +`/ (root)`, preserve the custom domain `status.trymighty.ai`, and keep HTTPS +enforcement enabled. Do not enable Cloudflare Pages, Workers, or proxying for the +same hostname without a reviewed hosting migration. -If Cloudflare Pages cannot be approved, publish `gh-pages` `/ (root)` with -GitHub Pages and configure `status.trymighty.ai` as the custom domain. Do not run -both providers for the same custom hostname. The public repository and Issues -remain the alternate channel. +GitHub Pages does not apply the Cloudflare-style rules in `assets/_headers`. +Those rules remain a reviewed baseline for a future hosting layer, not evidence +of live response headers. The public repository and Issues are the independent +fallback if the custom hostname or static page is unavailable. ## Launch validation @@ -95,29 +85,29 @@ curl --fail --show-error https://gateway.trymighty.ai/health Also verify: -- valid TLS, automatic HTTP-to-HTTPS redirect, and the security headers from `assets/_headers`; +- valid TLS and automatic HTTP-to-HTTPS redirect; treat `assets/_headers` as an unapplied baseline while GitHub Pages remains the host; - logo, CSS, component history, and incident links; - a future UTC maintenance test appears before its start, then moves into past history after closing/completion; - a repository admin can post an update when the Mighty application and normal identity path are assumed unavailable; -- Cloudflare redeploys an updated `gh-pages` commit; +- GitHub Pages deploys the updated `gh-pages` commit; - the GitHub Issues fallback is documented in the internal incident runbook; - private vulnerability reporting works without public disclosure; - an external monitor checks the public status page without depending on this - repository's scheduler or Cloudflare Pages; and -- launch screenshots, headers, workflow run URLs, DNS/TLS result, and test Issue - export are saved to the approved evidence store. + repository's scheduler or GitHub Pages; and +- launch screenshots, response headers, workflow run URLs, DNS/TLS result, and + test Issue export are saved to the approved evidence store. Do not claim the status-page control is operating until this test is complete. ## Ongoing hosting controls -- Monitor the status page itself from a service that does not depend on - Cloudflare Pages or this repository's scheduler. +- Monitor the status page itself from a service that does not depend on GitHub + Pages or this repository's scheduler. - Review repository access and workflow job permissions at the organization’s approved cadence; remove leavers promptly. -- Review Cloudflare Pages project access and deployment history. +- Review GitHub Pages settings and deployment history. - Export the repository and Issue/comment audit population for each SOC 2 review period. A screenshot alone is weak evidence. - Test the alternate publishing path at least annually and after material diff --git a/patches/upptime-status-page-main.patch b/patches/upptime-status-page-main.patch index 11b945e4e..ffb16d70f 100644 --- a/patches/upptime-status-page-main.patch +++ b/patches/upptime-status-page-main.patch @@ -1,13 +1,40 @@ +diff --git a/post-process.ts b/post-process.ts +index 70ea5ab..bf095ab 100644 +--- a/post-process.ts ++++ b/post-process.ts +@@ -13,7 +13,7 @@ export const postProcess = async () => { + } = load(await readFile(join("..", ".upptimerc.yml"), "utf8")) as any; + const baseUrl = (config["status-website"] || {}).baseUrl || "/"; + +- execSync(`sapper export --legacy --basepath ${baseUrl}`, { ++ execSync(`sapper export --legacy --basepath ${baseUrl} --entry "/ /history/website /history/api /history/scan-gateway"`, { + stdio: "inherit", + }); + diff --git a/src/components/LiveStatus.svelte b/src/components/LiveStatus.svelte -index 16eec5d..e266005 100644 +index 16eec5d..dd893d6 100644 --- a/src/components/LiveStatus.svelte +++ b/src/components/LiveStatus.svelte -@@ -17 +16,0 @@ +@@ -14,14 +14,13 @@ + if (!apiBaseUrl) apiBaseUrl = "https://api.github.com"; + if (!userContentBaseUrl) userContentBaseUrl = "https://raw.githubusercontent.com"; + - const graphsBaseUrl = `${userContentBaseUrl}/${owner}/${repo}/master/graphs`; -@@ -24 +23 @@ + let form = null; + + let selected = "week"; + + onMount(async () => { + try { - const res = await fetch(`${userContentBaseUrl}/${owner}/${repo}/master/history/summary.json`); + const res = await fetch(`${userContentBaseUrl}/${owner}/${repo}/main/history/summary.json`); -@@ -101,14 +100,2 @@ + sites = await res.json(); + } catch (error) { + handleError(error); +@@ -98,20 +97,8 @@ + + {:else if sites.length} + {#each sites as site} -
+ {site.name} + +@@ -153,12 +140,6 @@ + + +