Admin User Guide
Admin (aegis-admin, served at aegis.purdyandfigg.dev)
is the control panel for the whole platform. Almost every other module — Router,
Monitor, Analytics, compliance scanning, QA Bot — is configured or read through it.
It is also the MCP edge router: Claude connects to one endpoint here and Admin proxies the rest to the other workers over service bindings. See Claude MCP Setup.
What's where
| Section | What it does |
|---|---|
| Dashboard | Traffic summary and route health at a glance |
| Pages | Create, edit, sign off, lock, archive and promote pages. Its sub-sections: Proxied (served through get: Lovable, R2, Cloudflare), Shopify, Clones (Build Bot's built pages and their templates) and Aegis Cart (pages sold through Aegis's own cart: Ecommerce → Pages). Proxied, Shopify and Aegis Cart rows have tick boxes on the left: a bar then offers Archive, Unarchive, Watch and Delete for the ticked pages (only what they and you are allowed: a superadmin, the page's author, or the Archive / Delete permission), with a progress bar and a summary of what was done. Ecommerce → Pages has the same search and filters (region, status, watching) above its table. Opening the setup wizard from Proxied or Shopify sets the page's kind, and its title says which. |
| Checkouts | The checkout link builder |
| Pixel Manager (under Tools, with Ecommerce) | Tracking scripts and which routes get them |
| Analytics | Clicks, channels, attribution, visitor journeys |
| Uptime Monitor | Audit reports and visual baselines |
| Compliance | Ad-copy compliance scans (Passmark) |
| User Manager | Accounts and per-area permissions, including which Pages sections a person sees: Proxied, Shopify, Manage Ecommerce, Clones |
| GA Framework | The module list every page type is built from — elements, descriptions, screenshots, valid / unsure |
| Settings | General, Analytics, Audit, Integrations, Page Types, Slack, Sync, Tokens |
Two more live on the route's Options menu rather than in the sidebar, because they are about one page: 🧪 QA Test (write the test) and 📋 QA Status (read what it found, and triage). They replace the old standalone QA Bot app — see the QA User Guide. Global Test Templates is under the profile menu.
1. Routes
A route maps a path on your domain to whichever backend actually hosts that page, so the customer never leaves the brand domain.
Origins supported today: Shopify (native Liquid pages), Lovable React SPAs, Cloudflare Pages, and arbitrary external origins. R2 and Render have been requested but are not yet selectable.
Fields worth knowing:
| Field | Effect |
|---|---|
| Path + Store | Together these are unique. The same path can exist for uk and us |
| Origin | Where the request is proxied to |
| Preview URL | When set, used instead of the computed live URL across Admin, QA, compliance scanning and Monitor |
| Page type | Drives Slack routing rules and reporting. Managed in Settings → Page Types |
| Pixels | Per-route pixels override the global set |
| Monitor config | Which checks run at which tier — see Monitor User Guide |
| Slack notify / channel | Mute this route, or send its events to a specific channel |
If a route 404s or serves the wrong page, the origin is usually down — the Router
falls back to DEFAULT_ROUTE_URL rather than erroring, so "wrong page" often
means "origin unreachable".
Locking
Locking sets an edit password so a load-bearing route can't be changed casually. It is a don't-touch-this guard, not a security boundary — treat it that way.
Archiving
Archived routes stop serving and drop out of the default lists without being
deleted. Deleting is separate and permission-gated (access_delete).
Going live, and standing down
Enable and disable open a modal that walks the steps rather than flipping a switch silently. Each step reads a fact the server just returned, so it can come back amber — "signed off", "a test exists", "added to the monitor" are checks, not announcements. Going live on a Shopify-hosted page also scans it once to seed the GA framework from what is actually on the page.
Disable runs the same sequence in reverse, with one honest ending: if the page is not proxied, turning it off in Aegis does not take it down. It stays live at its host until you disable it there, and the last step says so rather than implying the page is now dark.
Creating a route also puts its QA suite in create test rather than seeding a placeholder — an empty suite should read as work outstanding, not as a test that passes because it asserts nothing.
2. Sign-off: four approvals, not three
A route cannot be activated until all four sign-offs are in:
| Role | Confirms |
|---|---|
| Page Author | The content is accurate |
| Head of Growth | Marketing angles and UTM tags are right |
| Head of Tech | Performance and technical QA |
| AI Evaluator | Passmark has scanned the copy |
Trying to activate without them returns:
Cannot activate: Page Author, Head of Growth, Head of Tech, and AI Evaluator must all sign off completely.
The first three are ticked by default on a new route; the AI sign-off is not. In practice the AI evaluation is the one blocking activation.
A human can override the AI sign-off, but the override requires a written reason and is recorded in the route audit log. That is deliberate: an override is a decision someone owns, not a checkbox.
This gate is what stops paid budget pointing at an unfinished page.
3. Checkout builder
Shopify cart permalinks are fiddly to hand-write. The builder constructs them from a UI: variants, quantities, selling plans, automatic discount codes and UTM parameters.
Built links are stored so the Monitor can validate them, and routes can reference one via checkout_id. Routes can also opt out with ignore_checkout where a checkout assertion doesn't apply.
Routes expose a shortlink at /checkout/<name>, matched case-insensitively and
with spaces interchangeable with hyphens.
4. Pixels
Pixel Manager holds the tracking scripts injected into the <head> of proxied
HTML responses.
- Pixels can be attached per route, and a per-route set overrides the global set.
- New routes can have pixels attached automatically via the per-pixel auto-apply flag.
- Several older routes have hand-attached pixels, which is why they drift from the global config. If you are unsure whether a route should have its own, ask before removing them — it may have been given a dedicated pixel for one campaign.
Whether a pixel actually fires is verified by the Monitor and by QA runs, not by Admin.
5. Analytics
Covered properly in the Analytics User Guide. In short: Admin shows clicks by source/medium/campaign, GA4 firing rate, UTM coverage, visitor touchpoints and session distribution, plus a raw export.
Treat click data as reliable and revenue/ROAS as not yet live — order data
needs the Shopify orders/paid webhook registered and enabled, and that is still
outstanding. The "Performance by Page Type" panel deliberately ghosts itself
rather than showing a misleading £0 table.
Analytics writes can be turned off entirely with Log Analytics in Settings → Analytics. That single switch now governs both the click path and the webhook queue path.
6. Users and permissions
Permissions are per area, not blanket admin. Ask for what you need.
access_routes, access_routes_viewer, access_pixels, access_checkouts,
access_analytics, access_qabot, access_nudger, access_nudger_viewer,
access_promote, access_archive, access_delete, plus region and
page-type restrictions that limit which routes a user can see at all.
Superadmins bypass these checks.
7. Slack
Aegis posts events to Slack by rule, not by hardcoded channel. Settings → Slack has three sections: Channels, What Aegis Sends and Routing Rules.
Channels — a name, a Slack channel ID, an active flag, and optionally one marked default.
Invite Aegis-QA to the channel first
Aegis posts through the Aegis-QA Slack app using a bot token. A bot can only post to channels it is a member of, so adding the channel in Aegis is not enough — you must also add the app to the channel in Slack:
- open the channel → Integrations → Add apps → Aegis-QA, or
/invitein the channel using the app's username (App Home → Default Username), which is not necessarily the same as its display nameThen press Test on the channel row in Aegis. A
not_in_channelerror means the app has not been added yet. Do this before relying on the channel — a missing invite is silent apart from that error.
Where to find the channel ID: open the channel in a browser and take the
second ID from the URL (app.slack.com/client/T…/C0AUDTK337U), or channel name
→ About → bottom. A channel ID is not a secret.
Webhook URL is the legacy transport, kept only so old channels keep working. A webhook URL is a credential — anyone who can read the database can post with it — and nothing posted through one can be threaded onto, reacted to or edited afterwards. Prefer the channel ID.
What Aegis Sends — a switch per update type, applying to the whole fleet. Off means off everywhere: no channel, no rule, not the legacy webhook. Only the disabled ones are stored, so a newly added update type sends by default.
This is the only control that reaches the digests. hourly_failure_summary,
daily_summary and monitor_summary cover the whole fleet rather than one
route, so a route's own Slack settings can never silence them — and a routing
rule only decides where an update goes, never whether it is sent.
The usual reason to use it: qa_test_failed and hourly_failure_summary report
the same failures, so leaving both on announces every failure twice.
Routing rules — match an event to a channel:
| Rule field | Matches on |
|---|---|
event_type |
A specific event, or all |
match_page_type |
Landing, AI Landing, Blog, FAQ… |
match_origin_kind |
Shopify, Cloudflare, Other |
match_store |
uk or us |
priority |
The order rules fire in — not a tie-break |
Events currently emitted, grouped as the dropdown groups them:
- Routes —
route_locked,route_signoff,route_archived,route_deleted,preview_went_live - QA —
qa_test_created,qa_test_preview,qa_test_signed_off,qa_test_failed,qa_test_failing_repeatedly - Summaries —
daily_summary,hourly_failure_summary,monitor_summary - Audits —
audit_shared,audit_manual_report,image_audit_failed,visual_drift,visual_audit
The rules that surprise people
- Every matching rule fires.
priorityorders them; it does not pick a winner. Three matching rules means three channels get the message. - A per-route channel replaces rule matching entirely. Set
slack_channel_idon a route and no other rule fires for it. - A muted route stays muted. With
slack_notify = 0, when nothing resolves Aegis deliberately does not fall back to the global webhook. - The bot must be in the channel. Rules can resolve perfectly and the post
still fails with
not_in_channelif Aegis-QA was never added to it.
If no rule matches and the route isn't muted, the default channel gets it. Slack failures are swallowed by design — an outage never breaks the action that triggered the message. That is also why a missing invite is quiet: use Test to check a channel rather than waiting for a real event.
Resolved alerts thread onto the original
Because Aegis posts with a bot token it keeps the message reference, so a visual drift alert is updated in place when someone accepts the new baseline: a ✅ reaction on the alert and the resolution as a threaded reply, rather than a second message further down the channel. If you accept a baseline and see no new message, check the thread on the original alert — that is the intended behaviour, not a failure.
monitor_summary belongs to no single route, so only rules without route
filters can match it. That is the usual reason a summary doesn't arrive.
8. Settings
| Tab | Contents |
|---|---|
| General | Application-wide settings |
| Analytics | log_analytics kill switch, Shopify order ingestion |
| Audit | Global visual ignore-selectors — what the Monitor hides before capture |
| Integrations | Connection details for external services |
| Page Types | The page-type list and badge styles used across routing and Slack rules |
| Slack | Channels and routing rules (above) |
| Sync | The database sync token used by the Hetzner fallback node |
| Tokens | External app tokens for API and export access |
Each setting's title carries its description on hover, and saving says what it saved — a toast naming the settings that actually changed, or why the save failed. A silent save is indistinguishable from a save that did not happen.
8a. The GA framework
Three levels, and reading them in this order is the whole thing:
- The framework (sidebar → GA Framework) says what each
data-modulecarries. One row per module, with itsdata-*elements, a description, an uploaded screenshot, and a valid / unsure state so a name nobody here can decode becomes a list to take to the developers. Removing a module archives it rather than deleting it, and archived rows can be restored. The table links back to the Confluence SSOT it is a copy of. - A page type (Settings → Page Types) says which modules it has. Every route of that type inherits the list.
- A page is measured against its type, by the Monitor — not by a QA run. Asserting an attribute exists is a cheap job that was being done by the most expensive runner on the estate.
ga_check_mode decides what a mismatch does: off, log (record it, raise
nothing) or on (file a defect). It is log today, and on on only a changed
value files anything — all eleven page types are still seeded with all
thirty-eight modules, so a missing module currently means the page type has not
been trimmed yet rather than that the page is wrong. Defects it files carry a GA
pill, one open defect per page, updated rather than re-filed.
8b. The day's findings
/api/report/findings, linked from the user menu, from the Forge report and from
the daily Slack post behind its own toggle. Every open defect grouped store →
severity → owner, with the run that found each one linked.
Two things about how to read it. Coverage is stated at the top and called partial below two thirds — a short list means less when half the estate has not been checked. And "not defects" are the ones a person judged: an automatic expiry after seven days is not a decision and is kept out.
The severity, store and not-a-defect filters live in the URL, so a narrowed view can be sent to whoever owns it. The QA report, the audit report and this page share one renderer and one palette, so the three read as one set.
9. Scheduled work
Admin runs a daily cron (0 0 * * *) which:
- syncs the Shopify product/discount cache for both stores
- prunes QA test runs older than 72 hours
- applies analytics retention (currently 365 days, deliberately long because there is no archive behind it yet)
10. Related
- QA Manager Handbook — running QA day to day
- Monitor User Guide — what the checks do
- Analytics User Guide — the metrics in depth
- Router User Guide — what happens at the edge