// Which DIDs does the LOCAL hpp actually host?
//
// ⛔ THE BUG THIS EXISTS TO CLOSE (2026-08-06). `isForeign` gates whether the appview may CREATE record
// bytes, and bin/favela-web.js supplied it as `(did) => !ROSTER.includes(did)`. But the guard in
// lib/appview.js means something narrower, and says so itself: "for a foreign peer NO LOCAL HPP HOLDS,
// we remain the only writer". Roster membership and local hosting are the same set on tabertron (hpp
// hosts wj62, and wj62 is on the roster) and DIFFERENT on ashatron (hpp hosts only e2jev, yet wj62 is
// on its roster). On ashatron every wj62 post was therefore refused — "hpp owns these" — while no local
// hpp would ever author them. Its appview froze at 2026-07-31T18:49, hours after the guard landed
// (d78f2e4), and neither restarting hpp nor favela-web could clear it: the refusal was working as coded.
//
// The authority for the answer is hpp's own config: `dids[]` is exactly the set it boots and serves
// (lib/config.js -> bootMultiDevice, and lib/diag.js reads the same array to decide what to probe).
// We take the DID strings and nothing else — the file also holds device mnemonics.
//
// No RPC and no boot ordering: a plain read of a file that is present whether or not hpp is up. When the
// answer is UNKNOWN (absent/unreadable/malformed) we return null and the caller falls back to the roster
// — the OLD behaviour, which is the conservative direction. Roster ⊇ hosted, so falling back can only
// refuse MORE writes, never permit one the previous version refused.
// Pure: parse hpp's config.json text -> the DIDs it hosts. null means "unknown", which is NOT the same
// as "hosts nothing" — the caller must distinguish, because empty would make every DID foreign and
// re-open the clobber the guard was built to stop.
export function parseHostedDids (text) {
if (typeof text !== 'string' || text.length === 0) return null
let cfg
try { cfg = JSON.parse(text) } catch { return null }
if (!cfg || !Array.isArray(cfg.dids)) return null
const dids = cfg.dids
.map((d) => (d && typeof d.did === 'string' ? d.did.trim() : null))
.filter((d) => d && d.startsWith('did:'))
// A `dids` array that parsed but named nothing usable is malformed, not an empty fleet.
return dids.length ? dids : null
}
// The one definition of "ours", used by BOTH the appview create-gate and the retention scope so they
// cannot drift apart (bin/favela-web.js wired the same expression twice by hand before this).
//
// A DID is OURS only when the local hpp hosts it — that is the whole correction. Everything else is
// foreign, which is what licenses us to author its bytes, and what keeps an ingested repo from freezing
// at its first-seen version.
export function createIsForeign ({ hosted, roster = [] } = {}) {
const known = Array.isArray(hosted) && hosted.length > 0
const ours = new Set(known ? hosted : roster)
return (did) => !ours.has(did)
}
// ---- IGNORED-BY-POLICY collections (spec/convergence.md §2.1) ---------------------------------------
//
// Collections we deliberately do not index. This is a DECISION list, not a dumping ground: each entry
// carries the reason, because "we don't use it" is not one. Anything not tracked and not here is
// UNKNOWN — a lexicon nobody has classified.
//
// Both classes are now reported rather than dropped, on BOTH sides of the question: `diff` classifies the
// authority's records (0.3.43), and the appview's ingest ledger records what actually arrives here
// (0.3.47, spec §2.2) — which is what `applyCommit`'s final bare `continue` discarded in silence.
export const IGNORED_COLLECTIONS = new Map([
['chat.bsky.actor.declaration',
'DM policy (allowIncoming/allowGroupInvites) consumed by bsky\'s chat service, which we do not run. ' +
'Referenced nowhere in this stack. The repo carries it and it still federates; we have no surface to render it.'],
['net.favela.test',
'Our own debugging debris ("delete-tip-fix verification", 2026-07-24). Referenced nowhere. Retained ' +
'in the repo because nothing here deletes from a repo.']
])
export const isIgnoredCollection = (c) => IGNORED_COLLECTIONS.has(c)