Skip to content

Commit 453dacd

Browse files
mpstatonclaude
andcommitted
plan(build-order): the humain-vc unlock flow as 12 executable steps
The execution sequence for Flow 1, written so a fresh session can pick up mid-stream: a verified state-as-of-writing header, then steps 1-8 local (email, org seeding, membership gate, attribution envelope, thesis vocabulary, curator liveness, sign-in wall, didi chat v0) and 9-12 the deploy tail (single-tenant box + Caddy, DNS + cookie day, rclone corpus sync, the two-laptop dress rehearsal). Each step names its repo, files, and verification; operator decisions are flagged (email provider, Aniel's address, hosting box, the pending id DNS records). Also answers "what happens to the existing curator data": nothing bad — SurrealDB is already the shared Cloud instance so every canonical row carries over with zero migration; the reach-edu corpus stays local by design (isolation by absence); local dev and the hosted instance coexist against the same canonical layer. Scope of record stays at the ai-labs flow plan; this doc only sequences. Files changed: - context-v/plans/Build-Order-Humain-VC-Unlock-Flow.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 572302b commit 453dacd

1 file changed

Lines changed: 247 additions & 0 deletions

File tree

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
---
2+
title: "Build order: the humain-vc unlock flow, step by step"
3+
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."
4+
date_created: 2026-07-06
5+
date_modified: 2026-07-06
6+
authors:
7+
- Michael Staton
8+
augmented_with:
9+
- Claude Code on Claude Fable 5
10+
semantic_version: 0.0.1.0
11+
status: Ready
12+
tags:
13+
- Plan
14+
- Build-Order
15+
- humain-vc
16+
- Didi-Platform
17+
- Auth
18+
- Deployment
19+
- Curator
20+
---
21+
22+
# Build order: the humain-vc unlock flow
23+
24+
> **Scope of record:** [[../../../context-v/plans/Unlock-Humain-VC-Team-Access-To-Augment-It|Unlock-Humain-VC-Team-Access-To-Augment-It]]
25+
> (ai-labs level) — Flow 1, the single-tenant scope cut, and the
26+
> deliberately-NOT-built list. Read it first; this doc only sequences.
27+
> Identity spec of record: `ai-labs/context-v/specs/Id-Didi-Sh-Identity-Service.md`.
28+
29+
## State as of writing (2026-07-06 — verify, don't assume)
30+
31+
- id-didi-sh **live on Fly** (`id-didi-sh.fly.dev`); cert for `id.didi.sh`
32+
issued but **DNS records still pending in Vercel** (A `66.241.125.92`,
33+
AAAA `2a09:8280:1::140:453c:0` on name `id`)
34+
- augment-it workspace-service **verifies `didi_session` on WS upgrade**
35+
(`services/workspace/src/didi.ts`, `DIDI_AUTH=optional`), shell has the
36+
**DidiBadge** sign-in; proven by `scripts/prove-didi-auth.mjs`
37+
- id has **email aliases** (Michael = mpstaton@gmail.com + michael@humain.vc
38+
+ michael@reach.edu, one didi_id); orgs/memberships tables **empty**
39+
- **No production email** — magic links work via dev echo only
40+
- augment-it runs **local-only** (docker compose); active-workspace
41+
split-brain fixed (browser pick authoritative on load)
42+
43+
Steps 1–8 are local, each verifiable on the laptop; 9–12 are the deploy
44+
tail. Steps marked ⚑ need an operator decision or action first.
45+
46+
---
47+
48+
## Step 1 ⚑ — Real email for magic links (id-didi-sh)
49+
50+
**Decision first:** Resend vs Postmark (sender: `no-reply@didi.sh`).
51+
52+
- Add the Swoosh adapter dep (`gen_smtp`/`resend` per pick) to `mix.exs`;
53+
configure in `config/runtime.exs` (prod) with the API key from env —
54+
dev keeps `Swoosh.Adapters.Local` and token echo.
55+
- ⚑ Sending-domain DNS in **Vercel DNS** (didi.sh registrar): the
56+
provider's SPF/DKIM/return-path records.
57+
- `fly secrets set EMAIL_API_KEY=… -a id-didi-sh`, deploy.
58+
- **Verify:** `mix id.seed` a throwaway with a real inbox; request a magic
59+
link against the deployed service; click it; land signed in on the
60+
`/access` fallback page. Per the open-graph discipline, check spam
61+
placement once.
62+
63+
## Step 2 — Org + membership seeding (id-didi-sh)
64+
65+
- New mix task `id.org` (create org by domain-as-id + name) and
66+
`id.member <email-or-didi_id> <org_id> <role>`; validate role against
67+
`Membership.roles/0`.
68+
- Seed locally: org `humain.vc`; Michael → `superuser`; Aniel (⚑ confirm
69+
address) → `org_owner`.
70+
- On prod later: `fly ssh console -a id-didi-sh`
71+
`/app/bin/id_didi_sh rpc` with the same context functions.
72+
- **Verify:** `/api/me` (signed in as Michael) returns the membership;
73+
tests for duplicate-membership upsert semantics.
74+
75+
## Step 3 — Membership gate (augment-it, workspace-service)
76+
77+
- Extend `services/workspace/src/didi.ts`: after cookie verify, when
78+
`DIDI_AUTH=required`, GET `${ID_BASE}/api/me` with the cookie forwarded;
79+
admit only if memberships include `REQUIRED_ORG_ID` (new env) or role
80+
`superuser`. Cache the verdict on the session; re-check on reconnect,
81+
not per-frame.
82+
- Reject → `socket.close(4403, 'membership required')`; the shell surfaces
83+
a "no access" state (DidiBadge already knows anonymous-vs-signed-in;
84+
add rejected).
85+
- Keep `DIDI_AUTH=optional` in local compose; `required` is the deployed
86+
instance's posture.
87+
- **Verify:** extend `scripts/prove-didi-auth.mjs`: member admitted,
88+
non-member (seed a stranger) rejected with 4403, superuser admitted.
89+
90+
## Step 4 — Actor attribution envelope (augment-it)
91+
92+
- `services/workspace/src/ws.ts`: the invoke path passes
93+
`actor: { didi_id }` (from the session) into `dispatch()`;
94+
`capabilities.ts` adds it to the NATS envelope beside the tenant
95+
context (see [[../specs/Workspaces-as-Tenant-Primitive|Workspaces-as-Tenant-Primitive]]
96+
§ tenant-aware envelope).
97+
- Handlers stamp `created_by`/`updated_by`: resolver
98+
(`services/record-surrealdb-resolver/src/domains.ts` — domains, sources,
99+
source_usages rows) and content-ingest (`corpus.ts` — frontmatter
100+
fields). Chat turns stamp acting user + `via: didi-agent`.
101+
- **No consumers** — no filtering, no views (the flow plan's rule).
102+
- **Verify:** run a `source.add` through the prove script with a cookie;
103+
confirm frontmatter + DB row carry the didi_id.
104+
105+
## Step 5 — Thesis vocabulary, minimal (augment-it)
106+
107+
- `clients/humain-vc/.env` gains `DEFAULT_DOMAIN_TYPE=thesis` (the
108+
per-workspace env map already loads it); expose via a small
109+
`workspace.config` capability (or extend `workspace.list`'s payload).
110+
- `apps/strategy-curator`: replace the `DOMAIN_TYPE = 'strategy'` constant
111+
(`curation.svelte.ts:19`) with the workspace default (fallback
112+
'strategy'); render the noun through headers/copy (singular + plural —
113+
mirror content-ingest's `DOMAIN_FOLDERS`).
114+
- Add `domain.retype` handler (resolver + content-ingest file move) and
115+
retype `consumer-immunology` strategy→thesis.
116+
- **Verify:** with humain-vc active, the curator reads "Thesis"; creating
117+
one writes `corpus/theses/<slug>/index.md`; reach-edu still reads
118+
"Strategy".
119+
120+
## Step 6 — Curator liveness (augment-it)
121+
122+
- Resolver + content-ingest handlers publish NATS events after mutations:
123+
`domain.created`, `domain.retyped`, `source.added`, `source.updated`,
124+
`source.removed`, `extract.added` (payload: slugs + client_id + actor).
125+
- Add those subjects to `BROADCAST_SUBJECTS` in
126+
`services/workspace/src/ws.ts`.
127+
- `apps/strategy-curator/src/curation.svelte.ts`: subscribe via the
128+
workspace singleton's event stream; refetch the affected list on events
129+
for the active domain/client (skip events from own invokes if double-
130+
render annoys; correctness first).
131+
- **Verify:** two browser windows, both on humain-vc; add a source in one;
132+
the other's list updates without refresh. This is the Flow-1 step-4
133+
acceptance, locally.
134+
135+
## Step 7 — Instance posture + sign-in wall (augment-it, shell)
136+
137+
- Shell: when the workspace-service reports `DIDI_AUTH=required` (expose
138+
the mode via the session frame or `workspace.config`) and the session
139+
has no `didi_id`, render the sign-in panel as a full pre-auth wall
140+
instead of mounting remotes; hide the WorkspaceSwitcher when the
141+
instance reports a pinned tenant (`ACTIVE_CLIENT_ID` set → include
142+
`pinned: true` in `workspace.list`).
143+
- **Verify:** flip `DIDI_AUTH=required` locally → wall appears; sign in →
144+
shell mounts; sign out → wall returns.
145+
146+
## Step 8 — didi chat v0 (augment-it)
147+
148+
The largest step; keep it to the flow's two jobs (inbox triage into
149+
theses; glitch assistance):
150+
151+
- Rename/persona: the chat rail presents as **didi**; system prompt names
152+
the flow context (workspace, active thesis, the curator's verbs).
153+
- Wire curator capabilities as chat-invokable verbs through the existing
154+
`dispatchChatTurn` path (`services/workspace/src/chat.ts`) — didi's
155+
writes ride the same envelope, stamped `via: didi-agent` (step 4).
156+
- Author the first agent-skill: `context-v/agent-skills/inbox-curation/`
157+
(the decile-hub-interface precedent is the format) — triage rules,
158+
thesis-assignment discipline, the curator capability catalog.
159+
- **Verify:** "didi, file this link under consumer-immunology" ends with a
160+
source in the right thesis, attributed correctly.
161+
162+
## Step 9 ⚑ — Deploy augment-it, single-tenant (infra decision)
163+
164+
**Decision first — where compose runs.** The stack is compose-shaped
165+
(11 services + NATS); Fly runs one container per machine. Lean for THIS
166+
flow: **one small VPS (Hetzner/DO) running docker compose + Caddy** for
167+
TLS at `augment.didi.sh` — zero rearchitecture. (Fly-per-service is the
168+
platform-shaped alternative; it's more moving parts than two users need.)
169+
170+
- Box provisioning; clone; `.env` with: `ACTIVE_CLIENT_ID=humain-vc`,
171+
`DIDI_AUTH=required`, `REQUIRED_ORG_ID=humain.vc`,
172+
`ID_JWKS_URL=https://id.didi.sh/.well-known/jwks.json`,
173+
`ID_ISSUER=https://id.didi.sh`, real API keys (Jina etc.).
174+
- Only humain-vc under `clients/` on the box (isolation by absence).
175+
- Caddy: `augment.didi.sh` → shell static build + `/ws` → workspace :3001;
176+
the shell's `PUBLIC_ID_BASE=https://id.didi.sh` at build.
177+
- **Verify:** `scripts/prove-didi-auth.mjs` with `ID_BASE=https://id.didi.sh
178+
WS_URL=wss://augment.didi.sh/ws` — member in, stranger out.
179+
180+
## Step 10 ⚑ — DNS + cookie day (Vercel DNS)
181+
182+
- ⚑ The **pending id records** land first (name `id`: A + AAAA above) —
183+
`fly certs check id.didi.sh` goes green.
184+
- `augment` record → the step-9 box. Both apps now under `.didi.sh`; the
185+
cookie is shared for real (sign in once, both surfaces).
186+
- id-didi-sh CORS config for prod: add `https://augment.didi.sh` to
187+
`cors_origins` (runtime env), redeploy id.
188+
- **Verify:** sign in on augment.didi.sh; didi_session Domain=.didi.sh in
189+
devtools; badge lights on reload.
190+
191+
## Step 11 — Corpus sync, option A (box ↔ R2 ↔ laptop)
192+
193+
- rclone remote for the existing R2 account on the box + laptop; bucket
194+
prefix `corpus/humain-vc/`.
195+
- Box: cron/systemd timer `rclone sync /srv/augment-it/clients/humain-vc
196+
r2:…` (push, scheduled + post-session manual); laptop pulls on demand.
197+
- **Single-writer discipline documented in the repo README**: while the
198+
team works hosted, the box is authoritative; Michael's local edits go
199+
through R2 deliberately, never concurrently.
200+
- **Verify:** add a source hosted → appears on laptop after pull;
201+
checksums match.
202+
203+
## Step 12 — Dress rehearsal (the acceptance run)
204+
205+
- Seed Aniel's membership on prod (step 2's task via fly ssh).
206+
- Two laptops (or two browsers), both on `augment.didi.sh`: run Flow 1
207+
end to end — sign-ins via real email, thesis creation, link + file
208+
adds, cross-screen liveness, didi triage, attribution spot-check
209+
(frontmatter shows who did what).
210+
- Changelog entries per shipped chunk along the way (the splash
211+
self-updates from them); update this plan's checkboxes as steps land.
212+
213+
## What happens to the existing curator data (nothing bad)
214+
215+
The source curator working locally against SurrealDB today is not
216+
disturbed by any step above:
217+
218+
- **SurrealDB is already shared.** It's the Cloud instance; the deployed
219+
box points at the SAME connection string. Every canonical row —
220+
`sources` registry, `domains`, `source_usages`, organizations, persons —
221+
carries over with zero migration, because it never lived on the laptop.
222+
Reads stay workspace-filtered per the client-tagging convention, and the
223+
shared registry is a feature here: a URL reach-edu already identified
224+
keeps its `source_uuid` when humain-vc cites it.
225+
- **The local corpus filesystem stays local.** `clients/reach-edu/` never
226+
goes near the box (isolation by absence); local dev keeps working as the
227+
reach-edu workbench exactly as today. `clients/humain-vc/` is nearly
228+
empty (one mis-filed domain) — since the flow's work is from scratch,
229+
either start the box's volume fresh and let `domain.retype` +
230+
re-creation rebuild it, or push the existing files up via step 11's
231+
rclone path. Both are fine; fresh is simpler.
232+
- **Local dev and the hosted instance coexist** against the shared
233+
canonical layer — domains created hosted appear in local queries and
234+
vice versa (client-tagged). The only discipline is step 11's
235+
single-writer rule for the corpus FILES, which the DB doesn't need
236+
(it's one database either way).
237+
238+
## Sequencing notes
239+
240+
- 1–2 (id) and 3–8 (augment-it) interleave freely; nothing in 3–8 waits
241+
on email. 9 waits on 3+7 minimum; 10 waits on 9 plus the id DNS; 12
242+
waits on everything.
243+
- Each step is one commit-or-few on `rebuild/turbo-rsbuild` (augment-it)
244+
/ `main` (id-didi-sh), pushed per the trunk cadence, changelog on
245+
coherent chunks.
246+
- If a fresh session picks this up: read the ai-labs flow plan first,
247+
then `git log --oneline -15` in both repos to locate the frontier.

0 commit comments

Comments
 (0)