fix(web): correct LinkedIn URL on about page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
663
docs/member-profile-spec.md
Normal file
663
docs/member-profile-spec.md
Normal file
@@ -0,0 +1,663 @@
|
||||
# Member Profile: Persistent Identity & Dashboard Management
|
||||
|
||||
> Spec for moving member identity (role tag, groups, display name, message
|
||||
> mode) from ephemeral CLI flags to persistent server-side state, editable
|
||||
> from the dashboard with configurable self-edit permissions.
|
||||
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
Today, launching a claudemesh session requires re-declaring your identity:
|
||||
|
||||
```bash
|
||||
claudemesh launch --name Alice --role lead --groups eng,review --message-mode push
|
||||
```
|
||||
|
||||
Every. Single. Time. These values live on the ephemeral `presence` row
|
||||
(per-WS connection) and `peerState` row (cross-session, but CLI-written
|
||||
only). There's no way for:
|
||||
|
||||
- An admin to assign someone's role/groups from the dashboard
|
||||
- A user to set their profile once and forget about it
|
||||
- An invite to pre-configure a new member's identity
|
||||
- The dashboard to show/manage who belongs to which groups
|
||||
|
||||
This creates friction for daily users and makes managed teams impossible.
|
||||
|
||||
---
|
||||
|
||||
## Design
|
||||
|
||||
### Move identity to `member` (persistent, server-side)
|
||||
|
||||
| Field | Current location | New location | Source of truth |
|
||||
|---|---|---|---|
|
||||
| `displayName` | presence (ephemeral) | **member** (persistent) | Server, CLI flag overrides per-session |
|
||||
| `roleTag` | nowhere (CLI `--role` flag only) | **member** (persistent) | Server, CLI flag overrides per-session |
|
||||
| `groups` | peerState (CLI-written) | **member** (persistent) | Server, CLI flag overrides per-session |
|
||||
| `messageMode` | config.json (local file) | **member** (persistent) | Server, CLI flag overrides per-session |
|
||||
| `status` | presence | presence (no change) | Ephemeral, changes per-minute |
|
||||
| `summary` | presence | presence (no change) | Ephemeral, changes per-task |
|
||||
| `cwd`, `pid` | presence | presence (no change) | Literal session metadata |
|
||||
|
||||
### Three-layer model
|
||||
|
||||
```
|
||||
member (persistent, server-side)
|
||||
│ Source of truth for identity. Set via dashboard, CLI profile command,
|
||||
│ or invite presets. Survives everything.
|
||||
│
|
||||
├── peerState (cross-session, server-side)
|
||||
│ Cumulative stats, visibility toggle, last-seen metadata.
|
||||
│ Still CLI-written. Not promoted — these are operational, not identity.
|
||||
│
|
||||
└── presence (ephemeral, per-connection)
|
||||
Runtime snapshot. Copies member defaults on connect.
|
||||
CLI flags override for this session only.
|
||||
Status, summary, cwd, pid — all transient.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schema changes
|
||||
|
||||
### Extend `mesh.member`
|
||||
|
||||
```sql
|
||||
ALTER TABLE mesh.member
|
||||
ADD COLUMN role_tag TEXT, -- free-text label (lead, backend-dev, observer)
|
||||
ADD COLUMN default_groups JSONB DEFAULT '[]', -- [{name: string, role?: string}]
|
||||
ADD COLUMN message_mode TEXT DEFAULT 'push', -- push | inbox | off
|
||||
ADD COLUMN dashboard_user_id TEXT; -- links to Payload CMS user.id (for CLI sync)
|
||||
|
||||
CREATE INDEX member_dashboard_user_idx
|
||||
ON mesh.member(dashboard_user_id)
|
||||
WHERE dashboard_user_id IS NOT NULL;
|
||||
```
|
||||
|
||||
**Note:** `member.displayName` already exists. `member.role` stays as the
|
||||
permission level enum (admin/member). `role_tag` is the new free-text label.
|
||||
|
||||
### Rename for clarity
|
||||
|
||||
The existing `member.role` (admin/member enum) controls **permissions**.
|
||||
The new `member.role_tag` is a **label** visible to peers. To avoid
|
||||
confusion in code and UI:
|
||||
|
||||
```
|
||||
member.role → member.permission -- "admin" | "member" (access control)
|
||||
member.role_tag → member.roleTag -- "backend-dev", "lead", etc. (display label)
|
||||
```
|
||||
|
||||
**DB migration:** rename the column for clarity:
|
||||
|
||||
```sql
|
||||
ALTER TABLE mesh.member RENAME COLUMN role TO permission;
|
||||
-- Also rename the enum type if feasible, or keep as-is (DB enum name is internal)
|
||||
```
|
||||
|
||||
**Impact:** Update all broker code that references `member.role` to
|
||||
`member.permission`. The `meshRoleEnum` values stay the same (admin/member).
|
||||
|
||||
### Extend `mesh.mesh` — self-edit policy
|
||||
|
||||
```sql
|
||||
ALTER TABLE mesh.mesh
|
||||
ADD COLUMN self_editable JSONB DEFAULT '{
|
||||
"displayName": true,
|
||||
"roleTag": true,
|
||||
"groups": true,
|
||||
"messageMode": true
|
||||
}';
|
||||
```
|
||||
|
||||
Controls what members can edit about themselves. Admins can always edit
|
||||
anyone. Mesh creator configures this on the dashboard.
|
||||
|
||||
### Extend `mesh.invite` — presets
|
||||
|
||||
```sql
|
||||
ALTER TABLE mesh.invite
|
||||
ADD COLUMN preset JSONB DEFAULT '{}';
|
||||
```
|
||||
|
||||
Preset schema:
|
||||
|
||||
```typescript
|
||||
interface InvitePreset {
|
||||
displayName?: string; // rarely set — joiner usually picks their own
|
||||
roleTag?: string; // "backend-dev", "observer", etc.
|
||||
groups?: Array<{ name: string; role?: string }>;
|
||||
messageMode?: "push" | "inbox" | "off";
|
||||
}
|
||||
```
|
||||
|
||||
When a member joins via this invite, preset values are applied to the
|
||||
member row as defaults. The joiner can change them later (if self-editable).
|
||||
|
||||
---
|
||||
|
||||
## Permission model
|
||||
|
||||
### Who can edit what
|
||||
|
||||
| Action | Who | Condition |
|
||||
|---|---|---|
|
||||
| Edit your own `displayName` | You | `mesh.selfEditable.displayName` is true |
|
||||
| Edit your own `roleTag` | You | `mesh.selfEditable.roleTag` is true |
|
||||
| Edit your own `groups` | You | `mesh.selfEditable.groups` is true |
|
||||
| Edit your own `messageMode` | You | `mesh.selfEditable.messageMode` is true |
|
||||
| Edit **any member's** profile fields | Mesh admins | Always |
|
||||
| Change `permission` (admin ↔ member) | Mesh admins | Always |
|
||||
| Revoke a member | Mesh admins | Always |
|
||||
| Change `selfEditable` policy | Mesh admins | Always |
|
||||
|
||||
### Default policy by tier
|
||||
|
||||
| Field | free | pro | team | enterprise |
|
||||
|---|---|---|---|---|
|
||||
| `displayName` | self | self | self | self |
|
||||
| `roleTag` | self | self | admin-only | admin-only |
|
||||
| `groups` | self | self | admin-only | admin-only |
|
||||
| `messageMode` | self | self | self | self |
|
||||
|
||||
These are defaults — the mesh creator can override any of them on the
|
||||
dashboard regardless of tier.
|
||||
|
||||
---
|
||||
|
||||
## Broker changes
|
||||
|
||||
### New HTTP endpoints
|
||||
|
||||
#### `PATCH /mesh/:meshId/member/:memberId`
|
||||
|
||||
Update a member's profile fields. Used by dashboard and CLI.
|
||||
|
||||
```typescript
|
||||
// Request
|
||||
PATCH /mesh/:meshId/member/:memberId
|
||||
Authorization: Bearer <dashboard-session-token> OR X-Pubkey + X-Signature
|
||||
{
|
||||
"displayName": "Alice",
|
||||
"roleTag": "lead",
|
||||
"groups": [{ "name": "eng", "role": "lead" }, { "name": "review" }],
|
||||
"messageMode": "push"
|
||||
}
|
||||
|
||||
// Response
|
||||
{
|
||||
"ok": true,
|
||||
"member": {
|
||||
"id": "member_123",
|
||||
"displayName": "Alice",
|
||||
"roleTag": "lead",
|
||||
"groups": [{ "name": "eng", "role": "lead" }, { "name": "review" }],
|
||||
"messageMode": "push",
|
||||
"permission": "admin"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Authorization logic:**
|
||||
|
||||
```
|
||||
if (caller is dashboard admin OR caller.memberId == targetMemberId with admin permission):
|
||||
→ allow all fields
|
||||
elif (caller.memberId == targetMemberId):
|
||||
→ check mesh.selfEditable for each field
|
||||
→ reject fields that are admin-only: 403 "field X is admin-managed in this mesh"
|
||||
else:
|
||||
→ 403 "not authorized"
|
||||
```
|
||||
|
||||
**Side effect:** If the target member has active WebSocket connections,
|
||||
push a `profile_updated` event to all their sessions:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "profile_updated",
|
||||
"memberId": "member_123",
|
||||
"changes": {
|
||||
"roleTag": "lead",
|
||||
"groups": [{ "name": "eng", "role": "lead" }, { "name": "review" }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The CLI handles this by updating its in-memory state for the current session.
|
||||
|
||||
#### `GET /mesh/:meshId/members`
|
||||
|
||||
List all members with their profiles. Used by dashboard and CLI.
|
||||
|
||||
```typescript
|
||||
// Response
|
||||
{
|
||||
"ok": true,
|
||||
"members": [
|
||||
{
|
||||
"id": "member_123",
|
||||
"displayName": "Alice",
|
||||
"roleTag": "lead",
|
||||
"groups": [{ "name": "eng", "role": "lead" }],
|
||||
"messageMode": "push",
|
||||
"permission": "admin",
|
||||
"dashboardUserId": "user_abc123",
|
||||
"joinedAt": "2026-04-01T10:00:00Z",
|
||||
"lastSeenAt": "2026-04-08T14:30:00Z",
|
||||
"online": true,
|
||||
"sessionCount": 2
|
||||
},
|
||||
{
|
||||
"id": "member_456",
|
||||
"displayName": "Bob",
|
||||
"roleTag": "backend-dev",
|
||||
"groups": [{ "name": "eng" }],
|
||||
"messageMode": "inbox",
|
||||
"permission": "member",
|
||||
"dashboardUserId": null,
|
||||
"joinedAt": "2026-04-03T09:00:00Z",
|
||||
"lastSeenAt": "2026-04-07T18:00:00Z",
|
||||
"online": false,
|
||||
"sessionCount": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`online` and `sessionCount` are derived from active `presence` rows
|
||||
(disconnectedAt IS NULL) for each member.
|
||||
|
||||
#### `PATCH /mesh/:meshId/settings`
|
||||
|
||||
Update mesh settings including self-edit policy. Dashboard only, admin only.
|
||||
|
||||
```typescript
|
||||
// Request
|
||||
PATCH /mesh/:meshId/settings
|
||||
{
|
||||
"selfEditable": {
|
||||
"displayName": true,
|
||||
"roleTag": false,
|
||||
"groups": false,
|
||||
"messageMode": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### hello_ack changes
|
||||
|
||||
When a peer connects, the `hello_ack` now includes the member's persistent
|
||||
profile so the CLI can apply defaults:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "hello_ack",
|
||||
"presenceId": "pres_789",
|
||||
"memberDisplayName": "Alice",
|
||||
"memberProfile": {
|
||||
"roleTag": "lead",
|
||||
"groups": [{ "name": "eng", "role": "lead" }, { "name": "review" }],
|
||||
"messageMode": "push"
|
||||
},
|
||||
"meshPolicy": {
|
||||
"selfEditable": { "displayName": true, "roleTag": false, "groups": false, "messageMode": true }
|
||||
},
|
||||
"restored": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Presence creation changes
|
||||
|
||||
When creating a `presence` row on hello, the broker now merges:
|
||||
|
||||
```
|
||||
1. Start with member defaults (displayName, roleTag → groups, messageMode)
|
||||
2. Override with CLI hello payload (if flags were provided)
|
||||
3. Write to presence row
|
||||
```
|
||||
|
||||
This means `presence.groups` is populated from `member.default_groups` if
|
||||
the CLI didn't send explicit groups in the hello. No more blank sessions.
|
||||
|
||||
### Join flow changes
|
||||
|
||||
When a member joins via `/join`, the broker applies invite presets:
|
||||
|
||||
```typescript
|
||||
// In handleJoinPost, after creating the member row:
|
||||
if (invite.preset) {
|
||||
const preset = invite.preset;
|
||||
await db.update(meshMember)
|
||||
.set({
|
||||
roleTag: preset.roleTag ?? null,
|
||||
defaultGroups: preset.groups ?? [],
|
||||
messageMode: preset.messageMode ?? "push",
|
||||
// displayName already set from the join request
|
||||
})
|
||||
.where(eq(meshMember.id, newMemberId));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI changes
|
||||
|
||||
### Launch flow (simplified)
|
||||
|
||||
```typescript
|
||||
// After config loaded and mesh selected:
|
||||
|
||||
// 1. Connect to broker (existing flow)
|
||||
// 2. Receive hello_ack with memberProfile + meshPolicy
|
||||
|
||||
// 3. Apply member defaults, CLI flags override
|
||||
const effectiveName = flags.name ?? helloAck.memberDisplayName;
|
||||
const effectiveRole = flags.role ?? helloAck.memberProfile.roleTag;
|
||||
const effectiveGroups = flags.groups ?? helloAck.memberProfile.groups;
|
||||
const effectiveMode = flags.messageMode ?? helloAck.memberProfile.messageMode;
|
||||
|
||||
// 4. No prompts. Flags or server defaults. Done.
|
||||
```
|
||||
|
||||
### `claudemesh profile` command
|
||||
|
||||
New command to view/edit your member profile from the CLI:
|
||||
|
||||
```bash
|
||||
# View current profile
|
||||
claudemesh profile
|
||||
# Name: Alice
|
||||
# Role: lead
|
||||
# Groups: eng (lead), review
|
||||
# Messages: push
|
||||
# Mesh: dev-team (admin)
|
||||
|
||||
# Edit fields (sends PATCH to broker)
|
||||
claudemesh profile --role-tag fullstack
|
||||
claudemesh profile --groups eng,frontend,review
|
||||
claudemesh profile --message-mode inbox
|
||||
claudemesh profile --name "Alice M."
|
||||
|
||||
# Edit another member (admin only)
|
||||
claudemesh profile --member Bob --role-tag junior-dev --groups onboarding
|
||||
```
|
||||
|
||||
Fields that are admin-managed show a lock icon:
|
||||
|
||||
```bash
|
||||
claudemesh profile
|
||||
# Name: Alice
|
||||
# Role: lead 🔒 (admin-managed)
|
||||
# Groups: eng (lead), review 🔒 (admin-managed)
|
||||
# Messages: push
|
||||
```
|
||||
|
||||
Attempting to edit a locked field:
|
||||
|
||||
```bash
|
||||
claudemesh profile --role-tag senior
|
||||
# Error: roleTag is admin-managed in this mesh. Ask a mesh admin to change it.
|
||||
```
|
||||
|
||||
### First launch stores displayName
|
||||
|
||||
When `--name Alice` is provided on first launch (or sync), the CLI sends
|
||||
it to the broker which persists it on the member row. Future launches
|
||||
don't need `--name`:
|
||||
|
||||
```bash
|
||||
# First time
|
||||
claudemesh launch --name Alice
|
||||
# → broker stores displayName="Alice" on member row
|
||||
|
||||
# Every subsequent launch
|
||||
claudemesh launch
|
||||
# → hello_ack returns displayName="Alice", no flag needed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Invite presets
|
||||
|
||||
### Creating an invite with presets (dashboard)
|
||||
|
||||
```
|
||||
Create invite link — dev-team
|
||||
|
||||
Permission: [member ▾] (admin/member)
|
||||
|
||||
Profile presets (applied to new members):
|
||||
Role tag: [backend-dev ]
|
||||
Groups: [eng ×] [review ×] [+ Add]
|
||||
Message mode: (●) Push ( ) Inbox ( ) Off
|
||||
|
||||
Link settings:
|
||||
Max uses: [10 ]
|
||||
Expires: [7 days ▾]
|
||||
|
||||
[Generate link]
|
||||
|
||||
────────────────────
|
||||
|
||||
ic://join/eyJhbGciOi...
|
||||
https://claudemesh.com/join/eyJhbGciOi...
|
||||
|
||||
[Copy link]
|
||||
```
|
||||
|
||||
### Creating an invite with presets (CLI)
|
||||
|
||||
```bash
|
||||
claudemesh invite create \
|
||||
--role-tag backend-dev \
|
||||
--groups eng,review \
|
||||
--message-mode push \
|
||||
--max-uses 10 \
|
||||
--expires 7d
|
||||
```
|
||||
|
||||
### Invite payload extension
|
||||
|
||||
The signed invite payload gains a `preset` field:
|
||||
|
||||
```json
|
||||
{
|
||||
"v": 1,
|
||||
"mesh_id": "mesh_xyz",
|
||||
"mesh_slug": "dev-team",
|
||||
"broker_url": "wss://ic.claudemesh.com/ws",
|
||||
"expires_at": 1713100000,
|
||||
"mesh_root_key": "...",
|
||||
"role": "member",
|
||||
"preset": {
|
||||
"roleTag": "backend-dev",
|
||||
"groups": [{ "name": "eng" }, { "name": "review" }],
|
||||
"messageMode": "push"
|
||||
},
|
||||
"owner_pubkey": "...",
|
||||
"signature": "..."
|
||||
}
|
||||
```
|
||||
|
||||
The `preset` is included in the canonical signed bytes (appended to
|
||||
the existing canonical format) so it can't be tampered with.
|
||||
|
||||
---
|
||||
|
||||
## Dashboard views
|
||||
|
||||
### Mesh members page
|
||||
|
||||
```
|
||||
dev-team — Members
|
||||
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ Name Role tag Groups Status Access │
|
||||
│─────────────────────────────────────────────────────────────── │
|
||||
│ ● Alice lead eng, review idle admin ▾ │
|
||||
│ ● Bob backend-dev eng working member ▾ │
|
||||
│ ○ Carol designer design, ux — member ▾ │
|
||||
│ ○ Dave — — — member ▾ │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
|
||||
● = online (has active session) ○ = offline
|
||||
|
||||
[Invite member]
|
||||
```
|
||||
|
||||
Clicking a member opens an edit panel:
|
||||
|
||||
```
|
||||
Edit member — Bob
|
||||
|
||||
Display name: [Bob ]
|
||||
Role tag: [backend-dev ]
|
||||
Groups: [eng ×] [+ Add]
|
||||
Message mode: (●) Push ( ) Inbox ( ) Off
|
||||
Permission: [member ▾]
|
||||
|
||||
Joined: Apr 3, 2026
|
||||
Last seen: 2 hours ago
|
||||
Sessions: 0 (offline)
|
||||
|
||||
[Save] [Revoke access]
|
||||
```
|
||||
|
||||
### Mesh settings page
|
||||
|
||||
```
|
||||
dev-team — Settings
|
||||
|
||||
General:
|
||||
Name: [dev-team ]
|
||||
Visibility: [private ▾]
|
||||
|
||||
Member self-edit permissions:
|
||||
What can members edit about themselves?
|
||||
|
||||
Display name: [✓]
|
||||
Role tag: [ ] ← only admins can assign
|
||||
Groups: [ ] ← only admins can assign
|
||||
Message mode: [✓]
|
||||
|
||||
[Save]
|
||||
```
|
||||
|
||||
### Live presence view
|
||||
|
||||
```
|
||||
dev-team — Live
|
||||
|
||||
┌────────────────────────────────────────────────────────────────┐
|
||||
│ ● Alice (lead) idle │
|
||||
│ eng (lead), review │
|
||||
│ Session 1: ~/Desktop/claudemesh — "Working on auth sync" │
|
||||
│ Session 2: ~/Desktop/cuidecar — "Reviewing PR #47" │
|
||||
│ │
|
||||
│ ● Bob (backend-dev) working │
|
||||
│ eng │
|
||||
│ Session 1: ~/Desktop/api — "Fixing migration bug" │
|
||||
└────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Auto-refreshes every 5s via WebSocket.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Real-time profile push
|
||||
|
||||
When an admin (or self) updates a member's profile via the dashboard or
|
||||
CLI, all active sessions for that member receive a push:
|
||||
|
||||
```
|
||||
Dashboard: admin changes Bob's groups
|
||||
→ PATCH /mesh/:meshId/member/:memberId { groups: [{name: "ops"}] }
|
||||
→ Broker updates member row
|
||||
→ Broker finds all active presence rows for this memberId
|
||||
→ Broker sends to each WS connection:
|
||||
{ type: "profile_updated", changes: { groups: [{name: "ops"}] } }
|
||||
→ Bob's CLI receives push, updates in-memory groups
|
||||
→ Bob's next list_peers / join_group reflects the change
|
||||
→ No restart needed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration from peerState
|
||||
|
||||
The existing `peerState` table stores `groups`, `profile`, `visible`,
|
||||
`lastDisplayName`, and `cumulativeStats`. After this change:
|
||||
|
||||
| peerState field | Migration |
|
||||
|---|---|
|
||||
| `groups` | Copy to `member.default_groups` for existing members. peerState.groups becomes a session-level overlay (for CLI `join_group`/`leave_group` within a session). |
|
||||
| `lastDisplayName` | Already on `member.displayName`. Drop from peerState. |
|
||||
| `profile` (avatar, title, bio) | Keep on peerState for now. These are presentation, not identity. Could move to member later. |
|
||||
| `visible` | Keep on peerState. Session-scoped toggle. |
|
||||
| `cumulativeStats` | Keep on peerState. Operational data, not identity. |
|
||||
|
||||
**The peerState table is NOT removed.** It still serves its purpose for
|
||||
cross-session operational state. The member table absorbs identity fields
|
||||
only.
|
||||
|
||||
---
|
||||
|
||||
## Implementation order
|
||||
|
||||
1. **DB migration:** Add columns to `member` (role_tag, default_groups,
|
||||
message_mode, dashboard_user_id), `mesh` (self_editable), `invite`
|
||||
(preset). Rename `member.role` → `member.permission`.
|
||||
2. **Broker:** `PATCH /mesh/:meshId/member/:memberId` endpoint with
|
||||
self-edit permission checks and real-time push.
|
||||
3. **Broker:** `GET /mesh/:meshId/members` endpoint with online status.
|
||||
4. **Broker:** `PATCH /mesh/:meshId/settings` endpoint.
|
||||
5. **Broker:** Update `handleHello` to include memberProfile + meshPolicy
|
||||
in hello_ack. Update presence creation to merge member defaults.
|
||||
6. **Broker:** Update `/join` to apply invite presets to new members.
|
||||
7. **CLI:** Update launch to read memberProfile from hello_ack, skip
|
||||
prompts when server has defaults, flags override.
|
||||
8. **CLI:** `claudemesh profile` command.
|
||||
9. **CLI:** Update invite creation to accept preset flags.
|
||||
10. **Web:** Member management page (list, edit, revoke).
|
||||
11. **Web:** Mesh settings page (self-edit policy).
|
||||
12. **Web:** Invite creation with presets.
|
||||
13. **Web:** Live presence view.
|
||||
|
||||
---
|
||||
|
||||
## What stays the same
|
||||
|
||||
- Ed25519 keypairs remain the mesh identity
|
||||
- E2E encryption unchanged (crypto_box with peer keys)
|
||||
- `presence` table stays ephemeral — status, summary, cwd, pid
|
||||
- `peerState` keeps operational data — stats, visibility, session profile
|
||||
- `list_peers` MCP tool still works (reads from presence, now enriched
|
||||
with member defaults)
|
||||
- CLI `--role`, `--groups`, `--message-mode` flags still work as
|
||||
per-session overrides
|
||||
- `join_group` / `leave_group` WS messages still work for session-scoped
|
||||
group changes (these update presence, not member)
|
||||
|
||||
---
|
||||
|
||||
## Open questions
|
||||
|
||||
1. **Session-scoped group changes vs member-level groups.** If member has
|
||||
`groups: [eng]` and the CLI does `join_group("review")` mid-session,
|
||||
does that add to the member row or just the presence? **Proposal: just
|
||||
presence.** Session-scoped join/leave is temporary. Use `claudemesh
|
||||
profile --groups` or dashboard for permanent changes.
|
||||
|
||||
2. **Profile conflicts across devices.** If Alice has two CLI devices with
|
||||
different keypairs (different member rows), they have independent
|
||||
profiles. This is correct — they're different identities in the mesh.
|
||||
But if she syncs from the same dashboard account, should her profile
|
||||
sync across devices? **Proposal: no, not in v1.** Each member row is
|
||||
independent. Dashboard shows all members linked to your account.
|
||||
|
||||
3. **Audit trail for profile changes.** Should profile edits go in the
|
||||
audit log? **Proposal: yes.** Event type: `member_profile_updated`,
|
||||
payload includes who changed what. Useful for managed teams.
|
||||
Reference in New Issue
Block a user