docs: plan upload + signing-identity hardening from Siti AI release session
Six findings from the Siti AI macOS App Store upload session, verified against development: no pre-upload altool --validate-app, no local-keychain signing doctor, exit-code-only success, imprecise .app error, no non-blocking await-processing, and list_builds pagination. Cross-references the klepon-xcode plan (non-overlapping: that added the upload mechanism, this hardens pre-flight + verification) and picks the 3 biggest wins. Co-Authored-By: siGit Code v1.6.6-acp <noreply@sigit.si>
Seto Elkahfi committed
Oct 11, 2026 at 14:22 UTC
b9031fe22af12a08be2f042de521e02200ef00a9
1 file changed
+130
.agents/plans/feature-plan-from-sitiai-release-session.md
new
+130
@@ -0,0 +1,130 @@
1
+# smbcloud-ascapi — upload + signing-identity hardening: plan
2
+
3
+Findings from the **Siti AI** release session (macOS App Store upload of
4
+`Siti AI.pkg` through App Store Connect). Repo:
5
+`github.com:smbcloudXYZ/smbcloud-ascapi`, base branch `development`, remote
6
+`setoelkahfi` (there is no `origin`, so use `git`/`gh`, not the sigit MCP
7
+tools).
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
+- Both sessions hit the same root pain: a **stale private key / signing
16
+ identity with the right id but wrong contents**, which surfaces as an
17
+ indistinguishable 401 (klepon plan, "Separate / unaddressed bug") or, in
18
+ this session, ~20 min lost to a stale `SIGNING_IDENTITY` in
19
+ `.env.appstore`. `ascapi apps upload` already dodges the *key* half by
20
+ passing `--p8-file-path` (klepon #10); the *local signing identity* half
21
+ (finding #2 below) is still uncovered.
22
+- Klepon's open "Live smoke test against ASC" item is the natural place to
23
+ also exercise findings #1 and #3 once implemented.
24
+
25
+## Session context
26
+
27
+- Uploaded a macOS build (`Siti AI.pkg`) to ASC successfully. The time sink
28
+ was a stale `SIGNING_IDENTITY` in `.env.appstore` — nothing in ascapi
29
+ inspects the local keychain, so the tool gave no signal it was stale.
30
+- `ascapi` *does* own upload + TestFlight/review orchestration (`asc apps
31
+ upload`, `xcrun altool`). It cannot build or sign — out of scope by
32
+ design. The findings below stay inside what ascapi already owns.
33
+- All six findings re-verified against `development` on 2025-10-11; none of
34
+ the merged PRs (resubmit-rejected, exact bundle-id filters, profile
35
+ bundle-ids) touch the upload or signing-identity paths.
36
+
37
+## Findings
38
+
39
+### 1. No pre-upload `altool --validate-app` pass — HIGH
40
+`frontend::upload::validate_package` (`crates/frontend/src/upload.rs:71`) is
41
+a *file* check only: extension is `.ipa`/`.pkg`, and the path
42
+`is_file()`. It never asks Apple whether the binary is acceptable, so the
43
+first real validation happens minutes into `--upload-package`. altool
44
+exposes `--validate-app` for exactly this: a fast server-side pre-flight
45
+(entitlements, provisioning, version collisions) before the slow upload.
46
+- **Fix:** add `asc apps validate <pkg>` (and/or a `--validate` flag on
47
+ `upload`) that runs `xcrun altool --validate-app` with the same
48
+ `--p8-file-path` auth parity as the upload path, surfacing Apple's JSON
49
+ verdict. Reuse `altool_args`/`outcome` shape.
50
+- **Value:** turns a ~minutes-late failure into a seconds-early one; catches
51
+ the common "wrong version / bad entitlement" class before upload.
52
+
53
+### 2. No local-keychain `signing doctor` — HIGH
54
+The `signing` crate (`certificate.rs`, `profile.rs`, `csr.rs`) talks **only
55
+to the ASC REST API**. `security find-identity` is called nowhere in the
56
+repo; there is no local-keychain code at all. So the exact stale-identity
57
+problem this session hit (and the klepon 401 key-drift bug) has **zero
58
+coverage**: ascapi can tell you what certs/profiles *Apple* has, but not
59
+what's installed and valid locally, nor whether they line up.
60
+- **Fix:** add `asc signing doctor` that runs `security find-identity -v
61
+ -p codesigning`, lists local valid signing identities, and cross-checks
62
+ them against the certificates the ASC API reports
63
+ (`CertificateApi::list_certificates`) — flagging identities that are
64
+ expired, missing locally, or present locally but revoked/absent on Apple.
65
+ Optionally resolve the `.p8` the CLI would use and confirm it exists and
66
+ is readable.
67
+- **Value:** directly closes the gap that cost ~20 min this session and is
68
+ the local half of klepon's key-drift bug. Bigger surface (new keychain
69
+ interaction, macOS-only) → its own branch.
70
+
71
+### 3. `success` trusts the exit code, not the JSON body — HIGH
72
+`outcome()` (`crates/frontend/src/upload.rs:129`) sets
73
+`success: exit_code == Some(0)`. altool has historically returned 0 while
74
+reporting warnings/soft failures in its JSON body, and `--wait` emits two
75
+documents (status then result) — neither is inspected for an error array.
76
+A caller (and the MCP tool) can be told an upload "succeeded" when altool's
77
+own body says otherwise.
78
+- **Fix:** derive `success` from exit code **and** the parsed body — treat a
79
+ non-empty `product-errors`/`errors` array (across altool's key spellings,
80
+ mirroring the `find_delivery_id` multi-spelling approach) as failure even
81
+ on exit 0. Keep exit code in the struct for debugging.
82
+- **Value:** small, same-file change; removes false "success" reports.
83
+
84
+### 4. `.app`-vs-`.pkg` gives a generic "does not exist" — MEDIUM
85
+In `validate_package`, a `.app` **bundle** (a directory) fails the
86
+extension check and falls through to the generic
87
+`"{} does not exist"` branch — misleading, since it very much exists; it's
88
+just not exported yet. People routinely point the tool at the `.app`.
89
+- **Fix:** detect a `.app` (or any directory) and return a precise message:
90
+ "this is an .app bundle, not an uploadable archive — export it first
91
+ (`xcodebuild -exportArchive`) to produce an .ipa/.pkg". Distinguish
92
+ "directory / wrong kind" from "path missing".
93
+- **Value:** tiny, same-file, removes a recurring confusing error.
94
+
95
+### 5. No non-blocking `await-processing` loop — MEDIUM
96
+The only way to wait for ASC to finish processing a build is altool's
97
+`--wait`, which blocks the altool process for the whole duration. There's
98
+no poll-based path that uses the builds API (`BuildsApi::list_builds`,
99
+shipped in klepon #8) to report processing state incrementally or let the
100
+caller poll on its own cadence.
101
+- **Fix:** add `asc apps await-processing <build-id|--build-number>` that
102
+ polls `/v1/builds` for `processingState` until `VALID`/`FAILED`/timeout,
103
+ with backoff. Complements klepon's builds API; an MCP-friendly
104
+ alternative to a blocking `--wait`.
105
+- **Value:** better UX for CI/agents; builds on existing klepon surface.
106
+
107
+### 6. `list_builds` limit=200 pagination — LOW (doc-only)
108
+`list_builds` caps at limit=200 with no cursor follow-through. Fine for
109
+normal apps; note it as a known limit. Overlaps klepon's open "MCP
110
+`list_builds` doesn't expose `BuildFilter`" item — fold into that rather
111
+than tracking separately.
112
+
113
+## The 3 biggest wins (picked)
114
+
115
+1. **#1 — pre-upload `altool --validate-app`.** Highest leverage: converts
116
+ late, expensive upload failures into fast pre-flight ones, squarely
117
+ inside what ascapi already owns, reusing existing auth-parity plumbing.
118
+2. **#2 — `asc signing doctor` (local keychain ↔ ASC cross-check).** The
119
+ one that actually cost this session ~20 min and the local half of
120
+ klepon's unresolved key-drift bug. Highest *pain* addressed; larger
121
+ surface, own branch.
122
+3. **#3 — body-aware `success`.** Smallest, same-file change with outsized
123
+ correctness payoff: stops the tool (and its MCP consumers) from
124
+ reporting a failed upload as successful.
125
+
126
+**Sequencing:** #3 then #1 together in `upload.rs` (one file, one test
127
+module, shared `--dry-run`/`altool_args` test scaffolding; #4 is cheap to
128
+fold in alongside). #2 on its own branch (new macOS-only keychain surface,
129
+`security` subprocess, cross-check against `CertificateApi`). Exercise #1
130
+and #3 under klepon's still-open "Live smoke test against ASC" item.