// ── The audience ceiling lattice ─────────────────────────────────────────── // // ENGAGEMENT.md §5.1a / G24. These are the tests for the security property the // whole rule model rests on: **composition may narrow, never widen**, and a // ceiling is about WHICH people rather than how many. // // The case worth naming is `staff` vs `owner`. Under the flat total order the // plan's wording invites — self < owner < staff < members < authenticated < // everyone — a trigger ceilinged at `staff` also permits `owner`, so a rule // could mail `uo.cheat.detected` to the player it detected. That is the bug this // file exists to keep out, so it is asserted explicitly rather than left implied // by the shape of the table. const { test } = require('node:test') const assert = require('node:assert/strict') const ceilings = require('../src/modules/ceilings') test('the six ceilings are the vocabulary, and nothing else is', () => { assert.deepEqual( [...ceilings.CEILINGS].sort(), ['authenticated', 'everyone', 'members', 'owner', 'staff', 'subscribers'], ) for (const id of ceilings.CEILINGS) assert.ok(ceilings.LABELS[id], `${id} has an operator label`) assert.equal(ceilings.isCeiling('nobody'), false) assert.equal(ceilings.isCeiling(undefined), false) }) test('everyone permits every ceiling; every ceiling permits itself', () => { for (const id of ceilings.CEILINGS) { assert.equal(ceilings.permits('everyone', id), true, `everyone permits ${id}`) assert.equal(ceilings.permits(id, id), true, `${id} permits itself`) } }) test('authenticated permits the four leaves but not everyone', () => { for (const leaf of ['subscribers', 'members', 'staff', 'owner']) { assert.equal(ceilings.permits('authenticated', leaf), true) } assert.equal(ceilings.permits('authenticated', 'everyone'), false) }) // The one that a flat ordering gets wrong. test('a staff ceiling does NOT permit owner — fewer people is not less exposure', () => { assert.equal(ceilings.permits('staff', 'owner'), false) assert.equal(ceilings.permits('owner', 'staff'), false) // …and the same for every other pair of leaves, so the property is the tree's // and not a special case someone wrote for cheat detection. const leaves = ['subscribers', 'members', 'staff', 'owner'] for (const a of leaves) { for (const b of leaves) { if (a === b) continue assert.equal(ceilings.permits(a, b), false, `${a} must not permit ${b}`) } } }) test('an unknown ceiling is permitted by nothing, on either side', () => { assert.equal(ceilings.permits('everyone', 'god'), false) assert.equal(ceilings.permits('god', 'owner'), false) assert.equal(ceilings.permits('everyone', undefined), false) }) test('A OR B takes the NARROWER of the two ceilings, not the wider', () => { assert.equal(ceilings.meet('everyone', 'staff'), 'staff') assert.equal(ceilings.meet('staff', 'everyone'), 'staff') assert.equal(ceilings.meet('authenticated', 'members'), 'members') assert.equal(ceilings.meet('members', 'members'), 'members') }) test('incomparable ceilings have no meet — the save is refused, not guessed', () => { assert.equal(ceilings.meet('staff', 'members'), null) assert.equal(ceilings.meet('owner', 'subscribers'), null) assert.equal(ceilings.meet('staff', 'nonsense'), null) }) test('meetAll folds, short-circuits to null, and has no opinion about an empty list', () => { assert.equal(ceilings.meetAll(['everyone', 'authenticated', 'members']), 'members') // members ∧ staff is undefined, so the whole composition is. assert.equal(ceilings.meetAll(['everyone', 'members', 'staff']), null) assert.equal(ceilings.meetAll(['owner']), 'owner') // Not 'everyone': an empty composition states no bound, and defaulting it to // the top would make "no audiences selected" the widest possible rule. assert.equal(ceilings.meetAll([]), null) assert.equal(ceilings.meetAll(null), null) })