Status: Implemented 2026-08-20 in Community Admin PR #142 and Avails PR #179
Date: 2026-08-20
Event DID: did:plc:mzvqnxye3oejamuwmfl4qvou
Producer: Community Admin
Consumer: Avails
A participant who has an active SIU event-participant grant can publish one
standing-availability record for SIU. Community Admin remains the sole authority
for that grant. Avails consumes Community Admin's decision and never queries the
Bluesky relationship graph to infer, confirm, or refresh SIU participation.
The grant authorizes only these bridge capabilities:
proposal-support, consumed inside Community Admin; andpublish-standing-availability, consumed by Avails.
It does not grant Community Admin membership, organizer authority, room entry, an invitation, attendance, transcript access, or permission to schedule a call.
- The authorization scope is the SIU DID, not Community Admin's local
siucommunity ID and not the mutableunconference.bsky.socialhandle. - The subject is the participant DID established by Avails' ATProto OAuth.
- Avails records use
{ "type": "ca-event", "value": "did:plc:mzvqnxye3oejamuwmfl4qvou" }. ca-eventis separate fromca-community; neither scope satisfies the other.- Avails' deterministic scope key preserves one record per participant per event DID, so availability is entered once for SIU rather than once per topic.
Community Admin exposes a bearer-gated online decision endpoint:
POST /internal/event-participant-grants/introspect
Authorization: Bearer <CA_CONFIG_SECRET>
Content-Type: application/json
{
"event_did": "did:plc:mzvqnxye3oejamuwmfl4qvou",
"subject_did": "did:plc:participant",
"capability": "publish-standing-availability"
}A successful decision is 200 for both active and inactive grants:
{
"relationship": "event-participant",
"event_did": "did:plc:mzvqnxye3oejamuwmfl4qvou",
"subject_did": "did:plc:participant",
"capability": "publish-standing-availability",
"active": true,
"reason": "active",
"checked_at": "2026-08-20T12:00:00.000Z",
"expires_at": "2026-08-20T12:05:00.000Z"
}Inactive reasons are not-following, blocking, blocked-by, and excluded.
An unknown or unresolved actor is inactive and reported as not-following; the
contract does not disclose whether Community Admin had seen that DID before.
Other responses:
400: malformed DID or unsupported capability.401: missing or invalid service credential.404: event DID is not bound to a Community Admin event.503: no unexpired observation exists and AppView could not refresh it.
Responses carry Cache-Control: no-store. They are authorization decisions for
the authenticated caller, not bearer grants or portable credentials, and cannot
be replayed as authority by another service.
Community Admin maps the event DID to exactly one local event binding, then reads
the existing event-participant row. The source observation TTL is controlled by
one shared EVENT_GRANT_TTL_MS, defaulting to five minutes. Proposal support and
Avails introspection must call the same decision function and use this same TTL.
When the observation is absent or expired, Community Admin calls the public
AppView app.bsky.graph.getRelationships with the SIU account as actor and the
participant DID in others. followedBy means the participant follows SIU;
blocking and blockedBy deny the grant. This is the orientation used by the
shipped AV1 producer.
Decision order is:
- A durable organizer exclusion denies immediately without a graph refresh.
- An unexpired positive or negative observation may be used until
expires_at. - An absent or expired observation must be refreshed before granting.
- A timeout, 429, malformed response, or other AppView failure returns
503once the prior observation has expired. It never creates or extends a right. - A successful negative refresh preserves historical support and availability evidence but makes it inactive.
Avails does not cache an active response across writes. Every event-scoped create or replacement is introspected online, which makes organizer exclusion effective on the next write even when the underlying follow observation remains fresh.
For POST /api/availability with a ca-event scope, Avails:
- uses its authenticated
req.userDidassubject_did; - validates that
scope.valueis a DID; - requests
publish-standing-availabilityintrospection from Community Admin; - writes to the participant's own PDS only when
activeis true; and - returns a non-authorizing error without writing in every other case.
The HTTP behavior is:
- active grant: existing
200/201upsert behavior; - inactive grant:
403with the inactive reason; - producer unavailable or expired verification:
503with a retryable message; - consumer misconfiguration:
503, never a bypass.
GET /api/availability/mine and DELETE /api/availability/:rkey remain available
regardless of grant state. A participant must always be able to inspect or delete
their own record. Editing or extending an event record is a new write and requires
an active grant.
- Unfollow or either-direction block becomes effective after explicit refresh or no later than the five-minute source TTL.
- Organizer exclusion is effective immediately in Community Admin and on the next Avails introspection; re-follow never clears it.
- Losing a grant does not delete or mutate the participant-owned Avails record.
- An inactive or expired record is excluded from readiness.
- If the grant later becomes active again, the existing unexpired record qualifies again without re-entry. An expired availability record still requires the participant to republish it.
AV2 must not reuse schedule_call, because that operation can create calendar
artifacts and send invitations. Avails adds a read-only, service-authenticated
evaluate_availability_overlap operation with this input:
{
"scope": {
"type": "ca-event",
"value": "did:plc:mzvqnxye3oejamuwmfl4qvou"
},
"eligibleDids": ["did:plc:a", "did:plc:b", "did:plc:c"],
"window": { "start": "2026-09-01", "end": "2026-09-30" },
"durationMinutes": 60,
"threshold": 3
}Community Admin supplies only current proposal liker DIDs whose event grant is
active in the same evaluation pass. Avails reads only those participants' PDS
records matching the exact ca-event scope and returns aggregate overlap:
Until SIU has a fixed event date, the approved readiness window is the next 56 calendar days and the candidate session duration is 60 minutes. This rolling window matches Avails' default standing-availability lifetime.
{
"ready": true,
"threshold": 3,
"eligibleSupporters": 3,
"supportersWithRecords": 3,
"maxOverlap": 3,
"candidateSlot": "2026-09-10T16:00"
}The operation has no booking ledger, ICS generation, email, invitation, poll, or
calendar side effect. ready is true only when at least three active supporters
share one slot. Three supporters without one shared three-person slot are not
ready. Community Admin may publish aggregate readiness and a generic reason, but
never DIDs, individual windows, or record contents on the public agenda.
For ca-event authorization and readiness, Avails must not call
app.bsky.graph.getRelationships, getFollowers, getList, or any equivalent
Bluesky social-graph endpoint. It may resolve participant DIDs to their PDS and
read their participant-owned availability records. Community Admin is the only
component that interprets a Bluesky follow or block as an SIU grant.
Tests must fail if an event path reaches Avails' Bluesky-list resolver or if a Community Admin outage falls through to a graph lookup or permissive write.
Avails currently stores standing availability in the participant's PDS, where the record is public to anyone who already knows the DID and collection. AV2 keeps that participant-owned storage model and adds clear UI disclosure; the SIU agenda exposes only aggregate readiness. This satisfies the AV2 slice's public-agenda boundary, but it does not make individual availability globally private.
SIU approved this bridge with explicit disclosure on 2026-08-20. If SIU later requires the availability record itself to be private, that needs a larger storage and revocation shape. Implementation must not imply that PDS records are private.
Approved together on 2026-08-20:
ca-eventplus the event DID is the correct stable scope;- five minutes is the accepted maximum unfollow/block detection delay;
- online introspection with fail-closed
503behavior is acceptable; - organizer exclusion is immediate without deleting participant records;
- readiness is a read-only three-person overlap operation; and
- participant-owned public PDS storage with explicit disclosure is acceptable, or a separate private-storage shape is required.