Files
margin/simplify/guidelines/distribution.md
T
2026-10-03 22:48:49 +05:30

107 lines
6.4 KiB
Markdown

# Distribution
Decided 2026-08-30 for Margin, Margin Calendar and Margin Docs. Margin Mail follows the same shape.
## Channels
All the apps are free. No licence gate, no in-app purchase, so Apple takes no cut and the App Store
anti-steering rules do not apply.
The Mac App Store is the primary macOS channel: sandboxed, no self-updater, a separate build track.
Direct download from margin.73ai.org stays the unrestricted build carrying the Tauri updater. macOS
also ships through the user's own Homebrew tap, `priyanshujain/homebrew-margin`, rather than upstream
homebrew-cask, which the repos do not clear the notability bar for.
Linux ships through a Nix flake. Do not propose the AUR again: the AUR job and PKGBUILD were removed
on 2026-09-03 because the user has no AUR account and signups are restricted. The Nix package is a
binary repackage of the released deb, for the same reason the AUR one was, which is that the Google
OAuth client is embedded at compile time from a file that is not in the repo, so a from-source build
by a stranger produces an app that cannot connect. The wrapper sets a `*_PACKAGED_BY=nix` variable so
the in-app updater announces new versions without trying to install over the store.
Nix is not installed on the user's Mac. Test a flake through the amd64 `nixos/nix` Docker image with
`filter-syscalls = false`, since seccomp fails under emulation, and a named volume on `/nix`.
No migration bridge was built for the library moving into the App Store sandbox container. There are
effectively no existing users and a first-run import is not worth building yet.
## Licensing
Margin is FSL-1.1-MIT: free for anything except a competing product, becoming MIT two years after
each release. Chosen because the user wants open code that nobody else monetises, which is not open
source by the OSI definition. AGPL was ruled out because it is incompatible with the Mac App Store.
Margin Mail is FSL-1.1-MIT too.
Margin Calendar and Margin Docs are still MIT and have not been relicensed. That is an open question,
and consolidating shared code into one package forces it: shared code cannot be under two licences.
## The updater pubkey lives in an overlay
`plugins.updater.pubkey` and `bundle.createUpdaterArtifacts` live in `src-tauri/tauri.release.conf.json`,
a release-only overlay merged with `--config` in the GitHub Actions release workflow. They are
deliberately kept out of the committed `tauri.conf.json`.
The reason is a Tauri bug (tauri-apps/tauri#14581): the mere presence of `plugins.updater.pubkey` in
`tauri.conf.json` makes `tauri build` demand a signing key, which breaks the local key-free build.
The overlay scopes signing and updater artifacts to CI. Only CI-built signed releases need to
self-update anyway.
The release workflow is a manual `workflow_dispatch` with prepare, a build matrix and publish. The
matrix should be `max-parallel: 1`, because tauri-action merges `latest.json` read-modify-write
across platforms and parallel jobs race.
It is not, in any app. Checked on 2026-09-06: `margin/.github/workflows/release.yml:91`,
`margin-caledar/.../release.yml:85` and `margin-mail/.../release.yml:85` set `fail-fast: false` and
nothing else, and Margin Docs has no matrix at all. The constraint was recorded once and then lost,
which is exactly the failure this consolidation is for. The shared build job in
[../release.md](../release.md) sets it.
## Signing
Signing keys and CSRs live in `~/.margin-signing`: Developer ID Application, Apple Distribution, the
installer certificate and the App Store Connect key, each with a `.pass` file. They were generated
with openssl rather than Keychain Access so CI `.p12` files can be rebuilt without a GUI. The
Developer ID is in the login keychain and `codesign` uses it without prompting.
Never print the contents of anything in that directory.
## Google Cloud
One Cloud project, `margin-500217`, numeric id `205537985128`, owned by **[email protected]** and not by
the Google account signed into the apps. Margin Calendar's desktop `google-credentials.json` is a
copy of Margin's, so they share one OAuth desktop client, which means they share the consent screen
and the enabled API list.
Enabling an API is per project, not per client. On 2026-08-10 the calendar scope was granted
correctly and every `calendarList` call still returned 403 `SERVICE_DISABLED`, because the Calendar
API had never been enabled on that project. A `gcloud services enable` run as the gmail account was
denied, because that account does not own the project. If a Google resource comes back empty while
auth succeeds, check the API is enabled before suspecting sync, and run the enable as pj@73ai.org.
Phones share the desktop client deliberately. A Desktop client may redirect to loopback on any port
without registering it, and Google's token endpoint checks the client id, secret and redirect rather
than the calling OS. Verified on 2026-08-12 on both an iOS simulator and an Android emulator. This
works because the protocol does not check, not because Google blesses it; the fallback if they ever
enforce it is a per-platform client, which the code already supports.
One thing still argues for a real iOS client: it switches iOS to `ASWebAuthenticationSession`, which
shares Safari's session, so the user is not asked to sign in again. Android needs nothing, because
Chrome Custom Tabs already share Chrome's cookies, which was measured rather than assumed. The iOS
session sharing could not be confirmed on the simulator and needs a real device.
The client secret is not confidential for an installed app. Embed it, through the same release
overlay pattern as the updater pubkey, so local builds stay clean.
Refresh tokens are not in any keychain. They are XChaCha20-Poly1305 sealed in a file under the app
data directory, so the old `security find-generic-password` check no longer applies anywhere.
## Never touch cloud infrastructure unasked
Reading is fine: list, describe, get, dry runs. Changing is not. Do not create, delete, rename or
reconfigure a project, bucket, database, service, key, credential, IAM binding or DNS record, do not
enable or disable an API, and do not attach billing without being asked for that exact thing on that
exact resource. Permission does not carry forward to the next request.
If something turns out to be blocked, say it is blocked and why. Do not route around it. A workaround
that provisions new infrastructure is a much bigger decision than the one that was made.