|
1 | 1 | --- |
2 | 2 | name: interchange-client-apps |
3 | | -description: Build a web or mobile client that deploys, triggers, and watches workflows on an Interchange hub with @intx/hub-client. |
| 3 | +description: Build a client-driven app that sets up its own Interchange hub (tenants, auth, grants, providers, installs, deploys, runs) using stock @intx and @corbits packages. |
4 | 4 | user-invocable: false |
5 | 5 | --- |
6 | 6 |
|
7 | | -# Interchange client apps |
| 7 | +# Client-driven apps on Interchange |
8 | 8 |
|
9 | | -A client-driven app talks to a hub over HTTP. The hub owns tenants, grants, credentials, and deploys. The client never talks to a sidecar. |
| 9 | +A client-driven app drives the whole setup. The hub boots vanilla (migrate, mount, serve). The app signs in, creates its tenants, grants, providers, and assets, deploys its workflows, and runs them, all over the hub's HTTP API using stock `@intx/*` and `@corbits/*` packages. Nothing about the product is baked into the hub. |
10 | 10 |
|
11 | | -## Pieces |
| 11 | +The flagship example is Corbits Solution Builder (`corbitsdev/corbits-solution-builder`). Its architecture note says it directly: the client runs the installer over the hub on first launch and after every credential change. Read `packages/installer/src/*.ts` first. Workbench (`corbitsdev/workbench`) is the server-composition reference. |
12 | 12 |
|
13 | | -- `@intx/hub-client`: browser-side `Transport`, run session, and workflow helpers. It is side-effect free at import. `apps/admin-ui` upstream is the reference client. |
14 | | -- `@intx/hub-api`: the hub's Hono app. Mount extra routes on the same auth stack with `createRequireGrant`. |
15 | | -- `@intx/hub-agent` is sidecar-side, not a client SDK. |
16 | | -- API reference: the hub serves `GET /openapi.json`, and upstream `docs/API.md` is generated. |
| 13 | +## The shape |
17 | 14 |
|
18 | | -## Auth and URLs |
| 15 | +- **Host** (once per launch): picks embedded or remote hub, opens the database, migrates, mints keys, mounts the hub, starts sidecar reconcilers. See `use_skill interchange-embed-hub`. |
| 16 | +- **Client** (browser or script): everything else, through one `Transport` (`fetch(method, path, body)` plus `subscribe`). |
| 17 | +- Keep an `install()` that is idempotent (list-then-create, tolerate 409). Run it on first launch and after changes. Solution Builder keeps it free of any database import. |
19 | 18 |
|
20 | | -Sign in with better-auth (cookie session, same origin). Tenant is always in the path: `/api/tenants/:tenantId/...`. Cross-tenant user views live under `/api/me/...`. Scripts that need git access use hub-issued `itx_pat_*` or `itx_svc_*` tokens. |
| 19 | +## Bootstrap recipe (client side) |
21 | 20 |
|
22 | | -## Deploy, trigger, watch |
| 21 | +1. **Auth.** better-auth, email and password. `POST /api/auth/sign-up/email {name,email,password}`, on 422 `POST /api/auth/sign-in/email`. Replay the `better-auth.session_token` cookie. The REST API has no bearer or service token, so an app that acts as "system" holds a user account and keeps its cookie. A desktop app can mint one fixed local owner with a keychain-held password. |
| 22 | +2. **Tenant.** `POST /api/tenants {name, slug, parentId?}`. The caller becomes the owner principal with `*`/`*` allow. Find existing ones via `GET /api/me` and `GET /api/me/principals`. A child tenant (`parentId`) per project inherits the parent's catalog, assets, and placement policy. Do not treat the hierarchy as a trust boundary yet: the create route does not appear to check the caller's rights on `parentId` (unverified). |
| 23 | +3. **Roles and grants.** `POST /roles`, `POST /grants {roleId|principalId, resource, action, effect, origin}`, `POST /principals/:p/roles/:r`. Model app authorities as grants such as `authority:<name>` / `hold`. An invited member must be PATCHed to `active` before the tenant accepts them. |
| 24 | +4. **Inference.** In order: `POST /providers` (set `apiBaseUrl`), `POST /credentials`, `POST /catalog/models`, `POST /catalog/providers` (bound to the credential), `POST /catalog/offerings`. A model provider's credential cannot be repointed, so rotate the same credential id. The offering ids are the failover chain you give each deploy. |
| 25 | +5. **Install code.** `POST /assets {kind:"workflow"|"skill"|"package-registry", name}`. Push source through a git token (`POST /git-tokens`, `can_read` and `can_push`, short expiry, revoke after). Poll until the push is visible before deploying. |
| 26 | +6. **Deploy.** `deployWorkflow(transport, tenantId, { source: { kind: "asset", assetId, package: { format: "source", commitSha } }, entry, sourceOfferingIds, defaultSourceOfferingId })`. The chain freezes at deploy, so a model switch is a new deployment. A deploy needs a non-empty chain and a sidecar provisioner. |
| 27 | +7. **Run.** `triggerWorkflowRun`, then fold `readWorkflowRunEvents` (or `createRunSession`, which polls every 2 s). Answer waits with `deliverWorkflowSignal(transport, tenantId, runId, { runId, signalName, signalId, payload })`. A repeated `signalId` with a different payload is a 409. Resolve tool approvals with `POST /approvals/:id/approve` or `/reject`, which needs `approval:*` / `resolve`. |
23 | 28 |
|
24 | | -```ts |
25 | | -import { |
26 | | - createBrowserTransport, |
27 | | - createRunSession, |
28 | | - deployWorkflow, |
29 | | - triggerWorkflowRun, |
30 | | - deliverWorkflowSignal, |
31 | | - findAwaitingSignal, |
32 | | -} from "@intx/hub-client"; |
| 29 | +## Rules that save time |
33 | 30 |
|
34 | | -const transport = createBrowserTransport(); |
35 | | -const deployment = await deployWorkflow(transport, tenantId, { |
36 | | - source, |
37 | | - entry: "./src/workflow.ts", |
38 | | - sourceOfferingIds: [offeringId], |
39 | | - defaultSourceOfferingId: offeringId, |
40 | | -}); |
41 | | -await triggerWorkflowRun(transport, tenantId, runId, { content: "start" }); |
42 | | - |
43 | | -const session = createRunSession({ |
44 | | - tenantId, |
45 | | - runId, |
46 | | - transport, |
47 | | - onChange: () => render(session.events), |
48 | | -}); |
49 | | -const stop = session.start(); // polls events every 2s until terminal |
50 | | -// when findAwaitingSignal(session.events) returns a signal, answer it: |
51 | | -// await deliverWorkflowSignal(transport, tenantId, runId, { runId, signalName, signalId, payload }) |
52 | | -``` |
53 | | - |
54 | | -- Sources are `asset-source`, `asset-tarball`, or `registry` (registry needs `pin: "name@range"`). |
55 | | -- `signalId` must be stable so retries dedupe. A terminal run cannot be retriggered. |
56 | | -- Errors throw `ApiError(status, code, message)`. Validate every response with arktype. |
57 | | -- Only a browser transport ships. A non-browser client must supply its own `Transport` that adds credentials. |
58 | | -- Live updates: `Transport.subscribe` uses SSE, but the shipped run session polls. |
59 | | -- Approvals: `GET /api/tenants/:t/approvals` with approve and reject POSTs. The docs disagree on whether these are live, so verify in the code. |
60 | | - |
61 | | -## UI |
62 | | - |
63 | | -Chat, run, approval, and step components exist in `@corbits/react-ui`: `use_skill corbits-ui-apps`. Scheduled and webhook runs: `use_skill corbits-hub-libs`. |
| 31 | +- Only the browser transport ships. Outside a browser, supply your own: base URL, cookie jar, JSON content type, `ApiError` mapping, empty 202 and 204 bodies. In process you can call `hub.app.fetch(new Request(origin + path))` with the session cookie. |
| 32 | +- `@intx/hub-client` covers workflows only. Solution Builder's `installer/src/hub.ts` is the broader typed client for tenants, roles, grants, and the catalog. Validate every response with arktype. |
| 33 | +- List endpoints return inherited rows. Filter assets by `tenantId` or you will adopt the parent's same-named asset. Send signals to the tenant the deployment belongs to. |
| 34 | +- Deploy lazily, per project or stage, when the work first needs it. Do not provision everything at install. |
| 35 | +- Client-side checks (for example guarding which grants the app may mint) are not security. The hub authorizes. |
| 36 | +- Never put secrets in responses, logs, or prompts. Credentials are sealed by the hub's cipher. |
64 | 37 |
|
65 | 38 | ## Need more |
66 | 39 |
|
67 | | -- Run topology and credentials: `use_skill interchange-run-modes`. |
68 | | -- Workflow authoring: `use_skill interchange-workflows`. |
| 40 | +- Composing, embedding, and running the hub itself: `use_skill interchange-embed-hub`. |
| 41 | +- Sidecars, placement, and credential delivery: `use_skill interchange-run-modes`. |
| 42 | +- Workflow code you will deploy: `use_skill interchange-workflows`. |
| 43 | +- Corbits libraries to mount (mailbox, artifacts, OAuth login, cron, memory): `use_skill corbits-hub-libs`. UI and reference apps: `use_skill corbits-ui-apps`. |
69 | 44 | - Overview: `use_skill interchange`. |
0 commit comments