Files
bitgate/packages/runtime/test/runtime.test.js
T
thePR0M3TH3AN da2c1cd923 feat(runtime): close the wiring gaps between interfaces and pipelines
Several contracts existed and were tested in isolation while nothing actually
used them. Reported as complete previously; this corrects that.

- storage was injected and never referenced. Adds persist/hydrate, hydration
  before querying relays, and last-known-good state with a stale flag when a
  relay is unreachable, which is what makes the characterized cache-fallback
  behavior real rather than reduction-level only
- signature verification existed as a tested helper the load path never called.
  Now applied through an injected verifier before state becomes authoritative,
  with rejection counts and an enabled/disabled readout in diagnostics
- trusted mute lists had no subscription flow, so the store never populated in
  a real deployment. Subscribes by author, chunked
- community source references decoded but were never resolved. Curator lists
  are now fetched and merged with a source marker, still capability-checked
- root-published policy was announced and discarded. Now applied, accepted only
  from the root, and a malformed document leaves the working policy intact
- adds import/export and capability queries

Two design corrections found while wiring:
- the cache key hashed the entire roster, so it rotated whenever a moderator
  was added and the cache was discarded. Now derived from root identity alone.
- the root administrator is a constructor option: it is deployment config, and
  hydration could not otherwise find its own cache, since the key derives from
  the very fingerprint the cache would have supplied.

634 tests; lint, typecheck, and the declaration build clean.
2026-08-09 19:04:05 -04:00

451 lines
14 KiB
JavaScript

import { describe, expect, it, vi } from "vitest";
import { createPolicyDefinition } from "@nostr-governance/core";
import {
CANONICAL_KIND,
MUTE_LIST_KIND,
REPORT_KIND,
canonicalIdentifier,
encodeContribution,
encodeRoles,
} from "@nostr-governance/nostr";
import { GovernanceRuntime, chunk, createGovernanceRuntime } from "../src/runtime.js";
import { createMemoryTransport, storageKey } from "../src/interfaces.js";
const ROOT = "a1".repeat(32);
const MODERATOR = "b2".repeat(32);
const CREATOR = "d4".repeat(32);
const VIEWER = "e5".repeat(32);
const TRUSTED = "01".repeat(32);
const EVENT_ID = "1b".repeat(32);
const NOW = 1_750_000_000;
const POLICY = createPolicyDefinition({
id: "app",
version: "1.0.0",
defaultProfile: "feed",
profiles: {
feed: {
name: "feed",
administrativeDeny: { visibility: "hide", interaction: "deny" },
viewerBlock: { visibility: "hide", interaction: "deny" },
reports: { default: { restrict: 1 } },
mutes: {},
},
},
});
/**
* @param {Partial<import('@nostr-governance/nostr').NostrEvent>} parts
* @returns {import('@nostr-governance/nostr').NostrEvent}
*/
const event = (parts) => ({
id: "00".repeat(32),
pubkey: MODERATOR,
kind: CANONICAL_KIND,
created_at: NOW,
tags: [],
content: "",
...parts,
});
const rolesEvent = () =>
event({
pubkey: ROOT,
id: "aa".repeat(32),
...encodeRoles({ actors: { [MODERATOR]: ["moderator"] }, protectedActors: [] }, "app"),
});
/** @param {import('@nostr-governance/core').GovernanceTarget} [target] */
const denyEvent = (target = { type: "user", pubkey: CREATOR }) =>
event({
id: "bb".repeat(32),
...encodeContribution({ actor: MODERATOR, kind: "user-deny", targets: [target] }, "app"),
});
function makeRuntime(events = []) {
const transport = createMemoryTransport(events);
const runtime = createGovernanceRuntime({
applicationId: "test-app",
namespace: "app",
transport,
policy: POLICY,
now: () => NOW,
});
return { runtime, transport };
}
describe("chunk", () => {
it("splits into bounded pieces", () => {
expect(chunk([1, 2, 3, 4, 5], 2)).toEqual([[1, 2], [3, 4], [5]]);
});
it("returns one chunk for a non-positive size", () => {
expect(chunk([1, 2], 0)).toEqual([[1, 2]]);
});
it("handles an empty list", () => {
expect(chunk([], 10)).toEqual([]);
});
});
describe("storageKey", () => {
it("namespaces by deployment and schema", () => {
expect(
storageKey({
applicationId: "bitroad",
namespace: "bitroad",
rootFingerprint: "abcd",
schemaVersion: "v1",
scope: "admin",
}),
).toBe("nostr-governance:bitroad:bitroad:abcd:v1:admin");
});
it("appends the viewer for viewer-scoped state", () => {
const key = storageKey({
applicationId: "a",
namespace: "n",
rootFingerprint: "f",
schemaVersion: "v1",
scope: "overrides",
viewerPubkey: VIEWER,
});
expect(key.endsWith(VIEWER)).toBe(true);
});
});
describe("construction", () => {
it("requires an application id and namespace", () => {
expect(() =>
new GovernanceRuntime(/** @type {any} */ ({ transport: createMemoryTransport() })),
).toThrow(/applicationId and namespace/);
});
it("requires a transport", () => {
expect(() =>
new GovernanceRuntime(/** @type {any} */ ({ applicationId: "a", namespace: "n" })),
).toThrow(/transport adapter/);
});
it("changes its storage key when the root authority changes", () => {
const { runtime } = makeRuntime();
const before = runtime.storageKeyFor("admin");
runtime.admin.setRoles({ root: ROOT, actors: {} });
expect(runtime.storageKeyFor("admin")).not.toBe(before);
});
});
describe("ingestEvent", () => {
it("routes a roles document", () => {
const { runtime } = makeRuntime();
expect(runtime.ingestEvent(rolesEvent())).toBe(true);
expect(runtime.admin.authority.root).toBe(ROOT);
});
it("routes a contribution", () => {
const { runtime } = makeRuntime();
runtime.ingestEvent(rolesEvent());
expect(runtime.ingestEvent(denyEvent())).toBe(true);
expect(runtime.admin.state.userDeny.has(`user:${CREATOR}`)).toBe(true);
});
it("routes a report", () => {
const { runtime } = makeRuntime();
const report = event({
kind: REPORT_KIND,
pubkey: TRUSTED,
tags: [["e", EVENT_ID, "spam"]],
});
expect(runtime.ingestEvent(report)).toBe(true);
expect(runtime.reports.recordsFor(`event:${EVENT_ID}`)).toHaveLength(1);
});
it("routes a mute list", () => {
const { runtime } = makeRuntime();
const list = event({ kind: MUTE_LIST_KIND, pubkey: TRUSTED, tags: [["p", CREATOR]] });
expect(runtime.ingestEvent(list)).toBe(true);
expect(runtime.mutes.toRecordMap().get(`user:${CREATOR}`)).toHaveLength(1);
});
it("rejects a policy document from anyone but the root", () => {
const { runtime } = makeRuntime();
runtime.ingestEvent(rolesEvent());
const rejected = vi.fn();
runtime.on("policy-rejected", rejected);
runtime.ingestEvent(
event({
pubkey: MODERATOR,
tags: [["d", canonicalIdentifier("app", "policy")]],
content: JSON.stringify({ id: "x", version: "1", profiles: { f: { name: "f" } } }),
}),
);
expect(rejected).toHaveBeenCalledWith(expect.objectContaining({ reason: "not-root" }));
expect(runtime.policies.rootPolicy).toBeNull();
});
it("applies a root-published policy", () => {
const { runtime } = makeRuntime();
runtime.ingestEvent(rolesEvent());
runtime.ingestEvent(
event({
pubkey: ROOT,
tags: [["d", canonicalIdentifier("app", "policy")]],
content: JSON.stringify({
id: "root-policy",
version: "3.0.0",
profiles: { feed: { name: "feed" } },
}),
}),
);
expect(runtime.policies.policy.id).toBe("root-policy");
});
it("keeps the working policy when the root publishes a malformed one", () => {
const { runtime } = makeRuntime();
runtime.ingestEvent(rolesEvent());
const rejected = vi.fn();
runtime.on("policy-rejected", rejected);
runtime.ingestEvent(
event({
pubkey: ROOT,
tags: [["d", canonicalIdentifier("app", "policy")]],
content: JSON.stringify({ id: "broken" }),
}),
);
expect(rejected).toHaveBeenCalledWith(expect.objectContaining({ reason: "invalid" }));
expect(runtime.policies.policy.id).toBe("app");
});
it("counts unrecognized events instead of throwing", () => {
const { runtime } = makeRuntime();
expect(runtime.ingestEvent(event({ kind: 1 }))).toBe(false);
expect(runtime.diagnostics.unknownEvents).toBe(1);
});
it("ignores malformed input", () => {
const { runtime } = makeRuntime();
expect(runtime.ingestEvent(/** @type {any} */ (null))).toBe(false);
});
});
describe("loadAdministrativeState", () => {
it("ingests only the newest document per coordinate", async () => {
const older = event({
id: "11".repeat(32),
created_at: NOW - 100,
...encodeContribution(
{ actor: MODERATOR, kind: "user-deny", targets: [{ type: "user", pubkey: CREATOR }] },
"app",
),
});
const newer = event({
id: "22".repeat(32),
created_at: NOW,
...encodeContribution({ actor: MODERATOR, kind: "user-deny", targets: [] }, "app"),
});
const { runtime } = makeRuntime([rolesEvent(), older, newer]);
await runtime.loadAdministrativeState();
expect(runtime.admin.state.userDeny.size).toBe(0);
});
it("can restrict the query to known authors", async () => {
const transport = createMemoryTransport([rolesEvent(), denyEvent()]);
const listSpy = vi.spyOn(transport, "list");
const runtime = createGovernanceRuntime({
applicationId: "a",
namespace: "app",
transport,
policy: POLICY,
now: () => NOW,
});
await runtime.loadAdministrativeState({ authors: [MODERATOR] });
expect(listSpy.mock.calls[0][0][0]).toMatchObject({ authors: [MODERATOR] });
});
});
describe("active targets and subscriptions", () => {
it("chunks report subscriptions rather than opening one per target", () => {
const transport = createMemoryTransport();
const subscribeSpy = vi.spyOn(transport, "subscribe");
const runtime = createGovernanceRuntime({
applicationId: "a",
namespace: "app",
transport,
policy: POLICY,
now: () => NOW,
chunkSize: 2,
});
runtime.setActiveTargets([
{ type: "event", id: "01".repeat(32) },
{ type: "event", id: "02".repeat(32) },
{ type: "event", id: "03".repeat(32) },
]);
runtime.subscribeToActiveTargetReports();
expect(subscribeSpy).toHaveBeenCalledTimes(2);
});
it("separates event and user filters", () => {
const transport = createMemoryTransport();
const subscribeSpy = vi.spyOn(transport, "subscribe");
const runtime = createGovernanceRuntime({
applicationId: "a",
namespace: "app",
transport,
policy: POLICY,
now: () => NOW,
});
runtime.setActiveTargets([
{ type: "event", id: EVENT_ID },
{ type: "user", pubkey: CREATOR },
]);
runtime.subscribeToActiveTargetReports();
const filters = subscribeSpy.mock.calls.map((call) => call[0][0]);
expect(filters.some((filter) => "#e" in filter)).toBe(true);
expect(filters.some((filter) => "#p" in filter)).toBe(true);
});
it("ingests reports delivered to a live subscription", () => {
const { runtime, transport } = makeRuntime();
runtime.setActiveTargets([{ type: "event", id: EVENT_ID }]);
runtime.subscribeToActiveTargetReports();
transport.deliver(
event({ kind: REPORT_KIND, pubkey: TRUSTED, tags: [["e", EVENT_ID, "spam"]] }),
);
expect(runtime.reports.recordsFor(`event:${EVENT_ID}`)).toHaveLength(1);
});
it("skips invalid targets", () => {
const { runtime } = makeRuntime();
runtime.setActiveTargets([
{ type: "user", pubkey: CREATOR },
/** @type {any} */ ({ type: "user", pubkey: "bad" }),
]);
expect(runtime.activeTargets.size).toBe(1);
});
it("closes subscriptions on close", () => {
const { runtime } = makeRuntime();
runtime.setActiveTargets([{ type: "event", id: EVENT_ID }]);
const handle = runtime.subscribeToActiveTargetReports();
expect(runtime.subscriptions.size).toBe(1);
handle.close();
expect(runtime.subscriptions.size).toBe(0);
});
});
describe("viewer lifecycle", () => {
it("clears viewer-specific state on switch", () => {
const { runtime } = makeRuntime();
runtime.setViewer(VIEWER);
runtime.trust.setBlocks([CREATOR]);
runtime.overrides.set(`user:${CREATOR}`, { visibility: "allow" });
runtime.setViewer(TRUSTED);
expect(runtime.trust.blocks.size).toBe(0);
expect(runtime.overrides.toMap().size).toBe(0);
});
it("keeps operator seeds across a viewer switch", () => {
const { runtime } = makeRuntime();
runtime.trust.setSeeds([TRUSTED]);
runtime.setViewer(VIEWER);
expect(runtime.trust.seeds.has(TRUSTED)).toBe(true);
});
it("does not churn when the viewer is unchanged", () => {
const { runtime } = makeRuntime();
runtime.setViewer(VIEWER);
const handler = vi.fn();
runtime.on("viewer", handler);
runtime.setViewer(VIEWER);
expect(handler).not.toHaveBeenCalled();
});
});
describe("evaluation", () => {
it("evaluates against live store state", () => {
const { runtime } = makeRuntime();
runtime.ingestEvent(rolesEvent());
runtime.ingestEvent(denyEvent());
const decision = runtime.evaluate({ type: "user", pubkey: CREATOR }, { profile: "feed" });
expect(decision.visibility.effect).toBe("hide");
});
it("reflects a report ingested afterwards", () => {
const { runtime } = makeRuntime();
runtime.trust.setContacts([TRUSTED]);
runtime.ingestEvent(
event({ kind: REPORT_KIND, pubkey: TRUSTED, tags: [["e", EVENT_ID, "spam"]] }),
);
const decision = runtime.evaluate({ type: "event", id: EVENT_ID }, { profile: "feed" });
expect(decision.visibility.effect).toBe("restrict");
});
it("applies viewer blocks", () => {
const { runtime } = makeRuntime();
runtime.trust.setBlocks([CREATOR]);
expect(
runtime.evaluate({ type: "user", pubkey: CREATOR }, { profile: "feed" }).visibility.effect,
).toBe("hide");
});
it("evaluates many targets consistently with one", () => {
const { runtime } = makeRuntime();
runtime.ingestEvent(rolesEvent());
runtime.ingestEvent(denyEvent());
/** @type {import('@nostr-governance/core').GovernanceTarget} */
const target = { type: "user", pubkey: CREATOR };
const many = runtime.evaluateMany([target], { profile: "feed" });
expect(many.get(`user:${CREATOR}`)).toEqual(runtime.evaluate(target, { profile: "feed" }));
});
});
describe("diagnostics and teardown", () => {
it("describes runtime state", () => {
const { runtime } = makeRuntime();
runtime.ingestEvent(rolesEvent());
const description = runtime.describe();
expect(description.applicationId).toBe("test-app");
expect(description.policyId).toBe("app");
expect(description.eventsIngested).toBeGreaterThan(0);
});
it("closes subscriptions and detaches listeners on destroy", () => {
const { runtime } = makeRuntime();
runtime.setActiveTargets([{ type: "event", id: EVENT_ID }]);
runtime.subscribeToActiveTargetReports();
const handler = vi.fn();
runtime.on("change", handler);
runtime.destroy();
expect(runtime.subscriptions.size).toBe(0);
runtime.admin.setRoles({ root: ROOT, actors: {} });
expect(handler).not.toHaveBeenCalled();
});
it("is safe to destroy twice", () => {
const { runtime } = makeRuntime();
runtime.destroy();
expect(() => runtime.destroy()).not.toThrow();
});
});