Files
bitgate/packages/core/test/fingerprint.test.js
thePR0M3TH3AN 4f689184e2 feat(core): rebuild governance core to the plan's decision model
The evaluator was a skeleton with invented thresholds (5/3/10/5) and two
unimplemented branches. Rebuild it against the migration plan's spec and the
behavior characterized from the pinned reference commit.

Core:
- four independent decision dimensions (ranking, visibility, interaction,
  transaction) with severity ladders and max-merge composition
- stable reason identifiers replacing free-text strings
- structured evidence, redacted unless a profile opts in
- administrative-state reduction with capability filtering at merge time, so
  revoking a role drops that actor's entries immediately
- protected actors stripped from every denial set
- snapshot fingerprints for cache invalidation
- generic policy engine: applications supply profiles and category thresholds;
  the core carries none of its own

Characterization:
- 34 generated fixtures covering every behavior the plan's Phase 0 requires
- conformance suite that actually evaluates them, replacing a placeholder that
  asserted a list's length
- reference thresholds (3/3/20/5, 60-day mute window) live in bitvid-compat as
  an application profile, not as engine defaults

Fixes found while building:
- reduceAdminState accepted malformed target identifiers, because getTargetKey
  formats anything it is given
- community-user-deny fired for any contributor rather than only for
  community-curated sources

273 tests pass; typecheck and the reference-import guard are clean.
2026-08-09 17:33:41 -04:00

95 lines
2.9 KiB
JavaScript

import { describe, expect, it } from "vitest";
import {
canonicalStringify,
fingerprint,
hashString,
snapshotFingerprint,
} from "../src/fingerprint.js";
describe("canonicalStringify", () => {
it("sorts object keys at every depth", () => {
expect(canonicalStringify({ b: 1, a: { d: 2, c: 3 } })).toBe(
canonicalStringify({ a: { c: 3, d: 2 }, b: 1 }),
);
});
it("preserves array order", () => {
expect(canonicalStringify([1, 2])).not.toBe(canonicalStringify([2, 1]));
});
it("serializes Sets order-independently", () => {
expect(canonicalStringify(new Set(["b", "a"]))).toBe(canonicalStringify(new Set(["a", "b"])));
});
it("serializes Maps order-independently", () => {
const a = new Map([["x", 1], ["y", 2]]);
const b = new Map([["y", 2], ["x", 1]]);
expect(canonicalStringify(a)).toBe(canonicalStringify(b));
});
it("skips undefined values", () => {
expect(canonicalStringify({ a: 1, b: undefined })).toBe(canonicalStringify({ a: 1 }));
});
it("handles primitives and null", () => {
expect(canonicalStringify(null)).toBe("null");
expect(canonicalStringify(5)).toBe("5");
expect(canonicalStringify("x")).toBe('"x"');
});
});
describe("hashString", () => {
it("returns a 16-character hex digest", () => {
expect(hashString("anything")).toMatch(/^[0-9a-f]{16}$/);
});
it("is stable across calls", () => {
expect(hashString("stable")).toBe(hashString("stable"));
});
it("separates similar inputs", () => {
expect(hashString("a")).not.toBe(hashString("b"));
expect(hashString("ab")).not.toBe(hashString("ba"));
});
it("handles the empty string", () => {
expect(hashString("")).toMatch(/^[0-9a-f]{16}$/);
});
});
describe("fingerprint", () => {
it("matches for structurally equal values", () => {
expect(fingerprint({ a: 1, b: 2 })).toBe(fingerprint({ b: 2, a: 1 }));
});
it("differs when a value changes", () => {
expect(fingerprint({ a: 1 })).not.toBe(fingerprint({ a: 2 }));
});
});
describe("snapshotFingerprint", () => {
it("ignores fields outside the snapshot", () => {
const base = { authority: { root: "x" }, admin: { userDeny: new Set(["a"]) } };
expect(snapshotFingerprint(base)).toBe(
snapshotFingerprint(/** @type {any} */ ({ ...base, viewer: "someone" })),
);
});
it("changes when administrative state changes", () => {
const before = snapshotFingerprint({ admin: { userDeny: new Set() } });
const after = snapshotFingerprint({ admin: { userDeny: new Set(["user:a"]) } });
expect(before).not.toBe(after);
});
it("changes when the policy changes", () => {
expect(snapshotFingerprint({ policy: { version: "1" } })).not.toBe(
snapshotFingerprint({ policy: { version: "2" } }),
);
});
it("tolerates a fully empty snapshot", () => {
expect(snapshotFingerprint({})).toMatch(/^[0-9a-f]{16}$/);
});
});