docs(agents): close out release-session plans; absorb remaining process notes (#38)

The three release-session plan files in .agents/plans/ were mostly stale: every shipped code finding from them is already on development. Close them out by checking items against the current tree, and move the few still-useful notes into AGENTS.md so future sessions don't have to dig through three plan files to find them. Plan-by-plan: * klepon (token-source + builds API): all scope items landed. Unit tests for token.rs (CountingSource in crates/core/src/client.rs, StaticToken / boxed source in crates/core/src/token.rs) cover the 'Client::new over a custom TokenSource' gap; the MCP list_builds tool already constructs BuildFilter from the request. The separate 'stale AuthKey_<id>.p8' finding is now documented in AGENTS.md under 'App Store Connect key location'. * sitiai (upload + signing-identity hardening): all six findings landed. Pre-flight altool --validate-app, body-aware success, .app-vs-.pkg distinction shipped in crates/frontend/src/upload.rs; asc signing doctor (PR #25); asc apps await-processing (PR #26); list_builds pagination doc (PR #24). The credential-blocked live ASC smoke test is the one remaining item and is shared with the klepon plan. * splitfire (TestFlight release session): testflight CLI surface shipped; the 'rebuild asc first' warning and the finish-the-release recipe are now in AGENTS.md under 'Verify before relying on a freshly built asc' and 'Finish-the-release recipe (TestFlight)'. AGENTS.md (symlinked from the repo root as both AGENTS.md and CLAUDE.md) gains three short sections instead of one big paragraph, so a reader scanning the doc index can find each rule directly. Verified locally: cargo fmt --check, cargo clippy -D warnings, cargo test, RUSTDOCFLAGS=-D warnings cargo doc --workspace, and the lower-crate solo builds (core / aso / signing) all clean. Co-authored-by: OndeCode v1.0.3-acp <noreply@ondeinference.com>

Seto Elkahfi committed Oct 13, 2026 at 21:20 UTC f6cab80c09b187b18b8f91a6c13255d8db329a05
4 files changed +143 -94
.agents/AGENTS.md
+56
@@ -104,6 +104,23 @@ Intra-doc links are load-bearing (a broken one fails CI), and the CSR tests
104 shell out to `openssl`, whose output differs between OpenSSL 3 and macOS
105 LibreSSL.
106
107 +## Verify before relying on a freshly built `asc`
108 +
109 +The released/installed `asc` binary can silently lag `development`. This
110 +hides because `asc apps upload` only shells out to `xcrun altool` and
111 +doesn't touch newer code paths, so an upload succeeds against a stale
112 +binary that lacks e.g. the `testflight` subcommand. Before relying on a
113 +newly-added command, rebuild from `development`:
114 +
115 +```sh
116 +cargo build --release
117 +# or, to be sure you're on the current branch's binary:
118 +cargo install --path crates/cli --locked
119 +```
120 +
121 +`unrecognized subcommand` on a command that's been merged is the symptom;
122 +the cure is a rebuild, not a workaround.
123 +
124 ## Architecture notes
125
126 - Layering is `core` → `aso`/`pricing`/`signing`/`reports` → `frontend` →
@@ -132,6 +149,45 @@ The CLI needs an ASC API key: `--key-id`/`ASC_API_KEY`,
149 `~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8`). `--mcp` resolves
150 credentials per tool call, so the server can list tools unconfigured.
151
152 +### App Store Connect key location
153 +
154 +`ascapi` itself defaults to `~/.appstoreconnect/private_keys/AuthKey_<key-id>.p8`.
155 +Some sibling repos use a different default and can drift when two copies of
156 +the same key id are on disk:
157 +
158 +- `onde-store/scripts/build-ios.sh` and `smbcloud-browser/scripts/build-ios.sh`
159 + default to `~/private_keys/AuthKey_<id>.p8`.
160 +
161 +A stale `AuthKey_<id>.p8` with the same id surfaces as an indistinguishable
162 +401 on ASC API calls — ASC says "valid key id, signature doesn't verify."
163 +`ascapi apps upload` dodges the half of the drift that goes through altool
164 +by passing `--p8-file-path` explicitly (so altool can't search for a stale
165 +key). For every other command the resolution rule is the one above; if a
166 +401 appears where it shouldn't, the first thing to check is which `.p8`
167 +both sides are actually reading. If you need the scripts to use a single
168 +key file, set `ASC_PRIVATE_KEY_PATH` explicitly in the environment
169 +instead of relying on either default.
170 +
171 +## Finish-the-release recipe (TestFlight)
172 +
173 +The shortest path from a signed `.pkg` to a TestFlight group:
174 +
175 +1. **Upload:** `asc apps upload <PKG> --wait` (always passes
176 + `--p8-file-path`; never call `altool` by hand — its key search can pick a
177 + stale `AuthKey_<id>.p8` and fail with a 401).
178 +2. **Poll to VALID:** `asc apps builds <APP_ID> --version <BUILD_NUMBER>`
179 + until `processingState` is `VALID`, then read the build id off the
180 + response. (`asc apps await-processing` is the non-blocking alternative
181 + to `--wait` if you're driving this from CI.)
182 +3. **Beta-review contact details** (one-time per app):
183 + `asc testflight app-review-detail update ...`.
184 +4. **Submit:** find the Public Link group with
185 + `asc testflight groups list <APP_ID>`, then either
186 + `groups add-build --build-id <BUILD_ID> <GROUP_ID>` or
187 + `app-review-submissions submit <BUILD_ID>`. Both are CLI-only — adding
188 + to an external group / submitting to Beta App Review can trigger review,
189 + which an MCP tool call shouldn't do unattended.
190 +
191 ## Review submissions (CLI)
192
193 ```sh
.agents/plans/feature-plan-from-klepon-xcode-release-session.md
+27 -13
@@ -1,4 +1,4 @@
1 -# smbcloud-ascapi — token-source + builds API: open plan items
1 +# smbcloud-ascapi — token-source + builds API: plan (closed)
2
3 This file tracks the remaining work from the builds-API / token-source
4 session on `smbcloud-ascapi` (repo: `github.com:smbcloudXYZ/smbcloud-ascapi`,
@@ -26,10 +26,13 @@ Shipped as gh stack #9 (draft PRs, bottom to top):
26 `/v1/apps/{id}/builds` and `/v1/apps/{id}/preReleaseVersions`, which
27 don't accept `filter[...]`. Now `/v1/builds` and
28 `/v1/preReleaseVersions` with `filter[app]` (#8).
29 -- [ ] Unit tests for `token.rs` (`StaticToken`, `Client::new` over a
30 - custom `TokenSource`) (#7).
31 -- [ ] MCP `list_builds` tool doesn't expose `BuildFilter` yet, so it lags
32 - the CLI.
29 +- [x] Unit tests for `token.rs` (`StaticToken`, `Client::new` over a
30 + custom `TokenSource`) (#7) — `CountingSource` mint-counting tests in
31 + `crates/core/src/client.rs::tests`, `StaticToken`/boxed-source tests
32 + in `crates/core/src/token.rs::tests`.
33 +- [x] MCP `list_builds` exposes `BuildFilter` — see
34 + `crates/mcp/src/lib.rs:201` (constructs `BuildFilter` from the
35 + request, including `processing-state`).
36 - [x] Documented `list_builds` limit=200 as page size, not a cap
37 (sitiai finding #6, PR #24).
38 - [x] `asc apps await-processing` poll loop built on `BuildsApi::list_builds`
@@ -46,14 +49,15 @@ Shipped as gh stack #9 (draft PRs, bottom to top):
49 - [ ] Live smoke test against ASC (needs `ASC_ISSUER_ID`): run
50 `apps builds <id> --pre-release-version …` and
51 `apps pre-release-versions <id> --platform ios` to confirm the
49 - `/v1/builds` filter fix, then one real `apps upload`.
52 + `/v1/builds` filter fix, then one real `apps upload`. **Still blocked
53 + on credentials; no owner.**
54 - [x] Commits with co-author trailer, signed (GitHub: verified).
55 - [x] `gh stack` PRs (`remote.pushDefault` is `setoelkahfi`, there is no
56 `origin`).
53 -- [ ] Mark PRs ready for review and merge (`gh stack merge --yes`).
57 +- [x] Mark PRs ready for review and merge (`gh stack merge --yes`).
58
55 -## Separate / unaddressed bug
56 -- [ ] ASC API 401 on builds query. Not in this repo: Klepon has no
59 +## Separate / unaddressed bug — documented in AGENTS.md
60 +- [→] ASC API 401 on builds query. Not in this repo: Klepon has no
61 `build-ios.sh`. `onde-store/scripts/build-ios.sh` and
62 `smbcloud-browser/scripts/build-ios.sh` default `ASC_PRIVATE_KEY_PATH`
63 to `~/private_keys/AuthKey_<id>.p8`, while ascapi defaults to
@@ -61,9 +65,19 @@ Shipped as gh stack #9 (draft PRs, bottom to top):
65 the same id can drift. Decide which file is current, then either
66 point the scripts at it or set `ASC_PRIVATE_KEY_PATH` once for both.
67 `ascapi apps upload` avoids the drift by passing the key explicitly.
68 + **No code change here.** The drift is between sibling repos, not
69 + inside `smbcloud-ascapi`; tracked as the "App Store Connect key
70 + location" section in `AGENTS.md` so future sessions don't
71 + re-investigate.
72
73 ## Cleanup
66 -- [ ] Main working tree on `development` still holds the uncommitted copy
67 - of #6–#8. It's now on the branches, so it can be discarded.
68 -- [ ] Old local `feature/token` (`0eac45b`, unpushed) is superseded by #7.
69 -- [ ] Worktree at `/tmp/ascapi-stack/wt` (`git worktree remove`).
74 +- [x] Main working tree on `development` clean — the uncommitted copy of
75 + #6–#8 was discarded once it landed on the branches.
76 +- [x] Old local `feature/token` (`0eac45b`, unpushed) is superseded by #7;
77 + branch is gone.
78 +- [x] Worktree at `/tmp/ascapi-stack/wt` removed (`git worktree remove`).
79 +
80 +## Status
81 +Closed. All plan items in scope of this repo are merged. The one remaining
82 +item (live ASC smoke test) is credential-blocked and not actionable from
83 +here. The cross-repo key-drift finding lives in `AGENTS.md`.
.agents/plans/feature-plan-from-sitiai-release-session.md
+41 -73
@@ -1,4 +1,4 @@
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,20 +8,21 @@ 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 -> **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
@@ -29,9 +30,10 @@ it. Shared context between the two:
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
@@ -41,23 +43,16 @@ it. Shared context between the two:
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
@@ -79,28 +74,20 @@ what's installed and valid locally, nor whether they line up.
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)
@@ -128,25 +115,6 @@ normal apps; note it as a known limit. Overlaps klepon's open "MCP
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
.agents/plans/from-splitfire-ai-apps-session.md
+19 -8
@@ -1,10 +1,10 @@
1 -# Findings — SplitFire AI apps release session (KaroKowe Jam TestFlight)
1 +# Findings — SplitFire AI apps release session (KaroKowe Jam TestFlight) (closed)
2
3 Context: shipping a macOS build (KaroKowe Jam) to TestFlight via the `asc`
4 CLI. Concrete app id / bundle id / build number are omitted — this is a public
5 repo; keep plan files to generalizable `asc` findings, not per-app identifiers.
6
7 -## 1. The release `asc` binary can silently lag `development`
7 +## 1. The release `asc` binary can silently lag `development` — DOC-NOTE ✅ SHIPPED
8
9 The installed/release `asc` predated the `testflight` subcommand, yet
10 `asc apps upload` still worked — because upload only shells out to `altool`
@@ -15,11 +15,13 @@ evidence that the binary is current.
15 rebuild `asc` from `development` first (`cargo build --release`), or you'll hit
16 `unrecognized subcommand` on a binary that otherwise looks healthy.
17
18 -## 2. `testflight groups` takes a nested subcommand, not an app id
18 +**Where this lives:** the rule is captured in `AGENTS.md` § "Verify before
19 +relying on a freshly built `asc`" so a future session doesn't relearn it.
20
20 -`asc testflight groups <APP_ID>` fails with
21 -`error: unrecognized subcommand '<APP_ID>'`. The app id is an argument of the
22 -leaf command, not of `groups`. Confirmed command surface:
21 +## 2. `testflight groups` takes a nested subcommand, not an app id — ✅ SHIPPED
22 +
23 +`asc testflight groups <APP_ID>` would have failed with
24 +`error: unrecognized subcommand '<APP_ID>'`. Confirmed command surface:
25
26 ```
27 asc testflight groups list <APP_ID>
@@ -39,7 +41,7 @@ Notes:
41 can trigger review — not something an MCP tool call should do unattended).
42 - Both support `--dry-run` to print the request body before writing.
43
42 -## 3. Finish-the-release recipe (for next time)
44 +## 3. Finish-the-release recipe — DOC-NOTE
45
46 1. Upload: `asc apps upload <PKG> --wait` (always `--p8-file-path`; never the
47 script's raw `altool`, which can pick a stale `AuthKey_<id>.p8` → 401).
@@ -50,9 +52,18 @@ Notes:
52 then either `groups add-build --build-id <BUILD_ID> <GROUP_ID>` or
53 `app-review-submissions submit <BUILD_ID>`.
54
53 -## 4. Process note
55 +This recipe is folded into the "Builds API (CLI)" section of `AGENTS.md`
56 +under the upload + processing sequence.
57 +
58 +## 4. Process note — DOC-NOTE ✅ SHIPPED
59
60 Plan files and any other changes land on a feature branch and go in through a
61 PR — never a direct commit to `development` (or `main`). It's easy to slip when
62 a change feels like "just docs"; treat the branching rule as unconditional.
63 See the **Git branching workflow** section of `AGENTS.md`.
64 +
65 +## Status
66 +Closed. Finding #2 shipped as code (testflight CLI tree plus
67 +build-localizations for "What to Test"). Findings #1, #3, #4 are now
68 +doc-notes absorbed into `AGENTS.md`. The recipe in #3 is the operational
69 +TL;DR for what `AGENTS.md` already documents command-by-command.
\ No newline at end of file