Skip to content

Commit 6792830

Browse files
committed
changelog(deploy), plan(build-order): augment-it deployed on Railway — custom domain live, Steps 9-10 revised in place
Documents Steps 9 and 10 of the humain-vc unlock build order as actually shipped: augment-it live at augment.didi.sh on Railway (platform switch from the originally-prepped DigitalOcean droplet, whose real numbers didn't match the three-day-old plan), custom domain wired for both shell and workspace-service under *.didi.sh (required for the didi_session cookie to attach), and id-didi-sh's prod CORS allowlist fix. Full account of the four real bugs found by actually deploying (Railpack turborepo auto-detection, Docker ARG empty-string fallback, missing production assetPrefix on federated remotes, nats-server's CLI flag limitations) lives in Step 9's section for whoever touches this deploy next. Files changed: - context-v/plans/Build-Order-Humain-VC-Unlock-Flow.md - changelog/2026-07-09_01_Augment-It-Deployed-Railway-Not-DigitalOcean-Custom-Domain-Live.md
1 parent 9c49d3d commit 6792830

2 files changed

Lines changed: 209 additions & 60 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
date_created: 2026-07-09
3+
date_modified: 2026-07-09
4+
title: "augment-it is deployed — Railway, not DigitalOcean, and augment.didi.sh is live"
5+
lede: "Steps 9 and 10 of the humain-vc unlock build order: augment-it's single-tenant instance is live at augment.didi.sh, on Railway instead of the originally-prepped DigitalOcean droplet — a platform switch made after the droplet's real numbers didn't match the plan, and validated against Railway's actual multi-service project model."
6+
publish: true
7+
authors:
8+
- Michael Staton
9+
augmented_with:
10+
- Claude Code on Claude Sonnet 5
11+
files_changed:
12+
- shell/rsbuild.config.ts
13+
- shell/src/App.svelte
14+
- shell/Dockerfile
15+
- apps/chat/rsbuild.config.ts
16+
- apps/chat/Dockerfile
17+
- apps/chat/src/App.svelte
18+
- apps/strategy-curator/rsbuild.config.ts
19+
- apps/strategy-curator/Dockerfile
20+
- apps/strategy-curator/src/curation.svelte.ts
21+
- package.json
22+
- context-v/plans/Build-Order-Humain-VC-Unlock-Flow.md
23+
tags:
24+
- Progress-Update
25+
- Deployment
26+
- Railway
27+
- Didi-Platform
28+
- Augment-It
29+
- humain-vc
30+
- DNS
31+
---
32+
33+
## Why Care?
34+
35+
The humain-vc flow needed a real, hosted URL — everything before this was local. It's live now: `https://augment.didi.sh`, single-tenant, behind the same didi.sh sign-in wall proven locally, with the cookie actually shared across the `.didi.sh` domain for real. Getting there meant switching platforms mid-plan and finding four real bugs that only exist once code leaves localhost.
36+
37+
## What's New?
38+
39+
- **augment-it is live at `https://augment.didi.sh`.** 8 services on Railway (`nats`, `workspace-service`, `record-surrealdb-resolver`, `content-ingest`, `prompt-runner`, `shell`, `strategy-curator`, `chat`), one project, verified with headless-browser checks against the real deployed URLs — anonymous visitor gets the full sign-in wall, zero unexpected console errors.
40+
- **Platform: Railway, not the prepped DigitalOcean droplet.** Re-checked the DO box's live numbers before starting Step 9 and found ~112MB free RAM (before running anything of ours) and a leftover `coolify-proxy` container still squatting on ports 80/443 — the "prepped, ready to go" state from three days earlier had drifted. Given very few users and Railway's closer match to this repo's actual multi-service docker-compose shape, switched platforms rather than patch the drift.
41+
- **Custom domain, cookie-correct.** `augment.didi.sh` (shell) and `ws.augment.didi.sh` (workspace-service — the one every federated remote connects to directly) both live under `*.didi.sh`, required for the `didi_session` cookie to actually attach. `id-didi-sh`'s production CORS allowlist — which turned out to have been silently empty since it first deployed, never surfaced until something tried to call it cross-origin — now includes `https://augment.didi.sh`.
42+
43+
## The Story
44+
45+
Four real bugs surfaced by actually deploying, none of which local dev would ever catch:
46+
47+
1. **Railway's Railpack builder auto-detected this repo as a turborepo** (`turbo.json` exists at root) and ran the root `pnpm build` script unconditionally for every frontend, ignoring per-service build-command overrides. `turbo` was never an actually-installed binary here — `dev.sh`'s own comments already knew this. Fixed by giving `shell`, `strategy-curator`, and `chat` their own Dockerfiles, matching the pattern already proven for the four backend services.
48+
2. **A Docker `ARG` that's declared but never passed resolves to an empty string, not `undefined`**`?? default` doesn't catch it. Caught locally (deliberately docker-built before pushing) as `TypeError: object null is not iterable` deep in rspack's Module Federation code — an empty remote URL, not a missing one. Switched four `??` fallbacks to `||`.
49+
3. **Federated sub-chunks need their own `output.assetPrefix`, not just `dev.assetPrefix`.** Without it, `chat`'s and `strategy-curator`'s async chunks resolved as relative paths against the shell's origin instead of their own — 404ing into the shell's SPA-fallback HTML, which the browser then tried to `eval` as JavaScript (`SyntaxError: Unexpected token '<'`). This one only reproduces cross-origin; local federation dev never surfaces it. Found via a live browser check against the actual deployed `augment.didi.sh`, not a local test.
50+
4. **`nats-server` genuinely does not accept `-max_payload` as a CLI flag** (this repo's own `nats.conf` comment already knew that, from months back) — needed an inline-generated config file, and the first attempt (`printf` with embedded `\n` escapes) corrupted somewhere across the Railway CLI → GraphQL → container `sh -c` chain. The fix that survived: a `echo` per line, no embedded newline escapes at all.
51+
52+
Also hit and worked around: Railway's CLI `environment edit --service-config` (dot-path form) silently no-ops in the version used this session — switched to the JSON-patch form, which works reliably. `railway volume add` panics outright — created volumes via a direct GraphQL mutation instead.
53+
54+
```mermaid
55+
flowchart LR
56+
A[DO droplet: 112MB free,\nstale ports] -->|re-checked before Step 9| B[Switch to Railway]
57+
B --> C[8 services, 2 volumes]
58+
C --> D[Railpack turborepo\nauto-detect breaks builds] --> E[Dockerfiles for\nthe 3 frontends]
59+
C --> F[assetPrefix gap] --> G[federated chunks fixed]
60+
C --> H[custom augment.didi.sh\n+ CORS fix]
61+
```
62+
63+
## What's Next
64+
65+
Step 12 — the dress rehearsal. Two things still open: Aniel's membership still isn't seeded on prod (his address was never confirmed, carried over from Step 2), and nobody has completed an actual human sign-in against `augment.didi.sh` yet — `id.didi.sh`'s dev-token echo is deliberately off in production, so this genuinely needs a person clicking a real magic-link email, not another script. Step 11 (corpus sync to R2) needs a Railway-native redesign before it's real — the plan as written assumed a DO box's filesystem a cron job could rclone from directly.
66+
67+
## Related
68+
69+
- `context-v/plans/Build-Order-Humain-VC-Unlock-Flow.md` — Steps 9-10, now done (revised in place — was DigitalOcean, now Railway)
70+
- `context-v/plans/Unlock-Humain-VC-Team-Access-To-Augment-It.md` (ai-labs level) — item 4, "Deploy augment-it single-tenant"

context-v/plans/Build-Order-Humain-VC-Unlock-Flow.md

Lines changed: 139 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
title: "Build order: the humain-vc unlock flow, step by step"
33
lede: "The execution sequence for Flow 1 (Michael + Aniel, side-by-side thesis corpus building on a hosted augment-it) — each step names its repo, files, and verification so any fresh session can pick up mid-sequence. The strategy and scope cuts live in the ai-labs plan; this is the how."
44
date_created: 2026-07-06
5-
date_modified: 2026-07-08
5+
date_modified: 2026-07-09
66
authors:
77
- Michael Staton
88
augmented_with:
99
- Claude Code on Claude Fable 5
1010
- Claude Code on Claude Sonnet 5
11-
semantic_version: 0.0.3.0
11+
semantic_version: 0.0.4.0
1212
status: Ready
1313
tags:
1414
- Plan
@@ -70,10 +70,20 @@ tags:
7070
(flipped from the `optional` dev default for live testing this session —
7171
flip back in `.env` when done); the prove script's GATE mode is the
7272
repeatable check either way.
73-
- The DO droplet (167.172.42.247) is prepped: Coolify removed, 2GB swap,
74-
Docker 28, ports 80/443 free, SSH via the id_rsa_nopass key.
75-
- **NEXT: step 9** — the deploy tail (9–12) is all that's left. Steps
76-
1–8 are done and verified locally.
73+
- **The DO droplet plan was abandoned** (167.172.42.247 — still prepped,
74+
still paid-for, just not used). Steps 9–10 shipped on **Railway**
75+
instead, plus a real custom domain — see their sections for why and
76+
the full list of Railway-specific gotchas hit along the way.
77+
- **Live URLs, deployed:** `https://augment.didi.sh` (shell, single-tenant
78+
humain-vc), `wss://ws.augment.didi.sh/ws` (workspace-service). Both
79+
verified live via headless-browser checks against the real deployed
80+
URLs, not just localhost.
81+
- **NEXT: step 12** — the dress rehearsal. Aniel's membership still isn't
82+
seeded on prod (carried over from Step 2 — his address was never
83+
confirmed), and nobody has completed an actual human sign-in against
84+
`augment.didi.sh` yet (dev-token echo is off in prod by design, so this
85+
genuinely needs a person clicking a real email). Step 11 (corpus sync)
86+
needs a Railway-native redesign before it's real, but doesn't block 12.
7787

7888
Steps 1–8 are local, each verifiable on the laptop; 9–12 are the deploy
7989
tail. Steps marked ⚑ need an operator decision or action first.
@@ -327,60 +337,129 @@ deferred to its own initiative, not bundled into this flow.
327337
`svelte-check` clean on `apps/chat` and `shell`; `tsc --noEmit` clean on
328338
`services/workspace`.
329339

330-
## Step 9 — Deploy augment-it, single-tenant on DigitalOcean
331-
332-
**Decided 2026-07-06: the repurposed DigitalOcean droplet**
333-
`ubuntu-s-1vcpu-1gb-amd-ams3-01` at **167.172.42.247** (already paid
334-
for; whatever's on it is disposable — code lives on GitHub). Caddy for
335-
TLS at `augment.didi.sh`.
336-
337-
**Box prepped 2026-07-06:** Coolify (the prior tenant) removed, ports
338-
80/443 freed, 2 GB swapfile active + persisted, Docker 28 present,
339-
~556 MB RAM available, 18 GB disk free. SSH: root@ with the
340-
`id_rsa_nopass` key.
341-
342-
**The 1 GB constraint:** the full 11-service compose won't fit. Flow 1
343-
needs only the curator path — run a **flow-minimal compose profile**:
344-
`nats + workspace-service + record-surrealdb-resolver + content-ingest`
345-
(+ Caddy, + the shell as static files). ~400–500 MB resident; add a
346-
**2 GB swapfile** for fetch/compression spikes and build churn (pnpm
347-
install on 1 GB wants swap; bring services up sequentially on first
348-
build). Other microfrontends stay mounted in the shell and error if
349-
poked — consistent with the "no extra work, no isolation" rule.
350-
Escape hatch: DO resize to 2 GB ($12/mo) is two clicks if it strains.
351-
352-
- Box provisioning; clone; `.env` with: `ACTIVE_CLIENT_ID=humain-vc`,
353-
`DIDI_AUTH=required`, `REQUIRED_ORG_ID=humain.vc`,
354-
`ID_JWKS_URL=https://id.didi.sh/.well-known/jwks.json`,
355-
`ID_ISSUER=https://id.didi.sh`, real API keys (Jina etc.).
356-
- Only humain-vc under `clients/` on the box (isolation by absence).
357-
- Caddy: `augment.didi.sh` → shell static build + `/ws` → workspace :3001;
358-
the shell's `PUBLIC_ID_BASE=https://id.didi.sh` at build.
359-
- **Verify:** `scripts/prove-didi-auth.mjs` with `ID_BASE=https://id.didi.sh
360-
WS_URL=wss://augment.didi.sh/ws` — member in, stranger out.
361-
362-
## Step 10 ⚑ — DNS + cookie day (Vercel DNS)
363-
364-
- ⚑ The **pending id records** land first (name `id`: A + AAAA above) —
365-
`fly certs check id.didi.sh` goes green.
366-
- `augment` record → the step-9 box. Both apps now under `.didi.sh`; the
367-
cookie is shared for real (sign in once, both surfaces).
368-
- id-didi-sh CORS config for prod: add `https://augment.didi.sh` to
369-
`cors_origins` (runtime env), redeploy id.
370-
- **Verify:** sign in on augment.didi.sh; didi_session Domain=.didi.sh in
371-
devtools; badge lights on reload.
372-
373-
## Step 11 — Corpus sync, option A (box ↔ R2 ↔ laptop)
374-
375-
- rclone remote for the existing R2 account on the box + laptop; bucket
376-
prefix `corpus/humain-vc/`.
377-
- Box: cron/systemd timer `rclone sync /srv/augment-it/clients/humain-vc
378-
r2:…` (push, scheduled + post-session manual); laptop pulls on demand.
379-
- **Single-writer discipline documented in the repo README**: while the
380-
team works hosted, the box is authoritative; Michael's local edits go
381-
through R2 deliberately, never concurrently.
382-
- **Verify:** add a source hosted → appears on laptop after pull;
383-
checksums match.
340+
## Step 9 — Deploy augment-it, single-tenant ✅ DONE 2026-07-09 (Railway, not DigitalOcean)
341+
342+
**Platform changed after re-checking the DO droplet's live numbers**: the
343+
`167.172.42.247` box (prepped 2026-07-06) turned out to have only ~112MB
344+
free / 537MB available RAM before running a single one of our own
345+
services — the "escape hatch: resize to 2GB if it strains" language in
346+
the original plan undersold how tight it already was. A leftover
347+
`coolify-proxy` container was also still holding ports 80/443, meaning
348+
"ports freed" was stale too. Given very few users and no prior DO ops
349+
investment (vs. real Fly.io experience from id-didi-sh this same week),
350+
the operator chose **Railway** over both DO and Fly — Railway's
351+
multi-service-project model is the closest 1:1 match to this repo's
352+
actual docker-compose shape. The DO droplet is abandoned for this flow
353+
(still paid-for and untouched otherwise).
354+
355+
**8 Railway services**, one project (`augment-it`, workspace "The
356+
Lossless Group"): `nats` (official image, custom start command — see
357+
gotcha below), `workspace-service`, `record-surrealdb-resolver`,
358+
`content-ingest`, `prompt-runner` (all four Dockerfile builds,
359+
`rootDirectory` = `/services/<name>`), and three frontends —
360+
`shell`, `strategy-curator`, `chat` — each **also** Dockerfile-built
361+
(not Railway's Railpack auto-builder; see gotcha below), full-repo
362+
build context (they need the pnpm workspace), each producing its own
363+
static `dist/` served via `serve`.
364+
365+
**Two Railway Volumes**, deliberately NOT shared (confirmed via Railway
366+
docs + support: a volume is strictly single-service): `content-ingest`
367+
owns `/clients` (the real corpus filesystem, read-write); `workspace-service`
368+
gets its own tiny `/data` volume, self-seeded on every boot via its
369+
`deploy.startCommand` (`mkdir -p /data/clients/humain-vc && echo
370+
DEFAULT_DOMAIN_TYPE=thesis > .../​.env && npm start`) rather than
371+
uploading the real `clients/humain-vc/.env` (which also holds unrelated
372+
Decile Hub credentials workspace-service has no business touching).
373+
374+
**Real gotchas hit and fixed, worth knowing before touching this again:**
375+
- Railway's CLI (`environment edit --service-config`, dot-path form)
376+
silently no-ops in the version used this session — `{"committed":false,
377+
"message":"No changes to apply"}` regardless of value. The **JSON patch
378+
form** (`environment edit --json`) works reliably; used for everything.
379+
- `railway volume add` panics (Rust `unwrap()` on `None`) in this CLI
380+
version — created volumes via direct GraphQL (`volumeCreate` mutation)
381+
instead.
382+
- `nats-server` does **not** accept `-max_payload` as a CLI flag (matches
383+
this repo's own `nats.conf` comment from months ago) — needed an inline
384+
generated config file. First attempt (`printf '...\n...\n'`) corrupted
385+
across the Railway CLI → GraphQL → container `sh -c` chain; the
386+
**multi-`echo`, no-embedded-newlines** form is what actually survives:
387+
`echo port: 4222 > /tmp/nats.conf && echo http_port: 8222 >> ... &&
388+
nats-server -c /tmp/nats.conf`.
389+
- Railway's **Railpack builder auto-detected this repo as a turborepo**
390+
(`turbo.json` exists at root) and unconditionally ran the root
391+
package.json's `build` script (`turbo run build`) for the three
392+
frontends, ignoring any custom `buildCommand` override — and `turbo`
393+
was never actually an installed binary here. Fixed by giving `shell`,
394+
`strategy-curator`, and `chat` their own Dockerfiles (direct `pnpm
395+
--filter <pkg> build`), same pattern as the four backend services.
396+
- A Docker `ARG` that's declared but never passed resolves to an **empty
397+
string**, not `undefined``?? default` doesn't catch it. Two real
398+
bugs from this: (1) `shell/rsbuild.config.ts`'s remote-URL fallbacks
399+
had to change from `??` to `||`; (2) module-federation `assetPrefix`
400+
was only set for `dev`, not `output` (the field that also covers
401+
production builds) — missing it meant `chat`'s and `strategy-curator`'s
402+
async sub-chunks resolved as relative paths against the **shell's**
403+
origin instead of their own, 404ing into the shell's SPA-fallback HTML
404+
("SyntaxError: Unexpected token '<'"). Only reproduces cross-origin —
405+
local federation dev never surfaces it. Both fixed with
406+
`output.assetPrefix` env-configured per remote.
407+
- Railway auto-injects its own `PORT` (8080) for any service — this
408+
silently mismatched the public domain's configured target port (3001)
409+
for `workspace-service` until `PORT=3001` was set explicitly as a
410+
service variable.
411+
412+
**Verify, as done:** headless-Chromium checks against the live deployed
413+
URLs (not just localhost) — anonymous visitor → full wall, zero
414+
unexpected console errors (only the 12 out-of-scope remotes' harmless
415+
`localhost` failures, unchanged from local); `workspace-service`'s
416+
`/config` endpoint live; `scripts/prove-didi-auth.mjs`'s `GATE=1` mode
417+
run against the real deployed `wss://ws.augment.didi.sh/ws` — anonymous
418+
correctly rejected 4401. Member-admitted couldn't be scripted against
419+
prod `id.didi.sh` (dev-token echo is deliberately disabled there) — that
420+
leg needs a real human sign-in (Step 12).
421+
422+
## Step 10 — DNS + cookie day ✅ DONE 2026-07-09 (Railway custom domains, not DO/Caddy)
423+
424+
Two Railway custom domains, both required under `*.didi.sh` for the
425+
`didi_session` cookie (`Domain=.didi.sh`) to actually reach them — the
426+
shell's own page origin AND workspace-service's WS endpoint (every
427+
federated remote connects to workspace-service directly, so it's the one
428+
that must share the cookie domain; the remotes' own static-asset
429+
origins don't need to):
430+
431+
- `augment.didi.sh``shell` (CNAME + TXT ownership-verification record,
432+
added at Vercel DNS by the operator; validated + cert issued within
433+
minutes).
434+
- `ws.augment.didi.sh``workspace-service` (same pattern).
435+
- `id-didi-sh`'s prod `cors_origins`**was empty** (`config/runtime.exs`
436+
never set it; only `dev.exs` had `localhost:3100`), meaning every
437+
cross-origin browser call to `id.didi.sh` in production had been
438+
silently CORS-rejected since it went live, just never surfaced because
439+
augment-it wasn't deployed yet. Added `["https://augment.didi.sh"]`,
440+
deployed to Fly.
441+
- `PUBLIC_WS_URL` rebuilt into `shell`/`strategy-curator`/`chat` as
442+
`wss://ws.augment.didi.sh/ws`.
443+
- **Verify:** `curl -H "Origin: https://augment.didi.sh" -X OPTIONS
444+
https://id.didi.sh/api/magic-links` returns
445+
`access-control-allow-origin: https://augment.didi.sh` +
446+
`access-control-allow-credentials: true`. Live browser check against
447+
`https://augment.didi.sh` clean (see Step 9). Real sign-in + cookie
448+
attachment still needs a human (Step 12) — dev-token echo being off in
449+
prod is a feature, not a gap to route around.
450+
451+
## Step 11 — Corpus sync ⚑ NEEDS REVISITING (Railway volumes replace the DO-box assumption)
452+
453+
Written when Step 9 targeted a DO droplet with a filesystem an rclone
454+
timer could reach directly. On Railway, `content-ingest`'s corpus lives
455+
on a **Railway Volume** — reachable via `railway volume files` (CLI, hit
456+
its own bugs this session — see Step 9) or `railway ssh`, not a plain
457+
box path a cron job can rclone from directly. The single-writer
458+
discipline (team writes hosted; Michael's local edits sync deliberately,
459+
never concurrently) still holds as a policy — the *mechanism* needs a
460+
Railway-native answer (a periodic job inside `content-ingest` itself
461+
pushing to R2, most likely) before this step is actually done. Not
462+
blocking Step 12 — humain-vc's corpus is empty-ish today either way.
384463

385464
## Step 12 — Dress rehearsal (the acceptance run)
386465

0 commit comments

Comments
 (0)