Sessions
Milestone 0 closed, the relay live, the app on TestFlight with its walkthrough
Intent
Start the build from the guide: verify the two Grafana assumptions, create the identities the app needs, ship the relay for the estate, and get the SwiftUI app compiling on the Mac mini.
What was done
- Grafana 13.2.1: every alerting, silence, search and user path in the guide answers 200. JWT auth is on (
X-JWT-Assertion, Keystone'sbrazierJWKS, expectissandaud, lookup by email, no auto sign-up) and was proven with a locally signed token of Keystone's claim shape: it lands on CJ's existing user as Admin, the alerts and silences APIs answer with it, and a wrong audience is refused. - Keystone application
brazier(provider 46): public client, PKCE,brazier://auth/callback, openid, email, profile and offline_access mappings. - Apple App ID
org.guysinc.brazier(WFDZ9J88XU) registered with Push Notifications through the App Store Connect API. - Relay under
relay/: one Worker, one KV namespace, HMAC overtimestamp:bodyverified against a real Grafana capture, device registry keyed by lower-case username, label routing, 7-day dedupe, APNs with provider tokens, 410 drops the device. 19 tests in the Workers runtime, in CI. Deployed tobrazier.gicloud.orgwith the webhook secret; a Blackbox probe watches/health. - App under
app/: 3,034 lines of Swift, xcodegen project, Servers, Auth, GrafanaClient, Alerts, Push, Settings, a Dashboards stub, the brand's tones and fonts, the Curl. Builds for the simulator with zero warnings and lists the estate's alerts. - Grafana contact point
brazier(webhook, HMAC, resolved messages on) provisioned in org 1 and the org's root policy moved to it, loaded with the provisioning reload API, no restart. A temporary rule fired through it: the relay's log shows the signed webhooks verified, parsed and routed, with APNs reported unconfigured. - APNs: CJ's first key came out sandbox-only (Apple caps team-scoped push keys at two, held by Honeywick and Guys Inc Cloud); Honeywick's team key carried the relay for an hour, then CJ's second key
PTDYNZWJJJreplaced it. Through the relay, a push to a bogus production token came back from Apple as BadDeviceToken and the device was dropped: transport, provider token and drop path exercised for real. - App Store Connect: CJ created the app record (6817156133). First archive and upload from the Mac mini as the claude user, cloud-managed distribution signing through the API key;
make archive uploadinapp/viascripts/remote.sh. Version 0.1.0 build 1 uploaded and processing. - Direction change from CJ mid-session: no presets, public from the first moment, a walkthrough. Decision 0004: sign-in is the Grafana's own login page in a web view (password or SSO), session kept and renewed; token second; OIDC through JWT auth advanced. Relay 0.2.0 verifies a device's owner by asking that Grafana
/api/userwith the app's credential, only for origins onGRAFANA_URLS; devices filed by login with email aliases; proven live with a throwaway service-account token. App build 2: Welcome, Server (live probe), Sign in (three methods), Notifications (optional relay with a test, skippable), Done; every preset removed. Boards, architecture page and README follow. - CJ's first run of build 2 on his phone: Keystone's passkey step failed inside the in-app web view (iOS allows passkeys only in the system sign-in sheet), and the "sign in with Grafana" versus "single sign-on" cards read as two things. Relay 0.3.0 publishes per-Grafana sign-in at
/.well-known/brazier(SIGN_INvar); build 3 takes the relay on the address step, leads with "Sign in with Guys Inc" through the system sheet, keeps Grafana's page for password Grafanas with a passkey warning, and moves manual issuer and client id to the server's advanced settings. Decision 0004 amended. - CJ: "Why is the relay required? Why can't it be auto discovered." Relay 0.4.0 serves its discovery document on the Grafana's own hostname through a Worker route, names its own address in it, and a DNS TXT record at
_brazier.<host>is the alternative for anyone without a proxy. Then CJ: "this needs to work for any OnPrem deployment of grafana with any IdP in front or the default grafana basic Auth." Checked against a throwaway Grafana 11.6 and the estate, and closed: relay 0.4.1 forwards the whole cookie jar and takes Basic-auth webhooks; build 4 discovers relay and provider from the address alone, adds a native username-and-password card, reads what each Grafana's login page offers to order the cards, keeps every cookie for a Grafana behind a sign-in proxy, and treats such a gate as reachable. Build 4 uploaded to TestFlight. Decision 0004 amended twice more;docs/reference/compatibility.mdis the matrix. docs/reference/identifiers.mdandrelay.mdcarry every id created tonight.
What was learned
- Grafana 13 removed the receiver test endpoint and its replacement accepts no body shape we tried; a temporary rule with
notification_settings.receiverforces a real send. The signature is hex HMAC-SHA256 over<timestamp>:<body>. - Grafana's
jwk_set_urlmust be https; test withjwk_set_file. Grafana keys the user onsuband syncs login and email from every token, so a test token that reuses a realsubwith another email renames that user. Grafana refusesPUT /api/users/:idon external users; the fix is another token with the right claims. - Keystone's client-credentials grant answers
invalid_grantin every form, so no headless ID token; the first real one comes from the app. - Grafana's OAuth-created user has the email as its login, so the JWT username claim is
email, notpreferred_usernameas the guide first said. - vitest-pool-workers 0.22 is a Vite plugin (
cloudflareTest), has nofetchMock, and its workerd caps the compatibility date; npm 10.9.8 needs--legacy-peer-depsfor this dependency set. - On the Mac, simulators are per user and a simulator build with signing disabled has no entitlements, so keychain writes fail silently; the Makefile signs ad hoc.
- Headless archive: xcodebuild says "User interaction is not allowed" when the build user's keychain is locked or a freshly created signing key has no access list for codesign. The Makefile now unlocks
claude.keychain-db, sets no timeout and runsset-key-partition-list. The first failed attempt still created a development certificate through the API with no local key; Xcode then refused to make another for "this machine" until that orphan was revoked (DELETE /v1/certificates/:id). - A brand-new APNs key takes about a minute to propagate at Apple; the first push right after loading it fails, the next succeeds.
- iOS refuses WebAuthn in a WKWebView, so any SSO that needs a passkey must go through ASWebAuthenticationSession; that forces an OIDC client for the app and Grafana's JWT auth, which is why the relay now publishes those settings.
- Cloudflare's browser check on brazier.gicloud.org refuses requests with no User-Agent; Grafana sends one, test clients must too.
- Grafana's JSON login endpoint (
POST /loginwithuserandpassword) answers with the session cookies on 200,password-auth.failedon 401, andauth.client.notConfigured(400) when the password form is off, so a native password card needs no page at all./api/frontend/settingsneeds a session, but the login page itself is public and itsgrafanaBootDatasays whether the form is on, which providers exist and whether anonymous access is on;?disableAutoLogin=trueshows it even on a Grafana that auto-redirects to its provider. - An auth proxy in front of Grafana keeps its own cookie, so a session credential must be the whole cookie jar for the host, not one cookie; the relay forwards it untouched. Grafanas older than 11 cannot sign a webhook, so the relay also takes HTTP Basic with the shared secret.
Open
- CJ and Daniel: install build 4 from TestFlight, enter grafana.gicloud.org (the relay and "Sign in with Guys Inc" appear on their own), tap Sign in with Guys Inc (system sheet, passkey), allow notifications; then a forced alert proves the first real push and a silence from the phone closes milestone 2. Daniel needs the Keystone group
meade-manor-adminsto sign in. The session capture and its ten-minute rotation are unproven until then. - Grafana orgs 2, 3 and 4 (Guys Inc Public, Personal, Meade Manor) still notify in-app only; copy the
brazierreceiver per org when CJ wants their alerts on the phone. - Milestone 3: dashboards web view session carry-over, uptime tiles, iPad. Also from the compatibility check: the fronted-Grafana path (Authelia, oauth2-proxy, Cloudflare Access) has fixtures but no live target yet; the token card should say a gate in front of Grafana blocks a token unless
/api/*is let through; anonymous access is browse-only, later.