Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` →
Expand Down Expand Up @@ -132,6 +149,45 @@ The CLI needs an ASC API key: `--key-id`/`ASC_API_KEY`,
`~/.appstoreconnect/private_keys/AuthKey_<key-id>.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_<key-id>.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_<id>.p8`.

A stale `AuthKey_<id>.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 <PKG> --wait` (always passes
`--p8-file-path`; never call `altool` by hand — its key search can pick a
stale `AuthKey_<id>.p8` and fail with a 401).
2. **Poll to VALID:** `asc apps builds <APP_ID> --version <BUILD_NUMBER>`
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 <APP_ID>`, then either
`groups add-build --build-id <BUILD_ID> <GROUP_ID>` or
`app-review-submissions submit <BUILD_ID>`. 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
Expand Down
40 changes: 27 additions & 13 deletions .agents/plans/feature-plan-from-klepon-xcode-release-session.md
Original file line number Diff line number Diff line change
@@ -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`,
Expand Down Expand Up @@ -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`
Expand All @@ -46,24 +49,35 @@ Shipped as gh stack #9 (draft PRs, bottom to top):
- [ ] Live smoke test against ASC (needs `ASC_ISSUER_ID`): run
`apps builds <id> --pre-release-version …` and
`apps pre-release-versions <id> --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_<id>.p8`, while ascapi defaults to
`~/.appstoreconnect/private_keys/AuthKey_<id>.p8`. Two copies with
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`.
114 changes: 41 additions & 73 deletions .agents/plans/feature-plan-from-sitiai-release-session.md
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -8,30 +8,32 @@ 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
indistinguishable 401 (klepon plan, "Separate / unaddressed bug") or, in
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

Expand All @@ -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 <pkg>` (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 <pkg>`. `--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.

Expand All @@ -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)
Expand Down Expand Up @@ -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.
27 changes: 19 additions & 8 deletions .agents/plans/from-splitfire-ai-apps-session.md
Original file line number Diff line number Diff line change
@@ -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`
Expand All @@ -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 <APP_ID>` fails with
`error: unrecognized subcommand '<APP_ID>'`. 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 <APP_ID>` would have failed with
`error: unrecognized subcommand '<APP_ID>'`. Confirmed command surface:

```
asc testflight groups list <APP_ID>
Expand All @@ -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 <PKG> --wait` (always `--p8-file-path`; never the
script's raw `altool`, which can pick a stale `AuthKey_<id>.p8` → 401).
Expand All @@ -50,9 +52,18 @@ Notes:
then either `groups add-build --build-id <BUILD_ID> <GROUP_ID>` or
`app-review-submissions submit <BUILD_ID>`.

## 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.
Loading