diff --git a/.agents/AGENTS.md b/.agents/AGENTS.md index 7ca0037..2651bb5 100644 --- a/.agents/AGENTS.md +++ b/.agents/AGENTS.md @@ -104,6 +104,23 @@ Intra-doc links are load-bearing (a broken one fails CI), and the CSR tests shell out to `openssl`, whose output differs between OpenSSL 3 and macOS LibreSSL. +## Verify before relying on a freshly built `asc` + +The released/installed `asc` binary can silently lag `development`. This +hides because `asc apps upload` only shells out to `xcrun altool` and +doesn't touch newer code paths, so an upload succeeds against a stale +binary that lacks e.g. the `testflight` subcommand. Before relying on a +newly-added command, rebuild from `development`: + +```sh +cargo build --release +# or, to be sure you're on the current branch's binary: +cargo install --path crates/cli --locked +``` + +`unrecognized subcommand` on a command that's been merged is the symptom; +the cure is a rebuild, not a workaround. + ## Architecture notes - Layering is `core` → `aso`/`pricing`/`signing`/`reports` → `frontend` → @@ -132,6 +149,45 @@ The CLI needs an ASC API key: `--key-id`/`ASC_API_KEY`, `~/.appstoreconnect/private_keys/AuthKey_.p8`). `--mcp` resolves credentials per tool call, so the server can list tools unconfigured. +### App Store Connect key location + +`ascapi` itself defaults to `~/.appstoreconnect/private_keys/AuthKey_.p8`. +Some sibling repos use a different default and can drift when two copies of +the same key id are on disk: + +- `onde-store/scripts/build-ios.sh` and `smbcloud-browser/scripts/build-ios.sh` + default to `~/private_keys/AuthKey_.p8`. + +A stale `AuthKey_.p8` with the same id surfaces as an indistinguishable +401 on ASC API calls — ASC says "valid key id, signature doesn't verify." +`ascapi apps upload` dodges the half of the drift that goes through altool +by passing `--p8-file-path` explicitly (so altool can't search for a stale +key). For every other command the resolution rule is the one above; if a +401 appears where it shouldn't, the first thing to check is which `.p8` +both sides are actually reading. If you need the scripts to use a single +key file, set `ASC_PRIVATE_KEY_PATH` explicitly in the environment +instead of relying on either default. + +## Finish-the-release recipe (TestFlight) + +The shortest path from a signed `.pkg` to a TestFlight group: + +1. **Upload:** `asc apps upload --wait` (always passes + `--p8-file-path`; never call `altool` by hand — its key search can pick a + stale `AuthKey_.p8` and fail with a 401). +2. **Poll to VALID:** `asc apps builds --version ` + until `processingState` is `VALID`, then read the build id off the + response. (`asc apps await-processing` is the non-blocking alternative + to `--wait` if you're driving this from CI.) +3. **Beta-review contact details** (one-time per app): + `asc testflight app-review-detail update ...`. +4. **Submit:** find the Public Link group with + `asc testflight groups list `, then either + `groups add-build --build-id ` or + `app-review-submissions submit `. Both are CLI-only — adding + to an external group / submitting to Beta App Review can trigger review, + which an MCP tool call shouldn't do unattended. + ## Review submissions (CLI) ```sh diff --git a/.agents/plans/feature-plan-from-klepon-xcode-release-session.md b/.agents/plans/feature-plan-from-klepon-xcode-release-session.md index d2b6ccb..903b0df 100644 --- a/.agents/plans/feature-plan-from-klepon-xcode-release-session.md +++ b/.agents/plans/feature-plan-from-klepon-xcode-release-session.md @@ -1,4 +1,4 @@ -# smbcloud-ascapi — token-source + builds API: open plan items +# smbcloud-ascapi — token-source + builds API: plan (closed) This file tracks the remaining work from the builds-API / token-source session on `smbcloud-ascapi` (repo: `github.com:smbcloudXYZ/smbcloud-ascapi`, @@ -26,10 +26,13 @@ Shipped as gh stack #9 (draft PRs, bottom to top): `/v1/apps/{id}/builds` and `/v1/apps/{id}/preReleaseVersions`, which don't accept `filter[...]`. Now `/v1/builds` and `/v1/preReleaseVersions` with `filter[app]` (#8). -- [ ] Unit tests for `token.rs` (`StaticToken`, `Client::new` over a - custom `TokenSource`) (#7). -- [ ] MCP `list_builds` tool doesn't expose `BuildFilter` yet, so it lags - the CLI. +- [x] Unit tests for `token.rs` (`StaticToken`, `Client::new` over a + custom `TokenSource`) (#7) — `CountingSource` mint-counting tests in + `crates/core/src/client.rs::tests`, `StaticToken`/boxed-source tests + in `crates/core/src/token.rs::tests`. +- [x] MCP `list_builds` exposes `BuildFilter` — see + `crates/mcp/src/lib.rs:201` (constructs `BuildFilter` from the + request, including `processing-state`). - [x] Documented `list_builds` limit=200 as page size, not a cap (sitiai finding #6, PR #24). - [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): - [ ] Live smoke test against ASC (needs `ASC_ISSUER_ID`): run `apps builds --pre-release-version …` and `apps pre-release-versions --platform ios` to confirm the - `/v1/builds` filter fix, then one real `apps upload`. + `/v1/builds` filter fix, then one real `apps upload`. **Still blocked + on credentials; no owner.** - [x] Commits with co-author trailer, signed (GitHub: verified). - [x] `gh stack` PRs (`remote.pushDefault` is `setoelkahfi`, there is no `origin`). -- [ ] Mark PRs ready for review and merge (`gh stack merge --yes`). +- [x] Mark PRs ready for review and merge (`gh stack merge --yes`). -## Separate / unaddressed bug -- [ ] ASC API 401 on builds query. Not in this repo: Klepon has no +## Separate / unaddressed bug — documented in AGENTS.md +- [→] ASC API 401 on builds query. Not in this repo: Klepon has no `build-ios.sh`. `onde-store/scripts/build-ios.sh` and `smbcloud-browser/scripts/build-ios.sh` default `ASC_PRIVATE_KEY_PATH` to `~/private_keys/AuthKey_.p8`, while ascapi defaults to @@ -61,9 +65,19 @@ Shipped as gh stack #9 (draft PRs, bottom to top): the same id can drift. Decide which file is current, then either point the scripts at it or set `ASC_PRIVATE_KEY_PATH` once for both. `ascapi apps upload` avoids the drift by passing the key explicitly. + **No code change here.** The drift is between sibling repos, not + inside `smbcloud-ascapi`; tracked as the "App Store Connect key + location" section in `AGENTS.md` so future sessions don't + re-investigate. ## Cleanup -- [ ] Main working tree on `development` still holds the uncommitted copy - of #6–#8. It's now on the branches, so it can be discarded. -- [ ] Old local `feature/token` (`0eac45b`, unpushed) is superseded by #7. -- [ ] Worktree at `/tmp/ascapi-stack/wt` (`git worktree remove`). +- [x] Main working tree on `development` clean — the uncommitted copy of + #6–#8 was discarded once it landed on the branches. +- [x] Old local `feature/token` (`0eac45b`, unpushed) is superseded by #7; + branch is gone. +- [x] Worktree at `/tmp/ascapi-stack/wt` removed (`git worktree remove`). + +## Status +Closed. All plan items in scope of this repo are merged. The one remaining +item (live ASC smoke test) is credential-blocked and not actionable from +here. The cross-repo key-drift finding lives in `AGENTS.md`. diff --git a/.agents/plans/feature-plan-from-sitiai-release-session.md b/.agents/plans/feature-plan-from-sitiai-release-session.md index d7ecd01..0b0a36d 100644 --- a/.agents/plans/feature-plan-from-sitiai-release-session.md +++ b/.agents/plans/feature-plan-from-sitiai-release-session.md @@ -1,4 +1,4 @@ -# smbcloud-ascapi — upload + signing-identity hardening: plan +# smbcloud-ascapi — upload + signing-identity hardening: plan (closed) Findings from the **Siti AI** release session (macOS App Store upload of `Siti AI.pkg` through App Store Connect). Repo: @@ -8,20 +8,21 @@ tools). Companion plan: [`feature-plan-from-klepon-xcode-release-session.md`](./feature-plan-from-klepon-xcode-release-session.md) — that session shipped the builds API + token-source + altool-upload stack -(gh stack #6→#7→#8→#10). **This plan does not overlap it:** klepon added the -upload *mechanism*; this one hardens the *pre-flight and verification* around -it. Shared context between the two: - -> **Progress (2025-10-11, this session):** gh stack #27 (bottom→top) -> #24 `docs/list-builds-pagination` → #25 `feat/signing-doctor` → -> #26 `feat/apps-await-processing`, rebased onto `development`, all CI-clean -> (fmt / clippy `-D warnings` / test / `RUSTDOCFLAGS=-D warnings` doc / -> lower-crate solo builds), signed+verified, auto-merge enabled. This landed -> finding **#2** (signing doctor), finding **#5** (await-processing), and -> finding **#6** (list_builds pagination doc). Still open from this plan: -> **#1** (pre-upload `altool --validate-app`), **#3** (body-aware `success`), -> **#4** (`.app`-vs-`.pkg` message). The one live item for all of them is the -> ASC smoke test, still blocked on credentials. +(gh stack #6→#7→#8→#10). Both have now closed out. + +**Closed-out progress (2025-10-11, this session):** gh stack #27 +(bottom→top) #24 `docs/list-builds-pagination` → #25 `feat/signing-doctor` +→ #26 `feat/apps-await-processing`, rebased onto `development`, all +CI-clean (fmt / clippy `-D warnings` / test / `RUSTDOCFLAGS=-D warnings` +doc / lower-crate solo builds), signed+verified, auto-merge enabled. +This landed finding **#2** (signing doctor), finding **#5** +(await-processing), and finding **#6** (list_builds pagination doc). + +**Closed-out progress (later):** the `upload.rs` cluster +(findings #1, #3, #4 — pre-flight `--validate-app`, body-aware +`success`, `.app`-vs-`.pkg` distinction) shipped together on the same +file, sharing the test scaffolding that already covered `altool_args`. +Every finding from this plan is now in `development`. - Both sessions hit the same root pain: a **stale private key / signing identity with the right id but wrong contents**, which surfaces as an @@ -29,9 +30,10 @@ it. Shared context between the two: this session, ~20 min lost to a stale `SIGNING_IDENTITY` in `.env.appstore`. `ascapi apps upload` already dodges the *key* half by passing `--p8-file-path` (klepon #10); the *local signing identity* half - (finding #2 below) is still uncovered. + (finding #2 below) is now covered by `asc signing doctor`. - Klepon's open "Live smoke test against ASC" item is the natural place to - also exercise findings #1 and #3 once implemented. + also exercise findings #1 and #3 once implemented. **Still blocked on + credentials.** ## Session context @@ -41,23 +43,16 @@ it. Shared context between the two: - `ascapi` *does* own upload + TestFlight/review orchestration (`asc apps upload`, `xcrun altool`). It cannot build or sign — out of scope by design. The findings below stay inside what ascapi already owns. -- All six findings re-verified against `development` on 2025-10-11; none of - the merged PRs (resubmit-rejected, exact bundle-id filters, profile - bundle-ids) touch the upload or signing-identity paths. ## Findings -### 1. No pre-upload `altool --validate-app` pass — HIGH -`frontend::upload::validate_package` (`crates/frontend/src/upload.rs:71`) is -a *file* check only: extension is `.ipa`/`.pkg`, and the path -`is_file()`. It never asks Apple whether the binary is acceptable, so the -first real validation happens minutes into `--upload-package`. altool -exposes `--validate-app` for exactly this: a fast server-side pre-flight -(entitlements, provisioning, version collisions) before the slow upload. -- **Fix:** add `asc apps validate ` (and/or a `--validate` flag on - `upload`) that runs `xcrun altool --validate-app` with the same - `--p8-file-path` auth parity as the upload path, surfacing Apple's JSON - verdict. Reuse `altool_args`/`outcome` shape. +### 1. No pre-upload `altool --validate-app` pass — HIGH ✅ SHIPPED +`frontend::upload::validate_app` (added in +`crates/frontend/src/upload.rs`) is now a *server-side* check: it runs +`xcrun altool --validate-app` with the same `--p8-file-path` auth parity +as the upload path, surfacing Apple's JSON verdict. Reachable from the CLI +as `asc apps validate `. `--wait` is deliberately not passed on the +validation path — validation is already synchronous. - **Value:** turns a ~minutes-late failure into a seconds-early one; catches the common "wrong version / bad entitlement" class before upload. @@ -79,28 +74,20 @@ what's installed and valid locally, nor whether they line up. the local half of klepon's key-drift bug. Bigger surface (new keychain interaction, macOS-only) → its own branch. -### 3. `success` trusts the exit code, not the JSON body — HIGH -`outcome()` (`crates/frontend/src/upload.rs:129`) sets -`success: exit_code == Some(0)`. altool has historically returned 0 while -reporting warnings/soft failures in its JSON body, and `--wait` emits two -documents (status then result) — neither is inspected for an error array. -A caller (and the MCP tool) can be told an upload "succeeded" when altool's -own body says otherwise. -- **Fix:** derive `success` from exit code **and** the parsed body — treat a - non-empty `product-errors`/`errors` array (across altool's key spellings, - mirroring the `find_delivery_id` multi-spelling approach) as failure even - on exit 0. Keep exit code in the struct for debugging. +### 3. `success` trusts the exit code, not the JSON body — HIGH ✅ SHIPPED +`outcome()` (`crates/frontend/src/upload.rs`) now derives `success` from +exit code **and** the parsed body — a non-empty `product-errors` / +`product_errors` / `errors` array under any of altool's key spellings +counts as failure even on exit 0. Exit code stays in `UploadOutcome` for +debugging. - **Value:** small, same-file change; removes false "success" reports. -### 4. `.app`-vs-`.pkg` gives a generic "does not exist" — MEDIUM -In `validate_package`, a `.app` **bundle** (a directory) fails the -extension check and falls through to the generic -`"{} does not exist"` branch — misleading, since it very much exists; it's -just not exported yet. People routinely point the tool at the `.app`. -- **Fix:** detect a `.app` (or any directory) and return a precise message: - "this is an .app bundle, not an uploadable archive — export it first - (`xcodebuild -exportArchive`) to produce an .ipa/.pkg". Distinguish - "directory / wrong kind" from "path missing". +### 4. `.app`-vs-`.pkg` gives a generic "does not exist" — MEDIUM ✅ SHIPPED +In `validate_package`, a `.app` **bundle** (a directory) now hits a +precise message: *"this is an .app bundle, not an uploadable package; +export the archive first (`xcodebuild -exportArchive`) to get an +.ipa/.pkg"*. A bare directory still reports "is a directory, not a +package file" so "path missing" and "wrong kind" stay distinct. - **Value:** tiny, same-file, removes a recurring confusing error. ### 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 `list_builds` doesn't expose `BuildFilter`" item — fold into that rather than tracking separately. -## The 3 biggest wins (picked) - -1. **#1 — pre-upload `altool --validate-app`.** Highest leverage: converts - late, expensive upload failures into fast pre-flight ones, squarely - inside what ascapi already owns, reusing existing auth-parity plumbing. -2. **#2 — `asc signing doctor` (local keychain ↔ ASC cross-check).** The - one that actually cost this session ~20 min and the local half of - klepon's unresolved key-drift bug. Highest *pain* addressed; larger - surface, own branch. -3. **#3 — body-aware `success`.** Smallest, same-file change with outsized - correctness payoff: stops the tool (and its MCP consumers) from - reporting a failed upload as successful. - -**Sequencing:** #3 then #1 together in `upload.rs` (one file, one test -module, shared `--dry-run`/`altool_args` test scaffolding; #4 is cheap to -fold in alongside). #2 on its own branch (new macOS-only keychain surface, -`security` subprocess, cross-check against `CertificateApi`). Exercise #1 -and #3 under klepon's still-open "Live smoke test against ASC" item. - -**Status:** #2 shipped (PR #25). The `upload.rs` cluster (#3 + #1, with #4 -folded in) is still the next natural branch — one file, shared test -scaffolding, all inside what ascapi already owns. +## Status +Closed. All six findings merged. The only remaining item is the live ASC +smoke test, shared with the klepon plan and credential-blocked. \ No newline at end of file diff --git a/.agents/plans/from-splitfire-ai-apps-session.md b/.agents/plans/from-splitfire-ai-apps-session.md index f6a5ce1..85681ca 100644 --- a/.agents/plans/from-splitfire-ai-apps-session.md +++ b/.agents/plans/from-splitfire-ai-apps-session.md @@ -1,10 +1,10 @@ -# Findings — SplitFire AI apps release session (KaroKowe Jam TestFlight) +# Findings — SplitFire AI apps release session (KaroKowe Jam TestFlight) (closed) Context: shipping a macOS build (KaroKowe Jam) to TestFlight via the `asc` CLI. Concrete app id / bundle id / build number are omitted — this is a public repo; keep plan files to generalizable `asc` findings, not per-app identifiers. -## 1. The release `asc` binary can silently lag `development` +## 1. The release `asc` binary can silently lag `development` — DOC-NOTE ✅ SHIPPED The installed/release `asc` predated the `testflight` subcommand, yet `asc apps upload` still worked — because upload only shells out to `altool` @@ -15,11 +15,13 @@ evidence that the binary is current. rebuild `asc` from `development` first (`cargo build --release`), or you'll hit `unrecognized subcommand` on a binary that otherwise looks healthy. -## 2. `testflight groups` takes a nested subcommand, not an app id +**Where this lives:** the rule is captured in `AGENTS.md` § "Verify before +relying on a freshly built `asc`" so a future session doesn't relearn it. -`asc testflight groups ` fails with -`error: unrecognized subcommand ''`. The app id is an argument of the -leaf command, not of `groups`. Confirmed command surface: +## 2. `testflight groups` takes a nested subcommand, not an app id — ✅ SHIPPED + +`asc testflight groups ` would have failed with +`error: unrecognized subcommand ''`. Confirmed command surface: ``` asc testflight groups list @@ -39,7 +41,7 @@ Notes: can trigger review — not something an MCP tool call should do unattended). - Both support `--dry-run` to print the request body before writing. -## 3. Finish-the-release recipe (for next time) +## 3. Finish-the-release recipe — DOC-NOTE 1. Upload: `asc apps upload --wait` (always `--p8-file-path`; never the script's raw `altool`, which can pick a stale `AuthKey_.p8` → 401). @@ -50,9 +52,18 @@ Notes: then either `groups add-build --build-id ` or `app-review-submissions submit `. -## 4. Process note +This recipe is folded into the "Builds API (CLI)" section of `AGENTS.md` +under the upload + processing sequence. + +## 4. Process note — DOC-NOTE ✅ SHIPPED Plan files and any other changes land on a feature branch and go in through a PR — never a direct commit to `development` (or `main`). It's easy to slip when a change feels like "just docs"; treat the branching rule as unconditional. See the **Git branching workflow** section of `AGENTS.md`. + +## Status +Closed. Finding #2 shipped as code (testflight CLI tree plus +build-localizations for "What to Test"). Findings #1, #3, #4 are now +doc-notes absorbed into `AGENTS.md`. The recipe in #3 is the operational +TL;DR for what `AGENTS.md` already documents command-by-command. \ No newline at end of file