Repository navigation
v0.7.0
Before you upgrade
This upgrade is one-way. Migrations run forward only. An older
binary opens a newer database without complaint and then runs every
destination with an empty stream key — enabled, live, publishing
nothing, with no error anywhere. Rolling back is not a supported
recovery path; restoring your backup is.
Back up the whole data directory, not just polyemesis.db.
Since 0.7.0 destination stream keys are sealed at rest with
secret.key. A database restored without that file comes back with
every destination disabled — by design, because a key that will not
open disables its destination rather than failing open. That restore
looks completely successful until someone goes live.
Re-run install.sh before upgrading an install older than 0.7.0.
update.sh and uninstall.sh are written to disk when you install,
so an older install is running older helpers — including one whose
backup check does not know to look for secret.key. Re-running the
installer regenerates them. See
docs/UPGRADING.md.
Windows is tested, not operated. Two known unresolved defects:
recordings are truncated on service stop, and #440 — an intermittent
Go runtime abort that ends the process mid-broadcast. See
docs/INSTALL.md.
Verify your download against SHA256SUMS, published below.
What's Changed
- council/findability: the vocabulary nobody searches for, and the comparison that omitted the incumbent by @rainmanjam in #146
- testing workstream: #103 and #96 fixed, #94's premise corrected, #107 started by @rainmanjam in #153
- A seam ledger for #126, and the roadmap correction from #108 by @rainmanjam in #152
- Audio can be copied bit-for-bit to SRT and file destinations (#144), plus a harness for #141 by @rainmanjam in #151
- Wire internal/upgrade behind three gated endpoints, and let the banner use them by @rainmanjam in #149
- Security: token upload bypass (#140), token scopes (#104), partial SRT bind (#105) by @rainmanjam in #150
- read means metadata, and library search was serving the words (#154) by @rainmanjam in #171
- Redaction contract, webhook-path disclosure, and three authorization behaviours (#162, #160, #159, #158, #170) by @rainmanjam in #172
- The ledger repeated the pathology it was built to catch: 42 of 50 sweeps had nothing to assert by @rainmanjam in #173
- ci(security): gitleaks scanned every fetched branch, so any branch reddened every PR (#174) by @rainmanjam in #175
- The ledger's claims about itself: a dead regeneration command, a bare-# discharge, and three shape proofs that resolved to nothing by @rainmanjam in #178
- Three termination incidents in one day: the interval between asking a process to die and observing it dead (#179, #180) by @rainmanjam in #199
- feat(media): probe an upload before accepting it, and show what it is by @rainmanjam in #118
- test: sixteen mutation-verified tests for the handlers that destroy things by @rainmanjam in #219
- test(engine): eight mutation-verified gaps in the selector, and the sweep that read the wrong hub by @rainmanjam in #225
- fix(routing): a bodyless preset apply now uses the defaults the catalogue advertises by @rainmanjam in #224
- test(api): the chunked case sent the defaults, so it could not fail by @rainmanjam in #232
- test(api): pin #171's transcript split, and both halves of the annotations write by @rainmanjam in #227
- test(api): the stored webhook, the surviving hook, and the empty-buffer 409 by @rainmanjam in #228
- chore(deps): bump modernc.org/sqlite from 1.55.0 to 1.56.0 in the go-minor-and-patch group by @dependabot[bot] in #212
- chore(deps): bump hls.js from 1.6.16 to 1.6.17 in /ui by @dependabot[bot] in #214
- chore(deps-dev): bump the dev-tooling group in /ui with 2 updates by @dependabot[bot] in #213
- chore(deps): bump lucide-react from 1.28.0 to 1.30.0 in /ui by @dependabot[bot] in #215
- fix(jobs): a state conflict is 409, and a running job is not deletable (#220, #221) by @rainmanjam in #233
- security board round: the pull-URL disclosure, the playout Vary, the upload TOCTOU, and the gitleaks cross-blame by @rainmanjam in #234
- fix(instruments): -cover on internal/api measured the preflight; a missing ffprobe skipped the upload gate by @rainmanjam in #235
- fix(api): /sources handed a read token the pull credential #234 missed by @rainmanjam in #236
- docs: the four breaking changes the CHANGELOG did not mention by @rainmanjam in #237
- test: the two branches that echoed the MQTT broker password, and eight tests that can each be made to fail by @rainmanjam in #238
- test(supervisor): the start-delay budget read Windows spawn cost as a reapplied delay by @rainmanjam in #239
- test(api): retire the playout OPTIONS deferral, and record why its tripwire never fired by @rainmanjam in #240
- docs(changelog): the security fix, the two shipped features, and one Fixed list instead of two by @rainmanjam in #241
- style(scripts): clear 40 of 45 SonarCloud shell findings; 4 quarantined with measured evidence by @rainmanjam in #252
- Seven-agent issue sweep: three shipped disclosures, the engine lifecycle, and the first attributed #126 occurrence by @rainmanjam in #251
- fix(engine): the scrubber collected a decoded password and searched a raw argv by @rainmanjam in #242
- uploads: the sweep, the claim window, the verdict contract and the pull-source gate (#185, #186, #188, #201, #203) by @rainmanjam in #244
- test(api,web): the shape registry runs its proofs (#176), the NotFound probes record their branch (#167), and /assets/ stops listing the bundles by @rainmanjam in #245
- CI and product cluster: strand-free clip exports, upgrade audit events, a split build step, and measurements for four issues that stay open by @rainmanjam in #246
- test(engine): the child lifecycle, and the ffprobe #183 asks for by @rainmanjam in #247
- test(rigor): a surviving backoff mutant, a floor with no ceiling, four guessed intervals, and a guard that could not see itself by @rainmanjam in #248
- The termination class: the interval between asking a process to die and observing it dead (#192, #193, #194, #196, #197, #207, #208, #209, #210) by @rainmanjam in #250
- test(alerts): the disclosure test raced the loop it did not need (#249) by @rainmanjam in #253
- fix(ffmpeg): pin the file protocol on the engine's pull input (#201); scope the re-verify job (#202) by @rainmanjam in #254
- test(ui): move the drift guards that could not see a render into the browser (#107) by @rainmanjam in #258
- ci: retry the FFmpeg download, which hard-failed two jobs today by @rainmanjam in #256
- test(api): the non-GET denials had no positive control; #157's residual is now a differential census by @rainmanjam in #257
- ci: retry apt, which is the install that has actually been failing by @rainmanjam in #260
- ledger: the done-criterion, written down and met on four of its five conjuncts (#156, #157, #167, #168) by @rainmanjam in #268
- feat(uploads): the third verdict state — a refusal the record can hold, and its four consumers by @rainmanjam in #264
- ci(site): a Cloudflare Pages deploy for web/, gated on a secret that does not exist yet by @rainmanjam in #262
- measure: predictedStepMs is a start-lag, the flake instrument's blind spot is 70% of failures, and #190 splits in two by @rainmanjam in #263
- feat(ffmpeg): count the length of a raw elementary stream instead of refusing it (#218) by @rainmanjam in #267
- fix(sources): the pull-upload gate was on the route the engine does not read (#255); two Dashboard drift guards become browser tests (#259) by @rainmanjam in #266
- feat(uploads): a re-verify job that records only what it established (#202) by @rainmanjam in #269
- ci: one FFmpeg source and one version on Linux and Windows, and macOS's gap named by @rainmanjam in #272
- test(api): the fake probe becomes a portable binary, and ten upload-probe tests stop skipping on Windows (#265) by @rainmanjam in #270
- feat(engine): an inherited pull source is re-checked at reconcile — unchecked warns, refused stops the ingest (#255) by @rainmanjam in #271
- docs(changelog): cut 0.7.0, and the four entries it was missing by @rainmanjam in #261
- docs(changelog): 0.7.0 carries the date it is tagged, not the day it was drafted by @rainmanjam in #276
- fix(playlist): a refusal recorded after normalisation names itself, and an item already on air keeps playing by @rainmanjam in #277
- fix(engine): the seam ledger stops publishing a step it never measured by @rainmanjam in #278
- ci: cache the Playwright browser so a CDN outage cannot fail the browser suite by @rainmanjam in #280
- fix(testenv): the UDP wait observes the socket table instead of competing for it by @rainmanjam in #281
- fix(acceptance): the mismatch step judges delivery by produced media, not by file size by @rainmanjam in #283
- ci(flake-rate): a Go test can now be repeated, on ubuntu, macOS or Windows by @rainmanjam in #282
- test(api): the bare-UI probe builds its own empty filesystem instead of borrowing the embedded one by @rainmanjam in #285
- test(ui): the four #107 controls, each watched to fail by @rainmanjam in #286
- fix(acceptance): finalise the mismatch destination before measuring its file by @rainmanjam in #288
- test(api): the shape list said this API emits four response headers; it emits sixteen (#168) by @rainmanjam in #287
- ci(flake-rate): state the floor where the run count is chosen, and stop 0.5% reading as a gate that was met (#206) by @rainmanjam in #289
- test(api): the seven routes that 404'd because nobody wrote the row (#163) by @rainmanjam in #292
- ci: cache the FFmpeg build so a refusing CDN cannot fail three jobs by @rainmanjam in #294
- test(acceptance): the DTS detector reads a container that stores DTS by @rainmanjam in #293
- ci(flake-rate): halve the default dispatch, and record why the cap is not the lever by @rainmanjam in #295
- test(api): the payload shapes name themselves in a media type, and one of them had no row (#168) by @rainmanjam in #296
- fix(db): the state database is private, and both deployments stop making its directory world-traversable by @rainmanjam in #299
- fix(db): secure the WAL and SHM sidecars too, which an existing install does not inherit by @rainmanjam in #300
- feat(db): destination stream keys are sealed at rest, and a key that will not open disables the destination by @rainmanjam in #301
- test(multistream): measure that four platforms each get THEIR mix, not A mix (#141) by @rainmanjam in #298
- fix(multistream): re-anchor the key-safety control now that stream keys are sealed by @rainmanjam in #302
- test(multistream): a LOOPBACK far end — one destination received by polyemesis's own RTMP ingest, and a wrong key refused by @rainmanjam in #303
- fix(api): a source reports what a delete would actually take with it by @rainmanjam in #305
- security: the stored spelling of a stream key and the spelling that reached the wire were allowed to differ (#306, #307) by @rainmanjam in #309
- test(multistream): a live run measures over 75s, because a platform preview is not instant by @rainmanjam in #308
- security: a refused destination wrote its stream key to server.log on every retry (#310) by @rainmanjam in #311
- feat(services): a platform registry seeded from OBS, and the Kick preset that could not publish (#312) by @rainmanjam in #313
- fix(platforms): the Kick note denied a key we fetch, and six presets had no capability row by @rainmanjam in #314
- test(multistream): the source is a real broadcast shape, not 360p by @rainmanjam in #315
- test(chat): a suite that opens a socket to the real platforms by @rainmanjam in #316
- docs(site-deploy): the mail records are not parking records by @rainmanjam in #317
- fix(probe): the stream key belongs in the URL fragment — and Twitch accepts multitrack by @rainmanjam in #319
- feat(chat): a fifth platform — Rumble's live-stream API does carry chat by @rainmanjam in #321
- test(oauth): most of an OAuth acceptance suite needs no credential, because a provider's OAuth surface is public by @rainmanjam in #322
- test(automod): a suite that talks to a real endpoint, and the endpoint's own key was in server.log by @rainmanjam in #324
- test(hooks): a live suite for the package whose every unit test replaces the HTTP client by @rainmanjam in #325
- test(transcribe): a live suite against the real model host, and three defects it found by @rainmanjam in #323
- test(engine): a respawn gate that waits on a feed which has not started yet (#290) by @rainmanjam in #327
- build: Go 1.26.6, for two stdlib advisories published today by @rainmanjam in #329
- fix(supervisor): a stop that arrives mid-spawn signalled nothing, and the child ran on unsignalled (#126) by @rainmanjam in #330
- feat(ffmpeg): RTMP egress can carry a second audio track, measured arriving as two distinct tracks (#141) by @rainmanjam in #331
- feat(twitch): Enhanced Broadcasting — the negotiated config, the VOD audio track, and the GPU gate nobody expected (#326) by @rainmanjam in #328
- build(web): Workers Static Assets, because Cloudflare stopped offering Pages projects by @rainmanjam in #332
- feat(twitch): Enhanced Broadcasting — two mixes in one graph, and a negotiation that falls back quietly (#326) by @rainmanjam in #333
- fix(web): Workers appends header values, so no-cache defeated every immutable asset by @rainmanjam in #334
- feat(web): a lightbox for the feature screenshots, and the third font preloaded by @rainmanjam in #335
- feat(web): version in the footer, who it is not for, and screenshots re-shot at 61% less by @rainmanjam in #336
- feat(web): stop repeating the comparison table, and give touch users the affordances the cursor was carrying by @rainmanjam in #337
- feat(web): unify code blocks, fix the tablet band, break the /features conveyor belt by @rainmanjam in #338
- test(srtserver): the partial-bind test reserved one half of a port and hoped for the other by @rainmanjam in #339
- docs: what shipped since v0.6.0, the copy it justifies, and the four claims that would have been false by @rainmanjam in #340
- docs: encoding, plus the two gaps it found — settable rate control (#341) and VFR timing pinned (#342) by @rainmanjam in #343
- docs: multitrack is about Enhanced versus classic RTMP, not RTMP versus SRT by @rainmanjam in #344
- feat(web): the second audio mix, five chat platforms, and one comparison row with blank cells by @rainmanjam in #345
- feat(ui): a password strength meter, and the truth about what that password protects by @rainmanjam in #346
- fix(ui): the first-run screen used four tokens from the marketing site's palette, not this one by @rainmanjam in #352
- fix(install): a binary install gets a guarded update.sh too, and both modes check for secret.key by @rainmanjam in #348
- ci: documentation-only pull requests stop running the acceptance matrix by @rainmanjam in #351
- test(install): the guard that stops an upgrade eating secret.key had no test (#348) by @rainmanjam in #353
- test(renditions): a stored maxrate is only proven by the encoder that was actually started (#341) by @rainmanjam in #354
- ci(transcribe): the suite that checks twenty hardcoded claims about somebody else's server ran nowhere by @rainmanjam in #355
- test(obs): the suite that disproved OBS multitrack ran nowhere, and could not have seen it change by @rainmanjam in #356
- ci: a skipped matrix job creates no per-leg contexts, so #351 made docs-only PRs unmergeable by @rainmanjam in #357
- docs: fold the coverage census into the note that already existed by @rainmanjam in #349
- release: consolidate everything v0.7.0 needs into one branch by @rainmanjam in #363
- docs(changelog): fold Unreleased into 0.7.0, and write up the thirty PRs it was missing by @rainmanjam in #358
- fix: the three things that must be true before v0.7.0 is tagged or its advisories published by @rainmanjam in #359
- docs: two pages that state the opposite of what the code does by @rainmanjam in #360
- feat(multitrack): a toggle that told the truth about nothing now decides where a broadcast publishes (#141, #326) by @rainmanjam in #361
- fix(ffmpeg): six of the twelve offered encoders had no profile, and hevc_vaapi could not start (#343) by @rainmanjam in #362
- docs(web): two false claims about a competitor, and a precondition the copy did not know about by @rainmanjam in #364
- security: seven findings from the v0.7.0 audit, one of them already true on every upgraded install by @rainmanjam in #366
- feat(routing): an editor for the second (VOD) audio mix, and an EXPERIMENTAL convention for the two features nobody has watched work by @rainmanjam in #365
- test(ui-drift): ten recovered guards, each one watched to fail against the defect it names by @rainmanjam in #367
- docs(changelog): fold Unreleased into 0.7.0 again, and record the drift guards by @rainmanjam in #368
- fix(acceptance): a sleep was doing two jobs, and only one of them was about time by @rainmanjam in #370
- test(ladder): three tiers, four destinations, and nobody had counted the encodes by @rainmanjam in #369
- chore: remove setup.png, which nothing has referenced since the day it landed by @rainmanjam in #371
- docs(notes): what tests each area, and how to run the one suite that reaches real platforms by @rainmanjam in #373
- fix: two things a fresh install showed that no test could by @rainmanjam in #385
- feat(config): say something when TLS is on and the listener is not 443 by @rainmanjam in #382
- fix(ui): the mobile drawer had one way out, and it was the one nobody tries by @rainmanjam in #386
- feat: the website, the docs, the tour, and the first step of #387 by @rainmanjam in #397
- fix(web): eleven copy defects three independent reviewers found by @rainmanjam in #400
- chore: record what shipped today, and stop a tag stranding it (#377) by @rainmanjam in #403
- ci: a docs-only PR still bought three OS matrices, and four guards a comment could satisfy (#378, #379) by @rainmanjam in #402
- Walk an operator through Let's Encrypt from the Security tab (#381) by @rainmanjam in #405
- chore(docs): internal working documents leave a public repository by @rainmanjam in #410
- feat(api): an install with no source can be looked at (#387) — PR 2 of 6, stacked on feat/387-1-replumb by @rainmanjam in #399
- fix(cli): the startup banner told a working install to configure its ingest by @rainmanjam in #412
- fix(web): the contrast failure Lighthouse found by @rainmanjam in #414
- docs: six things the docs said that the code does not do by @rainmanjam in #415
- feat(web): a pillar page for the head term, and the two pages the research earned by @rainmanjam in #413
- ci(sonar): move SonarCloud off automatic analysis, so this repo's own settings bind by @rainmanjam in #411
- feat: platform capability expansion — viewer stats, broadcast lifecycle, and the evidence behind them by @rainmanjam in #416
- fix(installer): offer to install FFmpeg when the host is below the floor, and render mermaid on the docs site by @rainmanjam in #417
- fix(installer): ask the binary what it can do, and the last two poka-yoke gaps by @rainmanjam in #418
- Debug mode, three user-facing bug fixes, and the test gaps a council found by @rainmanjam in #420
- Bound the debug bundle by bytes, not only by record count (#421) by @rainmanjam in #422
- build(deps): bump hls.js from 1.6.17 to 1.7.0 in /ui by @dependabot[bot] in #426
- build(deps): bump sonner from 2.0.7 to 2.0.8 in /ui by @dependabot[bot] in #428
- Pre-tag review: fix every confirmed finding, and the prose they made false by @rainmanjam in #429
- Fold [Unreleased] into [0.7.0] for the tag (#374) by @rainmanjam in #423
- Make the acceptance apt budget fit its ceiling, and fall back to another mirror (#430) by @rainmanjam in #431
- build(deps): bump lucide-react from 1.30.0 to 1.31.0 in /ui by @dependabot[bot] in #427
- build(deps): bump golang.org/x/crypto from 0.54.0 to 0.55.0 in the go-minor-and-patch group across 1 directory by @dependabot[bot] in #424
- build(deps-dev): bump oxlint from 1.77.0 to 1.78.0 in /ui in the dev-tooling group across 1 directory by @dependabot[bot] in #425
- Ignore the gitnexus index by @rainmanjam in #432
- The pre-release review: nine fixes, three of which lose data by @rainmanjam in #443
- fix(media): cancelling a job hung for ever on a grandchild that outlived it by @rainmanjam in #437
- fix(recording): the sweep deleted the subtitles of recordings that still existed by @rainmanjam in #434
- fix(automod): the model key and the spend counters were stored but never read by @rainmanjam in #435
- fix(db): renaming a hook destroyed a secret that was only unreadable by @rainmanjam in #436
- fix(db): a library with enough recordings in it rendered as if it were empty by @rainmanjam in #438
- fix(install): four defects found by actually running the installer by @rainmanjam in #449
- Everything from today: installer follow-ups, CI reliability, and the docs that were missing by @rainmanjam in #451
- fix(#453): a loudness monitor started for a destination that had already been deleted by @rainmanjam in #454
- docs(changelog): the work since the nine review fixes, into 0.7.0 by @rainmanjam in #455
- fix(relay): the receive buffer was set to 17% of FFmpeg's default, in the name of adding slack by @rainmanjam in #456
- fix(#430): the apt retries could not succeed once the first attempt was killed mid-dpkg by @rainmanjam in #458
- feat(#398): say where the recording actually went by @rainmanjam in #457
- ci(#440): run the race detector on Windows, where the crash actually happens by @rainmanjam in #459
- fix(#462): a destination on its way out reported as having no process at all by @rainmanjam in #463
- fix(web): three screenshots are 404ing on /features, and none of them could have a srcset by @rainmanjam in #464
- fix(#430): the failover suite could not say why a destination had no process by @rainmanjam in #461
- fix(ui): the app wore a different logo from the website, in three places by @rainmanjam in #465
- docs: two ways to connect an account without a public address, both already shipping by @rainmanjam in #466
- test(#460): the late subscriber is not starved — measured, on the binaries that ship by @rainmanjam in #467
- fix(ui): the delete dialog asked for a string it was misspelling by @rainmanjam in #470
- fix(probe): a probe on a quiet relay waited for ever, and the destination hold waited on it by @rainmanjam in #469
- fix(supervisor): a stop that finished on its deadline was reported as a timeout by @rainmanjam in #468
- fix(ui): the ingest URL was copied with still in it by @rainmanjam in #471
- fix(ui): the app's favicon was unparseable XML, so the tab kept the old logo by @rainmanjam in #476
- fix(web): a missing file was answered with the SPA, and the app shipped no .ico by @rainmanjam in #477
- fix: six open issues — the late-join probe budget, a header nobody wrote, and an unsafe.Pointer that moved by @rainmanjam in #475
- feat(dashboard): a preview per source, that follows what is on air and stops when it ends by @rainmanjam in #472
- fix(api): a destination was answered for by the first programme's engine, not its own by @rainmanjam in #478
- feat: pick the programme a destination carries, instead of the server picking by @rainmanjam in #479
- fix(ui): 47 mistake-proofing defects, and the toolchain they were found on by @rainmanjam in #480
- feat(dashboard): group destinations under the programme they carry by @rainmanjam in #481
- feat(dashboard): a lane per programme, so position says which is which by @rainmanjam in #483
- chore(ci): let flake-rate run the Go mode under -race, for #440 by @rainmanjam in #482
- test(engine): an unrelated supervisor warning failed the no-feed test, twice (#474) by @rainmanjam in #484
- fix(engine): the hold was bounded on a clock nobody was measuring (#473) by @rainmanjam in #485
- test(engine): the silence-tier test asserted a state it could not reach (#161) by @rainmanjam in #486
- docs(changelog): the nine merges since #475, and the entry [Unreleased] was holding (#374) by @rainmanjam in #487
- chore: remove three verification screenshots committed by accident by @rainmanjam in #488
- fix: the poka-yoke audit's findings, verified before fixed by @rainmanjam in #489
- fix: three pre-tag fixes, and a bug in the guard added yesterday (#504 #505 #509) by @rainmanjam in #511
- Pre-tag integration: two status fixes, the RTMP API migration, dependency upgrades, and an honest comparison table by @rainmanjam in #522
- docs: seven restream.io rows the comparison table was missing by @rainmanjam in #513
- fix: two status lies a demo capture photographed by @rainmanjam in #517
- chore(deps): npm minor and patch upgrades by @rainmanjam in #519
- chore(deps): pin @types/node to the oldest supported node, not the newest by @rainmanjam in #521
- fix(rtmp): take the gortmplib v1.0.1 bump with the API migration it needs by @rainmanjam in #518
- fix(api): the status payload and the scrape describe the box, not programme 1 by @rainmanjam in #523
- fix(ui): the dashboard's bitrate, and the destination list flipping between programmes by @rainmanjam in #524
- Pre-tag: 68 findings fixed, two platforms added, and the restyle by @rainmanjam in #604
- docs: date the 0.7.0 heading, which is what lets the tag through by @rainmanjam in #501
- Restyle: redundant encoding, density, drill-down IA, and an honest website by @rainmanjam in #525
- docs: 24 drift findings, every one verified against the code first by @rainmanjam in #595
- fix(release): the installer stops deleting installs it did not create, and the gates stop trusting their own premises by @rainmanjam in #596
- fix(db): open a real v0.6.0 database in the test suite, and stop copying broken migrations by @rainmanjam in #597
- fix(api): resolve the programme the request names, in sixteen more places by @rainmanjam in #598
- fix(ui): every programme-shaped request names a programme by @rainmanjam in #599
- fix: the backup key survives recovering the primary, and PLATFORMS.md gets its ninth column back by @rainmanjam in #600
- feat(trovo): OAuth sign-in, stream key, metadata and viewer stats by @rainmanjam in #602
- feat(vimeo): OAuth sign-in, and a gate the operator is told about before it bites by @rainmanjam in #603
- Four devices for the gap between "tests pass" and what CI runs by @rainmanjam in #605
- chore(deps): bump docker/setup-buildx-action from 4.2.0 to 4.3.0 by @dependabot[bot] in #490
- Nine defects a second programme made visible, and three guards that close their classes by @rainmanjam in #611
- The features page claimed two features and illustrated one by @rainmanjam in #616
- #440: an instrument that does not depend on someone reading the log; #398: four hypotheses ruled out by @rainmanjam in #617
- The changelog was a day behind, and a tag is next by @rainmanjam in #618
- The empty-changelog sentinel, and the gate that reads it by @rainmanjam in #619
- Date 0.7.0 to the day it actually ships by @rainmanjam in #620
Full Changelog: v0.6.0...v0.7.0