# Bean & Bark: ship Roastery Desk

The Orders API is live and the Pricing SDK is shared, but the physical roastery still runs on spreadsheets. Nadia wants **Roastery Desk** for batch intake, labels, and stock counts. Her MacBook is on the tasting floor, Tunde has a Windows laptop, and the packing station runs Linux. A publish folder on your machine is not a release any of them can install.

**You'll learn**

- Declare one versioned desktop product across macOS, Windows, and Linux runtime identifiers
- Create permission-safe first downloads and prove every routed cell without publication authority
- Stage a feed-derived candidate for hands-on testing on all three operating systems
- Promote the approved candidate byte-identically and understand the native-installer graduation path

> **Wednesday, 3:20pm — the tasting-floor ticket:** **Nadia:** *"the new batch screen is ready, but I can't hand the team a folder from your laptop 😅 I need it on my Mac, Tunde's Windows machine, and the Linux packing station before Saturday. can we test one release and know that exact thing is what ships?"*

## Name the product version once

Distribution reads the product version from the project or nearest `Directory.Build.props`. Put it where code review can see it; a candidate will append a feed-derived `-rc.N`, while the stable release keeps the clean base.

```xml
<PropertyGroup>
  <Version>1.0.0</Version>
</PropertyGroup>
```

## Turn a publish tree into three downloads

`--scaffold-archive` creates a small checked-in .NET helper and wires it into the manifest. Windows gets ZIP; macOS and Linux get permission-preserving TAR.GZ. The helper writes only declared archives into the output folder, so checksums and uploads do not accidentally include the loose publish tree.

```bash
kanject add distributable src/BeanAndBark.RoasteryDesk/BeanAndBark.RoasteryDesk.csproj   --id roastery-desk   --rid osx-arm64   --rid win-x64   --rid linux-x64   --self-contained   --scaffold-archive

kanject configure distribution   --env prod   --distributable roastery-desk
```

The guided distribution command records selector → destination rows in the `prod` stage. One wildcard can route every RID to the downloads bucket; exact RID rows can override it later without changing the application manifest.

> **A folder that runs is not a distributable:** `dotnet publish` proves the application can be assembled. It does not prove permissions survived download, users can identify the version, checksums cover the intended files, or an operating system will trust the result.

## Climb the safety ladder

Before AWS credentials enter the room, ask two progressively stronger questions: is the local plan coherent, and can the real artifact path finish?

```bash
kanject doctor --features

# Plan every cell without AWS or destination credentials
kanject aws distribute   --env prod   --distributable roastery-desk   --dry-run --offline

# Produce the real archives and checksums, but upload nothing
kanject aws distribute   --env prod   --distributable roastery-desk   --no-publish
```

The offline plan prints all three cells and the source of `1.0.0`. The build-only run publishes, archives, verifies, and checksums each cell under `.kanject/dist/roastery-desk/...`, but has no upload or ledger side effect. You unpack each archive and launch it on the matching machine.

**Nadia's Mac**

```bash
kanject aws distribute --env prod --distributable roastery-desk --rid osx-arm64 --no-publish
```

Confirm the arm64 archive launches and preserves executable permissions. A native DMG later adds `.app` identity, Developer ID signing, notarization, stapling, and Gatekeeper verification.

**Tunde's Windows**

```powershell
kanject aws distribute --env prod --distributable roastery-desk --rid win-x64 --no-publish
```

Confirm the self-contained ZIP launches without a preinstalled runtime. A native MSI later adds upgrade identity, shortcuts, WiX construction, and Authenticode verification.

**Packing station**

```bash
kanject aws distribute --env prod --distributable roastery-desk --rid linux-x64 --no-publish
```

Confirm the TAR.GZ preserves the executable bit. A native DEB later adds ownership, dependencies, desktop metadata, and package-manager inspection.

## Stage one candidate for three people

`distribute init` creates or adopts the stable downloads bucket and its isolated `-rc` sibling, then enforces public-access block, encryption, and versioning. Candidate numbering comes from the candidate feed, so the pipeline cannot collide by guessing a suffix.

```bash
# Create/adopt stable + candidate buckets and security posture
kanject aws distribute init --env prod

# Stage the next feed-derived 1.0.0-rc.N release
kanject aws distribute   --candidate   --env prod   --distributable roastery-desk

# After the three-machine check, promote the exact candidate
kanject aws distribute promote --env prod
```

Nadia, Tunde, and the packing operator download `1.0.0-rc.1` from the candidate channel. The approval records that all three cells passed hands-on checks. `promote` then performs server-side copies into stable destinations and flips the stable feed to `1.0.0`; no compiler, archive helper, or signing script runs again.

> **🎯 Saturday gets the candidate everyone tested:** The release is boring in the best way: three operating systems, one candidate decision, and no “can you rebuild the Mac one?” moment after approval. Stable is a channel transition over known bytes, not a second attempt to reproduce them.

## When an archive is no longer enough

Saturday's internal pilot can use archives. A customer-facing release needs native identity and OS trust. Scaffold the inert release kit only after the archive path is green:

```bash
kanject configure distribution scaffold   --distributable roastery-desk
```

The generated README and RID router carry runnable DMG, MSI, and DEB starters plus payload/container signing and independent verification. They are intentionally not activated for you: bundle identifiers, upgrade codes, entitlements, certificates, notarization credentials, install scope, and Linux metadata are product decisions.

> **Providers come later:** Today's shipped path is the auditable custom release kit. Proposal 0032 explores DotnetPackaging, Avalonia-focused Parcel, and Velopack update providers. Roastery Desk should not pretend those declarative providers or automatic updates exist until that proposal is implemented and accepted.

**Recap**

- A reviewed `<Version>` plus edition × RID manifest defines the product matrix; `--scaffold-archive` creates the first usable downloads.
- Offline dry-run proves local intent, while `--no-publish` executes the real artifact path without destination authority.
- The candidate feed owns `-rc.N`; people test those downloads on matching operating systems before approval.
- `promote` copies the approved bytes to stable without rebuilding; native DMG/MSI/DEB graduation stays explicit and matching-OS.

**Before Saturday's handoff**

**1. Why can't candidate planning be fully offline?**

- The next `rc.N` belongs to candidate-feed history ✓ — Uniqueness comes from reading the destination channel's existing versions. An offline runner can validate the matrix but cannot safely claim the next shared suffix.
- Desktop applications require AWS to compile — Compilation and `--no-publish` are local. Only destination-aware planning, candidate numbering, and publication cross the remote boundary.
- Checksums are stored only in S3 — Checksums are produced locally with the artifacts and are available during `--no-publish`.

**2. What happens during stable promotion?**

- Approved candidate objects are copied and the stable feed advances ✓ — Promotion is a byte-preserving channel transition. It does not rebuild the application or recreate its installers.
- The app is rebuilt without the `-rc.N` assembly suffix — That would produce untested bytes. The payload is preserved; only channel-facing artifact names/checksum references and feed version become stable.
- Every user downloads directly from the candidate bucket forever — Candidate and stable destinations stay separate. Promotion populates the stable channel after approval.

**Try it yourself**

**1. Inspect the three local artifacts**

Run `--no-publish` for a three-RID sample, unpack each archive, and compare executable permissions plus `SHA256SUMS` coverage.

_Hint:_ The archive helper intentionally uses ZIP for Windows and TAR.GZ where Unix permission bits matter.

_Solution:_ The declared archive and checksum file should be the release surface; the loose publish directory should not appear in `artifactGlobs` or the upload set. Unix executables should retain their executable bits after extraction.

**2. Explain the approval boundary**

Write the shortest argument for why a release pipeline should promote an RC rather than rebuild stable installers.

_Solution:_ Approval refers to evidence about specific tested bytes. A rebuild creates different, untested bytes—even from the same commit—so stable promotion must preserve the candidate payload.

> **Keep the checklist nearby:** The story gives the release a reason and a consequence. The neutral [Desktop distribution reference](https://www.kanject.com/docs/distribute/) is the page to keep open while authoring routes, CI jobs, native gates, and platform-specific release metadata.

---
_Source: https://www.kanject.com/docs/cli-bean-bark-desktop/ · Kanject Docs_
