Skip to content

Rig Cockpit surfaces

"Rig Cockpit" is three different clients, and they are not equivalent. Only one of them can talk to the GPU brain. Opening the wrong one is the most common way to conclude that something is broken when nothing is.

1. The three surfaces

Web cockpit iOS app Capacitor shell
Where rig-cockpit.dashecorp.com (also rig-cockpit-worker.invotekas.workers.dev) TestFlight, app "Rig Cockpit" not shipped
Repo dashecorp/rig-cockpit-worker (src/web/dashboard.html) dashecorp/rig-cockpit-ios dashecorp/rig-cockpit-worker (shell/)
Runs on Cloudflare Worker rig-cockpit-worker iPhone, bundle no.invotek.rig-cockpit, ASC app 6782443501 would wrap the web cockpit in a WKWebView / Android WebView
Chat with Rig-E / the GPU Yes — the only surface that can No. No chat code at all would inherit the web cockpit's
Rig status, tenants, activity Yes Yes (read-only) inherits
Push (APNs) no yes yes (native registration)
Status live; installable since 2026-09-09 build 9 in TestFlight; never released to the App Store merged, never built for release

The GPU brain is reachable from the web cockpit and nowhere else. The Manager tab has a "Talking to" target picker (Manager plus every agent returned by GET /v1/agents); choosing rig-e routes sends to POST /v1/agents/rig-e/chat, which the Worker proxies to rig-agent-runtime with the per-agent X-Chat-Token injected server-side. The iOS app has five tabs — Rig, Terminal, Activity, Tenants, Settings — and no chat surface in any of them (rig-cockpit-ios#44 would add one; still open).

Which one to open

Job Surface
Chat with Rig-E; wake or read the GPU brain Web cockpit — see gpu-brain.md
Glance at agents / stuck issues / tenants from the phone either — the iOS app is fine for read-only
Anything requiring a push notification on the phone iOS app (the web cockpit has no push)
Demo the cockpit as an app icon without TestFlight Web cockpit → Add to Home Screen

2. Web cockpit

A single-file SPA (src/web/dashboard.html) served by the Worker, which also proxies /api/* to rig-conductor behind Cloudflare Access. Both rig-cockpit.dashecorp.com and the workers.dev origin serve it; the custom-domain route is not declared in wrangler.toml on purpose — it is managed in OpenTofu so the app repo never owns DNS/route state. Note that APP_URL (the base used to build magic-link emails) still points at the workers.dev origin.

Installable (rig-cockpit-worker#99, merged 2026-09-09). The Worker now serves a /manifest.webmanifest, PWA icons (/icons/icon-{192,512}.png, /icons/icon-512-maskable.png, /apple-touch-icon.png), iOS meta tags, and a shell-only service worker at /sw.js. Add to Home Screen gives a real app icon and a standalone launch.

The service worker is deliberately conservative, because the cockpit is a live operations console: it precaches only the shell at install, and every /api/*, /v1/*, non-GET and cross-origin request is passed straight through without respondWith(). A stale agent roster is worse than an error, so nothing fetched at runtime can enter the cache.

3. iOS app (rig-cockpit-ios)

Native SwiftUI, forked from the Cuti-E admin shell. Read-only. It authenticates against the same Worker (POST /v1/auth/apple) and reads the same proxied conductor API.

Three things about this repo that cost time if you do not know them:

  • The default branch is feature/fork-cutie-admin-cockpit, not main. Branch from it, PR into it, and read files from it.
  • The app did not compile from 2026-08-20 to 2026-09-09. APIError gained a .notAuthorized case and five view-level switches over it were left non-exhaustive with no default:. Fixed in rig-cockpit-ios#51. Compile locally before pushing Swift: xcodebuild -project Cuti-E-Admin/Cuti-E-Admin.xcodeproj -scheme Cuti-E-Admin -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' CODE_SIGNING_ALLOWED=NO build
  • Certificates, provisioning profiles and the App Store Connect key are healthy. The 2026-09-09 build signed and uploaded, which was that build's real purpose.

App Store state: version 1.0 has sat in PREPARE_FOR_SUBMISSION since 2026-06-20. Nothing has ever been submitted for review. TestFlight builds expire 90 days after upload — the June builds (1–8) expire 2026-09-18/19; build 9 (2026-09-09) expires 2026-12-08.

4. Capacitor shell

shell/ in rig-cockpit-worker (#100) — a thin Capacitor 6 container that remote-loads https://rig-cockpit.dashecorp.com and adds native push, splash and status bar, so the same web codebase can ship to the App Store and Play Store. ios/ and android/ are not committed; CI regenerates them from capacitor.config.json.

It is not shipped. Two gates remain, both in rig-cockpit-worker:

  1. The signing secrets (MATCH_*, ASC_*) are not set in that repo, so build-ios-testflight.yml degrades to archive-only.
  2. shell/Gemfile.lock is not committed. The TestFlight job runs bundle install --deployment and refuses to run without it; the lock-fastlane.yml workflow (ubuntu, zero secrets in scope) has to be dispatched and its PR merged first.

5. Uploading is not releasing

Verified 2026-09-09. The build workflow ran green — UPLOAD SUCCEEDED with no errors — build 9 appeared in App Store Connect as processingState: VALID / APP_STORE_ELIGIBLE, and the operator's TestFlight app still showed build 8.

The cause: the Internal Testers beta group has hasAccessToAllBuilds: false, so every build must be attached to the group explicitly. Nothing did it. Build 9 was attached by hand.

You cannot fix this by flipping the flag. Apple rejects hasAccessToAllBuilds on an UPDATE with 409 ENTITY_ERROR.ATTRIBUTE.NOT_ALLOWED"can not be included in a 'UPDATE' operation". It is create-time only. Verified, not assumed.

How a build actually reaches the phone

Step What happens
1 Trigger iOS TestFlight (.github/workflows/build-and-upload.yml) — workflow_dispatch, or push a v* tag. Runs on GitHub-hosted macos-26.
2 The build job computes the next build number from App Store Connect, signs with fastlane match (read-only, certs from dashecorp/ios-certs), verifies the signed entitlements, and uploads with altool.
3 The release-to-testers job (ubuntu-latest, needs: build) polls GET /v1/builds until processingState: VALID — 20 min cap, 30 s interval — then resolves the beta group by name (Internal Testers) and POSTs to /v1/betaGroups/{id}/relationships/builds. Added in rig-cockpit-ios#55 (merged 2026-09-09). It runs on the cheap runner on purpose: the wait is pure idling and macOS minutes are billed at a premium.
4 Only now is the build in TestFlight. Confirm the operator can see it.

The group is resolved by name rather than UUID because the UUID changes if the group is ever recreated.

Manual attach (older build, or the job was skipped) — App Store Connect MCP: add_build_to_beta_group. Do not try to set the group flag instead; see the 409 above.

Rule: never say "it's on TestFlight" until a build is attached to the group and the operator can see it. A green workflow and a VALID build prove neither.

6. Why the cockpit is shaped this way

Client stack — one web codebase, thin native shells. Not React Native, not three native apps. Chosen from four proposals scored by three judges. Two factors decided it: the agent test loop (web tests run headless in milliseconds, while Swift needs a macOS runner and no agent can verify it locally) and the fact that every feature already lands on web first — which is precisely why the iOS app never grew a chat tab. Recorded in rig-cockpit-worker docs/2026-09-09-native-shell-decision.md.

Live transport (issue rig-cockpit-worker#95, open). A phone drops an idle request after ~60 s (Safari and WKWebView alike) and a GPU wake takes ~9 minutes, so chat fails on the phone on every stack. The design decided on 2026-09-09: the turn lives in the runtime's Postgres and is delivered over an authenticated streamno Durable Object. The insight is that when the phone locks every transport dies identically, so the transport decides nothing about the headline case; where the turn lives does. Since the connection to vLLM lives in the runtime pod, whoever resumes a turn must own that socket — and that pod already has durable storage. Nine PRs; PR 1 is open as rig-conductor#2068.

Capacitor was adopted without the security review this workspace requires. The review was done after the fact — verdict adopt with conditions: the packages are genuinely Ionic's, the lockfile is integrity-pinned, and none of the resolved packages has an install script. Three blockers were found and are now fixed on main: the pinned version predated a navigation fix (6.2.1 → 6.2.2), no aps-environment entitlement existed anywhere in the repo (so the shell's App Store 4.2 justification did not actually function), and fastlane was unpinned in the same job that holds the signing secrets. See docs/2026-09-09-shell-shippability.md.

Owning docs

Doc Covers
rig-cockpit-worker docs/web-cockpit.md Canonical for the web cockpit — every route, the auth flows, the Manager tab picker
rig-cockpit-worker docs/native-shell.md Canonical for the shell — files, endpoints, build pipeline
gpu-brain.md The GPU pod itself: cost, wake, status, stop, the waking chat replies
xcode-cloud.md The shared iOS build-and-upload pipeline