From 6023315cf3bde486f39d76adef062a4e37082a6b Mon Sep 17 00:00:00 2001 From: Fizz <400e8babadcee6a7f420103f10a2849d84c4a9c71d5bd04f3948c814216648a3@buzz.block.builderlab.xyz> Date: Sun, 20 Sep 2026 16:16:48 -0700 Subject: [PATCH] docs(protocol): propose channel artifacts and shared client contracts Signed-off-by: Fizz <400e8babadcee6a7f420103f10a2849d84c4a9c71d5bd04f3948c814216648a3@buzz.block.builderlab.xyz> --- VISION.md | 8 +- VISION_PROJECTS.md | 21 ++ docs/channel-artifacts-design-note.md | 67 ++++++ docs/nips/NIP-AR-CLIENTS.md | 140 ++++++++++++ docs/nips/NIP-AR.md | 315 ++++++++++++++++++++++++++ 5 files changed, 550 insertions(+), 1 deletion(-) create mode 100644 docs/channel-artifacts-design-note.md create mode 100644 docs/nips/NIP-AR-CLIENTS.md create mode 100644 docs/nips/NIP-AR.md diff --git a/VISION.md b/VISION.md index 900e5a9475..78fcc2035c 100644 --- a/VISION.md +++ b/VISION.md @@ -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. diff --git a/VISION_PROJECTS.md b/VISION_PROJECTS.md index 8601b87829..8c2c87f483 100644 --- a/VISION_PROJECTS.md +++ b/VISION_PROJECTS.md @@ -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. diff --git a/docs/channel-artifacts-design-note.md b/docs/channel-artifacts-design-note.md new file mode 100644 index 0000000000..a526d2eac7 --- /dev/null +++ b/docs/channel-artifacts-design-note.md @@ -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. diff --git a/docs/nips/NIP-AR-CLIENTS.md b/docs/nips/NIP-AR-CLIENTS.md new file mode 100644 index 0000000000..54fa70f73b --- /dev/null +++ b/docs/nips/NIP-AR-CLIENTS.md @@ -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::` +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", ""], + ["h", ""], + ["root", ""], + ["type", "buzz.task"], + ["title", "Handle payment timeout"], + ["op", "create"] + ], + "content": "{\"version\":1,\"description\":\"Preserve the pending payment\",\"state\":\"open\"}" +} +``` + +Later adding `["project", ""]` 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. diff --git a/docs/nips/NIP-AR.md b/docs/nips/NIP-AR.md new file mode 100644 index 0000000000..2518ea261e --- /dev/null +++ b/docs/nips/NIP-AR.md @@ -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. \ No newline at end of file