// The appview must NEVER overwrite canonical record bytes in the SHARED store.
//
// THE BUG THIS PINS. putPost called store.put() unconditionally, on the assumption that our re-encode is
// byte-identical to hpp's. On 2026-07-30 it was not: hpp wrote the record WITH langs (390 bytes) and
// committed a pointer to that hash; the appview re-encoded its decoded copy WITHOUT langs (380 bytes)
// over the same file. The converged pointer then referenced bytes that existed on no node, the signer
// could not resolve them, and wj62's repo head stopped advancing for ~42h — two posts never published.
//
// The control is the first test: it writes DIFFERENT bytes for an already-present record and asserts the
// file is untouched. That is the exact operation that caused the outage, so a regression cannot pass.
//
// Run: bare test/appview-no-clobber.test.mjs
import '../lib/bare-globals.js'
import { storeDir } from './_store-dir.mjs'
import Corestore from 'corestore'
import { Appview } from '../lib/appview.js'
import { OverlayRepoStore } from 'atproto-store/lib/overlay-repo-store.js'
import { join } from 'bare-path'
const assert = (cond, msg) => { if (!cond) throw new Error('FAIL: ' + msg); console.log(' ok -', msg) }
const dir = storeDir('appview-no-clobber-test')
const recRoot = join(dir, 'rec')
const DID = 'did:plc:alice'
const store = await new OverlayRepoStore(join(recRoot, 'room0'), { did: DID }).ready()
const av = await new Appview(new Corestore(dir), { getRecordStore: async () => store }).ready()
const COLL = 'app.bsky.feed.post'
const put = (rkey, record) => av.applyCommit(DID, [{ action: 'create', collection: COLL, rkey, record }])
// hpp's version: WITH langs — the shape that actually federates (writer.js declares langs so bsky's
// custom-feed tuner does not language-detect and drop the post).
const canonical = { $type: COLL, text: 'we workin', createdAt: '2026-07-30T01:25:12.455Z', langs: ['en'] }
await store.put(COLL, '3mrtbsse2qc22', canonical)
const before = (await store.getRaw(COLL, '3mrtbsse2qc22')).bytes
assert(before && before.length > 0, 'precondition: hpp wrote canonical bytes (' + before.length + ')')
console.log('\n# CONTROL: indexing a DIFFERENT serialization must not touch the file')
// The same record as the appview would hold it — langs dropped. This is the clobber.
await put('3mrtbsse2qc22', { $type: COLL, text: 'we workin', createdAt: '2026-07-30T01:25:12.455Z' })
const after = (await store.getRaw(COLL, '3mrtbsse2qc22')).bytes
assert(after.length === before.length, 'canonical byte LENGTH is unchanged (' + after.length + ')')
assert(Buffer.compare(Buffer.from(before), Buffer.from(after)) === 0, 'and the bytes are IDENTICAL — the pointer stays resolvable')
const still = await store.get(COLL, '3mrtbsse2qc22')
assert(Array.isArray(still.langs) && still.langs[0] === 'en', 'langs survived — the file is still hpp\'s version')
console.log('\n# the record is still INDEXED, even though its bytes were not rewritten')
const feed = await av.getAuthorFeed(DID, { limit: 10, filter: 'posts_with_replies' })
assert(feed.posts.some((p) => p.rkey === '3mrtbsse2qc22'), 'not writing bytes does not mean not indexing')
console.log('\n# a record NOBODY holds is still written — the reason this write exists')
await put('brandnew1', { $type: COLL, text: 'from a peer whose bytes no local hpp has', createdAt: '2026-07-30T02:00:00.000Z' })
const fresh = (await store.getRaw(COLL, 'brandnew1')).bytes
assert(fresh && fresh.length > 0, 'an ABSENT record is written as before')
assert((await store.get(COLL, 'brandnew1')).text.startsWith('from a peer'), 'and reads back correctly')
console.log('\n# an identical re-index is a no-op either way')
const b2 = (await store.getRaw(COLL, 'brandnew1')).bytes
await put('brandnew1', { $type: COLL, text: 'from a peer whose bytes no local hpp has', createdAt: '2026-07-30T02:00:00.000Z' })
const a2 = (await store.getRaw(COLL, 'brandnew1')).bytes
assert(Buffer.compare(Buffer.from(b2), Buffer.from(a2)) === 0, 'unchanged bytes stay unchanged')
// A blanket "never overwrite" ALSO passes every assertion above — and breaks editing, because for a peer
// whose records no local hpp holds we are the only writer. These two pin the other side of the line, and
// they are why the rule is LOSSY-subset rather than exists.
console.log('\n# a genuine EDIT still writes — this is not a blanket no-overwrite')
await put('brandnew1', { $type: COLL, text: 'EDITED text', createdAt: '2026-07-30T02:00:00.000Z' })
assert((await store.get(COLL, 'brandnew1')).text === 'EDITED text', 'a changed value overwrites, as it must')
console.log('\n# and ADDING a field writes (only DROPPING one is refused)')
await put('brandnew1', { $type: COLL, text: 'EDITED text', createdAt: '2026-07-30T02:00:00.000Z', langs: ['en'] })
const enriched = await store.get(COLL, 'brandnew1')
assert(Array.isArray(enriched.langs), 'a richer record replaces a poorer one')
await put('brandnew1', { $type: COLL, text: 'EDITED text', createdAt: '2026-07-30T02:00:00.000Z' })
assert(Array.isArray((await store.get(COLL, 'brandnew1')).langs), 'and dropping it again is refused — the asymmetry is the whole rule')
console.log('\nall no-clobber assertions passed')
// ── the CREATE branch — the hole the guard above could not see ───────────────────────────────────
//
// Everything above pins the OVERWRITE branch: a file is present, and a lossy copy must not replace it.
// But isLossySubset can only compare against a file that is ALREADY here, so when the file was ABSENT
// putPost wrote unconditionally — and "absent" is exactly hpp's state between committing a pointer and
// this node materialising the bytes. Our re-encode landed first, hashed differently from the pointer, and
// the signer could never resolve it: the head froze and nothing federated.
//
// That is why 2026-07-30 recurred on 2026-07-31 (392-byte pointer, 382-byte file, langs dropped) — the
// 0.2.50 fix covered the branch that had a file, not the branch that did not.
//
// The discriminator is WHOSE DID it is, not whether a file happens to be here: for a ROSTER did hpp is the
// authority and the sole author of those bytes.
console.log('\n# a ROSTER did: an ABSENT record must NOT be created by the appview')
const rosterAv = await new Appview(new Corestore(join(dir, 'roster-av')), {
getRecordStore: async () => store,
// PER-DID, exactly as bin/favela-web.js wires it (`(did) => !ROSTER.includes(did)`). A constant here
// would make the foreign assertion below vacuous — it is the asymmetry that is under test.
isForeign: (did) => did !== DID // DID is ours (hpp owns its bytes); anyone else is foreign
}).ready()
await rosterAv.applyCommit(DID, [{ action: 'create', collection: COLL, rkey: 'hppwillwrite', record: { $type: COLL, text: 'hpp has a pointer for this', createdAt: '2026-07-31T18:23:24.391Z' } }])
assert((await store.getRaw(COLL, 'hppwillwrite')) == null, 'no bytes are authored for a roster did — hpp may already hold a pointer')
// THE TRADE, stated honestly rather than assumed away. The index entry IS written, but the appview
// hydrates a post's fields from the byte store (_resolveRecord), so a post whose bytes we refused does not
// RENDER until hpp's bytes land here. That is the correct side of the trade — an unrenderable post that
// resolves moments later beats a repo whose head never advances again — but it is a real consequence and a
// reader deserves to see it pinned rather than discover it.
//
// In the live flow this window is small and usually zero: writer.post() goes THROUGH hpp, so hpp writes the
// file and the pointer before favela indexes. By then `existing != null` and the lossy guard above governs.
// This branch is the race, which is exactly where the defect lived.
const indexEntry = await rosterAv._ref.get(rosterAv._refKey(DID, 'hppwillwrite')).catch(() => null)
assert(indexEntry != null, 'the post IS indexed — we refuse the byte authorship, not the indexing')
assert((await rosterAv.getPost(DID, 'hppwillwrite')) == null, 'and it does not render until hpp materialises its bytes — the accepted cost')
// And the other side of the line: a foreign peer's absent record is still created, because for a peer whose
// records no local hpp holds we remain the only writer. A blanket refusal would freeze every ingested repo.
console.log('\n# a FOREIGN did: an absent record is still created')
await rosterAv.applyCommit('did:plc:stranger', [{ action: 'create', collection: COLL, rkey: 'foreign1', record: { $type: COLL, text: 'from a stranger', createdAt: '2026-07-31T18:00:00.000Z' } }])
assert((await store.getRaw(COLL, 'foreign1')) != null, 'foreign bytes are still authored — isForeign is per-DID, not a global off switch')
console.log('\nall create-branch assertions passed')