|
| 1 | +--- |
| 2 | +title: "Open augment.didi.sh to reach-edu — a second tenant instance, and Stephenie Tesoro as the first client user" |
| 3 | +lede: "The deployed stack already reads the shared SurrealDB Cloud — everything reach-edu's canonical layer holds is one auth gate away. This plan opens that gate the safe way: a per-client instance (the house pattern), not a relaxed org check that would drop reach.edu users into humain-vc's workspace." |
| 4 | +date_created: 2026-07-28 |
| 5 | +date_modified: 2026-07-28 |
| 6 | +authors: |
| 7 | + - Michael Staton |
| 8 | +augmented_with: |
| 9 | + - Claude Code on Claude Fable 5 |
| 10 | +semantic_version: 0.0.0.1 |
| 11 | +tags: |
| 12 | + - Plan |
| 13 | + - Augment-It |
| 14 | + - Deployment |
| 15 | + - Multi-Tenancy |
| 16 | + - Reach-Edu |
| 17 | +status: Draft |
| 18 | +--- |
| 19 | + |
| 20 | +# Open augment.didi.sh to reach-edu |
| 21 | + |
| 22 | +## Why care? |
| 23 | + |
| 24 | +reach-edu's canonical layer is now substantial — 400+ organizations with |
| 25 | +relations and strategy tags, 500+ people, registered corpora, a CRM |
| 26 | +already seeded from it — and the humans who work the pipeline (first: |
| 27 | +**Stephenie Tesoro <stesoro@reach.edu>**, a tracker row owner) have no |
| 28 | +way to see it except over Michael's shoulder. The production stack at |
| 29 | +augment.didi.sh already talks to the same SurrealDB Cloud instance |
| 30 | +(`rustic-forest-….surreal.cloud`), so the DATA is deployed; what's |
| 31 | +single-tenant is the **door**: `DIDI_AUTH=required`, |
| 32 | +`REQUIRED_ORG_ID=humain.vc`, `ACTIVE_CLIENT_ID=humain-vc` on |
| 33 | +workspace-service. |
| 34 | + |
| 35 | +## The load-bearing constraint |
| 36 | + |
| 37 | +`ACTIVE_CLIENT_ID` pins the tenant PER-INSTANCE, not per-user-org. |
| 38 | +Relaxing `REQUIRED_ORG_ID` alone would authenticate reach.edu users into |
| 39 | +the humain-vc workspace. Safe multi-client therefore means either |
| 40 | +per-client instances (each with its own pin) or real org→client session |
| 41 | +binding (a feature in workspace-service). The house has a strong |
| 42 | +precedent for the former: every client already gets its own Twenty, its |
| 43 | +own hub, its own stack folder (`self-host-stack/client-stacks/<client>`). |
| 44 | + |
| 45 | +## Recommended shape: a per-client instance (Option A) |
| 46 | + |
| 47 | +`reach.augment.didi.sh` — a sibling instance in the same Railway project, |
| 48 | +sharing the client-agnostic backends, pinning its own tenant: |
| 49 | + |
| 50 | +| Piece | Action | |
| 51 | +|---|---| |
| 52 | +| `nats`, `record-surrealdb-resolver`, `content-ingest`, `prompt-runner` | SHARED — already client-agnostic (client rides every request; canonical writes carry `client_access`) | |
| 53 | +| `workspace-service-reach` | NEW service from the same Dockerfile. Env: `DIDI_AUTH=required`, `REQUIRED_ORG_ID=reach.edu`, `ACTIVE_CLIENT_ID=reach-edu`, own tiny `/data` volume (sessions + clients root), `PORT=3001` | |
| 54 | +| `shell-reach` | NEW build of the shell with its WS URL baked to `wss://ws.reach.augment.didi.sh/ws` (the WS URL is a build-time constant — see DEPLOYMENT.md gotchas) | |
| 55 | +| `chat` / `strategy-curator` remotes | SHARED if the shell's remote registry allows cross-origin asset URLs per instance (they're static assets); else thin per-instance builds | |
| 56 | +| DNS | `reach.augment` + `ws.reach.augment` CNAMEs per the custom-domain-cutover skill; both must stay on `*.didi.sh` for the shared `didi_session` cookie | |
| 57 | + |
| 58 | +Why A over org-mapping: zero new auth code in the hot path, blast-radius |
| 59 | +isolation (a reach-edu session cannot even express a humain-vc |
| 60 | +workspace), per-client kill switch, and it matches the per-client stack |
| 61 | +doctrine everywhere else. |
| 62 | + |
| 63 | +**Option B (logged, not chosen): org→client binding in workspace-service** |
| 64 | +— map didi org → allowed client(s) at session establishment, one domain |
| 65 | +serves all tenants. Less infra, more auth surface; becomes worth it |
| 66 | +around client #4 or when cross-client operators (us) want one login. |
| 67 | +Revisit then. |
| 68 | + |
| 69 | +## Stephenie's onboarding (the identity half) |
| 70 | + |
| 71 | +1. **didi.sh account** under org `reach.edu` in id-didi-sh — the same |
| 72 | + unlock/invite flow humain's client user (Aniel) went through; the org |
| 73 | + must exist in the identity service before her invite. |
| 74 | +2. `checkMembership` then passes against `REQUIRED_ORG_ID=reach.edu` on |
| 75 | + the new instance; every write she makes carries her `didi_id` as actor |
| 76 | + (attribution rides the envelope — see Workspaces-as-Tenant-Primitive). |
| 77 | +3. Her didi_session cookie works across `*.didi.sh`, so the shared-domain |
| 78 | + requirement stands. |
| 79 | + |
| 80 | +## Caveats to carry into the build |
| 81 | + |
| 82 | +- **Corpus FILES are not on the deployed volume.** DB-backed surfaces |
| 83 | + (workbench, relations, tags, people) work fully; disk-half surfaces |
| 84 | + (corpus file browsing, triage's file moves) don't exist server-side. |
| 85 | + Fine for v1 — Stephenie's use is the workbench/pipeline view — but say |
| 86 | + so in her onboarding note rather than letting her find it. |
| 87 | +- **Anthropic credits** gate didi's crawls/chat for everyone, including |
| 88 | + her (currently exhausted; billing top-up pending). |
| 89 | +- **Which frontends she gets**: the deployed instance today is |
| 90 | + shell + chat + strategy-curator. The Org Workbench / search-rail |
| 91 | + remotes are NOT yet deployed anywhere — if her job is the org/pipeline |
| 92 | + view, deploying those remotes to the reach instance is part of this |
| 93 | + plan's scope (three more static-asset services + registry entries). |
| 94 | +- **prompt-runner is shared** — one Anthropic key, one spend pool across |
| 95 | + tenants. Acceptable now; per-client keys are an Option-B-era concern. |
| 96 | + |
| 97 | +## Build order (when signed off) |
| 98 | + |
| 99 | +1. Railway: mint `workspace-service-reach` (+ volume) and `shell-reach` |
| 100 | + with baked WS URL; wire env; deploy from `rebuild/turbo-rsbuild`. |
| 101 | +2. DNS: `reach.augment` / `ws.reach.augment` CNAMEs; wait out cert |
| 102 | + issuance per the cutover skill (including its stale-cache theater). |
| 103 | +3. Deploy the Augment-from-DB remotes (org-workbench, search-results, |
| 104 | + search-and-add, person-* ) as static-asset services; register them in |
| 105 | + shell-reach's remote registry. |
| 106 | +4. id-didi-sh: create org `reach.edu`, invite stesoro@reach.edu; verify |
| 107 | + the unlock flow end-to-end (the OAuth pilot discipline from the |
| 108 | + Twenty stacks applies: designed-but-unproven until she logs in). |
| 109 | +5. Browser drive against reach.augment.didi.sh (workbench loads, client |
| 110 | + pin is reach-edu, a humain-vc org is NOT visible), then the human |
| 111 | + walk-through: Stephenie's first login as the acceptance test. |
| 112 | +6. DEPLOYMENT.md gains the second-instance section; changelog entry. |
| 113 | + |
| 114 | +## Open decisions |
| 115 | + |
| 116 | +1. Sign off Option A (per-client instance) vs holding for Option B. |
| 117 | +2. Whether the Augment-from-DB remotes ship in this pass (recommended — |
| 118 | + they're the surface Stephenie actually needs) or the chat-first |
| 119 | + surface suffices for v1. |
| 120 | +3. Who sends Stephenie the invite + onboarding note (content drafted as |
| 121 | + part of step 4). |
| 122 | + |
| 123 | +## See also |
| 124 | + |
| 125 | +- `DEPLOYMENT.md` — the humain-vc instance this clones from, including |
| 126 | + the CLI gotchas. |
| 127 | +- [[../specs/Workspaces-as-Tenant-Primitive]] — the tenancy model; |
| 128 | + `client_access` scoping is why the shared backends are safe. |
| 129 | +- `custom-domain-cutover` skill — the DNS/cert recipe for `*.didi.sh`. |
| 130 | +- [[CRM-Starter-Export-Orgs-Then-People]] — the reach-edu Twenty that |
| 131 | + pairs with this instance for Stephenie's workflow. |
0 commit comments