| … | … | @@ -95,14 +95,17 @@ npm run test:bare # the appview and everything that opens a corest |
| 95 | 95 | npm run test:ws # the bare-ws label firehose |
| 96 | 96 | ``` |
| 97 | 97 | |
| 98 | 74 test files across the three scripts. `npm test` runs under node rather than `brittle-bare` | |
| 99 | because those files are the runtime-agnostic layer and say so, which is a divergence from the rest of | |
| 100 | the 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` | |
| 105 | covers the same vectors against the real stripper, which this one never did. | |
| 98 | 73 files in `test/`: 49 under node, 9 under bare, 1 for the firehose, 2 shared helpers — and **12 | |
| 99 | test files that no script runs**, four of them `appview-*` that look like they belong in `test:bare`. | |
| 100 | ||
| 101 | The split follows `hyper-fleeky/BARE-RUNTIME-POLICY.md` — the family convention is a node script | |
| 102 | beside 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 | |
| 104 | say so in their own headers; anything that opens a corestore or a swarm is in `test:bare`. | |
| 105 | ||
| 106 | This 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 | |
| 108 | for the pure modules, never the runtime. | |
| 106 | 109 | |
| 107 | 110 | Deployed as a pgh app (`pgh.type: daemon`, `runtime: bare`); the [atproto-bridge](../atproto-bridge) |
| 108 | 111 | membrane proxies the lens-export XRPC to it (`FAVELA_WEB_UPSTREAM`). |
| … | … | @@ -55,7 +55,7 @@ |
| 55 | 55 | "spike:seam": "node spike/atcute-seam.mjs", |
| 56 | 56 | "spike:p2p": "node spike/p2p-read.mjs", |
| 57 | 57 | "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", | |
| 59 | 59 | "test:ws": "bare test/subscribe-labels-ws.test.mjs", |
| 60 | 60 | "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", |
| 61 | 61 | "spike:pack": "bare spike/mobile-pack-probe.mjs", |
| … | … | @@ -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. | |
| 4 | import fs from 'node:fs' | |
| 5 | ||
| 6 | let pass = 0 | |
| 7 | const ok = (c, m) => { if (!c) { console.error('FAIL:', m); process.exit(1) } pass++ } | |
| 8 | ||
| 9 | const doc = JSON.parse(fs.readFileSync(new URL('../spec/vectors/markdown-strip.json', import.meta.url), 'utf8')) | |
| 10 | const V = doc.vectors | |
| 11 | ok(Array.isArray(V) && V.length >= 30, `at least 30 vectors (${V.length})`) | |
| 12 | ||
| 13 | const names = new Set() | |
| 14 | for (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. | |
| 49 | for (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. | |
| 58 | const mb = V.find((x) => x.name === 'emoji-before-facet') | |
| 59 | ok(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 | ||
| 62 | console.log(`\nSTRIP VECTORS OK: ${pass} assertions across ${V.length} vectors`) |