"MAS updater exclusion — `mas` cargo feature compiles the self-updater out of the Mac App Store build"
§1 — Description
apps/desktop/src-tauri/Cargo.tomlMUST declare amasfeature (marker, no deps). Default builds (no feature) MUST be byte-for-byte behavior-identical to today: updater registered, launch check runs.- All three updater code sites in
lib.rs(spawn_update_checkdefinition, thetauri_plugin_updaterregistration block, the setup-time launch call) MUST change#[cfg(desktop)]to#[cfg(all(desktop, not(feature = "mas")))]— with--features masthe plugin is never registered and no update check is compiled in, so the configuredplugins.updaterblock in tauri.conf.json becomes inert for the MAS target without touching that shared config (TASK-APP-003 §1 #1 forbids mutating it). release-mas.yml's build step MUST pass the feature (--features mas). If the Tauri CLI's cargo-feature passthrough flag differs on the pinned CLI major, the job fails loudly at build - an acceptable first-gated-run discovery, noted in-file (no macOS/cargo toolchain exists in this authoring environment to pre-verify).docs/deploy/mac-app-store-submission.mdhard blocker #1 MUST flip to resolved-pending-first-build, with one honest residual noted: the target-scoped dependency still compiles into the binary as dead code (registration excluded); shrinking it to an optional dependency is possible later on a real toolchain but is not submission-blocking the way active self-update was.
Length note: sanctioned lean profile — a two-attribute Rust change plus one CI flag; §5's checks are the complete machine surface available pre-toolchain.
§2 — Why this design
Compile-time exclusion is the only path TASK-APP-003's answer sheet accepts: runtime checks and config-overlay null-outs both leave the self-update code reachable and were explicitly rejected there as unverifiable submission-safety. A marker feature + not(feature) widening is the minimal diff that keeps the default build provably unchanged (the attribute is strictly narrower only when the feature is on).
§3 — API contract
[features]
custom-protocol = ["tauri/custom-protocol"]
mas = [] # Mac App Store target: compiles the self-updater OUT (TASK-IMP-075)
#[cfg(all(desktop, not(feature = "mas")))] // x3, replacing #[cfg(desktop)]
run: npx --yes @tauri-apps/cli@2 build --config src-tauri/tauri.mas.conf.json --bundles app --target universal-apple-darwin --features mas
§4 — Acceptance criteria
- Default-build neutrality:
grep -c '#\[cfg(all(desktop, not(feature = "mas")))\]' src/lib.rs== 3 andgrep -c '#\[cfg(desktop)\]' src/lib.rs== 0 — no site left half-gated. mas = []present in Cargo.toml[features].- release-mas.yml build step carries
--features mas; YAML parses. - Answer sheet blocker #1 updated; residual dead-code note present.
- First real toolchain run (
cargo checkthencargo check --features mas, later the gated CI build) compiles both ways — expected-pending here, executed on Stephen's machine or the first MAS_RELEASE run.
§5 — Verification
grep -c 'not(feature = "mas")' apps/desktop/src-tauri/src/lib.rs # 3
grep -c '#\[cfg(desktop)\]' apps/desktop/src-tauri/src/lib.rs # 0
grep -n '^mas = \[\]' apps/desktop/src-tauri/Cargo.toml
grep -n 'features mas' .github/workflows/release-mas.yml
python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release-mas.yml'))"
# pending real toolchain: cargo check && cargo check --features mas
§6 — Implementation skeleton
§3 is exhaustive.
§7 — Dependencies
Upstream: TASK-APP-003 (done) discovered and scoped this. Downstream: unblocks MAS_RELEASE=true (remaining blockers are Stephen's account-side items). Batched with TASK-IMP-074 (cone-independent).
§8 — Example payloads
Post-change gate output: mas-entitlement-lint: OK unchanged; MAS build log shows no cyberos updater: lines.
§9 — Open questions
- Optional-dependency shrink (dead code removal) — later, on a real toolchain, if App Review flags the compiled-but-unregistered plugin (unlikely; no update behavior exists to observe).
- Tauri CLI
--featurespassthrough name on the pinned major — confirmed at first gated run (clause 3 loud-failure posture).
§10 — Failure modes inventory
| Failure | Detection | Outcome | Recovery |
|---|---|---|---|
| cfg typo breaks the DEFAULT build | next desktop build/tag fails compile loudly | no silent ship | trivial attribute fix |
| CLI drops/renames --features | MAS job fails at build step | loud, pre-submission | adjust flag per CLI --help |
| feature on but a 4th updater site appears later | grep AC #1 in this spec + review norm | gate catches count drift | widen the new site |
| Apple flags dead plugin code | review feedback (external) | resubmit cycle | optional-dep follow-up (§9) |
| default build accidentally passes --features mas | only possible in release-mas.yml, which never builds the Developer ID channel | none | structural separation of workflows |
§11 — Implementation notes
The three-site count (not two, not four) comes from reading lib.rs, not convention — AC #1's grep pins it. Batched with TASK-IMP-074 under v2.5.0 §11a.
End of TASK-IMP-075.
Audit
§1 — Verdict summary
Lean profile, justified (two-attribute Rust change + one CI flag). 4 clauses, 5 ACs, 5 failure rows; toolchain-dependent checks honestly marked expected-pending.
§2 — Findings (resolved in-pass)
- ISS-001: draft asserted "3 cfg sites" without a pinning mechanism → AC #1 dual-grep (count==3 AND zero bare #[cfg(desktop)] remain) makes the count machine-checked, not prose.
- ISS-002: dead-code residual (dependency still compiled) was unstated → clause 4 + §9/§10 rows disclose it and scope the optional-dep shrink as a later, non-blocking follow-up.
§3 — Resolution
All resolved same pass. Score = 10/10. End of TASK-IMP-075 audit.