Skip to main content

Staff Console (internal)

Aglyn staff only

This area documents internal tools available to Aglyn staff with a staff claim. It's not accessible to regular host owners.

The staff console is where Aglyn operators manage the platform and support customer organizations.

Runbooks — read these before you need them

Every doc below is written to be followed under pressure, which is exactly when nobody has time to find it. Until AGL-2141 this index linked none of them, while two of them told the reader to pre-read a third.

WhenRunbook
A customer wrote inSupport triage — priority ladder, the billing answers, and every escalation route out of a ticket
The site is down or degradedIncident response · Platform health board
Personal data may have leftBreach notification
Someone reported abuse, DMCA or CSAMAbuse reports
An asset or a whole org has to be shut offLockdown — feature locks, read-only mode, org suspension, and the asset quarantine runbook
Data has to be restoredDisaster recovery
A DSAR or erasure arrivedPrivacy requests

The staff organizations directory

The staff audit log

The Password card on a user's staff detail page, offering a reset email or a directly
set password

What's there

Staff overview

Platform metrics, the newest organizations, purchases, and per-org usage; plus search. The MRR estimate counts only organizations with a live Stripe subscription — staff plan overrides, comped accounts, and canceled or unpaid subscriptions contribute $0, and annual plans count at their per-month equivalent rather than the month-to-month price. The tile shows how many organizations are billing and how many are comped, so a paid plan that bills nothing never inflates the headline.

Support queue

Every organization's support tickets in one triage list: filter by open/closed, reply as Aglyn staff, close or reopen.

Plugin reviews & realm trust

The marketplace review queue, plus a Listed plugins — realm trust table for granting or revoking realm trust per version. Rejecting a version is a verdict, not a kill: it stops new installs, but a site already pinned to those bytes keeps running them. Where that has happened the review panel says so and offers Stop this version, the per-version kill switch — every site pinned to it renders a placeholder on the next load, and the rest of the listing's versions are untouched. Taking the listing down is the wider hammer: it stops every version, including the approved one customers are using. Restoring a listing clears only what the takedown did, so a version stopped separately stays stopped.

Organization management

Audited plan and entitlement overrides, suspension, and GDPR-erasure flags, per organization. The directory is listed server-side with the Admin SDK (so it shows every org, not the subset client rules would return), ordered by organization id, 25 per page with Previous/Next.

Free workspace limit

How many free workspaces one account may hold, on a card at the top of the organizations page. Every free quota in the product — sites, media, bandwidth, Assist messages, form submissions, contacts — is counted per workspace, so without this ceiling one account multiplies the whole free allowance by however many workspaces it opens. The default is three.

What is counted is free workspaces the account owns now or created. Three consequences worth knowing before you answer a ticket about it:

  • Paid workspaces do not count. An agency or consultant whose workspaces are paid is unaffected at any number. A workspace whose subscription lapses becomes free again and counts again.
  • Being invited to somebody else's workspace never counts. A contractor on ten client rosters owns none of them.
  • Handing a workspace to another account does not free a slot, because the creator is recorded separately from the owner. That is deliberate: otherwise "transfer it to an alt account, create another, take it back" would be a way round the ceiling. Deleting a workspace does free a slot — the allowance it was consuming went with it.

Changing the number needs the super staff role and is audited with a before, an after and a typed reason. It takes effect within fifteen seconds across the platform and needs no deploy. Lowering it never removes anybody's workspaces: an account already over the new number keeps every one and simply cannot create another. Staff creating a workspace on a customer's behalf are exempt from the ceiling entirely.

A person who hits it sees the number, and is told to upgrade one, delete one, or ask us — so "support raised it for this account" is a real answer. Today that is done by raising the platform number; there is no per-account override.

Entitlement editor

Full override editor for an organization's entitlements, its plan, and per-organization release flags. Every override needs a reason chosen from a fixed list (negotiated contract, support remediation, early access, correction, sales trial, or other — which requires a note). The reason and note are written onto the audit row beside the before/after and shown wherever that row is: the audit log, the organization's own page, and the acting staff account's trail. The log is append-only, so a reason not given at the time cannot be added afterwards. The reason is checked by the server that performs the override, not only by the dialog, so a request without one is refused rather than applied. The database now refuses a plan, entitlement or release-flag change made any other way, so that server is the only route an override can take and the reason cannot be skipped by going around the console. Changing an organization's release flags needs the super staff role; plan and entitlement changes are open to billing staff as well. Clearing an override removes it. Emptying a quota field, or setting a feature or release flag back to Inherit, deletes that override on save and hands the organization back to its plan default — one at a time, so the overrides you leave in place are untouched. 0 is not empty: a quota of zero is a real override, a cap of none (a comped 0% fee, an organization held to no POS registers), and it is kept. A quota field that cannot be read as a number of 0 or more refuses the save rather than being ignored. The override and its audit row are saved together — either both land or neither does. Read the message on a failure rather than assuming: it says "nothing was written … safe to retry" when the server refused the change, and says the outcome is not known when the request never got an answer (a dropped connection, a gateway error). In that second case, check the organization and the audit log before saving again — saving blind would record a before-state that is already overridden.

Users admin

Staff-claim management and disabling users, with gated listing and an exact-email lookup for accounts beyond the loaded pages. Staff access is granted to an existing account, so if someone isn't found, have them sign in to Aglyn once and then search their email again. Each account opens a detail page showing identity/auth state, staff role, every organization membership with roles and per-site access, and its recent audit trail.

A Legal acceptances card on the same page answers the two questions a terms dispute asks: which version of the Terms and Privacy Policy this person accepted and when, and whether the 30-day arbitration opt-out window (ToS §18.5) is still open. The window runs from the person's first acceptance of any version, so a later re-acceptance does not restart it. Each row carries the content hashes of the exact documents that were shown, the door the acceptance came through, and the IP recorded at the time. The card is read-only — those records are evidence about the account holder, and nothing in the product can add, amend or delete one.

If the card says the records could not be read, that is not the same as "no acceptance on file": do not answer a dispute from that screen until it loads. An account can also legitimately have no record — accounts created before clickwrap capture, and SSO/invite doors, never passed a consent checkbox. Those accounts are asked to accept by a banner in the console the next time they sign in, as is anyone whose accepted version has been superseded by a newer publish.

Password help

On that detail page, a Password card can email the account a reset link, or set its password directly for an account that cannot receive mail. Setting a password revokes the account's refresh tokens (so every device signs out) and emails the holder that an admin changed it. Both actions need the super staff role and are audited; the password itself is never recorded. An account with no email address supports neither.

Staff notes

Free-text support/billing context on each organization's detail page, visible to staff only (never in tenant-readable data) and audited.

Broadcast announcements

Push a product announcement or maintenance notice as an in-app notification to every organization's owner/admins (optionally one plan tier), respecting each recipient's mute preferences; audited.

Billing insight

Every organization's Stripe invoice history and default payment method (with delinquency state) render on its detail page.

Refunds

Directly under the invoice history, Refund a charge issues a full or partial refund against one of that organization's charges without leaving Aglyn. Any staff role can read the charges and how much of each is already refunded; issuing one is super, because it is the only staff action that sends money out. A reason is required, the confirmation names the amount and the charge, and the result is audited — see the refunds runbook before you use it, particularly on why a refund is a loss rather than a reversal.

Impersonation

Staff can open the console as a customer account (audited with a required reason, AGL-2125 — the dialog will not submit without one; a pinned warning banner with one-click exit shows for the entire session; staff accounts cannot be impersonated).

System emails

The mail Aglyn itself sends (organization invites, the monthly usage summary, internal alerts). Each one ships with built-in copy and can be replaced with a designed template built in the besigner, using email-safe blocks only. Set the subject and preheader from the editor's Properties panel; merge tokens the email supplies are listed there, and any token left unresolved is blanked before sending. Reset to default puts the built-in copy back. The list is generated from the emails the product actually sends, so staff edit the system emails that exist — adding one is a code change. Password reset and email verification are Aglyn's own and are fully editable. Billing emails — receipts, failed payments, refunds — are sent by Stripe from its Dashboard and are listed read-only.

Platform send rate

At the top of the same page. Everything Aglyn sends leaves on one Resend key from one verified sending domain, under a p=reject DMARC record — so a throttle or a reputation hit there is a rejection, not a spam folder, and it lands on every customer's password resets at the same time. This is the ceiling on outbound mail per hour across the whole platform, and it is a value, not a deploy: a sending-domain warm-up or a deliverability incident is handled by changing the number here.

The card shows the current hour's volume beside the ceiling, because the question during an incident is never "what is the limit" but "are we near it".

What the ceiling can and cannot do. It can defer a marketing campaign and a scheduled bulk sweep (the monthly usage summary). It can never refuse transactional mail — password resets, invites, order receipts, booking reminders — at any value. Those are counted, because the ceiling is about total volume on the domain, but they send regardless.

Nothing is lost when the ceiling bites. A scheduled campaign over it goes back to scheduled and the 15-minute processor picks it up in the next window; a usage-summary run stops without stamping the orgs it did not reach, and the hourly firing on the 1st and 2nd of the month mails them.

Reading the value needs any staff role; changing it needs super, the same bar as feature flags, and every change writes an audit row with the before, the after and the reason typed into the Why box.

Feature flags

Release-gate console features via Remote Config, with percentage rollout; staff preview everything.

Multi-tenant architecture

How organizations, membership, security rules, subdomains, and billing attribution fit together.

Audit archival

A nightly cron moves audit entries past the 90-day retention window into a Storage compliance trail (JSON lines, month-partitioned) and reminds staff of GDPR erasure requests past their 7-day hold.

Organization suspension

A staff toggle that serves 503s on the org's sites and shows the owner a banner.

Sales tax return

The quarterly Texas return: pick a period, read the Form 01-114 figures for Texas, see every row the sweep could not fully read, and export the working papers for the Webfile session.

Audit log viewer

A record of staff actions.

Coupons

Discount codes for Aglyn's own subscriptions. They live in Stripe — the console creates them there and reads them back, so a coupon made in the Stripe Dashboard shows up here and vice versa. Nothing on this page touches the discount codes a customer creates for their own storefront; those belong to the commerce plugin on their site.

Create a coupon takes a name (the text that appears on the customer's invoice), a type — Percent off or Fixed amount off — a Duration of Once, Repeating (for a number of months) or Forever, and optionally a redemption code, a max redemptions cap and an expiry date. A coupon with no code is applied by staff to a subscription; a coupon with one can be typed by the customer at checkout.

Before you commit, the form shows a net-margin rating for the discount — what is left after Stripe's fees and the plan's own cost. It is illustrative only: the binding check runs on the server when the discount is actually applied, so a rating that looks survivable is not permission.

Existing coupons lists every Stripe coupon with its promotion codes, redemption count, and a valid or expired state.

Do not contact

The platform do-not-contact list for phone numbers — calls and texts. It is not the email unsubscribe list; email opt-outs live with the campaign that sent them.

Aglyn sends no marketing calls or texts today, and the page says so: there is no consent record behind them yet. The list exists so that an outbound programme has something to check the day one starts, and so that an opt-out we receive now is not lost.

Record a request is for an opt-out that arrived outside the product — by email to privacy@aglyn.com, or spoken on a call. Give the number, how the request arrived (Said on a call, or Other / staff), the channel it covers (Calls or Texts), and a note. Replying STOP to a text will be handled automatically once texting exists; this form is for everything that does not arrive that way.

If they also asked us to delete the number we hold, tick that box and give the account uid. The number stays on the suppression list — that is the only thing that keeps it from being dialled again — and what is deleted is the copy on their profile, with SSO blocked from re-asserting it on the next sign-in.

A suppression outlives the contact record, and it can be undone: a number the person later opts back in for is marked Opted back in rather than removed, so the history of what was asked and when survives.

Access

Access is gated on a staff claim, enforced per handler and by scoped Firestore rules. The area doesn't advertise itself: /admin/* returns a plain 404 to anyone without the claim, and the Staff console entry is hidden from the account menu rather than shown-and-refused.

Internal documentation

Deeper runbooks for staff operations live with the platform ops docs, not in this public site.

Which identity holds staff

A staff claim lives on one Firebase Auth user record, and Aglyn has more than one pool of them: the project-level pool, plus a separate pool for every organization using SAML SSO. The same person can exist in two pools with two different records, and a claim on one is invisible to the other — which is why a grant can look successful while the person still gets a 404.

Staff can be granted to any identity. SSO is an option, never a requirement:

  • an SSO identity in Aglyn's own tenant;
  • an identity in the project pool, signing in with a password or Google;
  • an SSO identity in a customer organization's tenant.

All three are valid and supported. When a grant is made, the audit row records the pool alongside the uid, because a uid on its own does not identify an account when two pools can hold the same one.

Staff inside a customer's tenant — a property worth knowing

If a staff claim is granted to an identity that lives in a customer's SSO tenant, that customer's IdP administrator controls authentication for it. They cannot mint a staff claim — the claim is Aglyn's — but they control who can authenticate as the identity that holds one, and they control whether it keeps existing.

This is allowed and sometimes unavoidable. Two things make it safe to live with:

  • prefer granting staff to an identity in a tenant Aglyn controls, where there is a choice;
  • treat a customer-tenant staff grant as a reviewable row: it is exactly what a staff-access review should be looking at, and the staff user list shows the tenant so it is not an undifferentiated email address.

Offboarding

How a staff person is removed depends on which identity holds the grant:

  1. Revoke the staff claim in the users admin. This is the step that always applies, and it targets the pool the identity actually lives in.
  2. Disable or delete the account in whichever directory owns it — Google Workspace for an @aglyn.com identity, the customer's IdP for a customer-tenant identity, or Firebase Auth directly for a project-pool account.

Revoking the claim is not instant on its own: a claim change reaches a signed-in session at its next ID-token refresh, which the console forces once per page load, so a reload picks it up within about an hour at worst. When speed matters, disabling the account and revoking its refresh tokens ends the session immediately.

Break-glass access

zachary.w.gover@gmail.com holds super staff in the project pool, permanently and by design. It is not an oversight, not a migration leftover, and should not be "tidied up" — it is the account that still works when SSO does not. If SAML is misconfigured, the IdP is down, or a domain rule is set wrong, this is the way back in.

It is a deliberate trade, and the cost is real: an identity outside Google Workspace has no enforced MFA, no central offboarding, and no admin session revocation. The whole mitigation sits on the Google account itself —

  • strong two-factor, ideally a passkey or hardware key;
  • a unique password, never reused anywhere;
  • treated as a privileged credential, because it is one.

The rule below is written so it can never refuse this account: the account is on an ungoverned domain, so it falls outside the rule by construction rather than by an exception someone has to remember to maintain.

Requiring SSO for a company domain

An operator can require that every identity on a given email domain authenticates through a specific SSO tenant. The point is narrow: an address on a company domain that signs in with a password or a personal Google account looks like a company identity while being governed like a personal one — outside the directory's MFA, offboarding and revocation.

This is a rule about a domain, not about staff. It applies whether or not the person is staff, and it never requires staff to use SSO. Staff on other domains are untouched.

Two settings, both empty/off by default:

SettingMeaning
AGLYN_SSO_REQUIRED_DOMAINSdomain=tenantId pairs, e.g. example.com=example-tenant. Empty means no domain is governed.
AGLYN_SSO_DOMAIN_ENFORCEMENTon to refuse non-compliant sign-ins. Anything else is off.

Both must be set for anything to be refused, and only one case is ever refused: an address on a governed domain signing in with no SSO tenant at all. An identity on a governed domain that signs in through a different tenant is allowed and flagged for review, not blocked.

Self-hosting

These default to empty, so a self-hosted install governs nothing unless its operator configures it — with their domain and their tenant. Aglyn's own domain is not compiled in anywhere.

Turning enforcement on is a one-way door for anyone it refuses, so before flipping it:

  1. confirm SSO staff access works through a real sign-in, not a token inspection;
  2. migrate or retire every existing identity on the governed domain that has no tenant — including automation accounts, which are easy to forget and will simply stop being able to sign in.

Why am I getting a 404?

/admin/* returns a plain 404 with no explanation, deliberately — a stranger should not learn the staff console exists. That makes a genuine staff member's failure hard to tell apart from a broken route.

GET /api/auth/staff-self-check, with your own ID token, answers it. It reports the uid, email and pool of the session you are actually signed in as, whether that token carries the staff claim, and — if your own address exists in more than one pool — which of those records holds the grant.

It only ever reports on the caller's own identity, so it discloses nothing about anyone else and cannot be used to find out who is staff.