// ⛔ THE RKEY IS PART OF THE CONTRACT, NOT A LABEL.
//
// MEASURED against the live network 2026-09-07: our PDS held 74 posts for did:plc:e2jevhuu…, three
// of them with non-TID rkeys (favela-…, favela-…, self.hello). bsky's AppView showed 71 — every one
// TID-shaped, none of the three. `app.bsky.feed.post` is keyed "tid" in the lexicon and the AppView
// enforces it by DROPPING what does not conform, silently. Our own PDS served all 74 happily, which
// is why the first check looked healthy.
//
// So the rkey must be a real TID. It must ALSO stay a pure function of the favela record, because
// that is what makes a second run idempotent: the bridge holds no map, and asks the sink what rkeys
// it already has.
import test from 'brittle'
import { isValidTid, tidMicros } from 'atproto-store/lib/tid.js'
import { tidFor } from '../lib/tid.js'
test('★★ a favela record name becomes a VALID tid', async (t) => {
// Validated by atproto-store's own checker, not by our idea of the format.
for (const name of ['1788822724600', '1788737926306', '1']) {
t.ok(isValidTid(tidFor(name)), name + ' -> ' + tidFor(name))
}
})
test('★★ the mapping is PURE — the same record always gets the same rkey', async (t) => {
// Idempotency depends on this. tidNow() carries module-level monotonic state, so calling it twice
// for one record yields two rkeys and a duplicate post.
t.is(tidFor('1788822724600'), tidFor('1788822724600'))
t.not(tidFor('1788822724600'), tidFor('1788822724601'))
})
test('★ it preserves ORDER, because a TID sorts by time', async (t) => {
const a = tidFor('1788737926306')
const b = tidFor('1788822724600')
t.ok(a < b, 'the earlier post sorts first: ' + a + ' < ' + b)
// And it decodes back to the moment the record claims.
t.is(Number(tidMicros(b) / 1000n), 1788822724600)
})
test('⛔ a name that is not a timestamp still yields a valid tid', async (t) => {
// Record names are ms-since-epoch today, but nothing enforces it and an invalid rkey is dropped
// by bsky without a word. Anything unparseable must still produce something conformant.
for (const odd of ['self', 'hello-world', '', null, undefined]) {
const k = tidFor(odd)
t.ok(isValidTid(k), JSON.stringify(odd) + ' -> ' + k)
}
})
// ── favela-peer 0.0.38 changed what a record is called ────────────────────────────────────────
//
// ⛔ THE OLD MAPPING BREAKS ON THE NEW NAME. `tidFor` stripped non-digits and read the rest as
// milliseconds. That was right while every name WAS milliseconds. A name is now 10 base32hex
// characters of timestamp plus 16 of randomness (favela-peer/lib/name.js), so the digit-subsequence
// of one is a meaningless number — and worse, two different names share one, because a-v are all
// dropped. Measured on the shape room.js used to mint: 65,536 distinct names collapsed to 11,111
// distinct TIDs. A collision here is not cosmetic: index.js:101 treats `have.has(tidFor(name))` as
// "already there" and never sends the post.
//
// These literals are minted by favela-peer's generator with a fixed clock and fixed bytes. They are
// the cross-repo contract: this bridge must not need to import the peer to read its names, so the
// FORMAT is pinned here and any change to it must break this test.
const NEW_A = '01k1v297vo008i4cq4alj7f24p' // at 1788822724600, bytes 0011..99
const NEW_B = '01k1v297vovvndrj5rlacogtr6' // SAME millisecond, different bytes
const NEW_LATER = '01k1v297vp008i4cq4alj7f24p' // one millisecond later
test('★★ a new-format name yields a valid tid', async (t) => {
for (const n of [NEW_A, NEW_B, NEW_LATER]) t.ok(isValidTid(tidFor(n)), n + ' -> ' + tidFor(n))
})
test('★★ two records minted in the SAME millisecond get DIFFERENT rkeys', async (t) => {
// The whole point. Under the old mapping these two collapse together and the second is silently
// reported "already there" and never delivered.
t.not(tidFor(NEW_A), tidFor(NEW_B))
})
test('★★ order survives the mapping', async (t) => {
t.ok(tidFor(NEW_A) < tidFor(NEW_LATER), 'a later record still sorts later')
t.is(Number(tidMicros(tidFor(NEW_A)) / 1000n), 1788822724600, 'and decodes to the moment it was minted')
})
test('★ legacy clock names keep working — records already on disk do not move', async (t) => {
t.ok(isValidTid(tidFor('1788822724600')))
t.is(Number(tidMicros(tidFor('1788822724600')) / 1000n), 1788822724600)
t.is(tidFor('1788822724600'), tidFor('1788822724600'), 'still pure')
})
test('⛔ the mapping stays PURE for the new format too', async (t) => {
t.is(tidFor(NEW_A), tidFor(NEW_A), 'a re-run posts nothing twice')
})