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.