One command, ports from the ledger
The engine starts with python -m backend.serve in the cwk-avatars Conda environment. Uvicorn binds every interface on the configured port with proxy_headers=False, and the comment above the call says what makes that safe: only loopback and tailnet peers pass the application's network gate. Binding wide and gating in the app is the pattern Solo Auth Quest calls the 0.0.0.0 decision, and turning proxy headers off means no forwarded header can make a request look like it came from somewhere else. The Protection Follows the Pixels track walks through the gates themselves.
No port is written in settings.py. The engine port and its Tailscale Serve HTTPS twin come from the family registry, a vendored copy of Firelink's port ledger, which places Avatars on engine 4100 and twin 4101, with Vite's dev server on 4102 listed as planned. Origins come from the same registry: Pippa and Firelink run on the studio's Mac, so they are dialed on loopback. Ember is dialed through its HTTPS twin, never a raw tailnet IP, and at this snapshot the registry places Ember on that same Mac too: the twin is the address the code reads, not a sign of distance. The public origin is the studio's own twin. The data root comes from the registry too, unless AVATARS_DATA overrides it for a test. Firelink Quest's track "One Owner Per Fact" is the doctrine underneath: a port has one owner, and everyone else reads it.
Literals that outlived the move
Two copies of those numbers survived. The iOS app's Engine.swift keeps static let twinPort = 4101, used only when the registry has no twin for Avatars and the app falls back to the Pippa host on that port. The dev proxy in vite.config.ts sends /api to http://127.0.0.1:4100. The house sign also says Vite must use strictPort, but neither the dev script (vite --host 127.0.0.1 --port 4102) nor the config sets it, so a busy 4102 would quietly move the dev server to another port. Today every value agrees with the ledger. They are still copies, and the day the ledger moves a port they become the bug. When code and docs disagree with the ledger, the ledger wins.
A LaunchAgent that asks where it belongs
scripts/install-service.py writes the LaunchAgent com.cwk.avatars-serve: run at load, keep alive, a ten-second throttle between restarts, logs under the data root. Its first act is a refusal. Avatars has one writer, and the script asks the registry with runs_here("cwkAvatars") whether this Mac is it. Until October 19 the check was a fleet label; when the family moved its services to a new host, asking the registry meant the installer moved with it. The last line it prints is a rule of evidence: inspect /api/health before claiming the service is live. Health answers the product name, ok and the version from cwk-product.json, which is 0.1.4.
The engine also serves the built web client from frontend/dist, with the shell sent as no-cache so an installed page revalidates on every launch. Rebuilding the client and restarting the service are separate steps, and a git pull does neither.
What the tests count
| Suite | Count at f6cd664 | Status |
|---|---|---|
| Backend, pytest | 41 in 7 files | run: 41 passed |
| Frontend, Vitest | 32 in 8 files | run: 32 passed |
| iOS unit, XCTest | 40 in 5 files | counted; last recorded run 0 failures |
| iOS UI, XCUITest | 2 | counted; the throttled upload bench skips without its engine |
The README and old commit messages carry smaller numbers from earlier days. A test count is a dated observation, so this table names its snapshot: cwkAvatars f6cd664, committed October 23, 2026. The iOS suites were counted there, and their last recorded run is fc0a21d on October 19. One backend test runs the family kit's own drift check against this checkout, so a hand-edited vendored file fails the suite; Sibling Kit Quest's track "A Check With Teeth" explains why that check lives in the consumer's tests.
TestFlight: four facts, kept apart
The iOS app ships through TestFlight, never a cable install. scripts/mobile-testflight.sh is vendored from the kit, one program for every family iOS app, and Avatars keeps only its facts beside it: bundle id com.cwk.avatars.mobile, the dedicated simulator, the inputs that must be committed, the build number from project.yml and the marketing version from cwk-product.json. Its guards run in one order, tests and a visual check of every first screen included, before the build number moves. Sibling Kit Quest's lesson "One Program, Many Configs" tells how eight hand-grown copies became that one program.
The pipeline's success line asserts one fact only. Delivery is four:
- Uploaded: App Store Connect has the build.
- Processed: Apple finished processing it.
- Assigned: the internal testing group has it.
- Installed: a tester's device has it. Apple's install count trails, so zero means not counted yet, never not installed.
There are pipeline commits for builds 1 through 21, and each one records an upload that passed the gate (Destination: upload), which is fact 1 of 4 and not a delivery. The README's own record keeps the facts apart the same way: build 8 was uploaded, processed and in internal testing, with installation on a device not yet counted. The product is v0.1.4; build 21 and the web package's 0.1.0 are different numbering domains.