T-HUB/packages/scheduling/README.md
glass beadian aa5e262f3a Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.

MIT licensed. See README.md and docs/.
2026-10-10 11:56:42 +00:00

56 lines
2.5 KiB
Markdown

# Scheduling
Shared schedule time semantics for WP3 (publishing) and WP13 (multi-output
schedules). Pure functions and types only — no app, database or Temporal
imports, and no runtime dependency beyond Node's `Intl` tzdata.
A schedule is a five-field cron expression plus an explicit IANA timezone
(never server-local; `UTC` allowed). Occurrences are evaluated in that zone as
nominal local date-times, then materialized to UTC fire instants. The
occurrence key is the materialized UTC instant, so `UNIQUE (workspace_id,
schedule_id, fire_at_utc)` holds by construction.
## Policies
- **DST gap** (nonexistent local time): fires at the first valid instant after
the gap (shift forward). Works for 1-hour gaps and Pacific/Apia's whole-day
skip alike.
- **DST fold** (ambiguous local time): fires ONCE, at its first (earlier-offset)
instant.
- **Collapse**: several nominal local times materializing onto one post-gap
instant produce exactly one occurrence, labelled with the latest collapsed
slot — the conservative value for the `last_fired_nominal_local` gate.
- **Re-materialization** on tzdata or timezone change: deletes all unclaimed
future occurrences, then regenerates only occurrences strictly after
max(change instant, last claimed fire instant) whose nominal slot is strictly
later than `last_fired_nominal_local`. A slot already fired under the old
zone (daily 09:00 fired in New York, zone switched to Los Angeles) never
fires again. The tzdata version is reported for recording.
## API
```ts
import {
parseCron, // five-field cron -> sets (rejects 6-field/`?`/`L`/`W`/`#`/macros)
nextOccurrences, // (spec, from, count, {exclusive}) -> Occurrence[]
materializeOccurrence, // ("2024-03-10T02:30", zone) -> epoch ms
rematerialize, // (oldSpec, newSpec, storedRows, {changeInstant, ...})
canonicalTimezone, // validates + canonicalizes; rejects "local"/offsets
currentTzdataVersion, // process.versions.tz
} from "@tactical-media-broadcast-hub/scheduling";
```
Errors: `SchedulingError` base, with `InvalidCronError`, `ImpossibleScheduleError`,
`InvalidTimezoneError`, `InvalidNominalLocalError`, `InvalidInstantError`. Note
`toInstantMs` rejects offset-less ISO datetimes that would otherwise parse in
the server's local zone.
## Verification
```
pnpm exec tsc -p tsconfig.json --noEmit
pnpm exec vitest run
```
`pnpm install` is required once so the workspace links this package's
`vitest`/`typescript` devDependencies into `packages/scheduling/node_modules`.