--- since: 1.1.3 --- # gitaiflow MCP Deployment > Deployment, signing, key rotation, build, and R2 synchronization guide for the gitaiflow MCP extension and AOT releases. ## 1. Purpose This document is the operational reference for releasing gitaiflow AOT binaries and the Claude Desktop MCPB extension. It covers: - AOT release builds - MCPB builds - Ed25519 release signing - Embedded public-key trust - Signing-key rotation - Signature verification - R2 synchronization - Cache behavior for mutable release metadata - Normal releases - Releases involving signing-key rotation - Local/test synchronization - Recommended release safeguards - Common failure modes and recovery The release process is intentionally split into build, signing, manifest generation, and R2 synchronization steps so each stage can be inspected independently. --- ## 2. Release Architecture gitaiflow has two related release artifacts: 1. **AOT binary** - Built with the gitaiflow AOT build process. - Published under `installers/AOT//`. - Protected by `SHA256SUMS` and `SHA256SUMS.sig`. 2. **MCPB extension** - Built for Claude Desktop. - Published under `installers/MCP//`. - The MCP server contains the public key used to verify trusted AOT releases. The trust relationship is: ```text Release Signing Key │ Ed25519 private key │ ▼ SHA256SUMS.sig │ │ verifies ▼ SHA256SUMS │ │ hashes ▼ AOT binaries ▲ │ trusted by │ │ embedded public key │ ┌──────┴──────┐ │ │ install.sh MCP binary.py ``` The private signing key is never committed to the repository. The public key is intentionally embedded in the release verification code: - `scripts/install/install.sh` - `mcp_server/binary.py` Both must contain the same public key. --- ## 3. Release Artifacts An AOT release has the following general structure: ```text installers/ └── AOT/ └── v1.1.3/ ├── gitaiflow-darwin-arm64 ├── gitaiflow-linux-amd64 ├── gitaiflow-linux-arm64 ├── gitaiflow-windows-amd64.exe ├── SHA256SUMS └── SHA256SUMS.sig ``` The MCP release has a structure similar to: ```text installers/ └── MCP/ └── v1.1.3/ └── gitaiflow-mcp-1.1.3-darwin-arm64.mcpb ``` The current MCP build process produces the MCPB from the MCP server and associated packaged resources. The build output is validated as an MCPB before being written to the installer directory. --- ## 4. Release Signing gitaiflow uses Ed25519 signing for release integrity. The signing process produces: ```text SHA256SUMS SHA256SUMS.sig ``` `SHA256SUMS` contains SHA-256 hashes for the AOT binaries. `SHA256SUMS.sig` is the Ed25519 signature over the checksum file. The verification chain is therefore: ```text AOT binary │ ▼ SHA-256 │ ▼ SHA256SUMS │ ▼ Ed25519 signature │ ▼ SHA256SUMS.sig │ ▼ Embedded trusted public key ``` A valid signature establishes that the checksum manifest was signed by the trusted release key. The checksum manifest then establishes the expected hash of each binary. --- ## 5. Signing Key Storage The private signing key is kept outside the repository. On the development machine, the current workflow uses macOS Keychain storage with: ```text Service: gitaiflow-release-signing-key Account: $USER ``` The private key must not be committed to Git. When a key is generated by the rotation script, the script writes the new private key outside the repository and explicitly requires the operator to move it into secure secret storage and remove the temporary copy. The rotation script also records public-key/timestamp information in: ```text scripts/release/KEY_ROTATIONS.log ``` No secret material should be logged. --- # 6. `gitai_r2.sh` Signing Decision Flow The R2 synchronization script supports two operating modes. ## Signing key present When `GITAIFLOW_SIGNING_KEY_FILE` is available, `gitai_r2.sh` inspects AOT release directories. For every release: - If `SHA256SUMS.sig` is absent: - sign the release - verify the resulting signature - If `SHA256SUMS.sig` already exists: - verify the existing signature - do not automatically replace or re-sign it - If verification fails: - stop the release process ## Signing key absent Without a signing key, the script operates in local/test mode. It warns that release signing was skipped. This is useful for local development and testing, but an unsigned release must not be promoted as a production release. ## Final flow ```mermaid flowchart TD A["gitai_r2.sh"] A --> B{"Signing key present?"} B -->|Yes| C["Inspect every AOT release"] B -->|No| D["Local / test mode"] C --> E{"SHA256SUMS.sig exists?"} E -->|No| F["Sign release"] F --> G["Verify signature"] E -->|Yes| H["Verify existing signature"] G --> I{"Signature valid?"} H --> I I -->|Yes| J["Sync to R2"] I -->|No| X["STOP
Invalid signature"] D --> K["Warning:
Release signing skipped"] K --> J ``` ### Important property An existing signature is **verified**, not blindly trusted merely because the file exists. This avoids the following unsafe situation: ```text SHA256SUMS.sig exists │ ▼ assume valid │ ▼ upload release ``` Instead: ```text SHA256SUMS.sig exists │ ▼ verify against SHA256SUMS + trusted key │ ├── valid ──► upload │ └── invalid ─► stop ``` --- # 7. R2 Synchronization `gitai_r2.sh` separates R2 synchronization into several categories. ## 7.1 Installer metadata The following files are treated as mutable release metadata: ```text SHA256SUMS SHA256SUMS.sig latest.txt ``` They are uploaded with: ```text Cache-Control: no-store, must-revalidate ``` This is intentional. These files can change at the same URL, particularly: ```text latest.txt SHA256SUMS SHA256SUMS.sig ``` Caching them aggressively could result in a client receiving an inconsistent combination of release metadata and binaries. ## 7.2 Installer binaries AOT and MCP binaries are synchronized separately. The mutable metadata files are excluded from the binary transfer. The synchronization also excludes common local filesystem artifacts such as: ```text .DS_Store Thumbs.db desktop.ini ``` ## 7.3 Installer entrypoints The R2 sync includes the installer entrypoints: ```text install.sh install-mcp.sh install.ps1 install-mcp.ps1 ``` ## 7.4 Generated manifest The gitaiflow manifest is synchronized separately after the installer artifacts. The general R2 flow is: ```mermaid flowchart LR A["Local release artifacts"] A --> B["Release metadata
SHA256SUMS
SHA256SUMS.sig
latest.txt"] A --> C["Installer binaries"] A --> D["Installer entrypoints"] A --> E["Generated manifest"] B --> F["R2"] C --> F D --> F E --> F ``` --- # 8. Why Metadata Uses `no-store` The release system overwrites stable URLs such as: ```text latest.txt SHA256SUMS SHA256SUMS.sig ``` A stale edge cache can create a dangerous mismatch: ```text latest.txt │ ▼ v1.1.3 │ ▼ SHA256SUMS from v1.1.2 ← stale cache ``` or: ```text SHA256SUMS │ ▼ new release SHA256SUMS.sig │ ▼ old release signature ``` Using: ```text Cache-Control: no-store, must-revalidate ``` for mutable metadata reduces this class of cache inconsistency. Release binaries can remain cacheable because versioned binary paths are effectively immutable: ```text installers/AOT/v1.1.3/gitaiflow-darwin-arm64 ``` A future release should use a new versioned path: ```text installers/AOT/v1.1.4/gitaiflow-darwin-arm64 ``` --- # 9. Normal Release Flow A normal release does not rotate the signing key. The recommended high-level order is: ```text Build AOT │ ▼ Build MCPB │ ▼ Sign AOT release │ ▼ Generate manifest │ ▼ Verify / sign through R2 sync │ ▼ Sync to R2 ``` ## Commands ```bash # Build the AOT release make build # Build the MCPB for macOS arm64 make mcp-build-mac-arm64 # Generate the gitaiflow R2 manifest python3 scripts/r2/generate_gitai_manifest.py # Synchronize the release to R2 ./scripts/r2/gitai_r2.sh ``` If signing is being performed explicitly before synchronization: ```bash ./scripts/release/sign_release.sh v1.1.3 python3 scripts/r2/generate_gitai_manifest.py ./scripts/r2/gitai_r2.sh ``` The explicit signing step is useful when the release operator wants the signature generation to be visible and independently verifiable before the R2 synchronization. When `GITAIFLOW_SIGNING_KEY_FILE` is configured, `gitai_r2.sh` can also sign an AOT release when its signature is missing. --- # 10. Key Verification Before Release Before signing a release, verify that the signing key and embedded public key agree. ```bash ./scripts/release/verify_release_signing_key.sh ``` The expected relationship is: ```text Private key │ ▼ derive public key │ ▼ compare │ ├── matches ──► release can be signed │ └── mismatch ─► stop ``` A successful verification should show that the derived key and embedded `binary.py` public key match. The same trust material is also embedded in `install.sh`. --- # 11. Signing an AOT Release Manually To explicitly sign a release: ```bash ./scripts/release/sign_release.sh v1.1.3 ``` This generates: ```text installers/AOT/v1.1.3/SHA256SUMS installers/AOT/v1.1.3/SHA256SUMS.sig ``` The checksum manifest covers the AOT binaries, for example: ```text gitaiflow-darwin-arm64 gitaiflow-linux-amd64 gitaiflow-linux-arm64 gitaiflow-windows-amd64.exe ``` Both files must be available to the R2 synchronization process. --- # 12. Signing Through `gitai_r2.sh` The R2 script supports: ```bash GITAIFLOW_SIGNING_KEY_FILE ``` When this is set, the script can sign AOT releases that do not already have signatures. Conceptually: ```bash export GITAIFLOW_SIGNING_KEY_FILE="/secure/path/release-signing.key.b64" ./scripts/r2/gitai_r2.sh ``` The script should not automatically re-sign an existing release merely because a signing key is available. That is important for release reproducibility. A release already signed by the trusted release key should remain signed by that key. --- # 13. Signing-Key Rotation Signing-key rotation is a special release operation. The most important rule is: > The MCPB must be rebuilt after the public key is rotated and before an AOT release is signed with the new private key. The correct order is: ```text ROTATE SIGNING KEY │ ▼ VERIFY EMBEDDED PUBLIC KEY │ ▼ REBUILD AOT + MCPB │ ▼ SIGN AOT RELEASE │ ▼ GENERATE MANIFEST │ ▼ SYNC TO R2 ``` ## Why this order matters The MCP server contains the trusted public key. If the MCPB was built before key rotation: ```text Old MCPB │ └── trusts OLD public key New AOT release │ └── signed with NEW private key ``` The old MCPB may reject the new AOT release. Therefore: ```text New signing key │ ├── new install.sh trust │ ├── new mcp_server/binary.py trust │ └── new AOT signatures ``` must be released consistently. --- # 14. Key-Rotation Commands ## Step 1 — Rotate the key ```bash ./scripts/release/rotate_signing_key.sh ``` The rotation script: - generates a new Ed25519 keypair - updates the embedded public key in `install.sh` - updates the embedded public key in `mcp_server/binary.py` - verifies that both files contain the same public key - writes the new private key outside the repository - records public-key/timestamp information in `KEY_ROTATIONS.log` The script itself requires the operator to move the new private key into secure secret storage and delete the temporary local copy. ## Step 2 — Verify the key ```bash ./scripts/release/verify_release_signing_key.sh ``` Do not proceed if the derived key and embedded public key do not match. ## Step 3 — Rebuild AOT ```bash make build ``` ## Step 4 — Rebuild MCPB ```bash make mcp-build-mac-arm64 ``` This step is mandatory after rotation because the MCPB must contain the newly trusted public key. ## Step 5 — Sign the AOT release ```bash ./scripts/release/sign_release.sh v1.1.3 ``` ## Step 6 — Generate the manifest ```bash python3 scripts/r2/generate_gitai_manifest.py ``` ## Step 7 — Synchronize to R2 ```bash ./scripts/r2/gitai_r2.sh ``` --- # 15. Complete Key-Rotation Release The complete sequence can therefore be run as: ```bash # Rotate ./scripts/release/rotate_signing_key.sh # Verify new public key is correctly embedded ./scripts/release/verify_release_signing_key.sh # Rebuild AOT make build # Rebuild MCPB AFTER rotation make mcp-build-mac-arm64 # Sign release using the new private key ./scripts/release/sign_release.sh v1.1.3 # Generate R2 manifest python3 scripts/r2/generate_gitai_manifest.py # Sync to R2 ./scripts/r2/gitai_r2.sh ``` --- # 16. Key-Rotation Safety Rule Never perform this sequence: ```text Build MCPB │ ▼ Rotate signing key │ ▼ Sign AOT with new key │ ▼ Publish ``` That creates a potentially incompatible release: ```text MCPB └── old public key AOT └── new signature ``` Instead: ```text Rotate │ ▼ Update trusted public key │ ▼ Rebuild MCPB │ ▼ Sign AOT │ ▼ Publish both ``` --- # 17. Existing Installed Versions After Rotation Rotating the signing key does not invalidate an already-installed AOT executable merely because the key changed. The existing binary can continue to run. The compatibility concern is the verification path used when obtaining or upgrading to a newly signed release. In particular, an older MCPB containing only the old public key may not accept a newly signed AOT release. This is why the new MCPB must be released as part of the key-rotation transition. --- # 18. Future Key Rotation Strategy The current release does not require dual-key verification. For a future rotation where a large population of older MCPB installations must be supported, a migration strategy can be considered: ```text Phase 1 ─────── MCP trusts OLD + NEW ↓ Phase 2 ─────── Release new MCPB ↓ Phase 3 ─────── Sign new AOT releases with NEW ↓ Phase 4 ─────── Allow migration window ↓ Phase 5 ─────── Remove OLD trust ``` This is a future migration strategy, not a requirement for the current release process. Do not introduce dual-key complexity unless an actual key rotation requires it. --- # 19. Local / Test R2 Synchronization The R2 script deliberately supports local/test usage without a signing key. If: ```bash GITAIFLOW_SIGNING_KEY_FILE ``` is not configured, the script warns that signing is skipped. This is useful when testing: - R2 paths - installer synchronization - manifest generation - cache behavior - MCP distribution - local release workflows The warning should be treated as a production safety boundary. ```text No signing key │ ▼ Local / test mode │ ▼ Warning │ ▼ Sync may proceed ``` Do not treat an unsigned synchronization as a production release. --- # 20. Recommended Release Policy The current architecture should remain intentionally simple. ## Keep - Ed25519 release signing - public key embedded in `install.sh` - public key embedded in `mcp_server/binary.py` - explicit key verification - `SHA256SUMS` - `SHA256SUMS.sig` - optional signing through `GITAIFLOW_SIGNING_KEY_FILE` - existing-signature verification - no automatic re-signing of valid existing releases - `no-store` metadata headers - versioned immutable binary paths - separate AOT and MCPB build steps - explicit MCPB rebuild after key rotation ## Do not add unless needed - mandatory signing for every local R2 sync - automatic re-signing after key rotation - dual-key verification without a migration requirement - unnecessary changes to the existing Makefile release workflow The Makefile remains the preferred high-level entry point for builds. The individual scripts remain available when an operator needs to inspect or repeat a specific release stage. --- # 21. Recommended Release Checklist ## Normal release - [ ] Update version - [ ] Build AOT - [ ] Build MCPB - [ ] Verify release signing key - [ ] Sign AOT release - [ ] Confirm `SHA256SUMS` exists - [ ] Confirm `SHA256SUMS.sig` exists - [ ] Verify signature - [ ] Generate R2 manifest - [ ] Run `gitai_r2.sh` - [ ] Confirm R2 metadata is current - [ ] Confirm versioned binaries are present - [ ] Test installer - [ ] Test MCPB in Claude Desktop ## Key-rotation release - [ ] Rotate signing key - [ ] Secure the new private key - [ ] Remove temporary private-key material - [ ] Verify embedded public key - [ ] Rebuild AOT - [ ] Rebuild MCPB after rotation - [ ] Sign AOT with the new key - [ ] Verify signature - [ ] Generate manifest - [ ] Sync to R2 - [ ] Test installer verification - [ ] Test MCPB AOT verification - [ ] Test MCP tools from Claude Desktop --- # 22. Troubleshooting ## `SHA256SUMS.sig` exists but verification fails Do not overwrite it automatically. Stop the release and determine why the signature does not match. Possible causes include: - wrong signing key - modified `SHA256SUMS` - stale signature - release artifacts changed after signing - embedded public key mismatch Correct sequence: ```text Invalid signature │ ▼ STOP │ ▼ Inspect release │ ▼ Determine cause │ ▼ Regenerate/re-sign intentionally ``` --- ## MCPB rejects a newly released AOT binary First check whether the signing key was rotated. If yes, verify: ```bash ./scripts/release/verify_release_signing_key.sh ``` Then confirm that the MCPB was rebuilt **after** the rotation. The common incorrect sequence is: ```text MCPB built ↓ key rotated ↓ AOT signed ↓ MCPB not rebuilt ``` The correct sequence is: ```text key rotated ↓ MCPB rebuilt ↓ AOT signed ↓ publish ``` --- ## R2 sync reports signing was skipped A warning similar to: ```text Warning: GITAIFLOW_SIGNING_KEY_FILE not set -- skipping release signing. ``` means the R2 script did not have access to the private signing key. It does **not** necessarily mean the release has no signature. If `sign_release.sh` was already run and produced: ```text SHA256SUMS.sig ``` the R2 script can upload that existing signature. For production releases, verify the signature independently before publishing. --- ## `latest.txt` appears stale Remember that `latest.txt` is mutable release metadata. The R2 synchronization intentionally uploads it with: ```text Cache-Control: no-store, must-revalidate ``` Verify: 1. the local latest version 2. the generated/uploaded `latest.txt` 3. the R2 object 4. the release directory 5. the checksum/signature metadata Avoid treating cached metadata as proof that the local release was not uploaded. --- # 23. Operational Command Reference ## Build ```bash make build ``` ## Build macOS arm64 MCPB ```bash make mcp-build-mac-arm64 ``` ## Rotate signing key ```bash ./scripts/release/rotate_signing_key.sh ``` ## Verify signing key ```bash ./scripts/release/verify_release_signing_key.sh ``` ## Sign a release ```bash ./scripts/release/sign_release.sh v1.1.3 ``` ## Generate manifest ```bash python3 scripts/r2/generate_gitai_manifest.py ``` ## Synchronize to R2 ```bash ./scripts/r2/gitai_r2.sh ``` --- # 24. Normal Release Diagram ```mermaid flowchart TD A["Source changes"] A --> B["Build AOT"] B --> C["Build MCPB"] C --> D["Verify release signing key"] D --> E["Sign AOT release"] E --> F["SHA256SUMS"] E --> G["SHA256SUMS.sig"] F --> H["Generate R2 manifest"] G --> H C --> H H --> I["gitai_r2.sh"] I --> J["Sync metadata"] I --> K["Sync binaries"] I --> L["Sync installer entrypoints"] I --> M["Sync manifest"] J --> N["R2"] K --> N L --> N M --> N ``` --- # 25. Key-Rotation Release Diagram ```mermaid flowchart TD A["Current signing key"] A --> B["rotate_signing_key.sh"] B --> C["Generate new Ed25519 key"] C --> D["Update install.sh public key"] C --> E["Update mcp_server/binary.py public key"] D --> F["verify_release_signing_key.sh"] E --> F F --> G{"Public keys match?"} G -->|No| X["STOP"] G -->|Yes| H["Secure new private key"] H --> I["Rebuild AOT"] H --> J["Rebuild MCPB"] I --> K["Sign AOT release"] J --> L["MCPB trusts new public key"] K --> M["Generate manifest"] L --> M M --> N["gitai_r2.sh"] N --> O["Sync to R2"] ``` --- # 26. End-to-End Deployment Model ```mermaid flowchart LR subgraph Build["Build"] A["Source"] B["AOT binary"] C["MCPB"] A --> B A --> C end subgraph Trust["Release Trust"] D["Ed25519 private key"] E["SHA256SUMS"] F["SHA256SUMS.sig"] G["Embedded public key"] B --> E D --> F E --> F G --> F end subgraph Publish["R2"] H["latest.txt"] I["Versioned binaries"] J["Installer entrypoints"] K["gitaiflow manifest"] end B --> I C --> I F --> H E --> H B --> H H --> L["gitai_r2.sh"] I --> L J --> L K --> L L --> M["Cloudflare R2"] ``` --- # 27. Final Release Principle The release system follows one simple trust model: ```text BUILD ↓ HASH ↓ SIGN ↓ VERIFY ↓ PUBLISH ``` And for key rotation: ```text ROTATE ↓ UPDATE TRUST ↓ REBUILD MCPB ↓ REBUILD AOT ↓ SIGN ↓ VERIFY ↓ PUBLISH ``` The most important invariant is: > **The public key embedded in the released verification code must match the private key used to sign the AOT release.** For the MCP release specifically: > **After rotating the signing key, rebuild the MCPB before signing and publishing the AOT release with the new key.** This keeps the AOT installer, MCP server, release signatures, and R2 artifacts aligned.