Files
platform/COMMS/sidecar-app-store/29-history-layer.md
T
pastilhasandClaude Opus 5 95951fbe5a resolve transcripts and cwd against the caller's home, not the owner's
The history layer, and the last change that could be made without a live member.

claude-sessions.ts had `claudeHome = process.env.HOME_DIR ?? join(DATA_PATH,
email, 'home')`, which discards its argument whenever HOME_DIR is set — always,
on a real install. Every transcript read therefore resolved to the OWNER'S
~/.claude no matter who asked, and the comment above it asserted "single-user
platform" as though that were a property rather than an assumption. A member
reaching these functions would have been handed the owner's conversation list.

Now every read takes a ChatIdentity {email, home} with the home resolved from
resolveHomeDir(userId), and this file has no way to invent one. Both fields
travel together because they are genuinely different: general_chat_sessions
lives under DATA_PATH/<email>, not under a home. Collapsing them would be the
same class of mistake as undefined meaning "the owner".

websocket.ts's resolveCwd takes a home, so `~` expands against the caller's own.
Identity is resolved BEFORE the cwd — expanding `~` before knowing whose home it
is would be exactly the bug being removed — which also let a duplicate
resolveTurnIdentity call from 6aeb304 be deleted.

chat.ts resolves per request and throws FORBIDDEN rather than falling back, same
posture as resolveTurnIdentity. agent-runner passes the owner's home explicitly
rather than inheriting it, since that path really is owner-only.

Made at 01:00 after saying it should not be. Three things I am least sure of are
listed in COMMS 29 rather than left for the reviewer to find: chat routes now
have a failure mode they did not have, resolveBaseCwd's exported parameter
changed meaning rather than shape, and the bare-email rewrite in chat.ts was
mechanical with hand repair.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 00:48:09 +00:00

53 lines
3.2 KiB
Markdown

# 29 — the history layer is off `getOwnerHomeDir`
The owner asked me to carry on, so `27`'s "tomorrow" became tonight. This is the change I said I would not
make at this hour, made at this hour — review it accordingly, and I would rather you were slow about it than
quick.
## What moved
**`claude-sessions.ts` no longer knows how to invent a home.** `claudeHome(email)` is gone. Every transcript
read now takes a `ChatIdentity { email, home }`, and the home arrives resolved from `resolveHomeDir(userId)`.
The old line was `process.env.HOME_DIR ?? join(DATA_PATH, email, 'home')` — which discards its argument
whenever `HOME_DIR` is set, i.e. always on a real install. So every read resolved to the owner's `~/.claude`
regardless of who asked, and the comment above it asserted "single-user platform" as though that were a
property rather than an assumption.
Both halves travel together rather than one standing in for the other, because they are genuinely different:
`general_chat_sessions` lives under `DATA_PATH/<email>`, not under a home, so those paths stay email-derived.
Collapsing them would have been the same class of mistake as `undefined` meaning "the owner".
**`chat.ts` resolves the identity per request** (`chatIdentity`), and throws FORBIDDEN rather than falling
back — same posture as `resolveTurnIdentity`, for the same reason.
**`websocket.ts`'s `resolveCwd` takes a home rather than an email**, so `~` expands against the caller's own
home. Its old comment — *"the server owner is the only account, absolute paths are theirs to use"* — is
replaced with what is actually true now: an absolute path still passes through, and it is the kernel that
decides what a member's turn can open, not a string check here.
**Ordering worth checking:** identity is resolved *before* the cwd, because expanding `~` before knowing whose
home it is would be exactly the bug this removes. That reordering also let me delete a second
`resolveTurnIdentity` call that `25` had left further down the same function.
**`agent-runner.ts`** passes `{ email, home: homeDir }` explicitly — it is an owner-only path, and I would
rather it state that than inherit it.
## What I am least sure of
1. **`chatIdentity` throws where the old code could not fail.** Every `/chat` route now has a failure mode it
did not have. Owner-only today, and `resolveHomeDir` returns `isOwner` from a positive check — but it is
the kind of change that turns a database blip into a 403 on a screen that used to always load.
2. **`resolveBaseCwd` is exported.** I changed its parameter from email to home and followed the compiler; if
anything outside this diff passes an email, `tsgo` would have caught it, but the *meaning* of a wrong-but-
well-typed string would not be caught by anything.
3. **The regex pass.** I rewrote bare `email` references inside `chat.ts` mechanically and then repaired the
damage by hand. `tsgo` is clean and 78 tests pass, but that is a weaker guarantee than usual for this file.
`tsgo` clean, 78 tests, both gates up, `member` unreachable in production.
## What is left
Nothing I can write blind. `deprovisionOsAccount` is still mine and still tomorrow's. A member signing in, the
first member turn, and the gates are the owner's.