johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
// The world the screenshots show: a reference PDS holding the same
// repositories, pulls and issues every time, so each page renders to the
// same pixels. Commits carry fixed dates, so their hashes are fixed too, and
// every record's createdAt is rewritten to a fixed time, in the order the
// records were made. The index a knotless-serve built while it was seeded
// is kept in `dir`, with its mirrors, for the next server to start from.
//
// node harness/shots/world.mjs seed one and keep it up, for
// `just shots` runs to reuse, until
// ctrl-c; written to target/shots/world.json
import { spawn, execFileSync } from 'node:child_process'
import { createHash } from 'node:crypto'
import { cpSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { DatabaseSync } from 'node:sqlite'
import { dirname, join, resolve } from 'node:path'
import { createInterface } from 'node:readline'
import { fileURLToPath } from 'node:url'
import { crc32, deflateSync } from 'node:zlib'
const here = dirname(fileURLToPath(import.meta.url))
export const root = resolve(here, '../..')
const fixture = join(here, 'fixture')
const bin = join(root, 'target/debug')
// Where the world answers, and the pages behind it on the next two ports:
// three of their own for each checkout, from its path, so the shots of two
// side by side do not meet, and three more for the end-to-end run's world
// (E2E=1), so it goes beside `shots-up`'s; SHOTS_PORT moves them.
const own =
(process.env.E2E ? 29000 : 20000) +
3 * (createHash('sha256').update(root).digest().readUInt16BE(0) % 3000)
export const port = Number(process.env.SHOTS_PORT ?? own)
export const base = `http://127.0.0.1:${port}`
export const saved = join(root, 'target/shots/world.json')
// The pages say how long ago things were: now is a few hours after the
// fixtures' last, for the pages and the browser both, so the pictures are
// the same any day.
export const now = '2026-10-20T14:00:00Z'
/// When some of each runner's minutes free up, as the pages show it.
const freeing = '2026-11-02T00:00:00Z'
// The views drawn signed in, and as whom: only from the recording, where
// the stand-in for knotless-serve answers getViewer as that account
// (fixtures.mjs). A world has no session to sign them in, so shots-live
// skips them.
export const viewers = {
settings: 'alice.test',
'settings-branches': 'alice.test',
'settings-labels': 'alice.test',
'settings-access': 'alice.test',
'settings-actions': 'alice.test',
notifications: 'alice.test',
// One who is neither the owner nor a collaborator: what to hear of the
// repository, and of the issue it opened.
'repository-watch': 'bob.test',
'issue-subscribe': 'bob.test',
}
const people = {
alice: { name: 'Alice Moreau', email: 'alice@example.com' },
bob: { name: 'Bob Tanaka', email: 'bob@example.com' },
}
export const until = async (what, check) => {
for (let i = 0; i < 300; i++) {
try {
if (await check()) return
} catch {}
await new Promise((r) => setTimeout(r, 100))
}
throw new Error(`timed out waiting for ${what}`)
}
/// What knotless keys a ref by: its SHA-256 in lower base32, cut to 26.
const refHash = (name) => {
const alphabet = 'abcdefghijklmnopqrstuvwxyz234567'
let bits = ''
for (const b of createHash('sha256').update(name).digest()) bits += b.toString(2).padStart(8, '0')
return (bits.match(/.{1,5}/g) ?? [])
.map((c) => alphabet[parseInt(c.padEnd(5, '0'), 2)])
.join('')
.slice(0, 26)
}
export const page = async (path) => {
const res = await fetch(base + path)
return res.ok ? res.text() : ''
}
/// The environment `who` runs the CLI, git or the server in: committing
/// `when`, as written `authored`, or then too.
export const environment = (net, dir, who, when, authored) => {
const clean = Object.fromEntries(
Object.entries(process.env).filter(([k]) => !k.startsWith('KNOTLESS_')),
)
const person = people[who] ?? { name: who, email: `${who}@example.com` }
return {
...clean,
PATH: `${bin}:${process.env.PATH}`,
KNOTLESS_HANDLE_RESOLVER: net.pds,
KNOTLESS_PLC_URL: net.plc,
KNOTLESS_COMPACT: 'off',
KNOTLESS_APPVIEW: base,
KNOTLESS_CONFIG_DIR: join(dir, who, 'config'),
KNOTLESS_CACHE_DIR: join(dir, who, 'cache'),
GIT_CONFIG_GLOBAL: '/dev/null',
GIT_CONFIG_NOSYSTEM: '1',
GIT_AUTHOR_NAME: person.name,
GIT_AUTHOR_EMAIL: person.email,
GIT_COMMITTER_NAME: person.name,
GIT_COMMITTER_EMAIL: person.email,
GIT_AUTHOR_DATE: authored ?? when ?? '2026-10-01T09:00:00Z',
GIT_COMMITTER_DATE: when ?? '2026-10-01T09:00:00Z',
}
}
/// The pages, from `build` (this checkout's web/build unless given),
/// answering on `at` and reading from `data`.
export const pages = (at, data, build = join(root, 'web/build')) =>
spawn('node', [build], {
env: {
...process.env,
HOST: '127.0.0.1',
PORT: String(at),
KNOTLESS_SERVE: data,
KNOTLESS_NOW: now,
HOST_HEADER: 'x-forwarded-host',
PROTOCOL_HEADER: 'x-forwarded-proto',
},
stdio: 'inherit',
})
/// Whether Zoekt is here to search code with: the world's server then
/// keeps a code index, and its code results are shown. `just
/// shots-record` needs it, as the shots record them.
export const zoekt = (() => {
try {
execFileSync('sh', ['-c', 'command -v zoekt-webserver && command -v zoekt-git-index'])
return true
} catch {
return false
}
})()
/// knotless-serve from this build, over the world's index and mirrors, on
/// `at`: in front of the pages, unless `alone`. With `gateway`, the https
/// front a PDS sends logins back to, it is a gateway too, which the pages
/// log in to.
export const serve = async (world, { at = port, alone = false, gateway } = {}) => {
const drawn = alone ? null : pages(port + 1, base)
const server = spawn(
join(bin, 'knotless-serve'),
[
'--listen',
`127.0.0.1:${at}`,
'--index',
join(world.dir, 'index.db'),
'--firehose',
world.net.pds.replace('http', 'ws'),
...(zoekt ? ['--code-index', join(world.dir, 'code')] : []),
...(alone ? [] : ['--web', `http://127.0.0.1:${port + 1}`]),
// atproto takes a did:web with a port only for localhost.
...(gateway ? ['--public-url', `http://localhost:${at}`, '--oauth-url', gateway] : []),
],
{
env: {
...environment(world.net, world.dir, 'server'),
...(gateway ? { KNOTLESS_DID_WEB_SCHEME: 'http' } : {}),
},
stdio: 'inherit',
},
)
const kill = () => {
server.kill()
drawn?.kill()
}
const up = alone ? `http://127.0.0.1:${at}/healthz` : base
try {
await until('knotless-serve and the pages', async () => (await fetch(up)).ok)
} catch (e) {
kill()
throw e
}
return { kill }
}
/// A new world: its PDS, a server, and the seeded data. Returns them with
/// the pages to shoot.
export async function create() {
const dir = mkdtempSync(join(tmpdir(), 'knotless-shots-'))
const children = []
const stop = () => {
for (const child of children.reverse()) child.kill()
rmSync(dir, { recursive: true, force: true })
}
try {
const pds = spawn('node', ['harness/pds.mjs', '--accounts', 'alice,bob,textkit'], {
cwd: root,
stdio: ['pipe', 'pipe', 'inherit'],
})
children.push(pds)
const [line] = await createInterface({ input: pds.stdout })
[Symbol.asyncIterator]()
.next()
.then((r) => [r.value])
const net = JSON.parse(line)
const account = Object.fromEntries(net.accounts.map((a) => [a.handle.split('.')[0], a]))
const work = dir
const env = (who, when, authored) => environment(net, dir, who, when, authored)
const run = (who, cmd, args, opts = {}) =>
execFileSync(cmd, args, {
env: env(who, opts.when, opts.authored),
cwd: opts.cwd ?? work,
input: opts.input ?? '',
})
.toString()
.trim()
const knotless = (who, ...args) => run(who, join(bin, 'knotless'), args)
const git = (who, dir, when, ...args) => run(who, 'git', args, { cwd: dir, when })
/// Copies files from a fixture step into `dir` and commits them, as
/// written `authored` if that was before.
const commit = (who, dir, when, message, files, authored) => {
for (const [from, to] of Object.entries(files)) {
mkdirSync(dirname(join(dir, to)), { recursive: true })
cpSync(join(fixture, from), join(dir, to))
}
git(who, dir, when, 'add', '-A')
run(who, 'git', ['commit', '-q', '-m', message], { cwd: dir, when, authored })
}
children.push(await serve({ net, dir }))
for (const who of ['alice', 'bob']) {
run(who, join(bin, 'knotless'), ['login', account[who].handle, '--password-stdin'], {
input: account[who].password,
})
}
const tally = 'knotless://alice.test/tally'
knotless(
'alice',
'repo',
'create',
tally,
'--description',
'Counts lines, words and bytes, with output that lines up',
'--link',
'https://tally.dev',
'--link',
'https://docs.tally.dev',
'--title',
'Docs',
)
const a = join(work, 'alice/tally')
mkdirSync(a, { recursive: true })
git('alice', a, null, 'init', '-q', '-b', 'main')
commit('alice', a, '2026-10-01T09:12:00Z', 'Start tally', {
'start/Cargo.toml': 'Cargo.toml',
'start/.gitignore': '.gitignore',
'start/README.md': 'README.md',
'start/main.rs': 'src/main.rs',
})
commit('alice', a, '2026-10-02T14:40:00Z', 'Count words, lines and bytes', {
'count/lib.rs': 'src/lib.rs',
'count/main.rs': 'src/main.rs',
})
commit(
'alice',
a,
'2026-10-03T08:05:00Z',
'Read from stdin when no file is given\n\nSo tally works at the end of a pipe, as wc does. The name shown\nfor stdin is "-".',
{ 'stdin/main.rs': 'src/main.rs' },
)
// Written days before it was committed, as a rebase leaves one: it is
// listed on the day it was committed, saying when it was written.
commit(
'alice',
a,
'2026-10-05T17:21:00Z',
'Add a logo',
{ 'logo/logo.svg': 'assets/logo.svg' },
'2026-10-02T11:30:00Z',
)
commit('alice', a, '2026-10-06T10:02:00Z', 'Document usage and output', {
'docs/README.md': 'README.md',
'docs/design.md': 'docs/design.md',
})
git('alice', a, null, 'mv', 'docs/design.md', 'docs/DESIGN.md')
commit('alice', a, '2026-10-08T11:47:00Z', 'Split parsing into its own module', {
'parse/parse.rs': 'src/parse.rs',
'parse/lib.rs': 'src/lib.rs',
})
const parse = git('alice', a, null, 'rev-parse', 'HEAD')
git('alice', a, null, 'push', '-q', tally, 'main')
const notes = 'knotless://alice.test/notes'
knotless('alice', 'repo', 'create', notes, '--description', 'Scratch notes 📝')
const n = join(work, 'alice/notes')
mkdirSync(n, { recursive: true })
git('alice', n, null, 'init', '-q', '-b', 'main')
commit('alice', n, '2026-09-20T19:30:00Z', 'Begin', { 'notes/README.md': 'README.md' })
git('alice', n, null, 'push', '-q', notes, 'main')
// A branch, then main moved past it: the feed lists only what main
// brought that the branch had not.
git('alice', n, null, 'checkout', '-q', '-b', 'reading')
commit('alice', n, '2026-09-22T08:15:00Z', 'Start a reading list', {
'reading/reading.md': 'reading.md',
})
git('alice', n, null, 'push', '-q', notes, 'reading')
git('alice', n, null, 'checkout', '-q', 'main')
git('alice', n, null, 'merge', '-q', '--ff-only', 'reading')
commit('alice', n, '2026-09-23T21:40:00Z', 'Add Working in Public', {
'reading/more.md': 'reading.md',
})
knotless('alice', 'repo', 'create', 'knotless://alice.test/scratch')
git('alice', n, null, 'push', '-q', notes, 'main')
const fork = knotless('bob', 'repo', 'fork', tally)
// A star and a follow, for the counts the pages show.
knotless('bob', 'repo', 'star', tally)
knotless('bob', 'account', 'follow', 'alice.test')
const b = join(work, 'bob/tally')
git('bob', work, null, 'clone', '-q', fork, b)
git('bob', b, null, 'checkout', '-q', '-b', 'empty-input')
commit('bob', b, '2026-10-09T09:31:00Z', 'Count no lines in empty input', {
'fix/parse.rs': 'src/parse.rs',
})
git('bob', b, null, 'push', '-q', 'origin', 'empty-input')
git('bob', b, null, 'checkout', '-q', '-b', 'json', 'main')
commit('bob', b, '2026-10-10T16:12:00Z', 'Print counts as JSON with --json', {
'json/main.rs': 'src/main.rs',
})
git('bob', b, null, 'push', '-q', 'origin', 'json')
// An issue the pull names to close, which its merge closes.
const empty = knotless('bob', 'issue', 'create', tally, 'Counts one line for an empty file')
const [, , issuer, , issueKey] = empty.split('/')
const pull = knotless(
'bob',
'pull',
'create',
tally,
'main',
fork,
'empty-input',
'Count no lines in empty input',
'-m',
`\`tally\` printed one line for an empty file. \`lines\` now yields nothing when the text is empty, and a test covers it.\n\nFixes [the empty file's count](https://knotless.dev/alice.test/tally/issue/${issuer}/${issueKey}).`,
)
knotless(
'alice',
'comment',
'add',
tally,
pull,
'Could `lines` check for emptiness once, rather than in the filter?',
'--changes',
)
knotless(
'bob',
'comment',
'add',
tally,
pull,
'It only runs per line, so the cost is one comparison each. Happy to change it if you prefer.',
)
knotless('alice', 'comment', 'add', tally, pull, 'Fair enough. Thanks!', '--approve')
// Merging and closing read the item from the index, which follows the
// PDS a moment behind.
await until(
'the pull',
async () =>
(await page('/alice.test/tally/pulls')).includes('Count no lines in empty input') &&
(await page('/alice.test/tally/issues')).includes('Counts one line'),
)
// Squashed into main, the merge's commit links back to the pull: bob
// wrote it, and alice committed it later that day.
knotless('alice', 'branch', 'protect', tally, 'main')
const [merged] = run('alice', join(bin, 'knotless'), ['pull', 'merge', tally, pull], {
when: '2026-10-09T15:00:00Z',
}).split('\n')
const json = knotless(
'bob',
'pull',
'create',
tally,
'main',
fork,
'json',
'Print counts as JSON with --json',
'-m',
'For scripts. The object has `lines`, `words`, `bytes` and `name`.',
)
const utf8 = knotless(
'bob',
'issue',
'create',
tally,
'Fails on files that are not UTF-8',
'-m',
"Running `tally image.png` panics:\n\n```\nthread 'main' panicked at src/main.rs:8:56:\nstream did not contain valid UTF-8\n```\n\nwc counts bytes for any file.",
)
const confirmed = knotless(
'alice',
'comment',
'add',
tally,
utf8,
'Confirmed. Reading bytes would fix it.',
)
// Said again, better: the page says it was edited.
knotless(
'alice',
'comment',
'edit',
tally,
confirmed,
'-m',
'Confirmed. Reading bytes and counting words lossily would fix it.',
)
// Labelled: one colored and described, one exclusive in its scope.
knotless(
'alice',
'label',
'define',
tally,
'kind/bug',
'--color',
'#d73a4a',
'--description',
'Something is broken',
)
knotless(
'alice',
'label',
'define',
tally,
'priority/high',
'--exclusive',
'--color',
'#fbca04',
)
knotless('alice', 'label', 'define', tally, 'priority/low', '--exclusive')
knotless('alice', 'issue', 'label', tally, utf8, 'kind/bug', 'priority/high')
const typo = knotless('bob', 'issue', 'create', tally, 'Typo in the README ✏️')
await until('the issue', async () =>
(await page('/alice.test/tally/issues')).includes('Typo in the README'),
)
knotless('alice', 'issue', 'close', tally, typo)
// Pulls into the fork, for its merge queue: one landed, which merging
// fast-forwards, and four to queue.
const change = (branch, when, message, path, text) => {
git('bob', b, null, 'checkout', '-q', '-B', branch, 'main')
mkdirSync(dirname(join(b, path)), { recursive: true })
writeFileSync(join(b, path), text)
git('bob', b, when, 'add', '-A')
git('bob', b, when, 'commit', '-q', '-m', message)
git('bob', b, null, 'push', '-q', 'origin', branch)
return knotless('bob', 'pull', 'create', fork, 'main', fork, branch, message)
}
const usage = change(
'usage',
'2026-10-11T08:00:00Z',
'Show usage on --help',
'docs/usage.md',
'tally [--json] [FILE]...\n',
)
await until('the usage pull', async () =>
(await page('/bob.test/tally/pulls')).includes('Show usage on --help'),
)
const [landed] = knotless('bob', 'pull', 'merge', fork, usage).split('\n')
git('bob', b, null, 'fetch', '-q', 'origin')
git('bob', b, null, 'checkout', '-q', '-B', 'main', 'origin/main')
const queued = [
change(
'bytes',
'2026-10-11T09:00:00Z',
'Count bytes in files that are not UTF-8',
'docs/bytes.md',
'Bytes are counted as they are.\n',
),
change(
'tabs',
'2026-10-11T10:00:00Z',
'Line the columns up with tabs',
'docs/tabs.md',
'Columns are tab separated.\n',
),
change(
'words',
'2026-10-11T11:00:00Z',
'Count words in any script',
'docs/words.md',
'Words are split on Unicode white space.\n',
),
]
const ejected = change(
'totals',
'2026-10-11T12:00:00Z',
'Print a total for many files',
'docs/totals.md',
'A total follows the files.\n',
)
// Each names itself the runner of its tally, whose checks are shown.
knotless('alice', 'runner', 'add', tally, 'alice.test')
knotless('bob', 'runner', 'add', fork, 'bob.test')
// The fork's main takes pulls through its merge queue.
knotless('bob', 'branch', 'protect', fork, 'main', '--merges', '--queue')
// Releases of alice's tally: the command twice, a pre-release of it
// after, and a server's image, as an OCI layout holds one. Bob's fork
// publishes none.
const shipped = git('alice', a, null, 'ls-remote', tally, 'refs/heads/main').split('\t')[0]
const dist = join(work, 'dist')
const built = (version) =>
['aarch64-apple-darwin', 'x86_64-unknown-linux-musl'].map((target) => {
const file = join(dist, version, `tally-${version}-${target}.tar.gz`)
mkdirSync(dirname(file), { recursive: true })
writeFileSync(file, `tally ${version} for ${target}\n`.repeat(4096))
return file
})
// Each a little after the last, as one write's records are ordered
// by their keys, not their times.
const publish = async (pkg, version, files, notes, ...more) => {
knotless(
'alice',
'release',
'publish',
tally,
pkg,
version,
...files,
'--commit',
shipped,
'--notes',
notes,
...more,
)
await new Promise((r) => setTimeout(r, 100))
}
await publish(
'tally',
'0.1.0',
built('0.1.0'),
'The first release: `tally` counts lines, words and bytes, as `wc` does.',
)
await publish(
'tally',
'0.1.1',
built('0.1.1'),
'### Fixed\n\n- An empty file counts no lines.\n- Input that is not UTF-8 is counted as bytes rather than failing.',
)
const layout = join(dist, 'oci')
mkdirSync(join(layout, 'blobs/sha256'), { recursive: true })
const blob = (bytes, mediaType) => {
const hex = createHash('sha256').update(bytes).digest('hex')
writeFileSync(join(layout, 'blobs/sha256', hex), bytes)
return { mediaType, digest: `sha256:${hex}`, size: bytes.length }
}
const layer = blob(
Buffer.from('tally-server layer\n'.repeat(2048)),
'application/vnd.oci.image.layer.v1.tar+gzip',
)
const config = blob(
Buffer.from(
JSON.stringify({
architecture: 'amd64',
os: 'linux',
rootfs: { type: 'layers', diff_ids: [layer.digest] },
}),
),
'application/vnd.oci.image.config.v1+json',
)
const manifest = blob(
Buffer.from(
JSON.stringify({
schemaVersion: 2,
mediaType: 'application/vnd.oci.image.manifest.v1+json',
config,
layers: [layer],
}),
),
'application/vnd.oci.image.manifest.v1+json',
)
writeFileSync(
join(layout, 'index.json'),
JSON.stringify({
schemaVersion: 2,
mediaType: 'application/vnd.oci.image.index.v1+json',
manifests: [manifest],
}),
)
writeFileSync(join(layout, 'oci-layout'), '{"imageLayoutVersion":"1.0.0"}')
await publish(
'tally-server',
'0.1.0',
[],
'Serves counts over HTTP: `POST /count` with the text, and the counts come back as JSON.',
'--oci',
layout,
)
await publish(
'tally',
'0.2.0-rc.1',
built('0.2.0-rc.1'),
'A first look at 0.2.0: try it before it is the latest.\n\n- `tally --json` prints the counts as JSON.',
)
// A workflow run only when asked, which the checks page offers to run:
// the fork's main's last commit, after the branches made from it, so no
// other's hash moves.
git('bob', b, null, 'checkout', '-q', 'main')
mkdirSync(join(b, '.knotless/workflows'), { recursive: true })
writeFileSync(
join(b, '.knotless/workflows/release.yml'),
'# Builds the release binaries for a tag and attaches them to it.\non: {asked: {refs: [refs/tags/*]}}\nsteps:\n - name: build\n run: cargo build --release\n',
)
git('bob', b, '2026-10-20T12:00:00Z', 'add', '-A')
git('bob', b, '2026-10-20T12:00:00Z', 'commit', '-q', '-m', 'Release from tags when asked')
git('bob', b, null, 'push', '-q', 'origin', 'main')
// Every record a fixed createdAt, keeping their order.
const records = []
for (const who of ['alice', 'bob']) {
const session = await fetch(`${net.pds}/xrpc/com.atproto.server.createSession`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ identifier: account[who].handle, password: account[who].password }),
}).then((r) => r.json())
const did = account[who].did
if (who === 'alice') {
const res = await fetch(`${net.pds}/xrpc/com.atproto.repo.putRecord`, {
method: 'POST',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${session.accessJwt}`,
},
body: JSON.stringify({
repo: did,
collection: 'app.bsky.actor.profile',
rkey: 'self',
record: {
$type: 'app.bsky.actor.profile',
displayName: people.alice.name,
description: 'Small tools for text. Writes tally.',
},
}),
})
if (!res.ok) throw new Error(`profile: ${await res.text()}`)
}
const { collections } = await fetch(
`${net.pds}/xrpc/com.atproto.repo.describeRepo?repo=${did}`,
).then((r) => r.json())
for (const collection of collections) {
let cursor = ''
do {
const listed = await fetch(
`${net.pds}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=${collection}&limit=100${cursor}`,
).then((r) => r.json())
for (const r of listed.records) {
if (typeof r.value.createdAt === 'string' && collection !== 'dev.knotless.ref')
records.push({ ...r, who, did, collection, jwt: session.accessJwt })
}
cursor = listed.cursor ? `&cursor=${listed.cursor}` : ''
} while (cursor)
}
}
// A new ref's push is its record's first version, which no claim
// records: the index keeps it as a push with nothing before it, under
// the key a claim on the generation before would have had, at the
// time it was made. Each takes its place among the records as that
// claim would, and its stamp is written into the index rather than the
// PDS. The ref records themselves keep the times they were pushed at,
// which no page shows.
const index = new DatabaseSync(join(dir, 'index.db'))
index.exec('PRAGMA busy_timeout = 10000')
const who = Object.fromEntries(Object.entries(account).map(([name, a]) => [a.did, name]))
for (const p of index
.prepare('SELECT uri, did, created_at FROM push WHERE before IS NULL')
.all()) {
records.push({
uri: p.uri,
who: who[p.did],
did: p.did,
collection: 'dev.knotless.claim',
value: { createdAt: p.created_at },
pushed: true,
})
}
// One write's records can be stamped a millisecond apart or in the same
// one, which would order them either way: those made within 50 ms of
// each other are ordered by account, collection and key instead: by
// the account's name, as its DID is new in every world.
const at = (r) => Date.parse(r.value.createdAt)
records.sort((x, y) => at(x) - at(y))
const writes = []
for (const r of records) {
const last = writes.at(-1)
if (last && at(r) - at(last.at(-1)) < 50) last.push(r)
else writes.push([r])
}
const named = (r) => `${r.who} ${r.collection} ${r.uri.split('/').pop()}`
records.splice(
0,
records.length,
...writes.flatMap((w) => w.sort((x, y) => (named(x) < named(y) ? -1 : 1))),
)
const start = Date.parse('2026-10-09T10:00:00Z')
const cids = new Map()
// Strong references to a record whose CID changed are pointed at the new one.
const repoint = (v) => {
if (Array.isArray(v)) return v.map(repoint)
if (v && typeof v === 'object') {
const out = Object.fromEntries(Object.entries(v).map(([k, x]) => [k, repoint(x)]))
if (typeof out.uri === 'string' && typeof out.cid === 'string' && cids.has(out.uri))
out.cid = cids.get(out.uri)
return out
}
return v
}
for (const [i, r] of records.entries()) {
const created = start + i * 37 * 60_000
// One edited says so a little after it was made.
const updated = r.value.updatedAt
? { updatedAt: new Date(created + 12 * 60_000).toISOString() }
: {}
if (r.pushed) {
index
.prepare('UPDATE push SET created_at = ? WHERE uri = ?')
.run(new Date(created).toISOString(), r.uri)
continue
}
const value = repoint({ ...r.value, createdAt: new Date(created).toISOString(), ...updated })
const res = await fetch(`${net.pds}/xrpc/com.atproto.repo.putRecord`, {
method: 'POST',
headers: { 'content-type': 'application/json', authorization: `Bearer ${r.jwt}` },
body: JSON.stringify({
repo: r.did,
collection: r.collection,
rkey: r.uri.split('/').pop(),
record: value,
}),
})
if (!res.ok) throw new Error(`putRecord ${r.uri}: ${await res.text()}`)
cids.set(r.uri, (await res.json()).cid)
}
// An organization, textkit, with bob a member in public and alice
// invited. Written as records, dated as the rest are: joining asks
// the organization's gateway, which this world has none of. Lapsing
// goes by the clock, not the world's now, so alice's invitation runs
// long enough to stay one.
const put = async (who, collection, rkey, record) => {
const session = await fetch(`${net.pds}/xrpc/com.atproto.server.createSession`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ identifier: account[who].handle, password: account[who].password }),
}).then((r) => r.json())
const res = await fetch(`${net.pds}/xrpc/com.atproto.repo.putRecord`, {
method: 'POST',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${session.accessJwt}`,
},
body: JSON.stringify({
repo: account[who].did,
collection,
rkey,
record: { $type: collection, ...record },
}),
})
if (!res.ok) throw new Error(`putRecord ${who} ${collection}: ${await res.text()}`)
}
await put('textkit', 'app.bsky.actor.profile', 'self', {
displayName: 'Textkit',
description: 'Tools for text, made together.',
})
await put('textkit', 'dev.knotless.org', 'self', { createdAt: '2026-10-12T09:00:00.000Z' })
await put('textkit', 'dev.knotless.orgMember', account.bob.did, {
member: account.bob.did,
roles: [{ role: 'admin' }],
invitedAt: '2026-10-12T09:00:00.000Z',
expiresAt: '2026-10-19T09:00:00.000Z',
joinedAt: '2026-10-12T11:00:00.000Z',
})
await put('textkit', 'dev.knotless.orgMember', account.alice.did, {
member: account.alice.did,
invitedAt: '2026-10-20T09:00:00.000Z',
expiresAt: '2099-10-27T09:00:00.000Z',
})
await put('bob', 'dev.knotless.orgAcceptance', account.textkit.did, {
subject: account.textkit.did,
createdAt: '2026-10-12T10:00:00.000Z',
})
// Checks, as a runner reports them: of tally's main, and of both pulls' branches.
const main = git('alice', a, null, 'ls-remote', tally, 'refs/heads/main').split('\t')[0]
const tip = (branch) => git('bob', b, null, 'rev-parse', branch)
const checks = [
['alice', 'alice/tally', 'main', main, 'test', 'success', '2026-10-11T09:00:00Z'],
['alice', 'alice/tally', 'main', main, 'lint', 'success', '2026-10-11T09:00:00Z'],
[
'bob',
'bob/tally',
'empty-input',
tip('empty-input'),
'test',
'success',
'2026-10-09T09:32:00Z',
],
['bob', 'bob/tally', 'json', tip('json'), 'build', 'success', '2026-10-10T16:13:00Z'],
['bob', 'bob/tally', 'json', tip('json'), 'test', 'failure', '2026-10-10T16:13:00Z'],
['bob', 'bob/tally', 'json', tip('json'), 'lint', 'running', '2026-10-10T16:13:00Z'],
// One the change left out, as the runner notes it.
[
'bob',
'bob/tally',
'json',
tip('json'),
'docs',
'success',
'2026-10-10T16:13:00Z',
'skipped: nothing changed in docs/**, assets/**, .knotless/workflows/docs.yml',
],
]
// What each run did, step by step, and the log of the one that failed.
const ran = {
success: [
['build', 'success', 0, 38400],
['test', 'success', 0, 2100],
],
failure: [
['build', 'success', 0, 41200],
['test', 'failure', 101, 3300],
],
running: [],
}
const failed = [
'==> build',
' Compiling tally v0.1.0 (/work)',
' Finished `test` profile [unoptimized + debuginfo] target(s) in 41.17s',
'==> test',
' Running unittests src/lib.rs (target/debug/deps/tally-3f9c2a1b)',
'',
'running 4 tests',
'test tests::counts_bytes ... ok',
'test tests::counts_lines ... ok',
'test tests::counts_words ... ok',
'test tests::prints_json ... FAILED',
'',
'failures:',
'',
'---- tests::prints_json stdout ----',
"thread 'tests::prints_json' panicked at src/lib.rs:41:9:",
'assertion `left == right` failed',
' left: "{\"lines\":2,\"words\":3,\"bytes\":14}"',
' right: "{\"lines\":2,\"words\":3,\"bytes\":14,\"name\":\"-\"}"',
'',
'test result: FAILED. 3 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s',
'',
].join('\n')
// What a failed shots run leaves: a page before and after a change, and
// where they differ, drawn as a band that moved down.
const band = (top) => (x, y) =>
y >= top && y < top + 24 && x >= 24 && x < 216 ? [60, 64, 72] : [244, 245, 247]
const pictures = [
['pull-desktop-light 1 before', band(40)],
['pull-desktop-light 2 after', band(72)],
[
'pull-desktop-light 3 difference',
(x, y) => (band(40)(x, y)[0] !== band(72)(x, y)[0] ? [230, 40, 40] : [250, 250, 250]),
],
]
const rkeys = {}
for (const [who, repo, branch, commit, workflow, status, startedAt, skipped] of checks) {
const [owner, name] = repo.split('/')
const did = account[owner].did
const { key } = await fetch(
`${net.pds}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=dev.knotless.repo&rkey=${name}`,
)
.then((r) => r.json())
.then((r) => r.value)
const ref = `refs/heads/${branch}`
const session = await fetch(`${net.pds}/xrpc/com.atproto.server.createSession`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ identifier: account[who].handle, password: account[who].password }),
}).then((r) => r.json())
const auth = { authorization: `Bearer ${session.accessJwt}` }
const log =
status === 'failure'
? await fetch(`${net.pds}/xrpc/com.atproto.repo.uploadBlob`, {
method: 'POST',
headers: { 'content-type': 'text/plain', ...auth },
body: failed,
})
.then((r) => r.json())
.then((r) => ({ log: r.blob }))
: {}
const outputs =
status === 'failure'
? {
outputs: await Promise.all(
pictures.map(async ([alt, draw]) => {
const image = await fetch(`${net.pds}/xrpc/com.atproto.repo.uploadBlob`, {
method: 'POST',
headers: { 'content-type': 'image/png', ...auth },
body: png(240, 136, draw),
})
.then((r) => r.json())
.then((r) => r.blob)
return { $type: 'dev.knotless.check#image', image, alt }
}),
),
}
: {}
const steps = skipped
? [{ name: 'build', status: 'skipped', durationMs: 0 }]
: ran[status].map(([name, status, exitCode, durationMs]) => ({
name,
status,
exitCode,
durationMs,
}))
const took = skipped ? 0 : steps.reduce((ms, s) => ms + s.durationMs, 0) + 1000
const record = {
$type: 'dev.knotless.check',
repo: { uri: `at://${did}/dev.knotless.repo/${name}`, key },
ref,
commit,
workflow,
status,
startedAt,
...(status === 'running'
? {}
: { finishedAt: new Date(Date.parse(startedAt) + took).toISOString() }),
steps,
...(skipped ? { skipped } : {}),
...log,
...outputs,
}
const res = await fetch(`${net.pds}/xrpc/com.atproto.repo.putRecord`, {
method: 'POST',
headers: { 'content-type': 'application/json', ...auth },
body: JSON.stringify({
repo: account[who].did,
collection: 'dev.knotless.check',
rkey: (rkeys[`${repo} ${workflow} ${status}`] =
`${refHash(did)}.${key}.${refHash(ref)}.${commit}.${workflow}`),
record,
}),
})
if (!res.ok) throw new Error(`check ${workflow}: ${await res.text()}`)
}
// The minutes each runner counts for its owner: alice nearing her
// allowance, bob past his.
for (const [who, minutes, frees] of [
['alice', 1640, 310],
['bob', 2140, 400],
]) {
const record = {
$type: 'dev.knotless.minutes',
owner: account[who].did,
minutes,
allowance: 2000,
freeing: [{ at: freeing, minutes: frees }],
updatedAt: '2026-10-25T17:52:00Z',
}
const session = await fetch(`${net.pds}/xrpc/com.atproto.server.createSession`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ identifier: account[who].handle, password: account[who].password }),
}).then((r) => r.json())
const res = await fetch(`${net.pds}/xrpc/com.atproto.repo.putRecord`, {
method: 'POST',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${session.accessJwt}`,
},
body: JSON.stringify({
repo: account[who].did,
collection: 'dev.knotless.minutes',
rkey: account[who].did,
record,
}),
})
if (!res.ok) throw new Error(`minutes ${who}: ${await res.text()}`)
}
// alice, the tally's runner, publishes a key its secrets are sealed to.
// None is set: this server has no gateway for `knotless secret` to sign
// for, so the settings draw the box empty, with its form.
{
const session = await fetch(`${net.pds}/xrpc/com.atproto.server.createSession`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
identifier: account.alice.handle,
password: account.alice.password,
}),
}).then((r) => r.json())
const record = {
$type: 'dev.knotless.runnerKey',
key: 'did:key:zDnaepwQjCS7ayRrLp2j3oFd8kfJ97mehqYTGFBNA3QqSsvNV',
createdAt: '2026-10-25T17:52:00Z',
}
const res = await fetch(`${net.pds}/xrpc/com.atproto.repo.putRecord`, {
method: 'POST',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${session.accessJwt}`,
},
body: JSON.stringify({
repo: account.alice.did,
collection: 'dev.knotless.runnerKey',
rkey: 'self',
record,
}),
})
if (!res.ok) throw new Error(`runner key: ${await res.text()}`)
}
// The fork's merge queue, as bob, its owner and runner, keeps it: three
// pulls queued, the first two in a batch being checked and the third
// running its pre-flight; one ejected by the checks of the batch it was
// in; and the batch that landed.
{
const did = account.bob.did
const { key } = await fetch(
`${net.pds}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=dev.knotless.repo&rkey=tally`,
)
.then((r) => r.json())
.then((r) => r.value)
const repo = { uri: `at://${did}/dev.knotless.repo/tally`, key }
const session = await fetch(`${net.pds}/xrpc/com.atproto.server.createSession`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ identifier: account.bob.handle, password: account.bob.password }),
}).then((r) => r.json())
const write = async (collection, record, rkey) => {
const res = await fetch(
`${net.pds}/xrpc/com.atproto.repo.${rkey ? 'putRecord' : 'createRecord'}`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${session.accessJwt}`,
},
body: JSON.stringify({
repo: did,
collection,
...(rkey ? { rkey } : {}),
record: { $type: collection, ...record },
}),
},
)
if (!res.ok) throw new Error(`${collection}: ${await res.text()}`)
return (await res.json()).uri
}
const base = git('bob', b, null, 'rev-parse', `${landed}^`)
const batch = (pulls, commit) => pulls.map((pull) => ({ pull, commit }))
const runs = [
[
'landed',
[usage],
landed,
base,
[
['build', 'success'],
['test', 'success'],
],
'2026-10-19T09:00:00Z',
],
[
'ejected',
[ejected],
tip('totals'),
landed,
[
['build', 'success'],
['test', 'failure'],
],
'2026-10-19T15:00:00Z',
],
[
'checking',
queued.slice(0, 2),
tip('tabs'),
landed,
[
['build', 'success'],
['test', 'running'],
],
'2026-10-20T10:00:00Z',
],
]
const failed = []
for (const [name, pulls, commit, baseCommit, ran, startedAt] of runs) {
for (const [workflow, status] of ran) {
const rkey = `${refHash(did)}.${key}.${refHash('queue refs/heads/main')}.${commit}.${baseCommit}.${workflow}`
const finished =
status === 'running'
? {}
: { finishedAt: new Date(Date.parse(startedAt) + 95_000).toISOString() }
const uri = await write(
'dev.knotless.check',
{
repo,
ref: 'refs/heads/main',
commit,
baseCommit,
batch: batch(pulls, commit),
workflow,
status,
startedAt,
...finished,
steps: [],
},
rkey,
)
if (name === 'ejected' && status === 'failure')
failed.push({ workflow, status, check: uri })
}
}
// Their pre-flights: the first two's passed, in six minutes each, and
// the third's has run since a little before now.
const flights = [
[queued[0], 'bytes', 'success', '2026-10-20T09:40:00Z'],
[queued[1], 'tabs', 'success', '2026-10-20T09:42:00Z'],
[queued[2], 'words', 'running', '2026-10-20T13:57:00Z'],
]
for (const [pull, branch, status, startedAt] of flights) {
const commit = tip(branch)
const rkey = `${refHash(did)}.${key}.${refHash(pull)}.${commit}.${landed}.test`
const finished =
status === 'running'
? {}
: { finishedAt: new Date(Date.parse(startedAt) + 360_000).toISOString() }
await write(
'dev.knotless.check',
{
repo,
ref: `refs/heads/${branch}`,
commit,
baseCommit: landed,
pull,
queue: 'first',
workflow: 'test',
status,
startedAt,
...finished,
steps: [],
},
rkey,
)
}
for (const [i, pull] of [...queued, ejected].entries()) {
await write('dev.knotless.queue', {
repo,
subject: pull,
state: 'queued',
createdAt: `2026-10-1${5 + i}T09:30:00.000Z`,
})
}
await write('dev.knotless.queue', {
repo,
subject: ejected,
state: 'ejected',
commit: tip('totals'),
checks: failed,
reason: `its batch failed its checks at ${tip('totals').slice(0, 12)}, on main at ${landed.slice(0, 12)}: test (failure)`,
createdAt: '2026-10-19T15:02:00.000Z',
})
}
const item = (kind, uri) => {
const [, , did, , rkey] = uri.split('/')
return `/alice.test/tally/${kind}/${did}/${rkey}`
}
const views = {
home: '/',
search: '/search?q=t',
code: '/search?type=code&q=count',
'code-repo': '/search?type=code&q=count+repo:alice.test/tally',
start: '/start',
terms: '/terms',
privacy: '/privacy',
guidelines: '/guidelines',
stats: '/stats',
profile: '/alice.test',
'profile-fork': '/bob.test',
'profile-repos': '/alice.test?tab=repos',
'profile-activity': '/alice.test?tab=activity',
org: '/textkit.test',
'org-members': '/textkit.test?tab=members',
repository: '/alice.test/tally',
'repository-watch': '/alice.test/tally',
directory: '/alice.test/tally/tree/main/src',
file: '/alice.test/tally/blob/main/src/lib.rs',
blame: '/alice.test/tally/blame/main/src/lib.rs',
markdown: '/alice.test/tally/blob/main/README.md',
'markdown-plain': '/alice.test/tally/blob/main/README.md?plain=1',
diagram: '/alice.test/tally/blob/main/docs/DESIGN.md',
image: '/alice.test/tally/blob/main/assets/logo.svg',
commits: '/alice.test/tally/commits/main',
commit: `/alice.test/tally/commit/${parse}`,
'commit-merged': `/alice.test/tally/commit/${merged}`,
pulls: '/alice.test/tally/pulls',
'pulls-closed': '/alice.test/tally/pulls?state=closed',
pull: item('pull', pull),
'pull-open': item('pull', json),
'pull-files': `${item('pull', pull)}/files`,
issues: '/alice.test/tally/issues',
'issues-closed': '/alice.test/tally/issues?state=closed',
issue: item('issue', utf8),
'issue-subscribe': item('issue', utf8),
'issue-closed': item('issue', typo),
'issue-fixed': item('issue', empty),
fork: '/bob.test/tally',
empty: '/alice.test/scratch',
missing: '/alice.test/tally/blob/main/nowhere',
check: `/bob.test/tally/check/${account.bob.did}/${rkeys['bob/tally test failure']}`,
queue: '/bob.test/tally/queue',
checks: '/bob.test/tally/checks',
'pull-queued': item('pull', queued[0]).replace('/alice.test/', '/bob.test/'),
'pull-ejected': item('pull', ejected).replace('/alice.test/', '/bob.test/'),
settings: '/alice.test/tally/settings',
'settings-branches': '/alice.test/tally/settings/branches',
'settings-labels': '/alice.test/tally/settings/labels',
'settings-access': '/alice.test/tally/settings/access',
'settings-actions': '/alice.test/tally/settings/actions',
releases: '/alice.test/tally/releases',
'releases-image': '/alice.test/tally/releases?package=tally-server',
'releases-empty': '/bob.test/tally/releases',
notifications: '/notifications',
}
// The pages whose social cards are shot, by the names of the shots.
const cards = {
home: '/',
repository: '/alice.test/tally',
notes: '/alice.test/notes',
pull: views.pull,
'pull-open': views['pull-open'],
issue: views.issue,
'issue-closed': item('issue', typo),
}
// The index has caught up when no page shows today's date, every
// record now being dated before it, and the merge shows. The day
// runner minutes free up on is a date the pages show too, and is left
// out, as on that day it is today's.
const day = (t) =>
new Date(t).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
timeZone: 'UTC',
})
const today = day(Date.now())
// What still holds the wait back, said if it times out.
let missing = []
const shown = [
'Merged as',
'Closed by',
'2 Closed',
'Forked from',
'1 Closed',
'2/2',
'prints_json',
'In a batch of 2 being checked',
'Landed',
'Runs only when asked',
'tally-server',
'Pre-release',
]
// Each tally's default branch, which its code is found at once indexed.
const tips = () =>
['alice.test', 'bob.test'].map(
(o) =>
run('alice', 'git', ['ls-remote', `knotless://${o}/tally`, 'refs/heads/main']).split(
/\s/,
)[0],
)
const indexed = (html) => tips().every((tip) => html.includes(`/blob/${tip}/`))
const caughtUp = async () => {
const names = Object.keys(views)
const pages = await Promise.all(Object.values(views).map(page))
const all = pages.join('')
const dated = names.filter((_, i) => pages[i].replaceAll(day(freeing), '').includes(today))
missing = [
...dated.map((v) => {
const at = pages[names.indexOf(v)].replaceAll(day(freeing), '').indexOf(today)
return `today's date on ${v}: ${JSON.stringify(pages[names.indexOf(v)].slice(Math.max(0, at - 160), at + 40))}`
}),
...shown.filter((t) => !all.includes(t)),
...(all.includes('Some checks failed') || all.includes('Some checks were not successful')
? []
: ['Some checks failed']),
...(/Ejected <time/.test(all) ? [] : ['Ejected <time']),
// Code, once Zoekt has indexed both tallies at their tips.
...(!zoekt || indexed(pages[names.indexOf('code')])
? []
: ["code results at each tally's tip"]),
...((await page('/bob.test')).includes('textkit.test')
? []
: ['textkit.test on /bob.test']),
...((await page(views['org-members'])).includes('bob.test')
? []
: ['bob.test on org-members']),
]
return missing.length === 0
}
try {
await until('the index', caughtUp)
} catch (e) {
throw new Error(`${e.message}, still missing: ${missing.join('; ')}`)
}
return { net, dir, views, cards, pds, server: children[1], stop }
} catch (e) {
stop()
throw e
}
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const world = await create()
world.server.kill()
mkdirSync(dirname(saved), { recursive: true })
writeFileSync(
saved,
JSON.stringify(
{ net: world.net, dir: world.dir, views: world.views, cards: world.cards },
null,
2,
),
)
console.log(`seeded; kept up for \`just shots\` until ctrl-c (${saved})`)
const end = () => {
rmSync(saved, { force: true })
world.stop()
process.exit(0)
}
process.on('SIGINT', end)
process.on('SIGTERM', end)
process.stdin.on('end', end)
process.stdin.resume()
}
/// A PNG of `width` by `height`, each pixel's [r, g, b] from `draw(x, y)`.
function png(width, height, draw) {
const chunk = (type, data) => {
const head = Buffer.alloc(8)
head.writeUInt32BE(data.length)
head.write(type, 4, 'latin1')
const sum = Buffer.alloc(4)
sum.writeUInt32BE(crc32(Buffer.concat([head.subarray(4), data])))
return Buffer.concat([head, data, sum])
}
const header = Buffer.alloc(13)
header.writeUInt32BE(width)
header.writeUInt32BE(height, 4)
header.set([8, 2, 0, 0, 0], 8)
const rows = Buffer.alloc((width * 3 + 1) * height)
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) rows.set(draw(x, y), y * (width * 3 + 1) + 1 + x * 3)
}
return Buffer.concat([
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
chunk('IHDR', header),
chunk('IDAT', deflateSync(rows)),
chunk('IEND', Buffer.alloc(0)),
])
}