feat(buzz-cli, buzz-sdk): add buzz projects command group (NIP-MP kind:30621 write path) - #4020
Open
wpfleger96 wants to merge 3 commits into
Open
feat(buzz-cli, buzz-sdk): add buzz projects command group (NIP-MP kind:30621 write path)#4020wpfleger96 wants to merge 3 commits into
buzz projects command group (NIP-MP kind:30621 write path)#4020wpfleger96 wants to merge 3 commits into
Conversation
…r B) Add two-layer builder implementation per NIP-MP Phase 2 spec (plan v3): Layer A (protocol): - `validate_project_envelope(tags, content)` — enforces the 8 NIP-MP ingest rules: d-cardinality, d-empty, member-tag-arity, member-cap (before dedup), member-coordinate-malformed, member-duplicate, metadata-cardinality, metadata-length. - `build_project_with_tags(content, tags)` — raw Layer A builder; no canonicalization; accepts all valid fixture envelopes including opaque metadata, non-empty content, relay hints, and unknown tags. - `ProjectMemberCoord` — parsed `30617:<owner-hex>:<repo-d>` coordinate with optional opaque relay hint; equality/Hash by coordinate only. Layer B (writer policy): - `build_project(slug, name, description, members, channel, visibility)` — constructs the single `d` tag, enforces UUID channel and listed|unlisted visibility, emits empty content; composes onto Layer A. Shared infrastructure: - `build_delete_addressable(kind, pubkey, d)` — generic NIP-09 kind:5 coordinate delete; validates addressable kind range and non-empty d. `build_workflow_delete` now delegates to this function. Conformance: - All 31 NIP-MP.fixtures.json cases exercised directly through `build_project_with_tags`; accepts build, rejects fail tied to the named rule; count assertion guards against omissions. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…write path)
Seven commands for creating and managing multi-repo projects:
buzz projects create <slug> --repo <coord> [--name] [--description]
[--channel <uuid>] [--visibility listed|unlisted]
buzz projects get <slug> [--owner <pubkey>]
buzz projects list [--owner <pubkey>] [--limit <n>]
buzz projects add-repo <slug> --repo <coord> [...]
buzz projects remove-repo <slug> --repo <coord> [...]
buzz projects update <slug> [--name|--clear-name] [--description|...]
[--channel <uuid>|--clear-channel]
[--visibility listed|unlisted|--clear-visibility]
buzz projects delete <slug>
Design:
- create: collision-guarded preflight; repeated create returns Conflict.
- update: clap argument group requires at least one setter or clearer;
omission preserves existing metadata; --clear-* drops the tag.
- delete: head-based tombstone (created_at = head + 1); post-submit
re-query verifies the tombstone landed; Conflict if raced.
- All mutations: strip auth, re-validate full envelope through Layer A
before publish; created_at advances from observed head, never wall-clock.
- --repo bare form (Buzz repo-id grammar) auto-expands with caller's pubkey;
full 30617:<owner>:<repo-d> form used for cross-owner or colon-bearing ids.
- Relay hints on existing members are preserved verbatim through RMW.
Shared infrastructure:
- commands/mod.rs: extract parse_write_response() shared helper; adopted by
repos.rs (thin wrapper) and projects.rs directly.
Limitations recorded for the PR body:
- No relay-hint authoring (read-preserved only).
- Signer-self delete only (NIP-OA owner-delete path deferred).
- Deletion durability against later arrival (watermark carry-over).
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
force-pushed
the
duncan/projects-cli
branch
from
July 31, 2026 22:07
153db41 to
4e5e4e9
Compare
…d-repo no-op IMPORTANT: fix clap mutation group — add required(true).multiple(true) to the Update ArgGroup so multiple independent setters/clearers are accepted and an empty update is rejected at parse time. Pairwise setter/clearer conflicts are retained. Add four parser-level tests covering all four cases Thufir specified. IMPORTANT: move member-cap check before arity loop in validate_project_envelope, matching the relay's ingest rule order (NIP-MP rule 3=cap, rule 4=arity). Correct rule-number doc comments. Add cap-wins-over-arity test with 65 members plus a malformed tag asserting member-cap fires first. MINOR: remove redundant double build_project_with_tags call in cmd_remove_repo — single rebuild_project call matches add-repo pattern and centralises auth strip. MINOR: return Conflict when add-repo adds zero new coordinates (all requested repos were already members). Consistent with create/update race reporting; add focused test verifying the error kind and message. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the
buzz projectscommand group — the NIP-MP Phase 2 write path. The relay accepted kind:30621 in #3171; this delivers the Rust builder (required by the NIP-MP conformance obligation) and the seven CLI commands that make projects writable from the CLI.What this adds
crates/buzz-sdk/src/builders.rs— two-layer builderLayer A (protocol):
validate_project_envelope(tags, content)— enforces the 8 NIP-MP ingest rules:d-cardinality,d-empty/length, member-tag-arity (2–3 elements), member-cap (64 rawatags before dedup), member-coordinate grammar (first-two-colons split, literal30617, lowercase 64-hex owner, non-empty remainder), member-duplicate (by coordinate only), singleton metadata cardinality, and byte bounds (name256 /description2048 /buzz-channel256 /buzz-visibility256).build_project_with_tags(content, tags)— raw Layer A builder; no canonicalization; accepts every valid fixture envelope including opaque metadata, non-empty content, relay hints, and unknown tags.ProjectMemberCoord—30617:<owner-hex>:<repo-d>+ optional opaque relay hint; equality/Hash by coordinate only.Layer B (writer policy):
build_project(slug, name, description, members, channel, visibility)— constructs the singledtag, enforces UUID channel andlisted|unlistedvisibility, emits empty content; composes onto Layer A.Shared:
build_delete_addressable(kind, pubkey, d)— generic NIP-09 kind:5 coordinate delete.build_workflow_deletenow delegates to this.NIP-MP.fixtures.jsoncases exercised directly throughbuild_project_with_tags; accepts build, rejects fail tied to the named rule; count assertion guards against omissions.crates/buzz-cli/— seven commandsCommand semantics:
create: collision-guarded preflight — returnsConflictif the slug already exists; requires ≥1--repo.update: at least one setter/clearer required (runtime guard before any network I/O); omission preserves;--clear-*drops the tag; setter + clearer are mutually exclusive per clap group.delete: head-based tombstone atcreated_at = head + 1; post-submit re-query verifies tombstone landed; returnsConflictif a concurrently newer head survived.auth, re-validate full envelope through Layer A before publish;created_atadvances from observed head, never wall-clock.--repobare form (Buzz repo-id[a-zA-Z0-9._-]{1,64}) auto-expands with caller's pubkey; full30617:<owner-hex>:<repo-d>for cross-owner or colon-bearing IDs.remove-repoof an absent coordinate returnsNotFound.Shared infrastructure:
commands/mod.rs:parse_write_response()extracted fromrepos.rs; adopted byrepos.rs(thin wrapper) andprojects.rsdirectly.Limitations (recorded, not in scope)
--repocarries a coordinate only; existing hintedatags survive RMW unchanged. Authoring syntax deferred until there's a demonstrated need.deletetargets the signer's own coordinate.deleteis best-effort against a later-arriving replacement.Live round-trip
Blocked pending relay deployment of #3171 to production (kind:30621 currently rejected at
wss://buzz.block.builderlab.xyzwith "unknown event kind"). Local Docker relay is also not available in the current environment. The round-trip will be run and the transcript posted to the thread as soon as either path is available.