Skip to content

[4/4] buzz teams: group published personas into rosters - #5845

Open
Maxwellimus wants to merge 1 commit into
maxwell/cli-personasfrom
maxwell/cli-teams
Open

[4/4] buzz teams: group published personas into rosters#5845
Maxwellimus wants to merge 1 commit into
maxwell/cli-personasfrom
maxwell/cli-teams

Conversation

@Maxwellimus

@Maxwellimus Maxwellimus commented Aug 14, 2026

Copy link
Copy Markdown

Completes the definition surface: buzz teams create|list|get|delete writes
kind:30176 over the personas from [3/4], from flags or a Desktop .team.json
export.

Membership resolution

The load-bearing part. A team event stores persona d-tags, and Desktop publishes
personas under their record id — usually a UUID — while its team export names
members only by display name. Matching on the slugified name would therefore
resolve nothing for every Desktop-exported roster.

--persona accepts a d-tag, a slugified d-tag, or a unique display name, in
that order. An ambiguous display name is an error rather than a guess, and a
member with no published persona is refused rather than published as a seat that
silently stays empty.

Team ids are used verbatim

The relay enforces the slug grammar on persona d-tags but only a length bound on
team ids, and Desktop writes raw UUIDs and ids like builtin-team:welcome.
Normalizing would address a different coordinate than the one Desktop published,
making its teams unreachable. Since kind:30176 has no envelope validator on the
relay, the CLI is the only guard against a blank or oversized id.

The one id create does slugify is one it derives from --name, so
teams get "Red team" misses the red-team it just wrote. Reads stay verbatim —
normalizing them is what would break Desktop's ids — so the not-found error names
the slug instead.

Always-publish for instructions and persona_ids

On the wire an absent field means "publisher predates always-publish, membership
unknown, preserve local" — distinct from an explicit empty. A new client must
never claim the former, or a write meant to clear a roster reads as "leave it
alone" and one meant to leave it alone wipes it.

Test plan

  • just ci
  • crates/buzz-test-client/tests/e2e_team.rs gains
    test_tombstone_older_than_head_does_not_delete, pinning the relay rule the
    delete path in [3/4] depends on.
  • Runbook rows added to crates/buzz-cli/TESTING.md §6.13.

Deferred

  • Teams get no definition-text validation, only the relay's content cap,
    which resolve_create now reports locally instead of letting the write fail
    opaquely after every member has resolved. Instructions are executable
    configuration reaching an ACP harness verbatim, so the invisible-character
    rules personas get in [2/4] apply on the merits — but Desktop applies no text
    rules to a team, and a CLI-only rule would refuse a roster Desktop exported
    legitimately. That asymmetry is the thing [2/4] exists to prevent, so closing
    this means adding the rule on the Desktop side first.

@Maxwellimus
Maxwellimus marked this pull request as ready for review August 14, 2026 06:22
@Maxwellimus
Maxwellimus requested a review from a team as a code owner August 14, 2026 06:22
@Maxwellimus
Maxwellimus force-pushed the maxwell/cli-teams branch 2 times, most recently from 8b726c0 to ccd1e37 Compare August 14, 2026 19:59
Completes the definition surface: `buzz teams create|list|get|delete`
writes kind:30176 over the personas from [3/4], from flags or a Desktop
`.team.json` export.

Membership resolution is the load-bearing part. A team event stores
persona d-tags, and Desktop publishes personas under their record id —
usually a UUID — while its team export names members only by display
name. Matching on the slugified name would therefore resolve nothing for
every Desktop-exported roster. `--persona` accepts a d-tag, a slugified
d-tag, or a unique display name, in that order; an ambiguous display name
is an error rather than a guess, and a member with no published persona
is refused rather than published as a seat that silently stays empty.

A team id is used verbatim, never normalized. The relay enforces the slug
grammar on persona d-tags but only a length bound on team ids, and
Desktop writes raw UUIDs and ids like `builtin-team:welcome` —
normalizing would address a different coordinate than the one Desktop
published, making its teams unreachable. Since kind:30176 has no envelope
validator on the relay, the CLI is the only guard against a blank or
oversized id.

`instructions` and `persona_ids` are always published. On the wire an
absent field means "publisher predates always-publish, membership
unknown, preserve local" — distinct from an explicit empty. A new client
must never claim the former, or a write meant to clear a roster reads as
"leave it alone" and one meant to leave it alone wipes it.

The e2e suite gains the relay rule the delete path depends on: a
tombstone older than its target head is accepted, deletes nothing, and
still reports OK.

Signed-off-by: Max Lampert <maxwell@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant