nx-master — chat authorization, as built
One Go service is the whole trust boundary. The browser talks only to it; it owns the session, the conversation and the transcript, and it is the only holder of the agent API key. The agent, in turn, holds one MCP token that is identical for every person on the box — which is the seam this drawing exists to make obvious.
What holds it together
- The thread id is not a capability. Every route resolves a thread through
GetChatSession(thread, userID), so pasting someone else's URL returns 404 and posting into it is refused. Verified by signing in as the second user: their rail showed only their own empty thread, and direct access, post and archive all 404'd while the first user's four messages stayed untouched. - A turn is a Run, not a request.
POST /ui/chatreturns in ~20 ms with a live bubble; the turn streams from the agent in a detached goroutine. That is what lets the composer stay usable and several turns sit in flight. - The identity block is text. Each turn prepends
Caller: <login> (role <r>, user id <n>), thread <id>so the agent answers as that person — "who am I?" → "You're hazemarian (Hazem Arian), role master — last sign-in …". Useful, and verified. It is also not a credential: a model can be talked out of an instruction, but not out of a token it does not have. - Context is ours. No
X-Hermes-Session-Keyis sent; the app replays the thread out of SQLite, compressed 5.3×. Paying Hermes to keep a second copy would double the prompt for no gain.
The seam — why this is not per-user authorization
Steps inside app are per-person and enforced. Everything from mcpclient outward is
per-machine: /mcp authenticates a token that identifies the bot, so it cannot tell
whose turn it is serving.
- the conversation tools were removed outright, because with one shared token they returned every user's threads; see the decision.
users.*can only carry fixed rules (members only), not "what may this person do".- Audit lines read
actor: mcp:bot, so the log cannot say who asked.
The identity block narrows this by instruction, not by construction. For two co-owners that is a reasonable trade; it is not a boundary.
This is being replaced by one bot per user — see the bot design.
Each user gets their own Hermes profile with their own MCP token, so the credential that
reaches /mcp already belongs to exactly one person and the question "whose turn is this?"
never has to be asked. chats.list and chats.messages are deleted in the
meantime: the UI renders conversations from SQLite, and not having the tool is stronger
than scoping it. With two co-owners on the box, that removal alone closes the gap today.
Rendered from nx-master/chat-authorization.md · OKF bundle · regenerate with npm run render