1
-# smbcloud-ascapi — upload + signing-identity hardening: plan
1
+# smbcloud-ascapi — upload + signing-identity hardening: plan (closed)
2
3
Findings from the **Siti AI** release session (macOS App Store upload of
4
`Siti AI.pkg` through App Store Connect). Repo:
8
9
Companion plan: [`feature-plan-from-klepon-xcode-release-session.md`](./feature-plan-from-klepon-xcode-release-session.md)
10
— that session shipped the builds API + token-source + altool-upload stack
11
-(gh stack #6→#7→#8→#10). **This plan does not overlap it:** klepon added the
12
-upload *mechanism*; this one hardens the *pre-flight and verification* around
13
-it. Shared context between the two:
14
-
15
-> **Progress (2025-10-11, this session):** gh stack #27 (bottom→top)
16
-> #24 `docs/list-builds-pagination` → #25 `feat/signing-doctor` →
17
-> #26 `feat/apps-await-processing`, rebased onto `development`, all CI-clean
18
-> (fmt / clippy `-D warnings` / test / `RUSTDOCFLAGS=-D warnings` doc /
19
-> lower-crate solo builds), signed+verified, auto-merge enabled. This landed
20
-> finding **#2** (signing doctor), finding **#5** (await-processing), and
21
-> finding **#6** (list_builds pagination doc). Still open from this plan:
22
-> **#1** (pre-upload `altool --validate-app`), **#3** (body-aware `success`),
23
-> **#4** (`.app`-vs-`.pkg` message). The one live item for all of them is the
24
-> ASC smoke test, still blocked on credentials.
11
+(gh stack #6→#7→#8→#10). Both have now closed out.
12
+
13
+**Closed-out progress (2025-10-11, this session):** gh stack #27
14
+(bottom→top) #24 `docs/list-builds-pagination` → #25 `feat/signing-doctor`
15
+→ #26 `feat/apps-await-processing`, rebased onto `development`, all
16
+CI-clean (fmt / clippy `-D warnings` / test / `RUSTDOCFLAGS=-D warnings`
17
+doc / lower-crate solo builds), signed+verified, auto-merge enabled.
18
+This landed finding **#2** (signing doctor), finding **#5**
19
+(await-processing), and finding **#6** (list_builds pagination doc).
20
+
21
+**Closed-out progress (later):** the `upload.rs` cluster
22
+(findings #1, #3, #4 — pre-flight `--validate-app`, body-aware
23
+`success`, `.app`-vs-`.pkg` distinction) shipped together on the same
24
+file, sharing the test scaffolding that already covered `altool_args`.
25
+Every finding from this plan is now in `development`.
26
27
- Both sessions hit the same root pain: a **stale private key / signing
28
identity with the right id but wrong contents**, which surfaces as an
30
this session, ~20 min lost to a stale `SIGNING_IDENTITY` in
31
`.env.appstore`. `ascapi apps upload` already dodges the *key* half by
32
passing `--p8-file-path` (klepon #10); the *local signing identity* half
32
- (finding #2 below) is still uncovered.
33
+ (finding #2 below) is now covered by `asc signing doctor`.
34
- Klepon's open "Live smoke test against ASC" item is the natural place to
34
- also exercise findings #1 and #3 once implemented.
35
+ also exercise findings #1 and #3 once implemented. **Still blocked on
36
+ credentials.**
37
38
## Session context
39
43
- `ascapi` *does* own upload + TestFlight/review orchestration (`asc apps
44
upload`, `xcrun altool`). It cannot build or sign — out of scope by
45
design. The findings below stay inside what ascapi already owns.
44
-- All six findings re-verified against `development` on 2025-10-11; none of
45
- the merged PRs (resubmit-rejected, exact bundle-id filters, profile
46
- bundle-ids) touch the upload or signing-identity paths.
46
47
## Findings
48
50
-### 1. No pre-upload `altool --validate-app` pass — HIGH
51
-`frontend::upload::validate_package` (`crates/frontend/src/upload.rs:71`) is
52
-a *file* check only: extension is `.ipa`/`.pkg`, and the path
53
-`is_file()`. It never asks Apple whether the binary is acceptable, so the
54
-first real validation happens minutes into `--upload-package`. altool
55
-exposes `--validate-app` for exactly this: a fast server-side pre-flight
56
-(entitlements, provisioning, version collisions) before the slow upload.
57
-- **Fix:** add `asc apps validate <pkg>` (and/or a `--validate` flag on
58
- `upload`) that runs `xcrun altool --validate-app` with the same
59
- `--p8-file-path` auth parity as the upload path, surfacing Apple's JSON
60
- verdict. Reuse `altool_args`/`outcome` shape.
49
+### 1. No pre-upload `altool --validate-app` pass — HIGH ✅ SHIPPED
50
+`frontend::upload::validate_app` (added in
51
+`crates/frontend/src/upload.rs`) is now a *server-side* check: it runs
52
+`xcrun altool --validate-app` with the same `--p8-file-path` auth parity
53
+as the upload path, surfacing Apple's JSON verdict. Reachable from the CLI
54
+as `asc apps validate <pkg>`. `--wait` is deliberately not passed on the
55
+validation path — validation is already synchronous.
56
- **Value:** turns a ~minutes-late failure into a seconds-early one; catches
57
the common "wrong version / bad entitlement" class before upload.
58
74
the local half of klepon's key-drift bug. Bigger surface (new keychain
75
interaction, macOS-only) → its own branch.
76
82
-### 3. `success` trusts the exit code, not the JSON body — HIGH
83
-`outcome()` (`crates/frontend/src/upload.rs:129`) sets
84
-`success: exit_code == Some(0)`. altool has historically returned 0 while
85
-reporting warnings/soft failures in its JSON body, and `--wait` emits two
86
-documents (status then result) — neither is inspected for an error array.
87
-A caller (and the MCP tool) can be told an upload "succeeded" when altool's
88
-own body says otherwise.
89
-- **Fix:** derive `success` from exit code **and** the parsed body — treat a
90
- non-empty `product-errors`/`errors` array (across altool's key spellings,
91
- mirroring the `find_delivery_id` multi-spelling approach) as failure even
92
- on exit 0. Keep exit code in the struct for debugging.
77
+### 3. `success` trusts the exit code, not the JSON body — HIGH ✅ SHIPPED
78
+`outcome()` (`crates/frontend/src/upload.rs`) now derives `success` from
79
+exit code **and** the parsed body — a non-empty `product-errors` /
80
+`product_errors` / `errors` array under any of altool's key spellings
81
+counts as failure even on exit 0. Exit code stays in `UploadOutcome` for
82
+debugging.
83
- **Value:** small, same-file change; removes false "success" reports.
84
95
-### 4. `.app`-vs-`.pkg` gives a generic "does not exist" — MEDIUM
96
-In `validate_package`, a `.app` **bundle** (a directory) fails the
97
-extension check and falls through to the generic
98
-`"{} does not exist"` branch — misleading, since it very much exists; it's
99
-just not exported yet. People routinely point the tool at the `.app`.
100
-- **Fix:** detect a `.app` (or any directory) and return a precise message:
101
- "this is an .app bundle, not an uploadable archive — export it first
102
- (`xcodebuild -exportArchive`) to produce an .ipa/.pkg". Distinguish
103
- "directory / wrong kind" from "path missing".
85
+### 4. `.app`-vs-`.pkg` gives a generic "does not exist" — MEDIUM ✅ SHIPPED
86
+In `validate_package`, a `.app` **bundle** (a directory) now hits a
87
+precise message: *"this is an .app bundle, not an uploadable package;
88
+export the archive first (`xcodebuild -exportArchive`) to get an
89
+.ipa/.pkg"*. A bare directory still reports "is a directory, not a
90
+package file" so "path missing" and "wrong kind" stay distinct.
91
- **Value:** tiny, same-file, removes a recurring confusing error.
92
93
### 5. No non-blocking `await-processing` loop — MEDIUM ✅ SHIPPED (PR #26)
115
`list_builds` doesn't expose `BuildFilter`" item — fold into that rather
116
than tracking separately.
117
131
-## The 3 biggest wins (picked)
132
-
133
-1. **#1 — pre-upload `altool --validate-app`.** Highest leverage: converts
134
- late, expensive upload failures into fast pre-flight ones, squarely
135
- inside what ascapi already owns, reusing existing auth-parity plumbing.
136
-2. **#2 — `asc signing doctor` (local keychain ↔ ASC cross-check).** The
137
- one that actually cost this session ~20 min and the local half of
138
- klepon's unresolved key-drift bug. Highest *pain* addressed; larger
139
- surface, own branch.
140
-3. **#3 — body-aware `success`.** Smallest, same-file change with outsized
141
- correctness payoff: stops the tool (and its MCP consumers) from
142
- reporting a failed upload as successful.
143
-
144
-**Sequencing:** #3 then #1 together in `upload.rs` (one file, one test
145
-module, shared `--dry-run`/`altool_args` test scaffolding; #4 is cheap to
146
-fold in alongside). #2 on its own branch (new macOS-only keychain surface,
147
-`security` subprocess, cross-check against `CertificateApi`). Exercise #1
148
-and #3 under klepon's still-open "Live smoke test against ASC" item.
149
-
150
-**Status:** #2 shipped (PR #25). The `upload.rs` cluster (#3 + #1, with #4
151
-folded in) is still the next natural branch — one file, shared test
152
-scaffolding, all inside what ascapi already owns.
118
+## Status
119
+Closed. All six findings merged. The only remaining item is the live ASC
120
+smoke test, shared with the klepon plan and credential-blocked.
\ No newline at end of file