SOURCE / PINNED RELEASE
Made of little things.
Black Site
- Release
- 666bc7c74ebd…
- Author-recorded commit
- 05e95af79676…
- License
- LICENSE
- Author’s source reference
- nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-d2225ddc391
Archive hash verified: 1544e072e245c4a8…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Remix lineage (spec/remix-lineage.md): NIP-22 style root/parent tags and author credit.
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { readFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
import { CAMPAIGN_EVENT_KIND, campaignEventTemplate, parseCampaignEvent } from '../spec/lib/campaign-event.mjs';
import { MAX_LINEAGE_AUTHORS, parseLineage, remixTags } from '../spec/lib/lineage.mjs';
import { MAP_EVENT_KIND, mapEventTemplate, parseMapEvent } from '../spec/lib/map-event.mjs';
const root = dirname(dirname(fileURLToPath(import.meta.url)));
const bytes = await readFile(join(root, 'spec/examples/maps/outpost.json'));
const map = JSON.parse(bytes.toString('utf8'));
const borderRun = JSON.parse(await readFile(join(root, 'spec/examples/campaigns/border-run.json'), 'utf8'));
const upload = { sha256: createHash('sha256').update(bytes).digest('hex'), size: bytes.byteLength };
const [alice, bob, carol] = ['a', 'b', 'c'].map((c) => c.repeat(64));
const sign = (template, pubkey, idChar) => ({ ...template, pubkey, id: idChar.repeat(64), sig: '0'.repeat(128) });
const relay = 'wss://relay.example.com';
const original = sign(mapEventTemplate(map, upload), alice, '1');
const remix = sign(mapEventTemplate({ ...map, id: 'outpost-night' }, upload, { remixOf: original, remixRelay: relay }), bob, '2');
const remix2 = sign(mapEventTemplate({ ...map, id: 'outpost-storm' }, upload, { remixOf: remix }), carol, '3');
const pick = (ev, names) => ev.tags.filter((t) => names.includes(t[0]));
const LINEAGE = ['A', 'E', 'K', 'P', 'a', 'e', 'k', 'p'];
test('an original has no lineage', () => {
assert.deepEqual(pick(original, LINEAGE), []);
assert.equal(parseMapEvent(original).map.lineage, null);
});
test('a first remix points root and parent at the original', () => {
assert.deepEqual(pick(remix, LINEAGE), [
['A', `35858:${alice}:outpost`, relay],
['E', '1'.repeat(64), relay, alice],
['K', '35858'],
['P', alice],
['a', `35858:${alice}:outpost`, relay],
['e', '1'.repeat(64), relay, alice],
['k', '35858'],
['p', alice],
]);
});
test('a remix of a remix keeps the root and credits every author', () => {
assert.deepEqual(pick(remix2, LINEAGE), [
['A', `35858:${alice}:outpost`, relay],
['E', '1'.repeat(64), relay, alice],
['K', '35858'],
['P', alice],
['a', `35858:${bob}:outpost-night`],
['e', '2'.repeat(64), '', bob],
['k', '35858'],
['p', bob],
['p', alice],
]);
const { lineage } = parseMapEvent(remix2).map;
assert.equal(lineage.root.address, `35858:${alice}:outpost`);
assert.equal(lineage.root.eventId, '1'.repeat(64));
assert.equal(lineage.parent.address, `35858:${bob}:outpost-night`);
assert.equal(lineage.parent.eventId, '2'.repeat(64));
assert.equal(lineage.parent.relay, null);
assert.deepEqual(lineage.authors, [bob, alice]);
});
test('long lineages are capped but always keep the root author', () => {
let ev = original;
for (let i = 0; i < MAX_LINEAGE_AUTHORS + 5; i++) {
const pubkey = i.toString(16).padStart(64, '0');
ev = sign(mapEventTemplate({ ...map, id: `r${i}` }, upload, { remixOf: ev }), pubkey, 'f');
}
const authors = parseMapEvent(ev).map.lineage.authors;
assert.equal(authors.length, MAX_LINEAGE_AUTHORS);
assert.equal(authors.at(-1), alice);
});
test('broken lineage is rejected', () => {
const edit = (ev, fn) => ({ ...ev, tags: fn(ev.tags.map((t) => [...t])) });
const errors = (ev) => parseLineage(ev).errors;
assert.deepEqual(errors(edit(remix2, (t) => t.filter((x) => x[0] !== 'A'))), ['a remix needs exactly one A (root) and one a (parent) tag of kind 35858']);
assert.deepEqual(errors(edit(remix2, (t) => t.filter((x) => !(x[0] === 'p' && x[1] === bob)))), ['p tags must include the parent author']);
assert.deepEqual(errors(edit(remix2, (t) => t.filter((x) => x[0] !== 'P'))), ['P tag must be the root author']);
assert.deepEqual(errors(edit(remix2, (t) => t.map((x) => (x[0] === 'k' ? ['k', '1'] : x)))), ['k tag must be "35858"']);
assert.deepEqual(errors(edit(remix2, (t) => t.map((x) => (x[0] === 'a' ? ['a', `35858:${carol}:outpost-storm`] : x)))), ['an event cannot be a remix of itself', 'p tags must include the parent author']);
assert.equal(parseMapEvent(edit(remix2, (t) => t.filter((x) => x[0] !== 'a'))).ok, false);
});
test('remixes keep their kind', () => {
const campaign = sign(campaignEventTemplate('border-run', borderRun), alice, '4');
assert.throws(() => mapEventTemplate(map, upload, { remixOf: campaign }), /can only remix a kind 35858 map/);
assert.throws(() => campaignEventTemplate('x', borderRun, { remixOf: original }), /can only remix a kind 35859 campaign/);
assert.throws(() => remixTags({ ...original, id: undefined }), /signed event/);
});
test('campaign remixes ignore stage map a tags when reading lineage', () => {
const campaign = sign(campaignEventTemplate('border-run', borderRun), alice, '4');
assert.ok(campaign.tags.some((t) => t[0] === 'a' && t[1].startsWith('35858:')));
assert.equal(parseCampaignEvent(campaign).campaign.lineage, null);
const remixed = sign(campaignEventTemplate('border-run', borderRun, { remixOf: campaign, remixRelay: relay }), bob, '5');
const parsed = parseCampaignEvent(remixed);
assert.equal(parsed.ok, true);
assert.equal(parsed.campaign.lineage.parent.address, `${CAMPAIGN_EVENT_KIND}:${alice}:border-run`);
assert.deepEqual(parsed.campaign.lineage.authors, [alice]);
// stage discovery tags are still there alongside the lineage
assert.equal(remixed.tags.filter((t) => t[0] === 'a').length, campaign.tags.filter((t) => t[0] === 'a').length + 1);
assert.equal(MAP_EVENT_KIND, 35858);
});
