# sethLabels Packaging — Design Spec

- **Status:** approved (brainstormed 2026-04-29, awaiting user review of this document before plan-writing)
- **Author:** Seth Freiberg, with assistance from Claude Code
- **Audience:** anyone implementing or maintaining sethLabels' packaging/release pipeline
- **Project:** [git.sethpc.xyz/Seth/sethLabels](https://git.sethpc.xyz/Seth/sethLabels)
- **Upstream:** [github.com/j-evins/glabels-qt](https://github.com/j-evins/glabels-qt) (the glabels.org team)

## 1. Problem & purpose

The upstream glabels-qt project explicitly does not publish self-hosted binary releases ("Currently there are no self-hosted binary snapshot releases available… I encourage you to try building the code yourself" — upstream README). sethLabels exists to fill exactly that gap: produce installable binary artifacts for Debian-family Linux and macOS without changing the upstream application itself.

The eventual goal is to publish sethLabels as a formally public packaging-fork on GitHub once the build/release pipeline is battle-tested. This spec describes the pipeline and discipline that make that possible.

## 2. Invariants (load-bearing)

These are not guidelines — they define the project's shape. Violating any of them moves sethLabels toward "real fork" territory and out of "deployment fork."

| # | Invariant | Why |
|---|-----------|-----|
| I1 | **Strict zero source patches.** No upstream file is ever edited. Every sethLabels addition lives in NEW files in NEW top-level directories at the repo root. | Makes `git pull --rebase upstream master` always succeed without conflicts (our scaffold commit re-applies cleanly on each new upstream base). Rebase friction = 0. |
| I2 | **Debian-family + macOS-via-brew target only.** No homelab-specific paths, hostnames, or assumptions anywhere in scripts. | Build must be reproducible on a clean Debian 13 / Ubuntu LTS VM. Public-fork future depends on this. |
| I3 | **Shell scripts under `scripts/` are the canonical build recipe.** CI YAML (when added later at the public-flip) calls these scripts verbatim — no logic in YAML. | Prevents recipe drift between manual builds and CI. The script is the truth. |
| I4 | **Manual local builds during battle-test phase.** No CI infrastructure (Gitea Actions, GitHub Actions, self-hosted runner) until the public-flip on GitHub. | Defers infra cost. Iteration speed > automation during exploration. |
| I5 | **No branding, icon, splash, or string changes.** sethLabels is a packaging fork, not a rebrand. Artifacts identify as `glabels-qt`. | Per IDEA.md ("branding/customization: minimal — this is for personal use, not a product") and strict-zero. |

The one exception to I1: `.gitignore` carries an appended sethLabels section. This was a one-time touch made at scaffold time and is treated as a known allowlisted exception throughout this spec.

## 3. Decisions (settled, with rationale)

Each decision below was the conclusion of a multiple-choice round during brainstorming. Recorded here so future maintainers can see what alternatives were considered and why the current choice won. See `DECISIONS.md` for the same list in shorter form.

### D1 — Linux distribution formats: `.deb` AND AppImage

- **Why:** `.deb` for Debian-family targets (clean apt-managed install/upgrade/uninstall); AppImage as portable fallback for any-Linux-anywhere. AppImage is essentially free once `.deb` works since linuxdeploy reuses upstream's `install()` rules.
- **Rejected:** AppImage-only (loses native package manager integration), `.deb`-only (loses portability to non-Debian Linux), Flatpak (heavyweight runtime requirement, overkill for this use case).
- **Cost:** AppImage bundling can be finicky around Qt6 plugin discovery — see §8.2 mitigation.

### D2 — macOS distribution: Homebrew tap, build-from-source

- **Why:** zero macOS CI requirement (brew runs the build on the user's machine), no $99/yr Apple Developer ID, no notarization pipeline complexity, brew handles Qt6 dependency resolution automatically (`depends_on "qt@6"`).
- **Rejected:** unsigned `.dmg` (Gatekeeper friction), signed+notarized `.dmg` (annual cost + CI complexity), tap-with-pre-built-cask (still requires building somewhere, defeats the simplification).
- **Constraint accepted:** first-time install on a Mac takes ~5–10 min (cmake build of Qt app from source). Acceptable for a technical user, which is the audience.
- **Tap repo:** lives separately at `git.sethpc.xyz/Seth/homebrew-tap`. Not in the sethLabels repo.

### D3 — Build infrastructure: manual local builds during battle-test

- **Why:** local feedback loop (~2 min) beats CI feedback loop (push + wait); during iteration on packaging recipes the local loop is essential. Scripts written for manual use translate to CI workflow YAML mechanically when needed.
- **Rejected:** Gitea Actions self-hosted runner (homelab CT spin-up cost not justified pre-public), public GitHub Actions from day one (skips the "battle-test in private" intent).
- **Forward path:** at public-flip on GitHub, scripts get wrapped in a `.github/workflows/release.yml` that calls them — no logic moves into YAML.
- **Build host expectation:** scripts must run on a clean Debian 13 / Ubuntu LTS box. Steel141 is acceptable as a build host but is not the target audience for resulting binaries.

### D4 — Versioning: `<upstream-tag>-seth<N>`

- **Why:** lineage in the version answers "which upstream am I running?" without changelog spelunking. Rebuild counter (`seth1` → `seth2`) lets us ship a packaging-only fix without lying about which upstream code is inside. Sorts correctly under `dpkg --compare-versions`.
- **Format:** `<upstream-tag>` = output of `git describe --tags --abbrev=0 upstream/master` (e.g., `3.99-master618`). `<N>` = count of existing `<upstream-tag>-seth*` tags + 1.
- **Rejected:** plain upstream tag (no way to distinguish v1 of the .deb from v2 after a packaging fix), independent semver (loses upstream-lineage info), date-based (loses both lineage and rebuild-counter info).

### D5 — Upstream-touch policy: strict zero

- **Why:** the IDEA brief explicitly calls for "diff small enough to rebase periodically." Strict zero takes that to its limit. CPack metadata is overridable via `cpack -D KEY=VALUE` flags at build time — no source edit required to package. Any future need for a CMake gap should be upstreamed as a PR to glabels-qt rather than carried as a local patch.
- **Rejected:** permissive small-patches (creates rebase friction), strict-zero with a CMakeLists carve-out (the carve-out is not actually needed, since CPack `-D` covers everything we want).
- **Enforcement:** `scripts/check-no-upstream-edits.sh` runs as a guardrail; allowlist = `.gitignore` only.

### D6 — Package name: `glabels-qt`

- **Why:** consistent muscle memory across `.deb`, AppImage, brew formula, and the binary the user runs. The "sethLabels" identity lives at the repo level + in the version string's `-seth<N>` marker, not in the package name. Future-proofs the public-fork narrative as "yet another packaging fork of glabels-qt."
- **Rejected:** `sethlabels` (would split identity from binary name, since strict-zero means we can't rename the executable; `apt install sethlabels` then `glabels-qt` is confusing).
- **Risk acknowledged:** if Debian later ships an official `glabels-qt` package, our sethLabels-versioned variant would still dominate by version-sort (`3.99-master618-seth1` > `3.99`-something-debian). Re-evaluate at that time.

## 4. Repo layout

The post-spec layout. Existing entries reflect what's already in the repo at `2026-04-29`. NEW entries are introduced by the spec implementation.

```
sethLabels/
├── (all upstream files — strict zero, never touched)
│   ├── glabels/, glabels-batch/, model/, backends/, ...
│   ├── CMakeLists.txt, README.md, LICENSE, ...
│   ├── data/, templates/, translations/, user-docs/
│   └── docs/                              ← upstream's docs dir, never written into
│
├── .gitignore                             ← I1 exception: sethLabels section appended
│
├── CLAUDE.md                              ← already present (project instructions)
├── IDEA.md                                ← already present (plain-language brief)
├── DECISIONS.md                           ← already present (decision log)
├── GITEA_API.md                           ← already present (gitignored symlink)
├── README.sethlabels.md                   ← NEW: fork purpose, install/build entry
│
├── scripts/                               ← NEW: canonical build recipe (I3)
│   ├── README.md                          ← human-readable run instructions
│   ├── build-deb.sh                       ← entry: builds the .deb
│   ├── build-appimages.sh                 ← entry: builds the 2 AppImages
│   ├── compute-version.sh                 ← emits "<upstream-tag>-seth<N>"
│   ├── check-no-upstream-edits.sh         ← guardrail enforcing I1
│   └── lib/
│       ├── deps-debian.sh                 ← apt-installable build deps
│       └── linuxdeploy.sh                 ← downloads/caches linuxdeploy + qt plugin
│
├── packaging/                             ← NEW: data files consumed by scripts
│   ├── deb-metadata.env                   ← maintainer, section, depends fallback
│   ├── appimage-recipe.env                ← linuxdeploy plugin allowlist, exclude list
│   └── changelog.md                       ← human-readable per-release notes
│
├── sethlabels-docs/                       ← NEW: sethLabels' own docs (this spec lives here)
│   └── specs/
│       └── 2026-04-29-packaging-design.md ← THIS file
│
└── .claude/                               ← already present
    └── handoffs/
        └── 2026-04-29-125823-scaffold-only.md
```

Companion repository (separate Gitea repo, not part of sethLabels):

```
homebrew-tap/                              ← git.sethpc.xyz/Seth/homebrew-tap
├── README.md
└── Formula/
    └── glabels-qt.rb                      ← brew formula
```

## 5. Build pipeline

### 5.1 Build host requirements

Either a clean Debian 13 (Trixie) or Ubuntu 24.04 LTS box. The build host produces both the `.deb` and the AppImage artifacts; one machine, two scripts.

`scripts/lib/deps-debian.sh` is the single source of truth for build dependencies and prints an actionable `apt install …` command when something is missing. Expected dependency set:

```
build-essential cmake ninja-build pkg-config
qt6-base-dev qt6-base-dev-tools qt6-svg-dev qt6-tools-dev qt6-tools-dev-tools
qt6-l10n-tools libqt6printsupport6 libqt6svg6 libqt6widgets6 libqt6xml6 libqt6gui6
libqt6concurrent6 libqt6core6 libqt6test6
zlib1g-dev libqrencode-dev libzint-dev libgnubarcode-dev
file dpkg-dev fakeroot
wget                # for downloading linuxdeploy on first AppImage build
```

`linuxdeploy` and `linuxdeploy-plugin-qt` are downloaded by `scripts/lib/linuxdeploy.sh` to a script-local cache (e.g., `scripts/.cache/linuxdeploy-x86_64.AppImage`) on first run. They are not apt-installable on Debian/Ubuntu, hence the bootstrap step. Cache is gitignored.

### 5.2 `scripts/build-deb.sh`

Sequential steps. Any non-zero exit aborts the build with a clear error message.

1. **Sanity check the build host:** confirm Debian-family (`/etc/os-release` ID is `debian` or `ubuntu`), confirm all `deps-debian.sh` packages are installed.
2. **Strict-zero guardrail:** call `check-no-upstream-edits.sh`. If any tracked-upstream file diverges from `upstream/master` (allowlist: `.gitignore`), abort.
3. **Compute version:** `VERSION=$(./scripts/compute-version.sh)`.
4. **Out-of-tree build:**
   ```
   cmake -S . -B build/deb -G Ninja -DCMAKE_BUILD_TYPE=Release
   cmake --build build/deb --parallel
   ```
5. **Run CPack with overrides:**
   ```
   cd build/deb && cpack -G DEB \
     -D CPACK_PACKAGE_VERSION="$VERSION" \
     -D CPACK_DEBIAN_PACKAGE_MAINTAINER="$(. ../../packaging/deb-metadata.env && echo "$MAINTAINER")" \
     -D CPACK_DEBIAN_PACKAGE_SECTION="graphics" \
     -D CPACK_DEBIAN_PACKAGE_SHLIBDEPS=ON \
     -D CPACK_DEBIAN_PACKAGE_HOMEPAGE="https://glabels.org" \
     -D CPACK_DEBIAN_FILE_NAME=DEB-DEFAULT
   ```
   `CPACK_DEBIAN_PACKAGE_SHLIBDEPS=ON` runs `dpkg-shlibdeps` against built binaries to compute exact runtime library deps automatically. This avoids hand-maintaining a Depends list.
6. **Smoke test the artifact:**
   - `dpkg-deb --info build/deb/glabels-qt_${VERSION}_amd64.deb` — verifies parse-ability.
   - `dpkg-deb --contents …` checked for both `/usr/bin/glabels-qt` and `/usr/bin/glabels-batch-qt`.
   - `lintian build/deb/glabels-qt_${VERSION}_amd64.deb` if available — warnings noted but not fatal during battle-test.
7. **Artifact path printed for the operator** to upload: `build/deb/glabels-qt_${VERSION}_amd64.deb`.

### 5.3 `scripts/build-appimages.sh`

1. Same sanity / guardrail / version-compute steps as 5.2.
2. **Out-of-tree build into `build/appimage`:**
   ```
   cmake -S . -B build/appimage -G Ninja -DCMAKE_BUILD_TYPE=Release \
     -DCMAKE_INSTALL_PREFIX=/usr
   cmake --build build/appimage --parallel
   ```
3. **Stage install tree to `AppDir`:**
   ```
   DESTDIR=$(pwd)/build/appimage/AppDir cmake --install build/appimage
   ```
   `AppDir/usr/bin/glabels-qt` and `AppDir/usr/bin/glabels-batch-qt` exist after this step (driven by upstream's existing `install()` rules).
4. **Bundle GUI AppImage:**
   ```
   linuxdeploy --appdir build/appimage/AppDir --plugin qt \
     --executable build/appimage/AppDir/usr/bin/glabels-qt \
     --desktop-file build/appimage/AppDir/usr/share/applications/org.glabels.glabels-qt.desktop \
     --icon-file build/appimage/AppDir/usr/share/icons/hicolor/scalable/apps/glabels.svg \
     --output appimage
   mv glabels-qt-x86_64.AppImage sethlabels-gui-${VERSION}-x86_64.AppImage
   ```
5. **Bundle Batch AppImage** — re-stage AppDir (or use a separate `AppDir-batch`) to avoid pulling GUI-only Qt plugins into the batch bundle:
   ```
   linuxdeploy --appdir build/appimage/AppDir-batch --plugin qt \
     --executable build/appimage/AppDir-batch/usr/bin/glabels-batch-qt \
     --output appimage
   mv glabels-batch-qt-x86_64.AppImage sethlabels-batch-${VERSION}-x86_64.AppImage
   ```
   Note: the batch CLI doesn't need a desktop file or icon (no GUI launcher).
6. **Smoke tests:**
   - `./sethlabels-batch-${VERSION}-x86_64.AppImage --version` — must exit 0 and print a version string.
   - `QT_QPA_PLATFORM=minimal ./sethlabels-gui-${VERSION}-x86_64.AppImage --help` — must exit 0 (validates that the Qt plugin set is complete enough to start a headless Qt event loop).
7. **Artifact paths printed for the operator** to upload.

### 5.4 `scripts/compute-version.sh`

Pure logic, no side effects. Emits version string to stdout.

```
upstream_tag=$(git describe --tags --abbrev=0 upstream/master)
existing_count=$(git tag --list "${upstream_tag}-seth*" | wc -l)
next_n=$((existing_count + 1))
echo "${upstream_tag}-seth${next_n}"
```

**Caller responsibility:** the local tag db must be fresh before running this script — `git tag --list` reads local refs only. The release flow (§6 step 1) does `git fetch --all --tags` to satisfy this. If invoked outside the release flow, the caller must run `git fetch origin --tags` first or risk a stale `<N>` value.

The script is idempotent: running it multiple times before a new tag is created always returns the same value. Both `build-deb.sh` and `build-appimages.sh` call this internally to embed the version into their artifacts. Step 6 of the release flow re-invokes it to obtain `$VERSION` for the `git tag` command — values agree by purity.

### 5.5 `scripts/check-no-upstream-edits.sh`

The script must catch BOTH committed and uncommitted edits to upstream files. Two checks:

1. **Committed drift** — files changed in commits unique to `HEAD` vs. `upstream/master`:
   ```
   committed=$(git diff --name-only upstream/master..HEAD)
   ```
2. **Working-tree drift** — uncommitted local edits to tracked files:
   ```
   working=$(git diff --name-only HEAD)
   ```

Both lists are filtered against the same allowlist. Any path NOT matching is a violation.

```
allowed_pattern='^\.gitignore$|^\.claude/|^scripts/|^packaging/|^sethlabels-docs/|^README\.sethlabels\.md$|^CLAUDE\.md$|^IDEA\.md$|^DECISIONS\.md$'
all_changes=$(printf "%s\n%s\n" "$committed" "$working" | sort -u | sed '/^$/d')
violations=$(echo "$all_changes" | grep -vE "$allowed_pattern" || true)
if [ -n "$violations" ]; then
  echo "ERROR: strict-zero policy violated. The following upstream files have been modified:"
  echo "$violations"
  echo "See sethlabels-docs/specs/2026-04-29-packaging-design.md §I1."
  exit 1
fi
```

Exit 0 silently on clean state.

Allowlist note: `GITEA_API.md` is intentionally NOT in the allowlist because it is `.gitignore`-excluded and will never appear in any git diff. Including it would be dead code.

## 6. Release flow

```
1. git fetch --all --tags                        (refreshes upstream commits AND local tag db, which compute-version.sh reads)
2. git rebase upstream/master                    (strict-zero ⇒ always succeeds without conflicts; scaffold commit re-applies on new base)
3. ./scripts/check-no-upstream-edits.sh          (guardrail; aborts on drift)
4. ./scripts/build-deb.sh                        (~2 min)
5. ./scripts/build-appimages.sh                  (~5 min, two AppImages)
6. VERSION=$(./scripts/compute-version.sh)
7. git tag "$VERSION"
8. git push origin main --tags
9. Create Gitea release for tag $VERSION via gitea CLI / API; attach:
   - build/deb/glabels-qt_${VERSION}_amd64.deb
   - sethlabels-gui-${VERSION}-x86_64.AppImage
   - sethlabels-batch-${VERSION}-x86_64.AppImage
10. Edit ../homebrew-tap/Formula/glabels-qt.rb:
    - bump tag pin to $VERSION
    - update sha256 of the source tarball
    - commit "bump glabels-qt to $VERSION", push
11. Smoke verify: download .deb on a fresh Debian 13 VM, install with `apt install ./...`, run `glabels-qt --version`.
```

The release flow is human-driven during the battle-test phase. Each step's output is visible; failures are immediately diagnosable. When the public-flip happens, steps 4, 5, 9, 10 move into a GitHub Actions `.github/workflows/release.yml` that triggers on tag push and runs the same scripts unmodified.

## 7. Homebrew tap

### 7.1 Tap repo layout

```
homebrew-tap/                            (separate Gitea repo: git.sethpc.xyz/Seth/homebrew-tap)
├── README.md                            (instructions: how to tap, how to install)
└── Formula/
    └── glabels-qt.rb                    (the formula)
```

### 7.2 Initial formula (starting point — finalized during plan execution)

```ruby
class GlabelsQt < Formula
  desc "gLabels Label Designer (Qt/C++) — Seth's packaging fork"
  homepage "https://glabels.org"
  url "https://git.sethpc.xyz/Seth/sethLabels.git",
      tag:      "3.99-master618-seth1",
      revision: "<placeholder, filled at first release>"
  license "GPL-3.0-only"
  head "https://git.sethpc.xyz/Seth/sethLabels.git", branch: "main"

  depends_on "cmake"   => :build
  depends_on "ninja"   => :build
  depends_on "pkgconf" => :build
  depends_on "qt"
  depends_on "zlib"
  depends_on "qrencode" => :recommended  # optional barcode backend
  depends_on "zint"     => :recommended  # optional barcode backend

  def install
    system "cmake", "-S", ".", "-B", "build",
           "-G", "Ninja",
           "-DCMAKE_BUILD_TYPE=Release",
           *std_cmake_args
    system "cmake", "--build", "build"
    system "cmake", "--install", "build"
  end

  test do
    assert_match "gLabels", shell_output("#{bin}/glabels-batch-qt --version")
  end
end
```

### 7.3 User install path (macOS)

```
brew tap seth/tap https://git.sethpc.xyz/Seth/homebrew-tap.git
brew install seth/tap/glabels-qt
```

The explicit URL form is required because brew defaults to GitHub for tap names. Documented in the tap's README. When the public-flip moves sethLabels (and the tap) to GitHub, the URL becomes the implicit GitHub default and the tap command shortens to `brew tap seth/tap`.

### 7.4 Tap maintenance per release

Single commit per release on the tap repo: bump `tag:` and `revision:` fields in `glabels-qt.rb`. No other edits expected.

## 8. Failure modes & guardrails

| # | Risk | Guardrail |
|---|------|-----------|
| F1 | Strict-zero policy creep | `check-no-upstream-edits.sh` runs at the start of each build script; allowlist explicit (see §5.5) |
| F2 | AppImage Qt plugin omissions (image formats, platform plugin) | linuxdeploy-plugin-qt covers most cases; smoke test (§5.3 step 6) launches each AppImage with `QT_QPA_PLATFORM=minimal` and verifies exit code |
| F3 | Build env drift (worked on steel141, fails on clean Debian 13 VM) | `deps-debian.sh` is the single source of truth for build deps; scripts fail fast on missing pkg with actionable apt-install command. Recommend re-validating on a fresh VM before each release. |
| F4 | Brew formula source URL changes (Gitea → GitHub on public flip) | Single-line edit in `glabels-qt.rb`; tap repo has its own commit history so the flip is one commit. Document the URL-flip in tap repo's README. |
| F5 | Re-package counter (`seth<N>`) collisions | Computed from existing tags via `git tag --list`, not from a state file; race-free under serial single-author releases. |
| F6 | Manual-build script rot | `scripts/README.md` documents required commands; future CI workflow YAML calls scripts directly — preventing recipe drift. Each release flow re-exercises the scripts. |
| F7 | Upstream rebase fails with conflicts (strict-zero broken without `check-no-upstream-edits.sh` having caught it) | Recover from a clean state in worst case: `git reset --hard upstream/master` then re-apply our scaffold commit by cherry-picking from `origin/main`. The single scaffold commit is the only thing to preserve. |
| F8 | dpkg-shlibdeps mis-detects a runtime dep, producing a `.deb` that won't install on a target system | Mitigation: smoke-test install on a clean Debian 13 VM as part of release flow (§6 step 11). If a false positive surfaces, override via `CPACK_DEBIAN_PACKAGE_DEPENDS` flag in `build-deb.sh`. |
| F9 | linuxdeploy or linuxdeploy-plugin-qt version drift breaks AppImage builds | `lib/linuxdeploy.sh` pins specific GitHub release versions; version bumps are deliberate, not transparent. |

## 9. Out of scope (explicit)

- **Windows packaging** — deferred per project brief. Upstream's NSIS support remains untouched and works for anyone who wants to build their own.
- **macOS `.dmg`, signed binaries, notarization** — replaced by Homebrew tap building from source.
- **Flatpak, Snap** — heavier infrastructure than warranted for the audience.
- **Arch AUR package** — already exists per upstream README (community-maintained `glabels-qt-git`).
- **Distribution to Debian official repos / backports / PPA / Ubuntu universe** — would require Debian Developer mentorship + ongoing policy compliance work; not the current goal.
- **Custom default templates baked into the package** — strict-zero forbids this. User-specific templates live in `~/.config/glabels-qt/templates/` outside the package, or in a future separate repo.
- **Branding, icon, splash, or string changes** — strict-zero forbids this.
- **Automated CI infrastructure (Gitea Actions runner, GitHub Actions, etc.) during the battle-test phase** — manual local builds. Reconsider at public-flip on GitHub.
- **GUI source changes of any kind** — strict-zero forbids this.
- **Repackaging upstream's existing tag-snapshot binaries** — we always build from source, never relabel upstream artifacts.

## 10. Smoke test definitions

The acceptance test for any release. All four must pass before a tag is pushed.

| # | Test | Pass condition |
|---|------|----------------|
| T1 | `dpkg-deb --info <deb>` parses and shows the correct version field | exit 0; version line matches `$VERSION` |
| T2 | `dpkg-deb --contents <deb>` includes both binaries | both `/usr/bin/glabels-qt` and `/usr/bin/glabels-batch-qt` present |
| T3 | `<batch-appimage> --version` runs | exit 0; output is non-empty |
| T4 | `QT_QPA_PLATFORM=minimal <gui-appimage> --help` runs | exit 0 |
| T5 (release-flow) | Install `.deb` on a clean Debian 13 VM, run `glabels-qt --version` | exit 0; version line matches `$VERSION` |

T5 is the strongest signal — it validates that `dpkg-shlibdeps` produced a correct depends list and that Qt6 from the system package manager satisfies runtime needs. Run T5 at least once per upstream-tag bump (i.e., on every `seth1` release; can be skipped on `seth2`+ if only packaging metadata changed).

## 11. Glossary

- **Deployment fork** — a fork whose only purpose is to ship binaries / install packages. No source-code intent. Diff against upstream is trivial; rebases are fast-forwards.
- **Strict zero (I1)** — the discipline that no upstream-tracked file is ever edited. All sethLabels content lives in NEW files in NEW top-level directories.
- **Battle-test phase** — the period before sethLabels is published as a formal public fork on GitHub. During this phase: manual builds, no CI, hosted at Gitea.
- **Public-flip** — the future event when sethLabels is moved (or dual-hosted) on GitHub as a public packaging fork. Triggers: GitHub Actions added as CI, brew tap source URL flips Gitea→GitHub, README updates.
- **Upstream-tag** — output of `git describe --tags --abbrev=0 upstream/master` against the j-evins/glabels-qt master branch. Currently of the form `3.99-master<N>` until upstream tags 4.0.

## 12. Open questions

None at spec time. All design questions raised during brainstorming were resolved before writing this document. Future open questions can be added here with their resolution moving to §3 once decided.

## 13. References

- [j-evins/glabels-qt README](https://github.com/j-evins/glabels-qt/blob/master/README.md) — the upstream "no binary releases" notice this fork addresses
- [`docs/BUILD-INSTRUCTIONS-LINUX.md`](../../docs/BUILD-INSTRUCTIONS-LINUX.md) — upstream's manual build instructions (the manual procedure our scripts automate)
- [CMake CPack DEB generator docs](https://cmake.org/cmake/help/latest/cpack_gen/deb.html)
- [linuxdeploy + qt plugin](https://github.com/linuxdeploy/linuxdeploy-plugin-qt)
- [Homebrew Formula Cookbook](https://docs.brew.sh/Formula-Cookbook)
- [Debian version comparison rules](https://www.debian.org/doc/debian-policy/ch-controlfields.html#version)
