Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ takes priority over features, throughput, and presentation.
[docs/release-plan.md](docs/release-plan.md) for the proposed Windows beta.
The release plan describes future work; verify the checkout before claiming
an installer, signing pipeline, or release gate exists.
- Packaging, signing, the bill of materials and the tag-triggered candidate
workflow are in [docs/build-windows.md](docs/build-windows.md); what an
update checks before it runs anything is in
[docs/updates.md](docs/updates.md). Hardware-key signing and clean-machine
qualification are still gates, so a build being implemented is not a
release being possible.
- Inspect the working tree before edits. Preserve unrelated user changes.
Keep this guide concise and link to detailed documentation rather than
copying it wholesale.
Expand Down Expand Up @@ -56,6 +62,7 @@ All module paths below are relative to `src/offloader/`.
| Timeline import | `timeline.py`: optional OpenTimelineIO integration, media resolution, and ambiguity handling |
| Desktop | `gui/main_window.py`, `gui/worker.py`, `gui/queue_view.py`, mode/editor widgets, and `gui/drives.py` |
| Persistent state | `config.py`, `presets.py`, `history.py`; `volumes.py` discovers storage and `naming.py` handles naming |
| Installation and updates | `installation.py` and `installation_lock.py` for transactional maintenance and the shared installed-instance lock; `update.py` finds, verifies and hands over a release, wrapped for the app by `gui/updates.py`. Neither may force-close a running transfer; see [docs/updates.md](docs/updates.md) |

Python 3.10+ is supported. Core dependencies are xxhash and ReportLab; PySide6
is the GUI extra. Timeline dependencies are separate extras. ffmpeg/ffprobe
Expand Down
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,40 @@ manifest lists and exits non-zero if anything is off, so a format script can gat
on it. `--allow-cache` skips the page-cache eviction — faster, and may verify
memory rather than the device.

### `update`

```sh
offloader update # is there a newer release?
offloader update --install # download it, verify it, run the installer
```

GitHub Releases is the feed, so there is no manifest server and no second
place a version number is recorded. Before anything runs, the download has to
be served from an allowlisted host *after* redirects, hash to what was
computed while streaming, and carry a valid Authenticode signature with this
project's certificate thumbprint, its publisher name, and an embedded
`FileVersion` matching the release. That last check is what stops an older
but still validly signed installer being re-served under a newer asset name.

One limit, stated because this page is meant to describe the checkout rather
than the plan: the feed is GitHub's `/releases/latest`, which is documented as
excluding prereleases. The first packaged release is planned as `0.1.0b1`, so
an installed beta cannot discover the next one through it, and a repository
holding only betas answers with nothing at all. The version grammar already
orders prereleases; it is the endpoint that does not carry them. Reading the
releases collection instead is the correction pending on
[#13](https://github.com/owenpkent/offloader/pull/13).

**It never closes a running Offloader.** The installer refuses maintenance
while a transfer is in flight, which is the guarantee rather than a
limitation, so updating means finishing or cancelling the job first. The
command says so before the elevation prompt appears.

The desktop app does the same thing from **Help, Check for updates now**, and
checks once on launch unless that is turned off in Options. Full detail,
including what is deliberately not copied from the reference implementation,
is in [`docs/updates.md`](docs/updates.md).

### Verification modes

| Mode | What it does | Catches |
Expand Down Expand Up @@ -516,7 +550,8 @@ general-purpose tool reports a filename, a size, and a placeholder icon.
| --- | --- |
| [`ROADMAP.md`](ROADMAP.md) | What is next, why, and what this will not become |
| [`docs/release-plan.md`](docs/release-plan.md) | Windows beta release sequence, packaging, signing, acceptance gates, and recovery |
| [`docs/build-windows.md`](docs/build-windows.md) | Build, sign, and check Windows desktop bundles and installers |
| [`docs/build-windows.md`](docs/build-windows.md) | Build, sign, and check Windows desktop bundles and installers; the bill of materials, and tagging a candidate |
| [`docs/updates.md`](docs/updates.md) | What an update checks before it runs anything, and why it never closes a running transfer |
| [`docs/data-safety.md`](docs/data-safety.md) | Threat model: what is guaranteed, what is not, and the bugs behind each guarantee |
| [`docs/report-layout.md`](docs/report-layout.md) | Every coordinate of the PDF, measured off the reference report |
| [`docs/performance.md`](docs/performance.md) | Why not robocopy, with benchmarks and the confounds that made the first run worthless |
Expand Down
9 changes: 7 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,13 @@ already does the stem-matching this needs.
it settles almost every case for a stat, then the checksum. What is missing
is only the wiring into the engine's own skip, where the file being skipped
is at the destination rather than under another search root.
- **Windows installer and code signing**, so it can be handed to someone who
does not have Python.
- **A signed, qualified Windows release.** The packaging is implemented:
frozen bundle, NSIS installer, transactional maintenance, signing hooks, a
bill of materials, an update client, and a tag-triggered candidate workflow.
What is left is not code. Signing needs the hardware token, the acceptance
matrix needs a clean machine, and Qt's LGPL/GPL terms against Offloader's
MIT licence need a redistribution decision, which the notices file flags on
every build. See [`docs/release-plan.md`](docs/release-plan.md).
- **Per-job report templates and custom branding.**

## Not planned
Expand Down
14 changes: 11 additions & 3 deletions docs/build-windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ The builder writes `.offloader-build.json` inside the bundle, an external
coverage, and `SHA256SUMS.txt` for the final installer, ZIP, and inventory.
A failed build leaves `.offloader-build-incomplete`; it must not be promoted.
Source changes during a build invalidate the candidate. These inventories are
provenance and tamper checks, not a complete third-party license inventory or SBOM.
provenance and tamper checks. The third-party licence inventory and the SBOM
are separate outputs of the same build, described below.

## Signing

Expand Down Expand Up @@ -128,8 +129,7 @@ PEP 440 forms are rejected rather than silently truncated.

The spec excludes optional timeline import, disables UPX, and includes the
project license and distribution metadata. ffmpeg and ffprobe remain external.
Missing media tools reduce metadata/thumbnails, not copy verification. A
release-ready third-party license inventory and SBOM remain separate work.
Missing media tools reduce metadata/thumbnails, not copy verification.

## Bill of materials and licences

Expand Down Expand Up @@ -219,6 +219,14 @@ that no job in the workflow attaches what it built to a release.

## Check the artifact

Source validation on 2026-09-21 (Windows x64, Python 3.13.5): 855 tests passed
with 12 skips; lint passed over `src`, `tests`, `scripts` and `build/windows`.
The source archive was built and checked to contain the Windows builder. This
run covers the update client, the tag-triggered candidate workflow and the
bill of materials. **It is a source test run and nothing more:** no bundle was
frozen, nothing was signed, and no installer was executed, so it establishes
none of the gates below.

Installer implementation validation on 2026-09-10 (Windows x64, Python 3.12.10,
NSIS 3.12): 727 tests passed with 5 skips and 85% line coverage. Lint passed.
The unsigned installer and portable bundle built, and the frozen smoke checks
Expand Down
21 changes: 14 additions & 7 deletions docs/release-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,20 @@ credentials, update endpoints, or installation paths.
- **Timeline support:** include and test OpenTimelineIO and the currently
declared adapter in the desktop bundle if timeline import is advertised for
that bundle. Otherwise mark that capability source-only for the beta.
- **Updates:** `offloader update` finds and verifies a release and runs the
signed installer; the in-app check remains deferred. Refuse replacement
while the app or CLI has an active job; never force-kill a copy to install
an update.
- **Scope freeze:** defer new media features, cloud services, notifications,
auto-update, and a marketing website. Fix integrity and packaging blockers
discovered during qualification.
- **Updates:** `offloader update` and the desktop app both check for a
release, verify it, and run the signed installer. Refuse replacement while
the app or CLI has an active job; never force-kill a copy to install an
update. See [updates.md](updates.md). Discovery is not yet a gate that can
be signed off: the feed is `/releases/latest`, which excludes prereleases,
so a beta cannot find its successor until that is corrected.
- **Scope freeze:** defer new media features, cloud services, notifications
and a marketing website. Fix integrity and packaging blockers discovered
during qualification.

Updating is inside the freeze only as far as it goes today: the *check* is
automatic, and installing never is. Nothing is downloaded, replaced or
restarted without someone asking for it, and a silent background update
stays out of scope.

These are working defaults for implementation, not statements that the
packaging or safeguards already exist.
Expand Down
11 changes: 11 additions & 0 deletions docs/updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ build that Windows then considers older than the one it replaced.
If either version is unreadable the answer is "not newer". String comparison
is what makes `1.0.10` look older than `1.0.9`.

**The feed above does not yet carry the prereleases this grammar orders.**
GitHub documents `/releases/latest` as returning the newest published full
release and excluding prereleases, so an installed `0.1.0b1` cannot discover
`0.1.0b2` or `0.1.0rc1` through it, and a repository holding only the betas
the candidate workflow publishes with `--prerelease` answers with nothing at
all. The ordering here is right and the endpoint is wrong; reading the
releases collection instead is the correction pending on
[#13](https://github.com/owenpkent/offloader/pull/13). Until that lands, take
the ordering as describing what the updater would do with a release it can
see, not as evidence that it can see one.

## What is checked before anything runs

| Check | What it stops |
Expand Down
Loading