mattrguerra/meridian-ops-boardmainPrivate on GitHub, published here in full

access.test.ts

Language TypeScript10.6 KB253 lines

apps/dashboard/test/access.test.ts

  1. /**
  2. * Who can read what.
  3. *
  4. * Every briefing addresses its reader directly as "you", so a page carrying all
  5. * of them with the others `hidden`, or the executive brief in a `<details>`
  6. * that opens on click, would let anyone through the Access door read them all
  7. * from view-source.
  8. *
  9. * Cloudflare Access cannot prevent that: it gates a URL, and has no view inside
  10. * the response. So enforcement is here, at render time, and these tests assert
  11. * the negative: that content is absent from the bytes, not merely hidden in
  12. * them. A test that only checked the visible view would pass on a page that
  13. * leaks.
  14. */
  15. import { describe, expect, it } from 'vitest'
  16. import { FIXTURE_SNAPSHOT } from '../src/fixtures.js'
  17. import { headline } from '../src/collect.js'
  18. import { render } from '../src/render.js'
  19. import { MERIDIAN_PEOPLE } from '../../../config/meridian-people.js'
  20. import { COMPANY_VIEWER, allViewers, companyBriefingKey, viewerFor } from '../src/viewer.js'
  21. import type { Briefings } from '../src/briefings.js'
  22. const EXEC_TEXT = 'EXEC-ONLY-SENTINEL-nora-capacity-view'
  23. const COMPANY_TEXT = 'COMPANY-FULL-SENTINEL-names-leadership-todos'
  24. const ALLHANDS_TEXT = 'ALLHANDS-SENTINEL-no-leadership-titles'
  25. const briefings: Briefings = {
  26. generatedAt: '2026-08-25T20:40:14+00:00',
  27. day: '2026-08-12',
  28. model: 'claude-opus-5',
  29. payloadGeneratedAt: FIXTURE_SNAPSHOT.syncedAt,
  30. mode: 'fixtures',
  31. briefings: {
  32. company: COMPANY_TEXT,
  33. 'company-allhands': ALLHANDS_TEXT,
  34. exec: EXEC_TEXT,
  35. ...Object.fromEntries(MERIDIAN_PEOPLE.map((p) => [p.name, `PERSONAL-SENTINEL-${p.name}`])),
  36. },
  37. }
  38. const pageFor = (email: string | null) =>
  39. render(FIXTURE_SNAPSHOT, headline(FIXTURE_SNAPSHOT), [], '2026-08-12', briefings, viewerFor(email))
  40. const EMAIL = Object.fromEntries(MERIDIAN_PEOPLE.map((p) => [p.name, p.email!])) as Record<string, string>
  41. describe('the executive briefing is Nora only, hard', () => {
  42. it('reaches Nora', () => {
  43. expect(pageFor(EMAIL['Nora Lindqvist'])).toContain(EXEC_TEXT)
  44. })
  45. it('is ABSENT from every other roster page, not hidden in it', () => {
  46. for (const p of MERIDIAN_PEOPLE) {
  47. if (p.name === 'Nora Lindqvist') continue
  48. expect(pageFor(p.email!), `${p.name} must not receive the exec brief`).not.toContain(EXEC_TEXT)
  49. }
  50. })
  51. it('is absent for an unrecognised address', () => {
  52. expect(pageFor('someone@meridiandisplay.example')).not.toContain(EXEC_TEXT)
  53. expect(pageFor(null)).not.toContain(EXEC_TEXT)
  54. })
  55. it('is absent even from a leadership reader who is not Nora', () => {
  56. // Sam may read Leadership to-dos. That is a different permission.
  57. expect(pageFor(EMAIL['Sam Whitaker'])).not.toContain(EXEC_TEXT)
  58. })
  59. it('is held by exactly one person on the roster', () => {
  60. expect(MERIDIAN_PEOPLE.filter((p) => p.seesExec).map((p) => p.name)).toEqual(['Nora Lindqvist'])
  61. })
  62. })
  63. describe('a personal briefing reaches one person', () => {
  64. it('gives each person their own', () => {
  65. for (const p of MERIDIAN_PEOPLE) {
  66. expect(pageFor(p.email!), p.name).toContain(`PERSONAL-SENTINEL-${p.name}`)
  67. }
  68. })
  69. it('gives nobody the briefing of anyone else', () => {
  70. // The whole failure in one assertion: seven people, forty-two pairings.
  71. for (const reader of MERIDIAN_PEOPLE) {
  72. const html = pageFor(reader.email!)
  73. for (const other of MERIDIAN_PEOPLE) {
  74. if (other.name === reader.name) continue
  75. expect(html, `${reader.name} must not see ${other.name}'s briefing`)
  76. .not.toContain(`PERSONAL-SENTINEL-${other.name}`)
  77. }
  78. }
  79. })
  80. it('gives an unrecognised address none of them', () => {
  81. const html = pageFor('nobody@meridiandisplay.example')
  82. for (const p of MERIDIAN_PEOPLE) {
  83. expect(html, p.name).not.toContain(`PERSONAL-SENTINEL-${p.name}`)
  84. }
  85. })
  86. it('no longer ships a person picker, which was the leak', () => {
  87. // The dropdown implied a choice between people. It only ever worked because
  88. // every person's card was already in the document.
  89. const html = pageFor(EMAIL['Marco Ibáñez'])
  90. expect(html).not.toContain('data-person-picker')
  91. })
  92. it('still shows the whole team on the board, which is not a leak', () => {
  93. // Deliberate, and the line is worth knowing. What is restricted is the
  94. // personalized briefings and the Leadership to-dos, not the existence of
  95. // colleagues or their load. "Who is loaded, who is not, at a glance" is the
  96. // board's original purpose and the reason it is organised by person at all.
  97. // Hiding names here would gut it while protecting nothing that was asked to
  98. // be protected.
  99. const html = pageFor(EMAIL['Marco Ibáñez'])
  100. expect(html).toContain('Theo Bassett')
  101. // ...but still none of his prose.
  102. expect(html).not.toContain('PERSONAL-SENTINEL-Theo Bassett')
  103. })
  104. })
  105. describe('the company brief comes in two versions', () => {
  106. it('serves the full one to leadership readers', () => {
  107. // An inbox grant for answering mail is not a grant to read the Leadership
  108. // Team's to-dos: that permission is set per person by name. Getting more
  109. // than you should is the failure that matters. One line in
  110. // meridian-people.ts changes it.
  111. for (const name of ['Nora Lindqvist', 'Sam Whitaker', 'Theo Bassett', 'Ines Carvalho']) {
  112. expect(pageFor(EMAIL[name]!), name).toContain(COMPANY_TEXT)
  113. }
  114. })
  115. it('serves the all-hands one to everyone else', () => {
  116. for (const name of ['Marco Ibáñez', 'Rafael Montero', 'Rosa Galván']) {
  117. const html = pageFor(EMAIL[name]!)
  118. expect(html, name).toContain(ALLHANDS_TEXT)
  119. expect(html, `${name} must not get the Leadership-naming brief`).not.toContain(COMPANY_TEXT)
  120. }
  121. })
  122. it('serves the all-hands one to an unrecognised address', () => {
  123. const html = pageFor('new.hire@meridiandisplay.example')
  124. expect(html).toContain(ALLHANDS_TEXT)
  125. expect(html).not.toContain(COMPANY_TEXT)
  126. })
  127. it('never falls back from all-hands to the full brief', () => {
  128. // If the redacted brief failed to generate, the page must say it is missing
  129. // rather than quietly serving the one that names Leadership work.
  130. const partial: Briefings = { ...briefings, briefings: { company: COMPANY_TEXT } }
  131. const html = render(
  132. FIXTURE_SNAPSHOT, headline(FIXTURE_SNAPSHOT), [], '2026-08-12', partial,
  133. viewerFor(EMAIL['Marco Ibáñez']),
  134. )
  135. expect(html).not.toContain(COMPANY_TEXT)
  136. expect(html).toContain('Not generated for this sync')
  137. })
  138. it('picks the key from the permission, not from the name', () => {
  139. expect(companyBriefingKey(viewerFor(EMAIL['Nora Lindqvist']))).toBe('company')
  140. expect(companyBriefingKey(viewerFor(EMAIL['Marco Ibáñez']))).toBe('company-allhands')
  141. expect(companyBriefingKey(COMPANY_VIEWER)).toBe('company-allhands')
  142. })
  143. })
  144. describe('identity resolution refuses to guess', () => {
  145. it('matches the roster addresses exactly', () => {
  146. expect(viewerFor('nora.l@meridiandisplay.example').person?.name).toBe('Nora Lindqvist')
  147. expect(viewerFor(' NORA.L@MeridianDisplay.example ').person?.name).toBe('Nora Lindqvist')
  148. })
  149. it('does not infer an address from a name', () => {
  150. // Six of seven are role mailboxes: hello@, orderdesk@, accounts@, frontdesk@,
  151. // projects@. Any name-derived rule would be wrong for almost everyone.
  152. for (const guess of ['sam@meridiandisplay.example', 'nora@meridiandisplay.example', 'marco@meridiandisplay.example']) {
  153. expect(viewerFor(guess).person, guess).toBeNull()
  154. }
  155. })
  156. it('falls back to the company viewer, which grants nothing', () => {
  157. for (const bad of [null, undefined, '', ' ', 'attacker@evil.com']) {
  158. const v = viewerFor(bad)
  159. expect(v.person, String(bad)).toBeNull()
  160. expect(v.seesExec).toBe(false)
  161. expect(v.seesLeadership).toBe(false)
  162. expect(v.ownBriefing).toBeNull()
  163. }
  164. })
  165. it('defaults render() to the company viewer when none is passed', () => {
  166. // A caller that forgets the argument must leak nothing. Getting less than you
  167. // should is a bug report; getting more is a breach.
  168. const html = render(FIXTURE_SNAPSHOT, headline(FIXTURE_SNAPSHOT), [], '2026-08-12', briefings)
  169. expect(html).not.toContain(EXEC_TEXT)
  170. expect(html).not.toContain(COMPANY_TEXT)
  171. })
  172. it('renders a variant for every person plus the company fallback', () => {
  173. const vs = allViewers(MERIDIAN_PEOPLE)
  174. expect(vs.map((v) => v.key)).toContain('company')
  175. for (const p of MERIDIAN_PEOPLE) expect(vs.map((v) => v.key)).toContain(p.name)
  176. expect(new Set(vs.map((v) => v.key)).size).toBe(vs.length)
  177. })
  178. })
  179. /**
  180. * An off-roster address keeps its email and gains nothing by it.
  181. *
  182. * Shared desks such as `help@` are granted the inbox by address, in both
  183. * INBOX_READERS and INBOX_SENDERS, and both lists key on the viewer's email.
  184. * A viewer with `email: null` would make those lines present, reviewed and
  185. * inert. A permission that silently does nothing is worse than a missing one,
  186. * because the missing one gets reported by the person who needed it.
  187. *
  188. * The two halves are both load-bearing and are tested as a pair: the address
  189. * is carried, and carrying it grants nothing on its own.
  190. */
  191. describe('an authenticated address nobody claimed', () => {
  192. it('keeps the address Access asserted, so a written grant can find it', () => {
  193. const v = viewerFor('help@meridiandisplay.example')
  194. expect(v.email).toBe('help@meridiandisplay.example')
  195. })
  196. it('normalizes it, so a grant cannot be dodged by casing or whitespace', () => {
  197. expect(viewerFor(' HELP@MeridianDisplay.example ').email).toBe('help@meridiandisplay.example')
  198. })
  199. it('still gets the company view and every permission false', () => {
  200. // Carrying the name is not carrying anything else. This is the half that
  201. // stops the fix from being a widening.
  202. const v = viewerFor('nobody-has-claimed-this@meridiandisplay.example')
  203. expect(v.key).toBe('company')
  204. expect(v.person).toBeNull()
  205. expect(v.seesExec).toBe(false)
  206. expect(v.seesLeadership).toBe(false)
  207. expect(v.ownBriefing).toBeNull()
  208. expect(companyBriefingKey(v)).toBe('company-allhands')
  209. })
  210. it('gives no identity at all to an absent or empty address', () => {
  211. // No address and an unclaimed address are different: the first can hold no
  212. // grant, the second can be granted one by name.
  213. for (const bad of [null, undefined, '', ' ']) {
  214. expect(viewerFor(bad).email, String(bad)).toBeNull()
  215. }
  216. })
  217. it('does not add a rendered variant: there is still one company page', () => {
  218. // allViewers filters on a roster match, so an off-roster address shares the
  219. // company variant rather than getting a KV key of its own.
  220. const vs = allViewers(MERIDIAN_PEOPLE)
  221. expect(vs.filter((v) => v.key === 'company')).toHaveLength(1)
  222. })
  223. })