mirror of
https://github.com/PR0M3TH3AN/bitgate.git
synced 2026-10-27 08:31:00 +00:00
Renames the project and makes it as easy to adopt as BitLogin, which was the stated bar and which it did not previously meet. Rename (ADR 0005): @nostr-governance/* -> @bitgate/*, governance-* elements -> bitgate-*, createGovernanceRuntime -> createBitGate. "Governance" reads as DAO voting to this audience and named a category rather than a product; BitGate names the benefit and joins the bit- family that BitVid and BitRoad compose with. Domain types keep their names — the product is BitGate, the subject matter is still governance. The migration plan is left as written; this ADR records the change rather than rewriting history. Drop-in distribution (ADR 0006): adopting BitGate meant supplying a transport, authoring a policy, and wiring every element in JavaScript. Now: - createRelayTransport(urls) — WebSocket transport with reconnection, backoff, subscription replay, and cross-relay dedupe - policy presets (social, commerce, admin-only) addressable by name; the engine still carries no thresholds of its own - <bitgate-provider> — configuration in markup, with descendants finding the runtime through a bubbling context event rather than DOM walking, which breaks across shadow boundaries - npm run build:widget — a self-contained dist/bitgate.js That last one came from running it in a browser rather than from a test. Bare specifiers resolve in Node and vitest but not in a browser, so the widget source worked everywhere except a plain static page — the one place it exists to serve. The bundle is not committed: consumers build from reviewed source. Also found by running it: the provider's ready event fires during element upgrade, before a page that defines elements after parsing can attach a listener, so it now exposes `ready` as a promise too. Four bugs fixed along the way, three found by new tests and one by the browser: admin state stayed silent when a roster change altered capabilities but no denials; the relay transport sent every subscription twice on connect; the commerce preset computed a meaningless visibility verdict at checkout and could hide a listing from its own seller; <bitgate-status> threw on an unusable decision. 759 tests; lint, typecheck, and both builds clean.
85 lines
3.2 KiB
JavaScript
85 lines
3.2 KiB
JavaScript
// Conformance suite for the characterization corpus.
|
|
//
|
|
// Every fixture under fixtures/bitvid/cases is evaluated against the reference
|
|
// application profile. A change to a threshold, a precedence rule, or an effect
|
|
// ladder must show up here as a failing case.
|
|
|
|
import { readdirSync, readFileSync } from "node:fs";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { runConformanceCase } from "@bitgate/testing/conformance";
|
|
import { REFERENCE_POLICY } from "@bitgate/bitvid-compat/profile";
|
|
|
|
const ROOT = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
const CASES_DIR = join(ROOT, "fixtures", "bitvid", "cases");
|
|
const CASE_INDEX = join(ROOT, "fixtures", "bitvid", "characterization-cases.json");
|
|
|
|
const caseFiles = readdirSync(CASES_DIR).filter((name) => name.endsWith(".json")).sort();
|
|
const fixtures = caseFiles.map((name) => JSON.parse(readFileSync(join(CASES_DIR, name), "utf8")));
|
|
|
|
describe("characterization corpus", () => {
|
|
it("has fixtures on disk", () => {
|
|
expect(fixtures.length).toBeGreaterThanOrEqual(15);
|
|
});
|
|
|
|
it("index matches the files on disk", () => {
|
|
const index = JSON.parse(readFileSync(CASE_INDEX, "utf8"));
|
|
expect([...index].sort()).toEqual(fixtures.map((fixture) => fixture.name).sort());
|
|
});
|
|
|
|
it("covers every behavior the migration plan requires", () => {
|
|
const names = new Set(fixtures.map((fixture) => fixture.name));
|
|
const required = [
|
|
"trusted-report-threshold-reached",
|
|
"duplicate-reporter-dedupe",
|
|
"blocked-reporter-ignored",
|
|
"trusted-mute-below-threshold",
|
|
"trusted-mute-at-threshold",
|
|
"expired-trusted-mute",
|
|
"personal-block-precedence",
|
|
"admin-user-deny",
|
|
"admin-event-deny",
|
|
"community-blacklist-merge",
|
|
"moderator-trust-seed",
|
|
"viewer-override-softens-hide",
|
|
"author-override",
|
|
"home-hide-bypass",
|
|
"cache-fallback-effective",
|
|
];
|
|
for (const name of required) {
|
|
expect(names, `missing required characterization case: ${name}`).toContain(name);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe.each(fixtures.map((fixture) => [fixture.name, fixture]))("%s", (_name, fixture) => {
|
|
it(fixture.description, () => {
|
|
const { passed, mismatches, decision } = runConformanceCase(fixture, REFERENCE_POLICY);
|
|
expect(passed, `${fixture.behavior}\n ${mismatches.join("\n ")}`).toBe(true);
|
|
expect(decision.policyProfile).toBe(fixture.profile);
|
|
expect(decision.policyVersion).toBe(REFERENCE_POLICY.version);
|
|
expect(decision.snapshotFingerprint).toMatch(/^[0-9a-f]{16}$/);
|
|
});
|
|
});
|
|
|
|
describe("evaluation determinism", () => {
|
|
it("produces identical decisions for identical snapshots", () => {
|
|
for (const fixture of fixtures) {
|
|
const first = runConformanceCase(fixture, REFERENCE_POLICY).decision;
|
|
const second = runConformanceCase(fixture, REFERENCE_POLICY).decision;
|
|
expect(second).toEqual(first);
|
|
expect(second.snapshotFingerprint).toBe(first.snapshotFingerprint);
|
|
}
|
|
});
|
|
|
|
it("does not mutate fixture inputs", () => {
|
|
for (const fixture of fixtures) {
|
|
const before = JSON.stringify(fixture);
|
|
runConformanceCase(fixture, REFERENCE_POLICY);
|
|
expect(JSON.stringify(fixture)).toBe(before);
|
|
}
|
|
});
|
|
});
|