@rizom/ops 0.2.0-alpha.71 → 0.2.0-alpha.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,6 @@ export interface ResolvedCohort {
14
14
  presetOverride?: PilotPreset;
15
15
  aiApiKeyOverride?: string;
16
16
  gitSyncTokenOverride?: string;
17
- mcpAuthTokenOverride?: string;
18
17
  }
19
18
  export interface ResolvedAnchorProfileSocialLink {
20
19
  platform: "github" | "instagram" | "linkedin" | "email" | "website";
@@ -29,6 +28,10 @@ export interface ResolvedAnchorProfile {
29
28
  story?: string;
30
29
  socialLinks?: ResolvedAnchorProfileSocialLink[];
31
30
  }
31
+ export interface ResolvedSetupDelivery {
32
+ delivery: "email";
33
+ email: string;
34
+ }
32
35
  export interface ResolvedUserIdentity {
33
36
  handle: string;
34
37
  cohort: string;
@@ -41,7 +44,7 @@ export interface ResolvedUserIdentity {
41
44
  discordAnchorUserId?: string;
42
45
  effectiveAiApiKey: string;
43
46
  effectiveGitSyncToken: string;
44
- effectiveMcpAuthToken: string;
47
+ setup?: ResolvedSetupDelivery;
45
48
  anchorProfile: ResolvedAnchorProfile;
46
49
  snapshotStatus: SnapshotStatus;
47
50
  }
package/dist/schema.d.ts CHANGED
@@ -15,7 +15,6 @@ export declare const pilotSchema: z.ZodObject<{
15
15
  aiApiKey: z.ZodString;
16
16
  gitSyncToken: z.ZodString;
17
17
  contentRepoAdminToken: z.ZodString;
18
- mcpAuthToken: z.ZodString;
19
18
  agePublicKey: z.ZodString;
20
19
  }, "strict", z.ZodTypeAny, {
21
20
  agePublicKey: string;
@@ -29,7 +28,6 @@ export declare const pilotSchema: z.ZodObject<{
29
28
  aiApiKey: string;
30
29
  gitSyncToken: string;
31
30
  contentRepoAdminToken: string;
32
- mcpAuthToken: string;
33
31
  }, {
34
32
  agePublicKey: string;
35
33
  schemaVersion: 1;
@@ -42,7 +40,6 @@ export declare const pilotSchema: z.ZodObject<{
42
40
  aiApiKey: string;
43
41
  gitSyncToken: string;
44
42
  contentRepoAdminToken: string;
45
- mcpAuthToken: string;
46
43
  }>;
47
44
  export declare const userSchema: z.ZodObject<{
48
45
  handle: z.ZodString;
@@ -58,7 +55,16 @@ export declare const userSchema: z.ZodObject<{
58
55
  }>;
59
56
  aiApiKeyOverride: z.ZodOptional<z.ZodString>;
60
57
  gitSyncTokenOverride: z.ZodOptional<z.ZodString>;
61
- mcpAuthTokenOverride: z.ZodOptional<z.ZodString>;
58
+ setup: z.ZodOptional<z.ZodObject<{
59
+ delivery: z.ZodLiteral<"email">;
60
+ email: z.ZodString;
61
+ }, "strict", z.ZodTypeAny, {
62
+ email: string;
63
+ delivery: "email";
64
+ }, {
65
+ email: string;
66
+ delivery: "email";
67
+ }>>;
62
68
  anchorProfile: z.ZodOptional<z.ZodObject<{
63
69
  name: z.ZodOptional<z.ZodString>;
64
70
  description: z.ZodOptional<z.ZodString>;
@@ -109,7 +115,10 @@ export declare const userSchema: z.ZodObject<{
109
115
  };
110
116
  aiApiKeyOverride?: string | undefined;
111
117
  gitSyncTokenOverride?: string | undefined;
112
- mcpAuthTokenOverride?: string | undefined;
118
+ setup?: {
119
+ email: string;
120
+ delivery: "email";
121
+ } | undefined;
113
122
  anchorProfile?: {
114
123
  name?: string | undefined;
115
124
  email?: string | undefined;
@@ -130,7 +139,10 @@ export declare const userSchema: z.ZodObject<{
130
139
  };
131
140
  aiApiKeyOverride?: string | undefined;
132
141
  gitSyncTokenOverride?: string | undefined;
133
- mcpAuthTokenOverride?: string | undefined;
142
+ setup?: {
143
+ email: string;
144
+ delivery: "email";
145
+ } | undefined;
134
146
  anchorProfile?: {
135
147
  name?: string | undefined;
136
148
  email?: string | undefined;
@@ -150,33 +162,28 @@ export declare const cohortSchema: z.ZodEffects<z.ZodObject<{
150
162
  presetOverride: z.ZodOptional<z.ZodEnum<["core", "default", "pro"]>>;
151
163
  aiApiKeyOverride: z.ZodOptional<z.ZodString>;
152
164
  gitSyncTokenOverride: z.ZodOptional<z.ZodString>;
153
- mcpAuthTokenOverride: z.ZodOptional<z.ZodString>;
154
165
  }, "strict", z.ZodTypeAny, {
155
166
  members: string[];
156
167
  aiApiKeyOverride?: string | undefined;
157
168
  gitSyncTokenOverride?: string | undefined;
158
- mcpAuthTokenOverride?: string | undefined;
159
169
  brainVersionOverride?: string | undefined;
160
170
  presetOverride?: "default" | "core" | "pro" | undefined;
161
171
  }, {
162
172
  members: string[];
163
173
  aiApiKeyOverride?: string | undefined;
164
174
  gitSyncTokenOverride?: string | undefined;
165
- mcpAuthTokenOverride?: string | undefined;
166
175
  brainVersionOverride?: string | undefined;
167
176
  presetOverride?: "default" | "core" | "pro" | undefined;
168
177
  }>, {
169
178
  members: string[];
170
179
  aiApiKeyOverride?: string | undefined;
171
180
  gitSyncTokenOverride?: string | undefined;
172
- mcpAuthTokenOverride?: string | undefined;
173
181
  brainVersionOverride?: string | undefined;
174
182
  presetOverride?: "default" | "core" | "pro" | undefined;
175
183
  }, {
176
184
  members: string[];
177
185
  aiApiKeyOverride?: string | undefined;
178
186
  gitSyncTokenOverride?: string | undefined;
179
- mcpAuthTokenOverride?: string | undefined;
180
187
  brainVersionOverride?: string | undefined;
181
188
  presetOverride?: "default" | "core" | "pro" | undefined;
182
189
  }>;
@@ -1,18 +1,15 @@
1
1
  import { z } from "@brains/utils";
2
2
  declare const encryptedUserSecretsSchema: z.ZodObject<{
3
3
  gitSyncToken: z.ZodOptional<z.ZodString>;
4
- mcpAuthToken: z.ZodOptional<z.ZodString>;
5
4
  discordBotToken: z.ZodOptional<z.ZodString>;
6
5
  aiApiKey: z.ZodOptional<z.ZodString>;
7
6
  }, "strict", z.ZodTypeAny, {
8
7
  aiApiKey?: string | undefined;
9
8
  gitSyncToken?: string | undefined;
10
- mcpAuthToken?: string | undefined;
11
9
  discordBotToken?: string | undefined;
12
10
  }, {
13
11
  aiApiKey?: string | undefined;
14
12
  gitSyncToken?: string | undefined;
15
- mcpAuthToken?: string | undefined;
16
13
  discordBotToken?: string | undefined;
17
14
  }>;
18
15
  export type EncryptedUserSecrets = z.infer<typeof encryptedUserSecretsSchema>;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.2.0-alpha.71",
7
+ "version": "0.2.0-alpha.72",
8
8
  "type": "module",
9
9
  "exports": {
10
10
  ".": {
@@ -20,16 +20,19 @@ GIT_SYNC_TOKEN=
20
20
  # @required @sensitive
21
21
  CONTENT_REPO_ADMIN_TOKEN=
22
22
 
23
- # MCP interface
24
- # Comes from the decrypted users/<handle>.secrets.yaml.age file.
25
- # @required @sensitive
26
- MCP_AUTH_TOKEN=
27
-
28
23
  # Discord (optional, per-user)
29
24
  # Comes from the decrypted users/<handle>.secrets.yaml.age file when enabled.
30
25
  # @sensitive
31
26
  DISCORD_BOT_TOKEN=
32
27
 
28
+ # Setup email delivery (optional, shared)
29
+ # Used when a user file declares setup.delivery: email.
30
+ # @sensitive
31
+ SETUP_EMAIL_API_KEY=
32
+
33
+ # @sensitive
34
+ SETUP_EMAIL_FROM=
35
+
33
36
  # ---- deploy/provision vars ----
34
37
 
35
38
  # @required @sensitive
@@ -103,7 +103,8 @@ jobs:
103
103
  env:
104
104
  SHARED_AI_API_KEY: ${{ secrets[steps.user_secrets.outputs.shared_ai_api_key_secret_name] }}
105
105
  SHARED_GIT_SYNC_TOKEN: ${{ secrets[steps.user_secrets.outputs.shared_git_sync_token_secret_name] }}
106
- SHARED_MCP_AUTH_TOKEN: ${{ secrets[steps.user_secrets.outputs.shared_mcp_auth_token_secret_name] }}
106
+ SETUP_EMAIL_API_KEY: ${{ secrets.SETUP_EMAIL_API_KEY }}
107
+ SETUP_EMAIL_FROM: ${{ secrets.SETUP_EMAIL_FROM }}
107
108
  HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }}
108
109
  HCLOUD_SSH_KEY_NAME: ${{ secrets.HCLOUD_SSH_KEY_NAME }}
109
110
  HCLOUD_SERVER_TYPE: ${{ secrets.HCLOUD_SERVER_TYPE }}
@@ -117,7 +118,6 @@ jobs:
117
118
  run: |
118
119
  export AI_API_KEY="${AI_API_KEY:-$SHARED_AI_API_KEY}"
119
120
  export GIT_SYNC_TOKEN="${GIT_SYNC_TOKEN:-$SHARED_GIT_SYNC_TOKEN}"
120
- export MCP_AUTH_TOKEN="${MCP_AUTH_TOKEN:-$SHARED_MCP_AUTH_TOKEN}"
121
121
  bun deploy/scripts/validate-secrets.ts
122
122
 
123
123
  - name: Seed content repo
@@ -175,14 +175,14 @@ jobs:
175
175
  env:
176
176
  SHARED_AI_API_KEY: ${{ secrets[steps.user_secrets.outputs.shared_ai_api_key_secret_name] }}
177
177
  SHARED_GIT_SYNC_TOKEN: ${{ secrets[steps.user_secrets.outputs.shared_git_sync_token_secret_name] }}
178
- SHARED_MCP_AUTH_TOKEN: ${{ secrets[steps.user_secrets.outputs.shared_mcp_auth_token_secret_name] }}
178
+ SETUP_EMAIL_API_KEY: ${{ secrets.SETUP_EMAIL_API_KEY }}
179
+ SETUP_EMAIL_FROM: ${{ secrets.SETUP_EMAIL_FROM }}
179
180
  KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
180
181
  CERTIFICATE_PEM: ${{ secrets.CERTIFICATE_PEM }}
181
182
  PRIVATE_KEY_PEM: ${{ secrets.PRIVATE_KEY_PEM }}
182
183
  run: |
183
184
  export AI_API_KEY="${AI_API_KEY:-$SHARED_AI_API_KEY}"
184
185
  export GIT_SYNC_TOKEN="${GIT_SYNC_TOKEN:-$SHARED_GIT_SYNC_TOKEN}"
185
- export MCP_AUTH_TOKEN="${MCP_AUTH_TOKEN:-$SHARED_MCP_AUTH_TOKEN}"
186
186
  bun deploy/scripts/write-kamal-secrets.ts
187
187
 
188
188
  - name: Provision server
@@ -20,7 +20,6 @@ const pilot = parseFlatYaml(readFileSync("pilot.yaml", "utf8"));
20
20
 
21
21
  writeGitHubEnv("AI_API_KEY", secrets["aiApiKey"] ?? "");
22
22
  writeGitHubEnv("GIT_SYNC_TOKEN", secrets["gitSyncToken"] ?? "");
23
- writeGitHubEnv("MCP_AUTH_TOKEN", secrets["mcpAuthToken"] ?? "");
24
23
  writeGitHubEnv("DISCORD_BOT_TOKEN", secrets["discordBotToken"] ?? "");
25
24
 
26
25
  writeGitHubOutput(
@@ -31,10 +30,6 @@ writeGitHubOutput(
31
30
  "shared_git_sync_token_secret_name",
32
31
  requireFlatValue(pilot, "gitSyncToken", "pilot.yaml"),
33
32
  );
34
- writeGitHubOutput(
35
- "shared_mcp_auth_token_secret_name",
36
- requireFlatValue(pilot, "mcpAuthToken", "pilot.yaml"),
37
- );
38
33
 
39
34
  function extractAgeIdentity(contents: string): string {
40
35
  const line = contents
@@ -4,7 +4,7 @@
4
4
  2. Run `bunx brains-ops age-key:bootstrap <repo> --push-to gh`.
5
5
  3. Fill in `pilot.yaml`.
6
6
  - keep your pinned `brainVersion`
7
- - confirm shared selectors for `aiApiKey`, `gitSyncToken`, `contentRepoAdminToken`, and `mcpAuthToken`
7
+ - confirm shared selectors for `aiApiKey`, `gitSyncToken`, and `contentRepoAdminToken`
8
8
  - use different tokens for `contentRepoAdminToken` and `gitSyncToken`: admin creates/checks content repos; sync is used by runtime directory-sync
9
9
  - confirm `agePublicKey`
10
10
  4. Run `bunx brains-ops user:add <repo> <handle> --cohort <cohort>`.
@@ -12,6 +12,8 @@
12
12
  - if the user should be an anchor there, add `--anchor-id <discord-user-id>`.
13
13
  - the command creates `users/<handle>.yaml`, `users/<handle>.secrets.yaml`, and the cohort membership without duplicating existing entries.
14
14
  5. Edit the generated user file if the anchor profile needs richer metadata.
15
+ - For browser/CMS-first onboarding, add `setup.delivery: email` and `setup.email` to the user file.
16
+ - Ensure `SETUP_EMAIL_API_KEY` and `SETUP_EMAIL_FROM` exist as GitHub Secrets before deploying any email-setup user.
15
17
  6. Run `bunx brains-ops render <repo>`.
16
18
  7. Run `bunx brains-ops ssh-key:bootstrap <repo> --push-to gh`.
17
19
  8. Run `bunx brains-ops cert:bootstrap <repo> --push-to gh`.
@@ -23,11 +25,11 @@
23
25
  - `https://<handle>.rizom.ai/health` returns `200`
24
26
  - unauthenticated `POST https://<handle>.rizom.ai/mcp` returns `401`
25
27
  14. For fleet upgrades, edit `pilot.yaml.brainVersion` and push once; CI rebuilds the shared image tag, refreshes generated user env files, and redeploys affected users.
26
- 15. Hand the Discord setup details to the user.
28
+ 15. For Discord users, hand the Discord setup details to the user. For email-setup users, confirm they received the setup email and completed passkey registration.
27
29
  16. Hand over the browser defaults:
28
30
  - Dashboard: `https://<handle>.rizom.ai/`
29
31
  - CMS: `https://<handle>.rizom.ai/cms`
30
32
  - GitHub token guidance for CMS access to the user's private content repo
31
- 17. If they need direct client access, also hand over the MCP connection details.
33
+ 17. If they need direct client access, use OAuth/passkey-capable clients where possible; do not use the deprecated static `MCP_AUTH_TOKEN` path for Rover pilot users.
32
34
  18. If you are also giving them a content repo workflow, describe it as optional and frame git/Obsidian as an advanced file-based path, not the default.
33
35
  19. Send `docs/user-onboarding.md` to the user as the pilot handoff guide.
@@ -70,6 +70,69 @@ Use these checks after deploy:
70
70
  - unauthenticated `POST https://<handle>.rizom.ai/mcp` should return `401 Unauthorized: Bearer token required`
71
71
  - a bare `GET /` may also return `401`; that is expected for rover core and does not indicate a bad deploy
72
72
 
73
+ ## Setup email checklist
74
+
75
+ Use this for browser/CMS-first users who should receive their own first-passkey setup link by email.
76
+
77
+ 1. Add setup delivery to the user file:
78
+
79
+ ```yaml
80
+ setup:
81
+ delivery: email
82
+ email: user@example.com
83
+ ```
84
+
85
+ 2. Configure these GitHub Secrets before deploy:
86
+ - `SETUP_EMAIL_API_KEY`
87
+ - `SETUP_EMAIL_FROM`
88
+
89
+ 3. Reconcile/deploy the user or cohort:
90
+ - `bunx brains-ops onboard . <handle>`
91
+ - or `bunx brains-ops reconcile-cohort . <cohort>`
92
+
93
+ 4. Verify the generated `users/<handle>/brain.yaml` contains `auth-service.setupEmail` and `email-resend` config.
94
+ 5. Ask the user to complete passkey setup from the email link, then use:
95
+ - Dashboard: `https://<handle>.rizom.ai/`
96
+ - CMS: `https://<handle>.rizom.ai/cms`
97
+
98
+ Notes:
99
+
100
+ - The setup URL is generated and sent by the running brain; operators should not scrape logs or SSH into the instance to retrieve it.
101
+ - The auth service owns setup email dedupe. It should not resend for the same persisted setup token after restart, but should retry failed delivery and resend after token rotation.
102
+ - `SETUP_EMAIL_FROM` is not marked required because fleets without email setup can omit it, but it is required for users with `setup.delivery: email`.
103
+
104
+ ## Legacy MCP token cleanup
105
+
106
+ Rover pilot onboarding no longer uses the deprecated static `MCP_AUTH_TOKEN` fallback. OAuth/passkeys and setup email are the default browser/CMS path.
107
+
108
+ For existing Rover pilot repos:
109
+
110
+ 1. Update the checked-in deploy contract first:
111
+ - remove `mcpAuthToken` from `pilot.yaml`
112
+ - remove `MCP_AUTH_TOKEN` from `.env.schema`
113
+ - remove `SHARED_MCP_AUTH_TOKEN` / `MCP_AUTH_TOKEN` exports from `.github/workflows/deploy.yml`
114
+ - update `deploy/scripts/decrypt-user-secrets.ts` so it no longer reads or writes `mcpAuthToken`
115
+
116
+ 2. Confirm no per-user or cohort MCP overrides exist:
117
+
118
+ ```sh
119
+ rg "mcpAuthToken|MCP_AUTH_TOKEN" users cohorts pilot.yaml
120
+ ```
121
+
122
+ 3. If there were no user/cohort overrides, no `.age` re-encryption is needed: the default token lived only as the GitHub Secret named `MCP_AUTH_TOKEN`, not inside `users/<handle>.secrets.yaml.age`.
123
+ 4. Redeploy all existing Rover users while the GitHub Secret still exists. A secret existing in GitHub is not inherited by jobs or containers unless the workflow references it.
124
+ 5. Verify the new deploy does not pass the token:
125
+ - generated `.kamal/secrets` does not contain `MCP_AUTH_TOKEN`
126
+ - the running container environment does not contain `MCP_AUTH_TOKEN`
127
+
128
+ 6. Delete the unused GitHub Secret last:
129
+
130
+ ```sh
131
+ gh secret delete MCP_AUTH_TOKEN
132
+ ```
133
+
134
+ Only decrypt and re-encrypt `users/<handle>.secrets.yaml.age` files if step 2 or a direct audit shows an actual `mcpAuthToken` override was stored there.
135
+
73
136
  ## Discord bot token checklist
74
137
 
75
138
  Use this when enabling Discord for a pilot user.
@@ -97,7 +160,7 @@ Notes:
97
160
  - Do not reuse the same Discord bot token across multiple pilot users.
98
161
  - Discord is the default pilot interface moving forward.
99
162
  - The encrypted `users/<handle>.secrets.yaml.age` file is the durable checked-in deploy input; your local env is only the operator staging source.
100
- - MCP is optional and mainly for direct client access or specific testing workflows.
163
+ - Direct MCP client access should use OAuth/passkey-capable clients where possible; do not reintroduce `MCP_AUTH_TOKEN` for Rover pilot users.
101
164
  - When explaining the content workflow, describe it first as a normal **git repo** of **markdown/text files**.
102
165
  - Position **Obsidian** as optional: it is just one possible editor for those same files, not the default requirement.
103
166
 
@@ -8,5 +8,4 @@ preset: core
8
8
  aiApiKey: AI_API_KEY
9
9
  gitSyncToken: GIT_SYNC_TOKEN
10
10
  contentRepoAdminToken: CONTENT_REPO_ADMIN_TOKEN
11
- mcpAuthToken: MCP_AUTH_TOKEN
12
11
  agePublicKey: age1replace-with-your-public-key