OverviewFilesCommitsIssuesDocsReleasesPeers

favela-web commits / 9f8fb28

test: drop strip-vectors, whose subject moved to favela-markdown on 09-02
`npm test` had aborted on its first file for 18 days. test/strip-vectors.test.mjs read spec/vectors/markdown-strip.json; that file left in 71f2724 (see spec/MOVED-record-specs.md) and the test did not go with it. Removed rather than repointed. favela-markdown's test/strip.test.js runs the same vectors against the real stripper — "every vector reproduces byte-for-byte", plus the facet invariants — while this file only ever checked the vectors were well-formed; its own header said "No stripper exists yet". Repointing it at the sibling would also re-create the coupling the MOVED note gives as the reason for the move: a contract owned by one of its three consumers is a contract the other two cannot depend on. Verified all three scripts after the change: npm test exit 0 49 files npm run test:bare exit 0 9 files npm run test:ws exit 0 1 file So node/bare parity holds, which BARE-RUNTIME-POLICY.md makes the actual gate. Also corrects this README, which I got wrong in 2e14256. It called the node script "a divergence from the rest of the fleet". It is not: the policy prescribes exactly a node script beside a bare one, "so bare-only breakage cannot ship invisibly", and names test:bare as the gate. And favela-web is unambiguously a Bare app — pgh.runtime is bare, the entrypoint imports bare-http1/bare-fs/bare-net/bare-ws, and pgh runs it under its own bare in production. The README now says that outright, since the previous wording invited exactly the opposite reading. Counted the files rather than estimating: 73 in test/, of which 49 + 9 + 1 are scripted, 2 are shared helpers, and 12 test files are in no script at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9f8fb287cde94b19c41b4789b3321e5b8e1dc646
by plankatron <fleeky@gmail.com> · 2026-09-20T20:56:50.000Z

Changed files (3) +12 -71

README.md +11 -8
……@@ -95,14 +95,17 @@ npm run test:bare # the appview and everything that opens a corest
9595npm run test:ws # the bare-ws label firehose
9696```
9797
9874 test files across the three scripts. `npm test` runs under node rather than `brittle-bare`
99because those files are the runtime-agnostic layer and say so, which is a divergence from the rest of
100the fleet; anything touching a store or a swarm is in `test:bare`.
101
102`npm test` currently fails at `test/strip-vectors.test.mjs`: it reads
103`spec/vectors/markdown-strip.json`, and that file moved to `favela-markdown` on 2026-09-02 (see
104`spec/MOVED-record-specs.md`) without the test moving with it. favela-markdown's `test/strip.test.js`
105covers the same vectors against the real stripper, which this one never did.
9873 files in `test/`: 49 under node, 9 under bare, 1 for the firehose, 2 shared helpers — and **12
99test files that no script runs**, four of them `appview-*` that look like they belong in `test:bare`.
100
101The split follows `hyper-fleeky/BARE-RUNTIME-POLICY.md` — the family convention is a node script
102beside a bare one, precisely so bare-only breakage cannot ship invisibly, and **the gate is
103`test:bare` being green or at node/bare parity**. The node files are the runtime-agnostic layer and
104say so in their own headers; anything that opens a corestore or a swarm is in `test:bare`.
105
106This is a Bare app: `pgh.runtime` is `bare`, the entrypoint imports `bare-http1`, `bare-fs`,
107`bare-net` and `bare-ws`, and in production pgh runs it under its own bare. Node is the test runner
108for the pure modules, never the runtime.
106109
107110Deployed as a pgh app (`pgh.type: daemon`, `runtime: bare`); the [atproto-bridge](../atproto-bridge)
108111membrane proxies the lens-export XRPC to it (`FAVELA_WEB_UPSTREAM`).
package.json +1 -1
……@@ -55,7 +55,7 @@
5555 "spike:seam": "node spike/atcute-seam.mjs",
5656 "spike:p2p": "node spike/p2p-read.mjs",
5757 "web": "bare bin/favela-web.js",
58 "test": "node test/render.test.mjs && node test/static-assets.test.mjs && node test/resize-plan.test.mjs && node test/notifications.test.mjs && node test/ap-followers.test.mjs && node test/reach.test.mjs && node test/strip-vectors.test.mjs && node test/edit.test.mjs && node test/facets.test.mjs && node test/mutes.test.mjs && node test/search.test.mjs && node test/threadgate.test.mjs && node test/bookmarks.test.mjs && node test/postgate.test.mjs && node test/lists.test.mjs && node test/cli-reachable.test.mjs && node test/strongref.test.mjs && node test/lens-import.test.mjs && node test/lens-thread-extract.test.mjs && node test/fronts-diag.test.mjs && node test/gate-policy.test.mjs && node test/sessions.test.mjs && node test/check.test.mjs && node test/auth.test.mjs && node test/browser-auth-client.test.mjs && node test/communities.test.mjs && node test/feedgen.test.mjs && node test/feed-reactor.test.mjs && node test/labeler-export.test.mjs && node test/label-firehose.test.mjs && node test/pairing.test.mjs && node test/writer.test.mjs && node test/blob-serve.test.mjs && node test/blobs.test.mjs && node test/spacedust.test.mjs && node test/spacedust-failclosed.test.mjs && node test/link-drain.test.mjs && node test/record-verify.test.mjs && node test/constellation.test.mjs && node test/retention.test.mjs && node test/hosted-dids.test.mjs && node test/repair-bytes.test.mjs && node test/repo-sync.test.mjs && node test/blob-refs.test.mjs && node test/inbound.test.mjs && node test/followers.test.mjs && node test/repair-strongrefs.test.mjs && node test/conformance.test.mjs && node test/dns-txt.test.mjs",
58 "test": "node test/render.test.mjs && node test/static-assets.test.mjs && node test/resize-plan.test.mjs && node test/notifications.test.mjs && node test/ap-followers.test.mjs && node test/reach.test.mjs && node test/edit.test.mjs && node test/facets.test.mjs && node test/mutes.test.mjs && node test/search.test.mjs && node test/threadgate.test.mjs && node test/bookmarks.test.mjs && node test/postgate.test.mjs && node test/lists.test.mjs && node test/cli-reachable.test.mjs && node test/strongref.test.mjs && node test/lens-import.test.mjs && node test/lens-thread-extract.test.mjs && node test/fronts-diag.test.mjs && node test/gate-policy.test.mjs && node test/sessions.test.mjs && node test/check.test.mjs && node test/auth.test.mjs && node test/browser-auth-client.test.mjs && node test/communities.test.mjs && node test/feedgen.test.mjs && node test/feed-reactor.test.mjs && node test/labeler-export.test.mjs && node test/label-firehose.test.mjs && node test/pairing.test.mjs && node test/writer.test.mjs && node test/blob-serve.test.mjs && node test/blobs.test.mjs && node test/spacedust.test.mjs && node test/spacedust-failclosed.test.mjs && node test/link-drain.test.mjs && node test/record-verify.test.mjs && node test/constellation.test.mjs && node test/retention.test.mjs && node test/hosted-dids.test.mjs && node test/repair-bytes.test.mjs && node test/repo-sync.test.mjs && node test/blob-refs.test.mjs && node test/inbound.test.mjs && node test/followers.test.mjs && node test/repair-strongrefs.test.mjs && node test/conformance.test.mjs && node test/dns-txt.test.mjs && node test/interaction-codec.test.js",
5959 "test:ws": "bare test/subscribe-labels-ws.test.mjs",
6060 "test:bare": "bare test/appview.test.mjs && bare test/appview-ingest.test.mjs && bare test/appview-dedup.test.mjs && bare test/appview-no-clobber.test.mjs && bare test/appview-blocks.test.mjs && bare test/appview-thread.test.mjs && bare test/appview-author-filter.test.mjs && bare test/appview-collections.test.mjs && bare test/bounded-fetch.test.mjs",
6161 "spike:pack": "bare spike/mobile-pack-probe.mjs",
test/strip-vectors.test.mjs +0 -62
……@@ -1,62 +0,0 @@
1// The strip vectors are the AUTHORITY for spec/markdown-strip.md, so an error in them is worse than
2// an error in an implementation — every implementation would then be "conformant" to the wrong thing.
3// No stripper exists yet; this validates the vectors are internally consistent and well-formed.
4import fs from 'node:fs'
5
6let pass = 0
7const ok = (c, m) => { if (!c) { console.error('FAIL:', m); process.exit(1) } pass++ }
8
9const doc = JSON.parse(fs.readFileSync(new URL('../spec/vectors/markdown-strip.json', import.meta.url), 'utf8'))
10const V = doc.vectors
11ok(Array.isArray(V) && V.length >= 30, `at least 30 vectors (${V.length})`)
12
13const names = new Set()
14for (const v of V) {
15 ok(v.name && !names.has(v.name), `unique name: ${v.name}`)
16 names.add(v.name)
17 ok(typeof v.why === 'string' && v.why.length > 10, `${v.name}: says WHY, citing a rule`)
18 ok(typeof v.source === 'string', `${v.name}: has source`)
19 ok(typeof v.text === 'string', `${v.name}: has expected text`)
20
21 const bytes = Buffer.from(v.text, 'utf8')
22 let prev = -1
23 for (const fa of v.facets) {
24 // The offsets must actually address the text — this is what catches a hand-counted vector.
25 ok(Number.isInteger(fa.byteStart) && Number.isInteger(fa.byteEnd), `${v.name}: integer offsets`)
26 ok(fa.byteStart >= 0 && fa.byteEnd <= bytes.length, `${v.name}: offsets inside the text`)
27 ok(fa.byteEnd > fa.byteStart, `${v.name}: non-empty range (§6.3 drops empties)`)
28 // §6.1 sorted ascending, §6.2 no overlaps
29 ok(fa.byteStart >= prev, `${v.name}: facets sorted by byteStart (§6.1)`)
30 prev = fa.byteEnd
31 ok(['link', 'mention', 'tag'].includes(fa.type), `${v.name}: known facet type`)
32 ok(typeof fa.value === 'string' && fa.value.length, `${v.name}: facet carries a value`)
33 // A facet must land on a UTF-8 CHARACTER boundary — the whole point of the multibyte vectors.
34 const slice = bytes.subarray(fa.byteStart, fa.byteEnd).toString('utf8')
35 ok(!slice.includes('�'), `${v.name}: facet is on a character boundary, not mid-codepoint`)
36 if (fa.type === 'link') ok(/^https?:\/\//.test(fa.value), `${v.name}: §3.2 link uri is http(s)`)
37 if (fa.type === 'mention') ok(fa.value.startsWith('did:'), `${v.name}: §3.4 mention carries a did`)
38 if (fa.type === 'tag') ok(!fa.value.startsWith('#'), `${v.name}: §3.7 tag value drops the hash`)
39 }
40
41 // §5 normalisation must already hold in the EXPECTED text, or the vector contradicts the spec.
42 ok(!v.text.includes('\r'), `${v.name}: §5.1 no CR in expected text`)
43 ok(!/\n{3,}/.test(v.text), `${v.name}: §5.3 no run of 3+ newlines`)
44 ok(v.text === v.text.trim(), `${v.name}: §5.4 no outer whitespace`)
45 ok(!/[ \t]+\n/.test(v.text), `${v.name}: §5.2 no trailing whitespace on a line`)
46}
47
48// The multibyte vectors are the reason this file exists — assert they are actually multibyte.
49for (const n of ['multibyte-before-facet', 'emoji-before-facet', 'multibyte-inside-mention']) {
50 const v = V.find((x) => x.name === n)
51 ok(v, `${n} present`)
52 ok(Buffer.from(v.text, 'utf8').length > v.text.length,
53 `${n}: expected text is genuinely multi-byte (bytes > chars) — else it tests nothing`)
54 ok(v.facets.length > 0, `${n}: carries a facet whose offset a char-based impl would get wrong`)
55}
56
57// And that at least one facet actually starts past a multi-byte char, which is the failure mode.
58const mb = V.find((x) => x.name === 'emoji-before-facet')
59ok(mb.facets[0].byteStart > mb.text.indexOf('here'),
60 'emoji-before-facet: byteStart EXCEEDS the char index — a char-offset impl would land early')
61
62console.log(`\nSTRIP VECTORS OK: ${pass} assertions across ${V.length} vectors`)