Skip to content

Add iPhone-only Journal release checks and guarded Fastlane upload - #103

Merged
RCmerci merged 5 commits into
mainfrom
codex/journal-testflight-release
Oct 21, 2026
Merged

RCmerci merged 5 commits into
mainfrom
codex/journal-testflight-release

Conversation

@RCmerci

@RCmerci RCmerci commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Journal's external release project defaulted to iPhone+iPad, causing Apple validation to reject its three iPhone orientations. Upload diagnostics also lost the useful failure when Altool returned exit 0 with an error. This brings the reusable release fixes into the repository.

  • Declare iPhone-only packaging and preserve the team's existing Logseq encryption declaration; assert both in generated and final packages.
  • Add pinned Fastlane prepare/archive/export/verify/upload lanes with operator-selected paths and version/build. Journal bundle, signing team, ASC app and distribution scope are explicit. Reuse existing signing identities/profiles only.
  • Verify IPA hash, signature, profile, entitlements, version, device family, arm64/iPhoneOS and source provenance. Check archive dSYM UUIDs and native/source inputs after building. Fix actual rubyzip extraction, disable plist Marshal decoding, and pass codesign's certificate prefix as one optional argument.
  • Preserve complete sanitized multiline/structured errors and child exit status. Use a hash-bound package snapshot, durable per-build attempt history, locking, a fresh ASC check and explicit retry. Upload uses hidden process-local input and does not distribute to groups.
  • Document upload, processing, compliance and internal-group availability as distinct stages. No new test files, test dependency or CI job is included.

The adapter remains experimental host-restamp with explicit opt-in, cross_abi_validated=false, actual native Debug configuration and Swift Release archive. Apple acceptance or a Release archive does not establish full cross-compilation, ABI correctness or runtime acceptance. The encryption key carries forward the team's declaration and owner-confirmed implementation equivalence; it is not a general legal guarantee.

Validation on final head 805c38175518c4e25d72182f3f178dae8dfe1301:

  • Exact-head CI passed: existing helper checks, all targets/native embed and full regression suite; build-and-test completed in 8m43s.
  • Actual Mac run from this checkout: maintained device build, Fastlane prepare, Release archive, signed export, independent verify lane and official Pilot upload preparation all exited 0. The new package's embedded source equals the final head; native-source compatibility and an immutable successful prior receipt were verified by the maintained builder. No receipt was edited. Missing local inputs were recovered from existing locked Git objects and verified runtime support files without installing or rebuilding OCaml.
  • Final package 1.0 (1.5877.58): Journal app/team/profile, UIDeviceFamily=[1], ITSAppUsesNonExemptEncryption=false, strict signature, arm64/iPhoneOS, experimental provenance and executable/dSYM UUID agreement passed. IPA SHA256: 4b8a7cc977a75d5420daf15f737f0bc4bb3df5e798ad069f9311a91ce3e474e7.
  • External real-IPA regression reproduced the codesign failure before the one-line fix and passed afterward, including temporary paths with spaces. Independent review confirmed actual leaf-certificate extraction and unchanged executable bytes. Earlier temporary checks covered release behavior (11 tests/146 assertions) and the two P1 fixes (6 tests/42 assertions); no temporary checks were added to the repository.
  • Upload preparation verified the package snapshot and actual pinned Pilot options only. No password was requested, Pilot upload was not called, no Apple transmission or ASC receipt check was performed, and no compliance/group/phone operation occurred. An existing Apple build with this version/build must not be retransmitted merely for this test.
  • Repository-required spec-dev-tool is unavailable locally; no decision documents were generated. Runtime acceptance/full cross ABI were not tested.

This is a formal PR; do not merge automatically. The two P1 findings are fixed. Three P2 findings remain open and outside the requested fix scope: archive-validation completion, installed provisioning-profile identity, and interrupted-upload evidence. Latest comments/reviews/threads were checked; no discussion was replied to or resolved. No credentials, signing/recovery material, graphs or release artifacts are included in the PR.

@RCmerci
RCmerci marked this pull request as ready for review October 21, 2026 03:23
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-21T03:33:15.543424Z c3524dc Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c3524dc7ab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fastlane/Fastfile Outdated
Comment on lines +63 to +64
app=File.join(archive,'Products/Applications/JournalApp.app')
JournalRelease.validate_info!(JournalRelease.read_plist(File.join(app,'Info.plist')),version: inputs.fetch('version'),build: inputs.fetch('build'),experimental: true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bind export to a successfully validated archive

In the documented archive → export flow, a post-build failure—such as the dSYM UUID check on line 49 or the second input-integrity check on line 44—leaves the newly created archive in place, but this lane accepts that same archive after only checking its Info.plist. An operator who continues after the archive lane fails can therefore sign and upload an archive that explicitly failed its release checks; record and require successful archive validation, or repeat all archive checks before export.

Useful? React with 👍 / 👎.

Comment thread fastlane/Fastfile
Comment on lines +68 to +70
export_options: {method: 'app-store-connect',destination: 'export',signingStyle: 'manual',
teamID: JournalRelease::TEAM,signingCertificate: identity,
provisioningProfiles: {JournalRelease::BUNDLE=>profile.fetch('UUID')},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Install or verify the supplied provisioning profile

When profile: points to an external .mobileprovision that is not already installed, this code only decodes the file and then gives xcodebuild its UUID; it never gives Xcode the file itself. Fastlane's pinned profile helper explicitly installs profiles by copying them into Xcode's provisioning-profile directory (source), so the runbook command can fail with “profile not found” despite validating the supplied file, or resolve a different installed profile. Install the exact supplied profile temporarily, or require and hash-check its installed copy before export.

Useful? React with 👍 / 👎.

Comment thread fastlane/lib/journal_verify.rb Outdated
destination=File.expand_path(entry.name,directory)
raise Failure, 'Unsafe IPA entry' unless destination.start_with?(directory+'/') && !entry.symlink?
FileUtils.mkdir_p(File.dirname(destination))
entry.extract(destination)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Extract IPA entries relative to the temporary directory

With the pinned rubyzip 3.7 API, the positional argument to Zip::Entry#extract is an entry path beneath the default destination_directory: '.'; passing this absolute-looking path therefore produces $PWD/tmp/..., not the intended /tmp/.... A normal IPA consequently either fails extraction or leaves files under the repository, while the later /tmp/.../Payload/*.app lookup sees an empty app and makes every export, verify, and upload fail. Extract entry.name with destination_directory: directory instead.

AGENTS.md reference: AGENTS.md:L3-L3

Useful? React with 👍 / 👎.

Comment thread fastlane/lib/journal_verify.rb Outdated
Comment on lines +4 to +5
def self.read_plist(path)
Plist.parse_xml(run!('/usr/bin/plutil','-convert','xml1','-o','-',path))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Disable Marshal decoding for package plists

The offline verifier parses the extracted, attacker-controlled Info.plist before checking its code signature, while plist 3.7.2 defaults <data> nodes to Marshal.load unless marshal: false is supplied (parser behavior). Verifying a crafted IPA can therefore instantiate arbitrary Ruby objects and execute deserialization hooks before the package is rejected; parse all package-derived plists with marshal: false.

Useful? React with 👍 / 👎.

Comment on lines +66 to +70
rescue StandardError => error
result['error_class']=error.class.name
result['error']=redact(error.message,secrets: [secret])
ensure
result['transport']=transport || {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist transport evidence when upload is interrupted

When the operator presses Ctrl-C during Altool, Interrupt bypasses this StandardError rescue. The ensure block still assembles the captured transport output, stage, and error codes in result, but the block never returns that result to Ledger#attempt!, so the durable record contains only the initial generic failed_or_uncertain state. This loses the evidence needed to decide whether Apple received the interrupted upload; merge the result into the ledger before re-raising non-standard exceptions.

Useful? React with 👍 / 👎.

@RCmerci
RCmerci merged commit 6a3695c into main Oct 21, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant