Skip to content

feat(framework): resolve one person across tools and machines #661

Description

@blafourcade

As a team lead reading a report
I want one person to appear as one person, whatever tool they used
So that a weekly figure is a fact rather than an artefact of how many tools someone happens to run

Acceptance

  • The same human working through two different tools appears once in a report, not twice.
  • The same human on two machines appears once.
  • A person absent from the mapping is reported as unresolved and counted separately, never silently merged into someone else or dropped.
  • The mapping is auditable: given a report line, one can see which raw identities produced it.
  • A person can see the identities mapped to them before anyone else reads a report about them.
  • Whether identities are stored as names or as pseudonyms follows the decision issue, and is not chosen here.

Why this is not trivial

Measured: the same human carries a different identifier per tool, and none of them is the git identity that appears in commits.

Tool Identity attributes it exports
Claude Code user.email, user.account_uuid, user.account_id, user.id, organization.id
Codex CLI user.email, account_id
Cursor user_email in the hook payload
GitHub Copilot enduser.pseudo.id, described as pseudonymous
OpenCode not established

Copilot's is pseudonymous by construction, so it cannot be reconciled by matching an address — it needs a mapping someone maintains, or it stays unresolved. Nothing in the design produces an actor attribute of its own: the run record has no author field at all.

"Team" has the same problem one level up: it is named in #656's output and defined nowhere, with no source of truth.

Out of scope

  • The anonymity decision itself, which gates this.
  • Any hosted directory integration. The mapping's source is a separate choice; this issue owns the shape and the guarantees.

Relations

Field Value
parent #652
depends_on the anonymity decision
blocks #656

Activity

  1. added this to the milestone on Aug 15, 2026
  2. blafourcade commented on Aug 16, 2026

    @blafourcade
    ContributorAuthor

    Scope grown by the decision of 2026-08-16

    Both modes are supported, so this issue no longer only reconciles identities — it owns the mapping that switches between them.

    • Records always carry a pseudonym. This issue produces it, and guarantees it is stable for one person across tools and machines.
    • The pseudonym-to-person mapping is a separate artefact, outside the repository, access-controlled. Its presence is what makes a deployment named; its absence is what makes it anonymous.
    • Resolution across the four vendor identities stays as specified. Copilot's is pseudonymous at the source, so it cannot be matched by address and needs the mapping or stays unresolved.

    Two properties to add, both consequences of serving two modes from one format:

    • Removing the mapping must anonymise every future reading, immediately, without touching stored records.
    • The mapping must be auditable: given a report line, one can see which raw identities produced it — and that audit must itself be gated, or it becomes a way around the anonymous mode.
  3. blafourcade commented on Aug 28, 2026

    @blafourcade
    ContributorAuthor

    Moved to the front

    A product decision taken 2026-08-28 makes this the pivot of the whole aggregation milestone.

    The line between what the local layer answers and what a hosted product answers is not a pricing choice, it is a structural one: a machine holds one person's files. Everything about your own work — tokens, models, steps, tasks — is answerable locally and should stay that way. Nothing about a second person is, and no amount of local cleverness changes it.

    That makes this issue the one thing that is simultaneously indispensable to any team view and impossible to fake locally. Per person, per team, per epic (#656), the task breakdown (#720), and the backlog join (#649) are all views; they need their key first, and this is the key.

    Practical consequence: it moves ahead of #654, which is a lookup table that opens nothing and can land at any time.

    One constraint worth carrying into the design, from the layer's existing shape: person_id is opted into per person on the local-read route and is deliberately not derived from any tool's own user attribute — telemetry-sink-record.ts excludes user.id on purpose, since an export carries it whenever the tool happens to set it, consent or not. Resolving one person across tools and machines must keep that property: an identity is something a person grants, never something a join infers.

  4. moved this from Ideation to Todo in AIDD Roadmapon Aug 28, 2026
  5. blafourcade commented on Sep 2, 2026

    @blafourcade
    ContributorAuthor

    Delivered by #706, squash-merged into next as 627408f.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Fields

    Priority

    High

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions