docs(protocol): propose channel artifacts and shared client contracts

Signed-off-by: Fizz <400e8babadcee6a7f420103f10a2849d84c4a9c71d5bd04f3948c814216648a3@buzz.block.builderlab.xyz>
This commit is contained in:
Fizz
2026-09-20 16:16:48 -07:00
parent ef2aa1ae38
commit 6023315cf3
5 changed files with 550 additions and 1 deletions

View File

@@ -152,7 +152,13 @@ Beyond chat: channels are workspaces.
The relay hosts git repos. Smart HTTP — standard `git clone`, `git push`, nothing special. Your npub signs pushes. Same domain, same auth, same identity as everything else on the relay.
Branches are channels. Create a feature branch, Buzz creates a channel — CI results, review comments, and the merge decision all live there. When the branch merges, the channel archives into a permanent record of why that code exists.
The proposed [channel artifact model](docs/nips/NIP-AR.md) replaces the earlier
“branches are channels” direction: channels contain work records and may host
several projects, tasks, or branches. A dedicated branch channel remains a useful
workspace choice, not a required identity or one-to-one relationship. Completing
one record does not automatically archive the shared channel. Existing Git
objects and review authority retain their separate protocol contracts during
migration. This is proposed direction, not shipped artifact support.
See [VISION_PROJECTS.md](VISION_PROJECTS.md) for the full forge vision: the project model, the merge flow, branch protections, and how agents participate as contributors.

View File

@@ -8,6 +8,27 @@ This document is the software-forge slice of the broader Buzz platform. [VISION.
---
## Proposed evolution: channels contain work
The [channel artifact proposal](docs/nips/NIP-AR.md) and its
[client contracts](docs/nips/NIP-AR-CLIENTS.md) supersede this document's earlier
one-branch/one-channel assumption as proposed direction, not shipped behavior.
Projects and tasks are editable records inside channels or threads; a channel
may contain many of either. Projects organize related work without being a
prerequisite for a task or granting access to it. Dedicated workspaces remain
an option. Completing a task, retiring a branch record, or archiving a project
does not automatically archive its channel.
The forge workflows below remain useful examples of dedicated workspaces,
not mandatory identity/cardinality rules. NIP-34 remains authoritative for Git
identity and operations. Existing NIP-MP projects remain the current shipped
representation pending an explicit migration; the proposal must not create a
second competing project record. Private artifact access does not automatically
make existing repository metadata, code, or reviews private.
For the intended experience without protocol details, see
[Work lives alongside the conversation](docs/channel-artifacts-design-note.md).
## The Project Model
A project lives on the relay. `myproject.com` in a browser shows the project home. Click a repo and you're at `repoa.myproject.com` — README rendered, file tree navigable, code syntax-highlighted, clone URL at the top. The same URL serves HTML to a browser and git protocol to `git clone`. Content negotiation. One URL, two audiences.

View File

@@ -0,0 +1,67 @@
# Work lives alongside the conversation
We’re proposing a simple way to bring tasks and projects into Buzz: **they live
inside channels and threads, alongside the conversations about them.** The
conversation captures how people reached a decision; the task or project keeps
the current agreed state easy to find and update.
A channel is still a place for people to work together—not a project in disguise.
A team can keep several projects in one channel, or give a larger project its own
channel. A thread can contain several tasks. People don’t need to create a
project or repository before they can track a task.
## What this means for the experience
- **Add structure where work starts.** Turn an emerging piece of work into a
task without replacing or relocating the conversation. Keep its outcome,
status, and assignees alongside the discussion.
- **One record, several useful views.** A task can appear in a project overview
and in its original conversation. These are views of the same task, not
copies. Adding it to a project does not move it.
- **The home channel determines who can see it.** A task in a private channel
stays private when linked from a broader project. A link does not invite
people, reveal private details, or give access to its discussion. Threads
share their channel’s audience; they are not separately private spaces.
- **Sharing follows the channel, not a separate task guest list.** Someone who
cannot access the home channel cannot be given access to just that task. Give
them access to the channel, or move the task to a channel they can access.
A separately shared summary is not access to the original task.
- **Editing follows the channel’s permissions.** People allowed to contribute
in the home channel can update the work; it is not locked to whoever created
it. Moving requires permission to contribute in both places; deleting requires
channel administration. In an open channel, people allowed to post can also
update its work, even if they have not joined the channel.
- **Give growing work more room deliberately.** A task can move from a thread
into a new channel and remain the same task. Before moving, show who will be
able to see its current details. The old discussion stays where it was and
keeps its original access; it is not automatically shared with the new group.
- **Keep progress distinct from conversation.** Changes should appear promptly
wherever the work is shown, without making every status edit look like a new
chat message or marking a conversation as read.
- **Finishing work does not close the room.** Completing a task or archiving a
project leaves the channel and other work in it alone.
For example: a timeout bug is discussed in a team thread. Someone adds a task
there, then includes it in the reliability project. People can follow it from
either place if they have access to its home channel. If a contractor later
needs to collaborate, an authorized person can move the task’s current details
to a shared channel, deliberately selecting any context to bring along. The
private team discussion does not come with it.
## What we need to design
Make it clear **what the work is, where its main conversation is, who can see
it, and whether you are editing the existing record or creating another one**.
Design for several tasks or projects in one place—not a single special card.
Project overviews must handle work with different audiences without exposing
private information or implying everyone sees the same complete list.
The same approach should accommodate other kinds of shared records later.
Desktop and mobile should agree on familiar types, and show a useful basic
card when they encounter a type they do not yet support. People should not have
to configure plugins to get a coherent default experience.
We’re using “artifact” as a working umbrella term. In the product, prefer the
specific name—“task,” “project,” and so on—rather than making people learn it.
Code repositories and reviews keep their own access rules; connecting them to a
task must not imply that its audience can also see the code.

140
docs/nips/NIP-AR-CLIENTS.md Normal file
View File

@@ -0,0 +1,140 @@
# Artifact client contracts
`draft` `clients` `not implemented`
Depends on [NIP-AR](NIP-AR.md). These are shared desktop, mobile, CLI, SDK, and
agent contracts, **not relay business validation rules**. All types use the same
artifact envelope and lifecycle. Each may live in a channel or a thread.
## Common content rules
Known version-1 payloads are JSON objects with an integer `version: 1`. Reject
ambiguous duplicate JSON keys when interpreting a known payload. Unknown fields
are permitted. Editors preserve unknown fields and annotation tags or refuse an
unsafe edit. Required title exists only in the envelope, not a second writable
payload field. Markdown descriptions are untrusted and rendered safely.
Version-1 writers limit the complete content to 65,536 UTF-8 bytes and
`status_label` to 128 UTF-8 bytes; they must also respect the relay event limit.
Clients use a bounded fallback rather than deeply parsing oversized content.
A malformed known type uses the fallback card with a clear unavailable-details
state. Unknown types/versions show type, title, last editor, and a route to the
home conversation when available. They must not disappear from the workspace.
The default experience works without selecting or installing plugins.
## `buzz.project`
A grouping of related work, not a channel identity or access-control container.
| Payload field | Type | Meaning |
|---------------|------|---------|
| `version` | integer | `1` |
| `description` | string | Project purpose/outcome, Markdown |
| `archived` | boolean | Inactive in default project views, not deleted |
Tasks declare their project relationships; the project must not contain a second
independently editable task-membership list. Project-owned `repository` tags may
reference multiple NIP-34 coordinates; `channel` tags may reference related
channel UUIDs. These are organizational references only. The project view is not
proof of completeness for work the reader cannot access.
A project may have a dedicated channel or share a team channel with other
projects. Archiving hides neither its channel nor its tasks and does not revoke
access. Clients may warn about adding work to an archived project; this is not a
relay rule or permission boundary.
## `buzz.task`
A living definition of work, independent of any prerequisite project or repo.
| Payload field | Type | Meaning |
|---------------|------|---------|
| `version` | integer | `1` |
| `description` | string | Intended outcome and agreed scope, Markdown |
| `state` | string | `open`, `done`, or `cancelled` |
| `status_label` | string, optional | Display refinement, e.g. “In review” |
The broad state supports common views; teams may use labels without a relay
deploy. Clients must not infer done/cancelled solely from an arbitrary label.
Assignees use repeatable `assignee` tags containing public keys. Assignment here
is task metadata, not permission to read or edit, and does not automatically
notify or subscribe the assignee. `assignee` deliberately differs from `p`,
which participates in mention/notification and recipient-validation behavior.
Notification policy is separate.
| Relationship tag | Count | Value |
|------------------|-------|-------|
| `project` | 0..N | Project artifact UUID |
| `parent` | 0..1 | Parent task artifact UUID |
| `branch` | 0..N | Branch artifact UUID |
Tags have two string elements and duplicate values are collapsed by conforming
clients. Relationships are editable; they need not have the same home channel.
A parent is organizational, not necessarily a conversation ancestor. A client
should prevent known cycles during authoring and must safely render cycles from
other writers. Missing/unreadable links do not invalidate the remaining task.
Completing or cancelling a task does not close its conversation, archive its
channel, or imply that a Git branch merged. Adding it to a project does not move
it or widen its audience. The same task may appear in several project views.
## `buzz.branch`
A collaboration record concerning a Git branch; not the ref itself or a second
review/merge authority.
| Payload field | Type | Meaning |
|---------------|------|---------|
| `version` | integer | `1` |
| `description` | string | Purpose/context, Markdown |
| `retired` | boolean | This collaboration record is no longer active |
Required tags are `repository` (a NIP-34 `30617:<owner-hex>:<identifier>`
coordinate) and `ref` (a valid full `refs/heads/...` Git ref). Split the coordinate
on its first two colons only. Clients validate Git ref syntax with established
Git-compatible validation, not an invented permissive approximation.
A record may precede first push. Its repository/ref pair is immutable in this
client contract: another ref or a recreated branch lifecycle gets a new record.
Retirement is terminal for this client version. These are client semantics;
the relay does not certify them. Multiple records may reference the same ref;
clients may offer duplicate cleanup without merging their identities silently.
Git tips, push rights, checks, approval, and merge state come from authoritative
Git/review sources under their own access checks, never this editable payload.
A branch card must not fetch and expose code to everyone who can see the card.
Automatic summaries/notifications must respect the destination audience too.
## Example: task started in a conversation
Standard signed-event fields are omitted. Placeholder IDs below are explanatory.
The numeric kind is proposed; see the allocation note in NIP-AR.
```json
{
"kind": 45010,
"tags": [
["ar", "1"],
["d", "<task-uuid>"],
["h", "<channel-uuid>"],
["root", "<conversation-event-id>"],
["type", "buzz.task"],
["title", "Handle payment timeout"],
["op", "create"]
],
"content": "{\"version\":1,\"description\":\"Preserve the pending payment\",\"state\":\"open\"}"
}
```
Later adding `["project", "<project-uuid>"]` uses `op=update` and `prev` naming
the current accepted revision. It preserves `d`, `h`, and `root`. The project may
live elsewhere; viewing that project does not grant access to this task.
## Compatibility release gate
Publish shared valid/invalid fixtures before shipping editors. Exercise them
in desktop, mobile, CLI/SDK, and agent tooling: unsupported versions, unknown
fields/tags, bad JSON, cycles, inaccessible links, and concurrent updates.
The relay suite should demonstrate both envelope rejection and acceptance of
opaque content that a business client declines to interpret. Acceptance by one
layer is not a correctness certificate from the other.

315
docs/nips/NIP-AR.md Normal file
View File

@@ -0,0 +1,315 @@
NIP-AR
======
Channel Artifacts
-----------------
`draft` `optional` `relay` `not implemented`
**Depends on:** NIP-01 (signed events), NIP-29 (channel access).
## Decision
An **artifact** is a durable, editable record inside a channel, optionally
attached to a conversation within it. Channels contain artifacts; they are not
artifacts themselves. Projects, tasks, and branch collaboration records use the
same envelope, with client-defined content types.
- One current home channel determines an artifact's current audience.
- Any number of artifacts, including of the same type, may share a location.
- Relationships organize work; they never grant access or operational authority.
- The relay validates the common envelope and lifecycle, not business schemas.
- Clients share published contracts for first-party types; unknown types remain
readable as a basic card.
This replaces the conversation-subject proposal in
[PR #7656](https://github.com/block/buzz/pull/7656): no exclusive/primary claims,
no type-specific event kinds or relay relationship validation, no required
project for a task, and no permanently fixed home. This is a specification PR,
not a claim that these behaviors are available in existing clients or relays.
MUST, MUST NOT, SHOULD, and MAY have their RFC 2119 meanings.
## Identity and envelope
The proposal uses one regular stored event kind, **45010**, for all artifact
revisions. This number is proposed, not allocated by this docs-only change;
implementation must reserve it in `crates/buzz-core/src/kind.rs`. It replaces,
not interoperates with, the unshipped project-kind proposal using that number.
Each revision is a complete snapshot, not a field patch. Common tags have
exactly two string elements. Required tags occur exactly once; optional tags at
most once. Duplicate common tags are invalid even if their values agree. The
relay MUST reject more than 256 total tags per revision, including common and
unknown tags; its advertised event-size limit also applies.
| Tag | Count | Meaning |
|-----|-------|---------|
| `ar` | 1 | Envelope version, initially `1` |
| `d` | 1 | Stable artifact UUID: canonical lowercase, hyphenated, non-nil |
| `h` | 1 | Current home channel UUID, same encoding |
| `root` | 0..1 | Conversation anchor event ID, 64 lowercase hex characters |
| `prev` | 0..1 | Immediately preceding accepted revision ID, 64 lowercase hex characters |
| `type` | 1 | Namespaced content type, e.g. `buzz.task` |
| `title` | 1 | Non-whitespace display title, at most 512 UTF-8 bytes |
| `op` | 1 | `create`, `update`, `move`, or `delete` |
`content` is opaque UTF-8 to the relay, subject to the relay's advertised event
size and rate limits. Type names are lowercase ASCII dot-separated components,
each starting with a letter and otherwise containing letters, digits, `_`, or
`-`; total length is at most 128 bytes. `buzz.*` names are reserved by convention
for published Buzz contracts, not proof of trusted authorship.
Identity is `(community, d)`, independent of signer, type, and home. It is not a
NIP-01 addressable coordinate. The relay retains accepted revisions and an
explicit current-head index; neither timestamps nor signer changes select a
winner. `type` and `d` are immutable. A new business-schema version belongs in
the content contract, not in a replacement artifact identity.
Unknown tags are bounded annotations. They MUST NOT override common tags or
confer authority. Business relationship tags are specified by clients, not
resolved or validated by this NIP. Unsupported envelope versions are rejected.
Missing `h` is a rejection, never a fallback to global visibility.
The existing canvas kind (40100) provides a channel-scoped, non-chat precedent;
see `required_scope_for_kind` and `requires_h_channel_scope` in
`crates/buzz-relay/src/handlers/ingest.rs`. Artifact implementation must explicitly
join that scoped class, not `is_global_only_kind`. Kind allocation alone does
not establish unread, quota, or fan-out behavior: implementation must audit and
test those paths. Artifacts remain subject to bounded storage/write quotas even
though they do not count as chat messages.
## Conversations and multiple artifacts
Without `root`, the artifact lives directly in its channel. When supplied or
changed, `root` must identify an existing, live (not deleted at commit time)
conversation anchor in `h` in the same community. An unchanged anchor may
subsequently become unavailable without preventing edits to the artifact.
Clients must retain an unavailable-discussion state and allow an authorized
writer to choose another anchor.
A nested conversation may be an anchor; its existing ancestry does not change.
Replies use the conversation's existing protocol, not the latest artifact
revision ID. An artifact revision is not a chat reply and MUST NOT increment
reply counters, chat-message counts, or unread-chat state.
There is no exclusive slot, primary slot, or per-type singleton. Two tasks in
one thread are two records, not competing declarations of what the thread is. A
preferred card, pin, or dedicated workspace is a presentation choice, not
uniqueness or authority. Archiving one project MUST NOT archive its channel or
other artifacts in it.
## Authorization and concurrency
Channel read access controls artifact read access. Threads introduce no
additional audience. This applies to direct-ID lookups, history, current state,
search, counts, previews, exports, and live delivery, within the host-derived
community boundary. No title or relationship metadata may leak through errors or
unreadable search/count results.
The proposed generic write policy is **channel collaboration**. “Write
permission” means the same authorization that admits a kind-9 message in that
channel: channel membership of any role (including agents/Bot), or open
visibility, plus existing authentication, token-channel restrictions,
moderation, and archive gates. It is not a `MemberRole` threshold. Thus an
otherwise authorized community participant may create/edit in an open channel
without first joining it. “Administration” means Admin/Owner channel authority
under existing channel checks. Implementation must choose the artifact API scope
consistently across clients; message-equivalent channel authority must not
exclude agents.
| Operation | Required authority at commit time |
|-----------|------------------------------------|
| Create | Write permission in destination channel |
| Update content, title, or anchor within home | Write permission in current home |
| Delete | Administration permission in current home |
| Move to another channel | Write permission in source AND destination |
Being an artifact's creator is attribution, not a permanent exclusive editing
right. A reader without write permission cannot edit. Removed members lose
write permission in private channels even if they created the record; an open
channel still follows the open-channel admission rule above. These rules do not vary by
`type` and do not introduce per-artifact access lists or plugin-defined rules.
Existing authenticated-agent/channel authorization applies; provenance alone
must not create a new grant. Signed authorship remains the actual event signer.
Creation uses `op=create`, no `prev`, and an unused `d`. All subsequent operations
must name the current accepted head in `prev`. Ordinary updates and deletion
must keep `h`; only `move` may change it. A move must actually change channels.
`op` MUST agree with these structural constraints; it cannot disguise a move as
an update or an update as a new creation.
The relay checks permission, previous head, envelope, and anchor, then stores
the event and advances the head in one transaction. Two writes against the
same head cannot both succeed. Knowing `prev` is not edit authorization.
On conflict, clients must refetch the current accessible head and reconcile
their intended change, not blindly retry or silently overwrite newer state. Retrying an accepted event ID is idempotent;
permission to retrieve its contents is still checked.
Rejected writes never enter history, search, or live delivery. Error responses
must not reveal an inaccessible current head or destination. The relay MUST reject NIP-09
kind-5 requests targeting artifact revisions, with an explicit reason such as
`invalid: use artifact delete operation`; it must not report successful deletion
or release an artifact identity through that path.
Deletion is a terminal tombstone on the same identity, with empty content.
Delete events permit only the common envelope tags and the existing validated
authentication/provenance tags (such as NIP-OA `auth`), not annotation tags.
This generic allowlist removes relationships without recognizing business types.
Its type, `h`, `root`, and title must equal the head it deletes; deletion must
not rename or relocate it. Its type and last title remain under its home
permissions for an unavailable/deleted display. It is absent from active lists;
subscribers receive a removal. Retained history follows retention/moderation
policy; deletion is not a promise to erase downloaded copies. A deleted `d` is
never reused. Business completion/archive is not deletion.
## Moving and audience changes
Moving preserves identity and publishes a complete current snapshot into the
destination. The UI MUST identify the destination audience and what fields and
context will be shared before confirmation. This includes titles, descriptions,
relationship labels, attachments, and any private material copied into content.
A reference does not automatically authorize copying its resolved contents.
The move revision is scoped to the destination. Its anchor must be absent or
belong to that destination; the old conversation does not move with it. **Moving
an artifact moves zero conversation messages.** Prior revisions stay under the
visibility of the channel in which each was published. A destination-only reader
can load the current snapshot without reading the old chain. A source-only
reader retains access to permitted source history, not later destination
revisions. Moves cannot revoke previously learned data.
One commit must advance the single head and durably record changes for both
scopes: removal/invalidation in the source and arrival in the destination. These
are two scoped change records, not one signed event pretending to have two `h`
values. The destination carries the client-signed revision; the source uses a
relay-authenticated invalidation in the platform sync change envelope. That
envelope must bind community, source scope, artifact identity, and replay
position, with no destination payload. If transported as a Nostr event instead,
it requires a separately allocated relay-only sidecar kind and rejection of
client-authored substitutes. Its wire schema is a sync-integration release gate,
not an already allocated part of kind 45010. Publication must be retryable after
a crash. The source removal must not contain the destination channel, title, or
payload; a forward link may resolve only with the requesting reader's
destination permissions. Concurrent edits/moves cannot create two active copies.
Moving back to a former home still uses the one current head.
Channel membership and open/private visibility changes are also audience
changes: artifact access follows channel policy, rather than being frozen to the
membership at creation. Every subsequent read and delivery must honor the new
access policy; clients must invalidate cached views when access is revoked.
Offline copies cannot be recalled; UI must not promise otherwise.
## Current state, history, and sync
Use the generic signed-event ingestion path, not artifact-specific CRUD APIs.
Artifact changes belong in channel/thread sync alongside other scoped changes,
but **applied updates and human read positions are different state**. Receiving
an update neither marks conversation read nor necessarily notifies someone.
Explicit notification policy may call attention to meaningful changes. Clients
MUST NOT add the artifact kind to conversational-unread kind sets; rendering a
card alongside `root` is a separate presentation decision.
A compliant implementation must offer:
1. Bounded, authorized current-state queries without replaying all revisions or
conversation history, including for a newly joined destination after a move.
2. Explicit revision-history queries, distinct from current-state listing.
3. Snapshot-to-live handoff with no gaps, durable replay after disconnect,
tombstones/removals, and a reset/snapshot path for expired checkpoints.
4. Current-head filtering before pagination and counting. A task removed from
project P must not still match P because an older revision carried its tag.
5. Aggregate-view invalidation for transitions OUT of a filter, not just events
that still match it. This includes project/assignee/anchor changes, moves, deletion,
and revoked access. No previously inaccessible content may be disclosed.
6. Bounded discovery of readable artifacts across channels; loading a task list
must not require opening every thread. Channel visibility still gates results.
Multi-character tag filters are a Buzz extension, not portable NIP-01 behavior.
Unsupported predicates must be rejected, never silently ignored. `EOSE` is not a
durable applied checkpoint. A cursor must identify its covered stream/filter; a
filtered result cannot stand in for a complete channel checkpoint.
**Implementation boundary:** this draft specifies those observable guarantees,
not a second sync protocol. The current-state query mode, snapshot/checkpoint
wire shape, move-removal delivery, and expired-cursor/reset response must be
bound to the platform sync specification before implementing or advertising NIP-
AR support. Ordinary timestamp-sorted `REQ` history alone does not satisfy this
contract. Until move delivery is supported, relays must reject `move`, not
approximate it as independent delete/create writes.
## Client types and privileged operations
[Artifact client contracts](NIP-AR-CLIENTS.md) define the initial Buzz types.
The relay accepting `type=buzz.task` does not certify a valid task. Clients must
validate before interpreting business fields; unsupported or malformed content
falls back to a safe card. Render title/type as untrusted text, not executable
markup. A type namespace is not a publisher identity or installed capability.
Unknown versions must not be edited destructively. Editors must preserve unknown
content fields and annotation tags when safe, or decline editing that version.
Relations may be missing, inaccessible, deleted, self-referential, or cyclic.
Traversals must be bounded and fail visibly without crashing.
Links and payloads cannot authorize push, merge, membership changes, payments,
or another privileged action. Those actions require their own trusted service
checks. A client-observed status is not a relay-certified operational fact.
## Existing Git and project objects
This proposal does not create a second Git repository or replace NIP-34
identity. Repository announcements (30617), ref state (30618), reviews, patches,
and their statuses remain their existing protocol objects. Branch artifacts
describe collaboration around a ref, not authoritative Git state.
**Legacy exception, not completed consolidation:** existing global
project/issue/ repository objects do not acquire private channel reads or
channel sync simply because they contain `buzz-channel`. A push binding alone is
insufficient. NIP-34 read, search, clone/fetch, preview, and live-delivery
scoping require a separate coordinated migration before promising private
repository workspaces. This draft does not tighten or widen existing Git access
as a side effect.
Existing 30621 projects and 1621 issues must not be dual-written as competing
new artifacts. Migration requires an explicit canonical mapping and cutover,
retaining old links, ownership information, and existing access until changed
through an authorized migration. Previously published data cannot be made secret
retroactively. No automatic conversion is specified here.
## Acceptance scenarios
These are implementation requirements, not tests claimed to pass in this PR.
| Scenario | Required result |
|----------|-----------------|
| Two projects or tasks at the same location | Both valid and separately discoverable |
| Task without project or repository | Valid |
| Another channel writer edits an artifact | Valid with current head; author accurately recorded |
| Read-only principal or removed creator in a private home edits | Rejected |
| Non-member permitted to post in an open home edits | Valid under the same message-admission gates |
| Two revisions race on one head | Exactly one accepted |
| A task joins/leaves a project in another channel | Same identity/home; both affected views refresh; no access grant |
| Malformed known type or unknown type/version | Safe fallback; no destructive edit |
| Cyclic parent references | Bounded client handling; relay does not walk the graph |
| Move races edit; relay crashes after commit | One head; durable source removal and destination arrival |
| Destination member cannot read source | Can load current state, not source history |
| Source member cannot read destination | Receives removal, not destination metadata |
| Access revoked while a request is in flight | No newly unauthorized delivery; stale local view invalidated |
| Reconnect after deletion or filter exit | No ghost current records |
| Background revision arrives | State updates; chat unread/read position unchanged |
| Generic repository tag on a task | No push, clone, review, or membership capability gained |
## Non-goals and naming
No per-artifact audience lists, relay-executed plugins, schema-registration
service, generic automation engine, or one-record-per-room requirement. Existing Buzz DMs are channels: an artifact with their `h` inherits participant
access just like their messages (see `handle_dm_open` in
`crates/buzz-relay/src/handlers/command_executor.rs`). This is not a new DM ACL.
Administrative deletion remains gated by actual channel administration; merely
being a DM participant does not manufacture that role. Personal/no-room artifacts
need a separately specified scope; missing `h` must not become an accidental
global/private mode.
“Artifact” is the protocol/design term. User interfaces should usually say
“task,” “project,” or the specific type name. “Work item” sounds task-specific;
“card” describes a presentation. Neither is a better common identity name.
Build-output artifacts/files remain distinct from these editable records.