Skip to content

refactor: user_type === "mind" is a proxy for the wrong question, and keeps producing the same bug #817

Description

@psamiton

Problem

user_type === "mind" is used throughout the codebase as a proxy for "is this actor a mind," but it does not mean that. The spirit's user row is user_type: "system", and bridge puppets are "puppet". So the check silently excludes both — and whether that is correct depends entirely on what the call site meant, which nothing records.

The result is a check that is wrong by default and right by accident, and it keeps costing us:

  • fix: resolve the system spirit in extension SDK helpers #786 fixed one instance (spirit misclassified by a user_type !== "mind" guard).
  • One day later, review on feat: read signals on Pages #816 caught the same bug being re-introduced in new code.
  • packages/extensions/intentions/src/routes.ts:163 carries a comment warning "Do NOT use user_type === "mind" as a proxy" — someone hit this, understood it, and left a warning for the next person instead of a fix. That comment is the clearest evidence the primitive is wrong rather than the callers being careless.

Scale

Roughly 30 call sites compare user_type/userType against a string literal across packages/web, packages/cli, packages/daemon, and packages/extensions. Each is independently a judgment about whether the spirit and puppets belong, and there is no way to tell a deliberate exclusion from an accidental one by reading it.

The two unions disagree

packages/api/src/types.ts:

:95    userType:  "human" | "mind" | "puppet" | "system";
:228   user_type: "human" | "mind" | "system";           // no "puppet"

Four values are actually written to the DB (human, mind, puppet, system), so the narrower union is simply wrong, and a puppet flowing through it type-checks as something it isn't.

packages/daemon/src/lib/schema.ts:39 types the column as plain text with a "human" default, so the database enforces nothing.

Why this recurs

user_type is carrying at least three different questions at once:

  1. Is this actor an agent rather than a person? (spirit: yes. puppet: sort of — it stands in for a person.)
  2. Does this actor have a local mind directory / registry row? (spirit: yes. external minds: no.)
  3. How should this actor be rendered? (spirit wants its own icon; puppets currently render as humans.)

Call sites want different ones of these and all reach for the same field. packages/web/src/ui/lib/user-kind.ts already exists as a partial answer for the frontend, which suggests the right shape — it just isn't shared, isn't complete, and isn't used by the ~30 raw comparisons.

Suggested direction

Not prescriptive, but the shape that seems indicated:

  • Named predicates expressing the actual question (isMindLike, hasLocalMindDir, isAgentActor — whatever the real distinctions turn out to be), shared across daemon/web/CLI/extensions rather than reimplemented per package.
  • Reconcile the two unions and make the schema type the column as the union rather than free text.
  • Migrate the raw comparisons to the predicates, so a deliberate exclusion of the spirit reads as deliberate.

The goal is that the next person writing a guard cannot accidentally exclude the spirit without saying so.

Context

Surfaced during #807 (Notes/Pages merge). Related: #786.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions