mirror of
https://github.com/PR0M3TH3AN/bitgate.git
synced 2026-10-27 08:31:00 +00:00
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.
319 lines
10 KiB
JavaScript
319 lines
10 KiB
JavaScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
INTERACTION_LADDER,
|
|
NEUTRAL_POLICY,
|
|
RANKING_LADDER,
|
|
TRANSACTION_LADDER,
|
|
VISIBILITY_LADDER,
|
|
applyEffects,
|
|
applyThresholds,
|
|
composeDecisions,
|
|
createNeutralDecision,
|
|
createPolicyDefinition,
|
|
effectSeverity,
|
|
maxEffect,
|
|
normalizeCategoryThresholds,
|
|
normalizeDimensionEffects,
|
|
normalizePolicyDefinition,
|
|
resolveProfile,
|
|
resolveThresholds,
|
|
} from "../src/policy.js";
|
|
import { createReason } from "../src/reasons.js";
|
|
|
|
describe("effect ladders", () => {
|
|
it("orders visibility from permissive to severe", () => {
|
|
expect(VISIBILITY_LADDER).toEqual(["allow", "warn", "restrict", "hide", "deny"]);
|
|
});
|
|
|
|
it("orders the remaining dimensions", () => {
|
|
expect(RANKING_LADDER).toEqual(["normal", "downrank"]);
|
|
expect(INTERACTION_LADDER).toEqual(["allow", "require-explicit-action", "deny"]);
|
|
expect(TRANSACTION_LADDER).toEqual(["allow", "require-review", "deny"]);
|
|
});
|
|
|
|
it("ranks effects by severity", () => {
|
|
expect(effectSeverity("visibility", "allow")).toBeLessThan(effectSeverity("visibility", "hide"));
|
|
expect(effectSeverity("visibility", "hide")).toBeLessThan(effectSeverity("visibility", "deny"));
|
|
});
|
|
|
|
it("returns -1 for effects outside the ladder", () => {
|
|
expect(effectSeverity("visibility", "banish")).toBe(-1);
|
|
});
|
|
|
|
it("throws for an unknown dimension", () => {
|
|
expect(() => effectSeverity(/** @type {any} */ ("vibes"), "allow")).toThrow(/Unknown decision dimension/);
|
|
});
|
|
|
|
it("keeps the more severe effect", () => {
|
|
expect(maxEffect("visibility", "allow", "hide")).toBe("hide");
|
|
expect(maxEffect("visibility", "deny", "warn")).toBe("deny");
|
|
expect(maxEffect("interaction", "allow", "require-explicit-action")).toBe("require-explicit-action");
|
|
});
|
|
|
|
it("falls back to the valid effect when one side is unknown", () => {
|
|
expect(maxEffect("visibility", "nonsense", "warn")).toBe("warn");
|
|
expect(maxEffect("visibility", "warn", "nonsense")).toBe("warn");
|
|
});
|
|
});
|
|
|
|
describe("normalizeDimensionEffects", () => {
|
|
it("passes valid effects through", () => {
|
|
expect(normalizeDimensionEffects({ visibility: "hide", interaction: "deny" })).toEqual({
|
|
visibility: "hide",
|
|
interaction: "deny",
|
|
});
|
|
});
|
|
|
|
it("rejects an effect from the wrong ladder", () => {
|
|
expect(() => normalizeDimensionEffects({ interaction: /** @type {any} */ ("hide") })).toThrow(
|
|
/invalid interaction effect/,
|
|
);
|
|
});
|
|
|
|
it("returns an empty object for missing input", () => {
|
|
expect(normalizeDimensionEffects(/** @type {any} */ (undefined))).toEqual({});
|
|
});
|
|
});
|
|
|
|
describe("normalizeCategoryThresholds", () => {
|
|
it("lowercases categories and floors values", () => {
|
|
expect(normalizeCategoryThresholds({ SPAM: { hide: 5.7 } })).toEqual({ spam: { hide: 5 } });
|
|
});
|
|
|
|
it("drops non-numeric gates", () => {
|
|
expect(normalizeCategoryThresholds({ spam: { hide: /** @type {any} */ ("many") } })).toEqual({
|
|
spam: {},
|
|
});
|
|
});
|
|
|
|
it("rejects negative thresholds", () => {
|
|
expect(() => normalizeCategoryThresholds({ spam: { hide: -1 } })).toThrow(/must be >= 0/);
|
|
});
|
|
});
|
|
|
|
describe("normalizePolicyDefinition", () => {
|
|
const valid = {
|
|
id: "app",
|
|
version: "1.0.0",
|
|
profiles: { feed: { name: "feed" } },
|
|
};
|
|
|
|
it("normalizes a minimal policy", () => {
|
|
const policy = normalizePolicyDefinition(valid);
|
|
expect(policy.id).toBe("app");
|
|
expect(policy.defaultProfile).toBe("feed");
|
|
expect(policy.profiles.feed.allowViewerOverride).toBe(true);
|
|
});
|
|
|
|
it("requires an id and a version", () => {
|
|
expect(() => normalizePolicyDefinition({ ...valid, id: "" })).toThrow(/non-empty id/);
|
|
expect(() => normalizePolicyDefinition({ ...valid, version: "" })).toThrow(/version string/);
|
|
});
|
|
|
|
it("requires at least one profile", () => {
|
|
expect(() => normalizePolicyDefinition({ ...valid, profiles: {} })).toThrow(/at least one profile/);
|
|
});
|
|
|
|
it("rejects a default profile that does not exist", () => {
|
|
expect(() => normalizePolicyDefinition({ ...valid, defaultProfile: "nope" })).toThrow(
|
|
/is not defined/,
|
|
);
|
|
});
|
|
|
|
it("names a profile from its map key when the profile omits a name", () => {
|
|
const policy = normalizePolicyDefinition({
|
|
...valid,
|
|
profiles: { checkout: /** @type {any} */ ({}) },
|
|
});
|
|
expect(policy.profiles.checkout.name).toBe("checkout");
|
|
});
|
|
|
|
it("defaults the hide ceiling when bypassHide is set", () => {
|
|
const policy = normalizePolicyDefinition({
|
|
...valid,
|
|
profiles: { feed: { name: "feed", bypassHide: true } },
|
|
});
|
|
expect(policy.profiles.feed.bypassHideCeiling).toBe("restrict");
|
|
});
|
|
|
|
it("rejects a negative mute window", () => {
|
|
expect(() =>
|
|
normalizePolicyDefinition({
|
|
...valid,
|
|
profiles: { feed: { name: "feed", muteWindowSeconds: -5 } },
|
|
}),
|
|
).toThrow(/muteWindowSeconds/);
|
|
});
|
|
|
|
it("is exposed as createPolicyDefinition", () => {
|
|
expect(createPolicyDefinition(valid).id).toBe("app");
|
|
});
|
|
});
|
|
|
|
describe("resolveProfile", () => {
|
|
const policy = createPolicyDefinition({
|
|
id: "app",
|
|
version: "1.0.0",
|
|
defaultProfile: "feed",
|
|
profiles: { feed: { name: "feed" }, checkout: { name: "checkout" } },
|
|
});
|
|
|
|
it("returns the requested profile", () => {
|
|
expect(resolveProfile(policy, { surface: "x", policyProfile: "checkout" }).name).toBe("checkout");
|
|
});
|
|
|
|
it("falls back to the default profile", () => {
|
|
expect(resolveProfile(policy, { surface: "x" }).name).toBe("feed");
|
|
});
|
|
|
|
it("throws for an unknown profile rather than silently falling back", () => {
|
|
expect(() => resolveProfile(policy, { surface: "x", policyProfile: "ghost" })).toThrow(
|
|
/Unknown policy profile/,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("resolveThresholds", () => {
|
|
const table = { spam: { hide: 5 }, default: { restrict: 3 } };
|
|
|
|
it("prefers an exact category match", () => {
|
|
expect(resolveThresholds(table, "spam")).toEqual({ hide: 5 });
|
|
});
|
|
|
|
it("falls back to the default entry", () => {
|
|
expect(resolveThresholds(table, "nudity")).toEqual({ restrict: 3 });
|
|
});
|
|
|
|
it("returns an empty object when nothing matches", () => {
|
|
expect(resolveThresholds({ spam: { hide: 5 } }, "nudity")).toEqual({});
|
|
expect(resolveThresholds(undefined, "spam")).toEqual({});
|
|
});
|
|
});
|
|
|
|
describe("applyThresholds", () => {
|
|
it("fires gates at or above the threshold", () => {
|
|
const { effects } = applyThresholds(3, { restrict: 3 });
|
|
expect(effects.visibility).toBe("restrict");
|
|
});
|
|
|
|
it("does not fire below the threshold", () => {
|
|
const { effects, firedGates } = applyThresholds(2, { restrict: 3 });
|
|
expect(effects).toEqual({});
|
|
expect(firedGates).toEqual([]);
|
|
});
|
|
|
|
it("keeps the most severe gate when several fire", () => {
|
|
const { effects } = applyThresholds(10, { warn: 1, restrict: 3, hide: 5 });
|
|
expect(effects.visibility).toBe("hide");
|
|
});
|
|
|
|
it("treats a zero threshold as disabled rather than always-on", () => {
|
|
const { effects } = applyThresholds(5, { hide: 0 });
|
|
expect(effects).toEqual({});
|
|
});
|
|
|
|
it("ignores non-positive counts", () => {
|
|
expect(applyThresholds(0, { warn: 1 }).effects).toEqual({});
|
|
expect(applyThresholds(Number.NaN, { warn: 1 }).effects).toEqual({});
|
|
});
|
|
|
|
it("maps gates onto their own dimensions", () => {
|
|
const { effects } = applyThresholds(5, {
|
|
downrank: 1,
|
|
restrict: 2,
|
|
requireExplicitAction: 3,
|
|
transactionDeny: 4,
|
|
});
|
|
expect(effects).toEqual({
|
|
ranking: "downrank",
|
|
visibility: "restrict",
|
|
interaction: "require-explicit-action",
|
|
transaction: "deny",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("applyEffects", () => {
|
|
it("escalates but never softens", () => {
|
|
const decision = createNeutralDecision();
|
|
applyEffects(decision, { visibility: "hide" });
|
|
applyEffects(decision, { visibility: "warn" });
|
|
expect(decision.visibility.effect).toBe("hide");
|
|
});
|
|
|
|
it("accumulates downrank weight", () => {
|
|
const decision = createNeutralDecision();
|
|
applyEffects(decision, { ranking: "downrank" });
|
|
applyEffects(decision, { ranking: "downrank" });
|
|
expect(decision.ranking.weight).toBe(2);
|
|
expect(decision.ranking.effect).toBe("downrank");
|
|
});
|
|
|
|
it("creates the transaction dimension on demand", () => {
|
|
const decision = createNeutralDecision();
|
|
expect(decision.transaction).toBeUndefined();
|
|
applyEffects(decision, { transaction: "deny" });
|
|
expect(decision.transaction).toEqual({ effect: "deny" });
|
|
});
|
|
|
|
it("deduplicates reasons", () => {
|
|
const decision = createNeutralDecision();
|
|
applyEffects(decision, {}, [createReason("viewer-block"), createReason("viewer-block")]);
|
|
expect(decision.reasons).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe("composeDecisions", () => {
|
|
const withVisibility = (effect, weight = 0) => {
|
|
const decision = createNeutralDecision();
|
|
decision.visibility.effect = effect;
|
|
decision.ranking.weight = weight;
|
|
return decision;
|
|
};
|
|
|
|
it("returns a neutral decision for an empty list", () => {
|
|
const composed = composeDecisions([]);
|
|
expect(composed.visibility.effect).toBe("allow");
|
|
});
|
|
|
|
it("keeps the most severe effect", () => {
|
|
expect(composeDecisions([withVisibility("warn"), withVisibility("hide")]).visibility.effect).toBe(
|
|
"hide",
|
|
);
|
|
});
|
|
|
|
it("is order-independent", () => {
|
|
const a = composeDecisions([withVisibility("warn"), withVisibility("hide")]);
|
|
const b = composeDecisions([withVisibility("hide"), withVisibility("warn")]);
|
|
expect(a.visibility.effect).toBe(b.visibility.effect);
|
|
});
|
|
|
|
it("sums ranking weight", () => {
|
|
expect(composeDecisions([withVisibility("allow", 2), withVisibility("allow", 3)]).ranking.weight).toBe(5);
|
|
});
|
|
|
|
it("makes a decision non-overridable if any input was", () => {
|
|
const locked = createNeutralDecision();
|
|
locked.visibility.overridable = false;
|
|
expect(composeDecisions([createNeutralDecision(), locked]).visibility.overridable).toBe(false);
|
|
});
|
|
|
|
it("does not mutate its inputs", () => {
|
|
const first = withVisibility("warn");
|
|
composeDecisions([first, withVisibility("deny")]);
|
|
expect(first.visibility.effect).toBe("warn");
|
|
});
|
|
});
|
|
|
|
describe("NEUTRAL_POLICY", () => {
|
|
it("enforces administrative denial", () => {
|
|
expect(NEUTRAL_POLICY.profiles.default.administrativeDeny?.visibility).toBe("hide");
|
|
});
|
|
|
|
it("applies no trust thresholds of its own", () => {
|
|
expect(NEUTRAL_POLICY.profiles.default.reports).toEqual({});
|
|
expect(NEUTRAL_POLICY.profiles.default.mutes).toEqual({});
|
|
});
|
|
});
|