// maxReach — the lattice. library.md §6.1, record-vocabulary.md §5.2, interaction.md §5.2
//
// ⛔ THIS IS THE ONE PLACE A DEFECT IS A PRIVACY BUG rather than a rendering bug (library §6.1).
// Written here once, this check would otherwise be written three times — favela-web,
// hyper-proto-peer, ap-bridge — and the third one would eventually be written wrong.
//
// Everything is deny-by-default. Unknown room type, unknown verb, missing argument, hostile input:
// the answer is {favela}, which is not a restriction but a fact — "every post is local; the
// canonical record always exists on the P2P side; that is what authoring one means"
// (record-vocabulary §5.4). Local is the substrate, not a scope you opt into.
//
// SIGNATURE CHANGED 2026-09-05. library.md §6.1 signs this `maxReach(visibility)`, from when
// visibility was a frontmatter field. Visibility is now a PLACE — the room you wrote in
// (SOCIAL-MODEL §4) — so the inputs are the room's kind and the record's verb. This library has no
// storage and cannot inspect a room, so the CALLER supplies the kind. That is honest about the
// library boundary (§1: no storage, no network) rather than pretending to knowledge it lacks.
export const NETWORKS = ['favela', 'bsky', 'fediverse']
// What each kind of room can be expressed as, on each network.
//
// public → everywhere. AS2 Public; an atproto repo is world-readable by construction.
// followers → the fediverse ONLY. AP has addressing, so a followers room maps onto the followers
// collection. ⛔ atproto has NO addressing at all (record-vocabulary §5.2), so a
// projection it cannot restrict is a leak, and the only safe answer is to withhold.
// members → favela only. AS2 *can* address actors individually, but an arbitrary room's
// dm membership is not expressible as an audience a receiving server will honour, and
// approximating it is how a private post reaches a stranger. Fails closed.
const ROOM_REACH = {
public: ['favela', 'bsky', 'fediverse'],
followers: ['favela', 'fediverse'],
members: ['favela'],
dm: ['favela']
}
// What each verb can be expressed as. interaction.md §5.4 (the signed set) and §5.5 (native).
// ⛔ A verb absent from this table reaches {favela} and nothing else — that IS the extension
// mechanism (interaction §5.2): a native-only verb cannot fork a signed projection, because it
// never reaches a shared derivation. Adding a verb here is a spec change, not a plugin point.
const VERB_REACH = {
post: ['favela', 'bsky', 'fediverse'],
reply: ['favela', 'bsky', 'fediverse'],
like: ['favela', 'bsky', 'fediverse'],
repost: ['favela', 'bsky', 'fediverse'],
follow: ['favela', 'bsky', 'fediverse'],
// AS2 `Block` exists but Mastodon does not federate it, so it projects with a loss entry rather
// than being withheld — the receiving server simply does nothing with it.
block: ['favela', 'bsky', 'fediverse'],
// AS2 `Flag` is a REPORT to the origin's moderators, a different act from a shared signal, so
// conflating them would misrepresent the moderator (SOCIAL-MODEL §6.7). atproto has a real
// equivalent — a signed com.atproto.label, which we already emit.
flag: ['favela', 'bsky'],
// Internal: how a node knows two names, or two byte-sets, denote one thing. No projection
// anywhere, by design (interaction §5.5, record-vocabulary §3.5).
alias: ['favela'],
'variant-of': ['favela']
}
const LOCAL = () => new Set(['favela'])
/**
* maxReach({ roomType, verb }) -> Set<network>
*
* The MEET of what the room can express and what the verb can express. TOTAL: never throws, and
* always contains 'favela'.
*/
export function maxReach (opts) {
const roomType = opts && typeof opts.roomType === 'string' ? opts.roomType : null
const verb = opts && typeof opts.verb === 'string' ? opts.verb : null
// Exact match only. 'PUBLIC' and 'public ' are NOT 'public': normalising input here would mean a
// typo could widen an audience, which is precisely the class of bug §6.1 exists to prevent.
const room = (roomType && Object.prototype.hasOwnProperty.call(ROOM_REACH, roomType))
? ROOM_REACH[roomType]
: null
const verbs = (verb && Object.prototype.hasOwnProperty.call(VERB_REACH, verb))
? VERB_REACH[verb]
: null
if (!room || !verbs) return LOCAL()
const out = new Set()
for (const n of room) if (verbs.includes(n)) out.add(n)
out.add('favela') // §5.4 — local is the substrate; there is no way to opt out of it
return out
}
export default { maxReach, NETWORKS }