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

push-guard.ts

Language TypeScript3.8 KB96 lines

apps/dashboard/src/push-guard.ts

  1. /**
  2. * Whether the local artifacts are safe to publish to the deployed board.
  3. * Runtime-neutral and pure, so it can be tested without a filesystem or a network.
  4. *
  5. * `pnpm push` writes straight into the Worker's KV, which is what the team
  6. * reads. There is no review step between a laptop and that
  7. * page, so the checks that would normally be someone noticing have to be here.
  8. *
  9. * The one that matters most is fixture mode. `pnpm dashboard --fixtures` writes
  10. * the same filenames as a live run, so a push immediately after one would put
  11. * sample data on the production board under "live" source pills: the exact
  12. * failure `Snapshot.mode` exists to prevent, with an audience.
  13. */
  14. export interface PushInputs {
  15. /** `mode` from briefing-payload.json. */
  16. payloadMode: string | null | undefined
  17. /** `generatedAt` from briefing-payload.json, equal to the sync's syncedAt. */
  18. payloadGeneratedAt: string | null | undefined
  19. /** Length of dashboard.html, to catch a truncated or empty render. */
  20. htmlBytes: number
  21. /** `mode` from briefings.json. Null when no briefings exist yet. */
  22. briefingsMode?: string | null
  23. /** `payloadGeneratedAt` from briefings.json: the sync the prose describes. */
  24. briefingsPayloadGeneratedAt?: string | null
  25. /** How many briefings were written. */
  26. briefingCount?: number
  27. }
  28. export interface PushVerdict {
  29. ok: boolean
  30. /** Why the push must not happen. Non-empty means refuse. */
  31. refusals: string[]
  32. /** Worth saying out loud, but not a reason to stop. */
  33. warnings: string[]
  34. }
  35. /** A page this small is not a dashboard; the last render must have failed. */
  36. const MIN_HTML_BYTES = 20_000
  37. export function checkPush(input: PushInputs): PushVerdict {
  38. const refusals: string[] = []
  39. const warnings: string[] = []
  40. // Fixtures must never reach the deployed board. Sample data renders under the
  41. // same filenames as live data; only `mode` distinguishes them.
  42. if (input.payloadMode === 'fixtures') {
  43. refusals.push(
  44. 'briefing-payload.json is FIXTURE data. Pushing it would put sample orders on ' +
  45. 'the board the team reads. Run `pnpm dashboard` (no --fixtures), then push.',
  46. )
  47. }
  48. if (input.briefingsMode === 'fixtures') {
  49. refusals.push(
  50. 'briefings.json was written from FIXTURE data, so its prose names people as ' +
  51. 'holding work that is not real. Regenerate it from a live sync.',
  52. )
  53. }
  54. if (input.payloadMode !== 'live' && input.payloadMode !== 'fixtures') {
  55. refusals.push(
  56. `briefing-payload.json has mode ${JSON.stringify(input.payloadMode)}, which this ` +
  57. 'does not recognise. Refusing rather than guessing whether it is real.',
  58. )
  59. }
  60. if (!input.payloadGeneratedAt) {
  61. refusals.push('briefing-payload.json has no generatedAt, so nothing on it can be dated.')
  62. }
  63. if (input.htmlBytes < MIN_HTML_BYTES) {
  64. refusals.push(
  65. `dashboard.html is only ${input.htmlBytes} bytes, well under the ${MIN_HTML_BYTES} a ` +
  66. 'real render produces. Re-run `pnpm dashboard` and check it for errors.',
  67. )
  68. }
  69. // Briefings are optional. Absent is a normal state and the page says so, but it
  70. // is worth naming: a silent push of numbers-only looks identical to a push
  71. // that included the prose.
  72. if (!input.briefingCount) {
  73. warnings.push(
  74. 'No written briefings to push. The board will render the deterministic ' +
  75. 'briefing and say the written one was not generated. Run `pnpm morning` for both.',
  76. )
  77. } else if (
  78. input.briefingsPayloadGeneratedAt &&
  79. input.briefingsPayloadGeneratedAt !== input.payloadGeneratedAt
  80. ) {
  81. warnings.push(
  82. 'The briefings were written from an EARLIER sync than the page being pushed, so ' +
  83. 'the board will carry a staleness caveat. Run `pnpm morning` to pair them.',
  84. )
  85. }
  86. return { ok: refusals.length === 0, refusals, warnings }
  87. }