Files
buzz/docs/nips/NIP-MP.fold-fixtures.json
Will PflegerandGitHub 33bf7caa6e docs(nips): specify kind:30621 multi-repo projects (NIP-MP) (#3163)
Buzz renders one card per `kind:30617`, so a project spanning several
repositories has no representation — the relay, desktop app, and mobile
app look like three unrelated things. This adds the spec for the
container event that fixes that, plus the two shared fixture files that
make it machine-checkable. Docs only; no code changes.

Membership cannot live in the repository announcements themselves. A
project spanning Alice's and Bob's repositories would need *both* of
them to publish a tag naming the group, and Alice cannot sign for Bob's
key. A project's own name, description, and channel binding likewise
have no single writer when scattered across per-repository tags, and no
deletion story. That is why multi-repo grouping is the one forge concept
in Buzz that warrants a custom kind.

## `docs/nips/NIP-MP.md`

`kind:30621`, an addressable event per NIP-01, addressed by `(pubkey,
30621, d)`. Members are `a` tags holding canonical
`30617:<lowercase-64-hex-owner>:<repo-d>` coordinates, following
NIP-01's 2-or-3-element grammar where the optional third element is a
relay hint clients MAY use and whose content ingest does not parse.
Metadata is `name`, `description`, `buzz-channel`, `buzz-visibility`.

- **Authority stops at the container.** The signer can replace their own
project and nothing else — no edit, delete, push, or admin over any
member. Deletion additionally admits the signer's registered NIP-OA
owner, because `validate_standard_deletion_event`
(`crates/buzz-relay/src/handlers/side_effects.rs`) grants that
platform-wide so a human can clean up events published by an agent they
own; the spec documents it as a Buzz extension to NIP-09 rather than
carving `kind:30621` out of it. `buzz-channel` on a project is metadata
only; git push policy reads the repository's own `kind:30617`
(`crates/buzz-relay/src/api/git/policy.rs`) and a project never becomes
an input to it.
- **Ingest validation contract**, with named rules the fixtures
reference: `d-cardinality`, `d-empty`, `member-cap` (64, counting every
`a` tag), `member-tag-arity`, `member-coordinate-malformed`,
`member-duplicate`, `metadata-cardinality`, `metadata-length`. Arity is
its own rule rather than part of coordinate parsing, because a
four-element member tag can carry a valid coordinate — the tag's shape
is what is wrong, and ignoring elements past the relay hint would admit
unvalidated data no consumer reads. Duplicates are rejected rather than
normalized — a relay cannot rewrite tags inside a signed event without
invalidating its id and signature.
- **Metadata interpretation is normative, not left to the reader.**
Ingest bounds cardinality and length and interprets nothing; clients
resolve absent `name` to the `d` value, any unrecognized
`buzz-visibility` token to `listed` (a typo is not a privacy signal),
and an unresolvable `buzz-channel` to a project rendered without a
channel rather than dropped. `content` carries no meaning: writers
SHOULD emit `""`, and readers and relays MUST ignore any value rather
than reject it.
- **Claim authority.** A project suppresses a member's standalone card
only when it is listing eligible *and* its signer is that repository's
owner or appears in the repository's own `maintainers` tag. Without
this, anyone could publish a project naming your repository and pull it
out of the collection into a container you never consented to. An
unauthorized project still renders, and still renders its members — it
just cannot remove a repository from where its owner expects to find it.
- **Deterministic client fold**, seven steps, with a table of required
cases: exhaustive enumeration (a fixed `limit: 200` makes repository 201
vanish), multiple membership, fallback to a standalone card,
unresolvable members marked unavailable rather than dropped, and local
hide of a container never hiding repositories. On a relay that provides
no exhaustive mode, the conformant behavior is a persistently marked
possibly-incomplete collection — not a violation of the enumeration
requirement.
- **Pagination is specified in two modes**, because exhaustive
enumeration is not universally achievable. Both modes share an explicit
three-condition relay contract: a relay must (1) apply the complete
filter before enforcing any limit, (2) expose the exact effective page
limit it enforces, and (3) saturate pages — return `min(effective limit,
remaining matches)`, so a short page proves all remaining matches were
returned. A relay satisfying any proper subset does not provide the
guarantee, and absent it a client MUST mark the collection possibly
incomplete. On a relay exposing a composite `(created_at, event id)`
keyset cursor — Buzz does on its authenticated HTTP bridge endpoint, via
`until` + `before_id`; the NIP-01 websocket REQ path silently discards
`before_id`, so a websocket client against Buzz is in mode 2 — clients
MUST page by it; within the relay contract the cursor's uniqueness means
no skips or re-reads and a short page is an unambiguous end signal, but
cursor uniqueness alone does not substitute for the relay contract. A
vanilla NIP-01 filter has no id tiebreak, so `until` alone either skips
a second's unread events or never advances; there a client MUST drain
the boundary second explicitly. The spec also adds normative guidance on
query shapes: a client MUST use only query shapes the relay applies
completely before limiting, and where a needed constraint (such as `#a`)
is post-applied, MUST widen to a pushable shape and match the rest
client-side.
- **Kind allocation** recorded with the checks performed: `30621` is
unassigned in the upstream nostr NIPs kind table and has no
nostrbook.dev entry, and it is the one free number between `30620` and
`30622` locally.

## `docs/nips/NIP-MP.fixtures.json`

The ingest contract: 31 cases — 11 accept, 20 reject — as unsigned
templates consumers sign with their own test key. Coverage includes
minimal and full projects, zero members, the 64-member boundary from
both sides, cross-owner and same-`d`-different-owner members,
colon-bearing repository `d` values, relay hints, non-empty `content`,
and every rejection rule. Each of the two 256-byte `buzz-` bounds gets
its own reject case so neither can hide behind the other's rejection,
and duplicate detection is pinned to the coordinate alone by a case
whose two identical coordinates carry different relay hints. A
four-element member tag carrying an otherwise valid coordinate pins
arity separately from coordinate parsing. Every rejection case names the
rules that may fire, so an implementation cannot pass by rejecting a bad
event for an unrelated reason.

## `docs/nips/NIP-MP.fold-fixtures.json`

The fold oracle: 12 cases covering every row of the required-fold-cases
table, including the discriminating case where one authorized and one
unauthorized project list the same repository — an implementation that
requires every listing project to be authorized emits a spurious
implicit card, and one that lets any listing project suppress drops a
card it owes the owner.

Inputs are semantic rather than signed envelopes: a repository or
project is named by its coordinate plus only what the fold reads —
signer, members, `maintainers`, visibility, viewer-hidden, deletion.
Every collection in `expect` is compared as a set, including each
container's `members`, since the fold fixes placement and not order.
Signing would re-test the ingest contract and obscure what is under
test. The fold is where claim authority lives, so without a shared
oracle two clients could each satisfy the prose and still render
different collections from identical heads.

## `VISION_PROJECTS.md`

Line 41's "zero custom kinds" now reads "no custom kind for the repo
itself", with a new "One Project, Many Repos" section recording why the
one exception is warranted. `30621` rows added to the kind and status
tables.

Related: #3171 (the `KIND_PROJECT` constant, relay ingest validation of
this contract, and the inclusive `created_at <= tombstone` bound this
spec's coordinate-deletion rule cites). Independent — either can merge
first.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-30 11:28:49 -04:00

528 lines
22 KiB
JSON

{
"$comment": "NIP-MP fold fixtures \u2014 the shared oracle for the client-side fold in docs/nips/NIP-MP.md (\"The fold\" and \"Required fold cases\"). Every case in the spec's required-cases table appears here, keyed by `spec_case`. Inputs are SEMANTIC, not signed envelopes: a repository or project is named by its coordinate plus the fold's inputs (signer, members, `maintainers`, visibility, viewer-hidden, deletion). Signing and envelope validation are the ingest contract and live in NIP-MP.fixtures.json; this file assumes every input is a valid, accepted head and tests only the placement the fold derives from it. `expect.containers` lists each rendered project with the members rendered inside it; `expect.implicit_cards` lists the repositories that additionally render as their own single-repository cards. EVERY collection in `expect` is compared as a set \u2014 the containers, each container's `members`, and the implicit cards alike \u2014 because the fold fixes placement, not order. `render` is `resolved` when the member coordinate resolves to a live head and `unavailable` when it does not. `state` is `live` or `deleted`; `visibility` is `listed` or `unlisted`; a value of `viewer_hidden` is a local, per-viewer decision, not event state.",
"version": 1,
"project_kind": 30621,
"repository_kind": 30617,
"pubkeys": {
"alice": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bob": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"carol": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
},
"cases": [
{
"name": "owner_project_claims_own_repository",
"spec_case": "Owner's own project lists their repository",
"note": "The signer is the member coordinate's owner, so the project claims the repository and step 3 suppresses its implicit card.",
"repositories": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"state": "live",
"viewer_hidden": false
}
],
"projects": [
{
"coordinate": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"signer": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"members": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
],
"visibility": "listed",
"state": "live",
"viewer_hidden": false
}
],
"expect": {
"containers": [
{
"project": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"members": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"render": "resolved"
}
]
}
],
"implicit_cards": []
}
},
{
"name": "stranger_project_does_not_claim",
"spec_case": "Stranger's project lists someone else's repository",
"note": "Bob is neither the owner nor a maintainer, so his project renders the member but claims nothing. The repository keeps its own card: an unendorsed grouping cannot pull a repository out of where its owner expects to find it.",
"repositories": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"state": "live",
"viewer_hidden": false
}
],
"projects": [
{
"coordinate": "30621:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:borrowed",
"signer": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"members": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
],
"visibility": "listed",
"state": "live",
"viewer_hidden": false
}
],
"expect": {
"containers": [
{
"project": "30621:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:borrowed",
"members": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"render": "resolved"
}
]
}
],
"implicit_cards": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
]
}
},
{
"name": "maintainer_signer_claims",
"spec_case": "Project signer is in the member repository's `maintainers` tag",
"note": "Claim authority is read from the member repository's own head, not from ownership alone. Carol is listed in `maintainers`, so her project claims the repository exactly as the owner's would.",
"repositories": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"state": "live",
"viewer_hidden": false,
"maintainers": [
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
]
}
],
"projects": [
{
"coordinate": "30621:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc:platform",
"signer": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"members": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
],
"visibility": "listed",
"state": "live",
"viewer_hidden": false
}
],
"expect": {
"containers": [
{
"project": "30621:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc:platform",
"members": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"render": "resolved"
}
]
}
],
"implicit_cards": []
}
},
{
"name": "two_claiming_projects_both_render_member",
"spec_case": "Repository is a member of two projects that both claim it",
"note": "Multiple membership is not a move. The repository renders inside both containers and has no implicit card, and it appears once per container rather than twice in either.",
"repositories": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"state": "live",
"viewer_hidden": false
}
],
"projects": [
{
"coordinate": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"signer": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"members": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
],
"visibility": "listed",
"state": "live",
"viewer_hidden": false
},
{
"coordinate": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:infra",
"signer": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"members": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
],
"visibility": "listed",
"state": "live",
"viewer_hidden": false
}
],
"expect": {
"containers": [
{
"project": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"members": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"render": "resolved"
}
]
},
{
"project": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:infra",
"members": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"render": "resolved"
}
]
}
],
"implicit_cards": []
}
},
{
"name": "repository_removed_from_every_project",
"spec_case": "Repository removed from every project",
"note": "A live project that no longer lists the repository. The container renders empty rather than being hidden, and the unclaimed repository falls back to its own card.",
"repositories": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"state": "live",
"viewer_hidden": false
}
],
"projects": [
{
"coordinate": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"signer": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"members": [],
"visibility": "listed",
"state": "live",
"viewer_hidden": false
}
],
"expect": {
"containers": [
{
"project": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"members": []
}
],
"implicit_cards": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
]
}
},
{
"name": "unlisted_project_absent_member_falls_back",
"spec_case": "Project is `unlisted`, or locally hidden",
"note": "An unlisted project is not listing eligible, so it claims nothing even though its signer owns the member. The container that would hold the repository is not on screen, so the repository must render as its own card or vanish.",
"repositories": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"state": "live",
"viewer_hidden": false
}
],
"projects": [
{
"coordinate": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:skunkworks",
"signer": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"members": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
],
"visibility": "unlisted",
"state": "live",
"viewer_hidden": false
}
],
"expect": {
"containers": [],
"implicit_cards": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
]
}
},
{
"name": "locally_hidden_project_absent_member_falls_back",
"spec_case": "Project is `unlisted`, or locally hidden",
"note": "The second half of the same rule, reached by a different input: hiding a container is a statement about the grouping only. The viewer-hidden project claims nothing and its member returns as an implicit card.",
"repositories": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"state": "live",
"viewer_hidden": false
}
],
"projects": [
{
"coordinate": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"signer": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"members": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
],
"visibility": "listed",
"state": "live",
"viewer_hidden": true
}
],
"expect": {
"containers": [],
"implicit_cards": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
]
}
},
{
"name": "viewer_hidden_repository_absent_everywhere",
"spec_case": "Viewer has hidden a member repository",
"note": "Hiding a repository hides it everywhere, including inside every project that lists it \u2014 otherwise someone else's grouping could undo the viewer's decision. The sibling member is unaffected.",
"repositories": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"state": "live",
"viewer_hidden": true
},
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz-infra",
"state": "live",
"viewer_hidden": false
}
],
"projects": [
{
"coordinate": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"signer": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"members": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz-infra"
],
"visibility": "listed",
"state": "live",
"viewer_hidden": false
}
],
"expect": {
"containers": [
{
"project": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"members": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz-infra",
"render": "resolved"
}
]
}
],
"implicit_cards": []
}
},
{
"name": "unresolvable_member_renders_unavailable",
"spec_case": "Member coordinate resolves to nothing",
"note": "No repository answers the coordinate \u2014 never announced, deleted, or absent from this relay. It renders inside its project as explicitly unavailable: dropping it silently would make the project look smaller than its author declared, and promoting it to a standalone card would invent a repository.",
"repositories": [],
"projects": [
{
"coordinate": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"signer": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"members": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:ghost"
],
"visibility": "listed",
"state": "live",
"viewer_hidden": false
}
],
"expect": {
"containers": [
{
"project": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"members": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:ghost",
"render": "unavailable"
}
]
}
],
"implicit_cards": []
}
},
{
"name": "deleted_project_absent_member_falls_back",
"spec_case": "Project head deleted",
"note": "Deletion does not cascade. The container is gone; the member repository, its refs and its channel survive and it falls back to an implicit card.",
"repositories": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"state": "live",
"viewer_hidden": false
}
],
"projects": [
{
"coordinate": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"signer": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"members": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
],
"visibility": "listed",
"state": "deleted",
"viewer_hidden": false
}
],
"expect": {
"containers": [],
"implicit_cards": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
]
}
},
{
"name": "authorized_and_unauthorized_projects_share_member",
"spec_case": "One authorized and one unauthorized project both list the same repository",
"note": "The discriminating case for step 3's \"at least one\": Alice's claim suppresses the implicit card, and Bob's unauthorized project still renders the member. An implementation that requires every listing project to be authorized would wrongly emit an implicit card here; one that lets any listing project suppress would wrongly emit none in `stranger_project_does_not_claim`.",
"repositories": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"state": "live",
"viewer_hidden": false
}
],
"projects": [
{
"coordinate": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"signer": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"members": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
],
"visibility": "listed",
"state": "live",
"viewer_hidden": false
},
{
"coordinate": "30621:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:borrowed",
"signer": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"members": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz"
],
"visibility": "listed",
"state": "live",
"viewer_hidden": false
}
],
"expect": {
"containers": [
{
"project": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"members": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"render": "resolved"
}
]
},
{
"project": "30621:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:borrowed",
"members": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"render": "resolved"
}
]
}
],
"implicit_cards": []
}
},
{
"name": "exhaustive_enumeration_across_pages_with_tied_timestamps",
"spec_case": "More repositories and projects than one page holds, with several sharing one `created_at`",
"note": "Exercises step 1 rather than the placement rules: the inputs exceed `enumeration.page_size` and three entities share one `created_at`, so a timestamp-only cursor loses events at the page boundary. Every repository and project must still render. `created_at` is present only on this case, and only because the cursor is what is under test.",
"enumeration": {
"page_size": 2,
"cursor": "composite",
"note": "The harness must serve inputs in pages of `page_size`, ordered `(created_at DESC, coordinate ASC)`, and the client under test must page with a composite `(created_at, id)` cursor per the Pagination section. Ordering by coordinate stands in for event id, which unsigned semantic fixtures do not carry."
},
"repositories": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"state": "live",
"viewer_hidden": false,
"created_at": 1000
},
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz-infra",
"state": "live",
"viewer_hidden": false,
"created_at": 1000
},
{
"coordinate": "30617:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:buzz-mobile",
"state": "live",
"viewer_hidden": false,
"created_at": 1000
}
],
"projects": [
{
"coordinate": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"signer": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"members": [
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz-infra"
],
"visibility": "listed",
"state": "live",
"viewer_hidden": false,
"created_at": 900
},
{
"coordinate": "30621:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:mobile",
"signer": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"members": [
"30617:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:buzz-mobile"
],
"visibility": "listed",
"state": "live",
"viewer_hidden": false,
"created_at": 900
}
],
"expect": {
"containers": [
{
"project": "30621:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:platform",
"members": [
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz",
"render": "resolved"
},
{
"coordinate": "30617:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:buzz-infra",
"render": "resolved"
}
]
},
{
"project": "30621:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:mobile",
"members": [
{
"coordinate": "30617:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:buzz-mobile",
"render": "resolved"
}
]
}
],
"implicit_cards": []
}
}
]
}