timothy d52268f2ed feat(610): migration script + field-level equivalence harness
The migration is a MOVE, so correctness is provable rather than reviewable:
parse the corpus before, migrate, parse after, assert the Record sets are
identical field-for-field with byte-identical rationale.

scripts/migrate_decisions_split.py
  166 records -> docs/decisions/records/<area>/<topic>.md (active) or
  docs/decisions/archive/<area>/<topic>.md (superseded/retired), 26 directories.
  Path is DERIVED from the key, so identity stays the key. Refuses to run if any
  record lacks a key, and aborts on a destination collision.

  Source files are KEPT, stripped to their narrative -- not deleted. 506 lines of
  the corpus sit outside any record: decisions.md's lifecycle-schema header (the
  status vocabulary, supersession rules, the edit-token contract) and each topic
  file's preamble explaining why those records exist. For most of it that is the
  only copy. It also cannot be filed per-area -- topic files hold several areas
  and 4 of 23 areas span several files -- so the files themselves stay.

scripts/tests/test_migration_equivalence.py
  Runs the real migration against a COPY of the real corpus in tmp_path, never
  the working tree. Asserts: all 166 records survive with the same keys, every
  metadata field round-trips, titles round-trip from headings, every rationale
  body is byte-identical, path matches key, archived records land in the archive
  wing, the legacy files keep their narrative, and no parseable record is left
  behind in them.

Proven non-vacuous: corrupting one migrated record's prose is caught by the
byte-identical check, and deleting one is caught by the survival check.

One test-authoring note: an early assertion string-matched "## " to prove no
records were left in decisions.md. That is wrong -- the schema header quotes an
illustrative "## 2026-07-17 ..." example in prose. Whether records remain is a
PARSING question, so the parser-based leftover test is the real invariant.
2026-07-25 19:08:58 +02:00
2022-04-28 06:56:01 -05:00
2025-11-11 06:12:46 -06:00
2021-02-08 21:13:53 -06:00
2023-08-13 09:33:33 -05:00
2025-11-26 10:49:01 -06:00
2026-02-24 15:27:51 -06:00

ErsatzTV

ErsatzTV lets you transform your media library into a personalized, live TV experience - complete with EPG, channel scheduling, and seamless streaming to all your devices. Rediscover your content, your way.

contact features community

epg-example

How It Works

  1. Install ErsatzTV: Download and set up the server on your system.
  2. Add Your Media: Connect your media libraries and collections.
  3. Create Channels: Design and schedule your own live channels.
  4. Stream Anywhere: Watch on any device with IPTV and EPG support.

Key Features

  • Custom channels: Create and schedule your own live TV channels.
  • IPTV & EPG: Stream with IPTV and Electronic Program Guide support.
  • Hardware Transcoding: High-performance streaming with hardware acceleration (NVENC, QSV, VAAPI, AMF, VideoToolbox)
  • Media Server Integration: Connect Plex, Jellyfin, Emby and more.
  • Music & Subtitles: Mix music videos and enjoy subtitle support.
  • Open Source: Free, open, and community-driven project.

Documentation

Documentation is available at ersatztv.org.

License

This project is inspired by pseudotv-plex and the dizquetv fork and is released under the zlib license.

S
Description
Fork of ErsatzTV - custom IPTV channel server (upstream archived Feb 2026)
Readme Zlib
79 MiB
v26.11.0
Latest
2026-07-20 22:22:04 +02:00
Languages
C# 64.6%
TypeScript 15.7%
Python 10.1%
JavaScript 5.8%
Shell 2.6%
Other 1.1%