github: upstream's signing, zsh release and workspace checks go

Nothing in release.yml, hanzo.yml, the Makefile or scripts/ names any of
them: the zsh release script and its dotslash config (upstream dropped the
zsh fork), verify_cargo_workspace_manifests.py and
verify_tui_core_boundary.py (checks of a codex-rs this repository does not
hold), the Linux, macOS and Windows code-signing actions and the macOS
notarizing scripts (upstream's identities), and upstream's issue templates,
which asked about the Codex app and CLI.
This commit is contained in:
2026-10-15 10:03:29 -07:00
parent 0f0f83ce3a
commit bcb26d732d
18 changed files with 0 additions and 2368 deletions
-54
View File
@@ -1,54 +0,0 @@
name: 🖥️ Codex App Bug
description: Report an issue with the Codex App
labels:
- app
body:
- type: markdown
attributes:
value: |
Before submitting a new issue, please search for existing issues to see if your issue has already been reported.
If it has, please add a 👍 reaction (no need to leave a comment) to the existing issue instead of creating a new one.
- type: input
id: version
attributes:
label: What version of the Codex App are you using (From “About Codex” dialog)?
validations:
required: true
- type: input
id: plan
attributes:
label: What subscription do you have?
validations:
required: true
- type: input
id: platform
attributes:
label: What platform is your computer?
description: |
For macOS and Linux: copy the output of `uname -mprs`
For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console
- type: textarea
id: actual
attributes:
label: What issue are you seeing?
description: Please include the full error messages and prompts with PII redacted. If possible, please provide text instead of a screenshot.
validations:
required: true
- type: textarea
id: steps
attributes:
label: What steps can reproduce the bug?
description: Explain the bug and provide a code snippet that can reproduce it. Please include session id, token limit usage, context window usage if applicable.
validations:
required: true
- type: textarea
id: expected
attributes:
label: What is the expected behavior?
description: If possible, please provide text instead of a screenshot.
- type: textarea
id: notes
attributes:
label: Additional information
description: Is there anything else you think we should know?
-60
View File
@@ -1,60 +0,0 @@
name: 💻 CLI Bug
description: Report an issue in the Codex CLI
labels:
- bug
body:
- type: markdown
attributes:
value: |
Thank you for submitting a bug report! It helps make Code better for everyone.
If you need help or support using Code, and are not reporting a bug, please post on [code/discussions](https://github.com/hanzoai/dev/discussions), where you can ask questions or engage with others on ideas for how to improve Code.
Make sure you are running the [latest](https://npmjs.com/package/@hanzo/dev) version of the Code CLI. The bug you are experiencing may already have been fixed.
- type: input
id: version
attributes:
label: What version of Code is running?
description: Copy the output of `code --version` (or `coder --version`)
- type: input
id: model
attributes:
label: Which model were you using?
description: Like `gpt-5`, `o3`, etc.
- type: input
id: platform
attributes:
label: What platform is your computer?
description: |
For macOS and Linux: copy the output of `uname -mprs`
For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console
- type: input
id: terminal
attributes:
label: What terminal emulator and version are you using (if applicable)?
description: |
Also note any multiplexer in use (screen / tmux / zellij).
E.g., VS Code, Terminal.app, iTerm2, Ghostty, Windows Terminal (WSL / PowerShell)
- type: textarea
id: steps
attributes:
label: What steps can reproduce the bug?
description: Explain the bug and provide a code snippet that can reproduce it.
validations:
required: true
- type: textarea
id: expected
attributes:
label: What is the expected behavior?
description: If possible, please provide text instead of a screenshot.
- type: textarea
id: actual
attributes:
label: What do you see instead?
description: If possible, please provide text instead of a screenshot.
- type: textarea
id: notes
attributes:
label: Additional information
description: Is there anything else you think we should know?
-37
View File
@@ -1,37 +0,0 @@
name: 🪲 Other Bug
description: Report an issue in Codex Web, integrations, or other Codex components
labels:
- bug
body:
- type: markdown
attributes:
value: |
Before submitting a new issue, please search for existing issues to see if your issue has already been reported.
If it has, please add a 👍 reaction (no need to leave a comment) to the existing issue instead of creating a new one.
If you need help or support using Codex and are not reporting a bug, please post on [codex/discussions](https://github.com/openai/codex/discussions), where you can ask questions or engage with others on ideas for how to improve codex.
- type: textarea
id: actual
attributes:
label: What issue are you seeing?
description: Please include the full error messages and prompts with PII redacted. If possible, please provide text instead of a screenshot.
validations:
required: true
- type: textarea
id: steps
attributes:
label: What steps can reproduce the bug?
description: Explain the bug and provide a code snippet that can reproduce it.
validations:
required: true
- type: textarea
id: expected
attributes:
label: What is the expected behavior?
description: If possible, please provide text instead of a screenshot.
- type: textarea
id: notes
attributes:
label: Additional information
description: Is there anything else you think we should know?
@@ -1,27 +0,0 @@
name: 🎁 Feature Request
description: Propose a new feature for Code
labels:
- enhancement
body:
- type: markdown
attributes:
value: |
Is Code missing a feature that you'd like to see? Feel free to propose it here.
Before you submit a feature:
1. Search existing issues for similar features. If you find one, 👍 it rather than opening a new one.
2. Please be aware that this feature may be automatically implemented through our Issue Triage - if this happens you will receive a reply to this issue with information on how to run it. We'd really appreciate any feedback on the implementation as it helps us improve the feature for everyone!
- type: input
id: variant
attributes:
label: What variant of Codex are you using?
description: (e.g., App, IDE Extension, CLI, Web)
validations:
required: true
- type: textarea
id: feature
attributes:
label: What feature would you like to see?
validations:
required: true
-27
View File
@@ -1,27 +0,0 @@
name: 📗 Documentation Issue
description: Tell us if there is missing or incorrect documentation
labels: [documentation]
body:
- type: markdown
attributes:
value: |
Thank you for submitting a documentation request. It helps make Codex better.
- type: dropdown
attributes:
label: What is the type of issue?
multiple: true
options:
- Documentation is missing
- Documentation is incorrect
- Documentation is confusing
- Example code is not working
- Something else
- type: textarea
attributes:
label: What is the issue?
validations:
required: true
- type: textarea
attributes:
label: Where did you find it?
description: If possible, please provide the URL(s) where you found this issue.
@@ -1,49 +0,0 @@
name: linux-code-sign
description: Sign Linux artifacts with cosign.
inputs:
target:
description: Target triple for the artifacts to sign.
required: true
artifacts-dir:
description: Absolute path to the directory containing built binaries to sign.
required: true
binaries:
description: Space-delimited binary basenames to sign.
default: "codex codex-responses-api-proxy"
runs:
using: composite
steps:
- name: Install cosign
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
- name: Cosign Linux artifacts
shell: bash
env:
ARTIFACTS_DIR: ${{ inputs.artifacts-dir }}
BINARIES: ${{ inputs.binaries }}
COSIGN_EXPERIMENTAL: "1"
COSIGN_YES: "true"
COSIGN_OIDC_CLIENT_ID: "sigstore"
COSIGN_OIDC_ISSUER: "https://oauth2.sigstore.dev/auth"
run: |
set -euo pipefail
dest="$ARTIFACTS_DIR"
if [[ ! -d "$dest" ]]; then
echo "Destination $dest does not exist"
exit 1
fi
for binary in ${BINARIES}; do
artifact="${dest}/${binary}"
if [[ ! -f "$artifact" ]]; then
echo "Binary $artifact not found"
exit 1
fi
cosign sign-blob \
--yes \
--bundle "${artifact}.sigstore" \
"$artifact"
done
@@ -1,349 +0,0 @@
name: Set up AKV PKCS11 code signing
description: Download prebuilt rcodesign and Azure Key Vault PKCS11 provider artifacts, then export macOS signing environment.
inputs:
setup-mode:
description: signing configures Azure and exports signing env vars; tools-only only downloads signing tools.
required: false
default: signing
rcodesign-blob-uri:
description: Azure Blob URI for the prebuilt Linux/amd64 rcodesign binary.
required: true
rcodesign-sha256:
description: Expected SHA-256 digest for the prebuilt rcodesign binary.
required: true
akv-pkcs11-library-blob-uri:
description: Azure Blob URI for the prebuilt Linux/amd64 AKV PKCS11 provider library.
required: true
akv-pkcs11-library-sha256:
description: Expected SHA-256 digest for the prebuilt AKV PKCS11 provider library.
required: true
azure-client-id:
description: GitHub OIDC client ID for the Azure signer application.
required: true
azure-tenant-id:
description: Azure tenant ID for the signer application.
required: true
azure-subscription-id:
description: Azure subscription ID that owns the signing vault.
required: true
key-vault-name:
description: Azure Key Vault name containing the certificate-backed signing key.
required: true
key-name:
description: Key Vault certificate/key name used as the PKCS11 key label.
required: true
key-version:
description: Optional Key Vault key version to pin while signing.
required: false
default: ""
certificate-sha256:
description: Optional expected SHA-256 fingerprint for the downloaded public certificate.
required: false
default: ""
outputs:
pkcs11-library:
description: Path to the downloaded AKV PKCS11 provider library.
value: ${{ steps.paths.outputs.pkcs11_library }}
signing-certificate-pem:
description: Path to the downloaded public signing certificate.
value: ${{ steps.paths.outputs.signing_certificate_pem }}
rcodesign:
description: Path to the downloaded rcodesign binary.
value: ${{ steps.paths.outputs.rcodesign }}
runs:
using: composite
steps:
- name: Validate pinned signing artifacts
shell: bash
env:
SETUP_MODE: ${{ inputs.setup-mode }}
RCODESIGN_BLOB_URI: ${{ inputs.rcodesign-blob-uri }}
RCODESIGN_SHA256: ${{ inputs.rcodesign-sha256 }}
AKV_PKCS11_LIBRARY_BLOB_URI: ${{ inputs.akv-pkcs11-library-blob-uri }}
AKV_PKCS11_LIBRARY_SHA256: ${{ inputs.akv-pkcs11-library-sha256 }}
KEY_VAULT_NAME: ${{ inputs.key-vault-name }}
KEY_NAME: ${{ inputs.key-name }}
run: |
set -euo pipefail
case "$SETUP_MODE" in
signing|tools-only)
;;
*)
echo "setup-mode must be 'signing' or 'tools-only', got '$SETUP_MODE'." >&2
exit 1
;;
esac
for variable_name in RCODESIGN_SHA256 AKV_PKCS11_LIBRARY_SHA256; do
value="${!variable_name}"
if [[ ! "$value" =~ ^[0-9a-f]{64}$ ]]; then
echo "$variable_name must be a lowercase SHA-256 digest." >&2
exit 1
fi
done
for variable_name in RCODESIGN_BLOB_URI AKV_PKCS11_LIBRARY_BLOB_URI; do
value="${!variable_name}"
if [[ ! "$value" =~ ^az://[^/]+/[^/]+/.+ ]]; then
echo "$variable_name must use az://<account>/<container>/<blob>." >&2
exit 1
fi
done
if [[ "$SETUP_MODE" == "signing" ]]; then
for variable_name in \
KEY_VAULT_NAME \
KEY_NAME; do
if [[ -z "${!variable_name}" ]]; then
echo "$variable_name is required for AKV PKCS11 signing." >&2
exit 1
fi
done
fi
- name: Resolve signing tool paths
id: paths
shell: bash
run: |
set -euo pipefail
if [[ "${RUNNER_OS}" != "Linux" ]]; then
echo "Prebuilt AKV PKCS11 signing tools are only vendored for Linux runners, got ${RUNNER_OS}." >&2
exit 1
fi
if [[ "${RUNNER_ARCH}" != "X64" && "${RUNNER_ARCH}" != "AMD64" ]]; then
echo "Prebuilt AKV PKCS11 signing tools are only vendored for amd64 runners, got ${RUNNER_ARCH}." >&2
exit 1
fi
provider_root="${RUNNER_TEMP}/akv-pkcs11-provider"
rcodesign_root="${RUNNER_TEMP}/rcodesign-root"
signing_certificate_pem="${RUNNER_TEMP}/akv-signing-cert.pem"
library_name="libakv_pkcs_11.so"
mkdir -p "$provider_root" "$rcodesign_root/bin"
{
echo "pkcs11_library=$provider_root/$library_name"
echo "pkcs11_manifest=$provider_root/akv-pkcs11-provider.manifest"
echo "rcodesign_root=$rcodesign_root"
echo "rcodesign=$rcodesign_root/bin/rcodesign"
echo "signing_certificate_pem=$signing_certificate_pem"
} >> "$GITHUB_OUTPUT"
- name: Validate Azure credentials for private signing artifacts
shell: bash
env:
AZURE_CLIENT_ID: ${{ inputs.azure-client-id }}
AZURE_TENANT_ID: ${{ inputs.azure-tenant-id }}
AZURE_SUBSCRIPTION_ID: ${{ inputs.azure-subscription-id }}
run: |
set -euo pipefail
for variable_name in AZURE_CLIENT_ID AZURE_TENANT_ID AZURE_SUBSCRIPTION_ID; do
if [[ -z "${!variable_name}" ]]; then
echo "$variable_name is required for private AKV PKCS11 signing artifacts." >&2
exit 1
fi
done
- name: Log in to Azure with GitHub OIDC
uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
with:
client-id: ${{ inputs.azure-client-id }}
tenant-id: ${{ inputs.azure-tenant-id }}
subscription-id: ${{ inputs.azure-subscription-id }}
- name: Install prebuilt signing tools
shell: bash
env:
RCODESIGN_BLOB_URI: ${{ inputs.rcodesign-blob-uri }}
RCODESIGN_SHA256: ${{ inputs.rcodesign-sha256 }}
RCODESIGN: ${{ steps.paths.outputs.rcodesign }}
AKV_PKCS11_LIBRARY_BLOB_URI: ${{ inputs.akv-pkcs11-library-blob-uri }}
AKV_PKCS11_LIBRARY_SHA256: ${{ inputs.akv-pkcs11-library-sha256 }}
PKCS11_LIBRARY: ${{ steps.paths.outputs.pkcs11_library }}
PKCS11_MANIFEST: ${{ steps.paths.outputs.pkcs11_manifest }}
run: |
set -euo pipefail
download_az_blob_uri() {
local uri="$1"
local destination="$2"
local rest account container blob
rest="${uri#az://}"
account="${rest%%/*}"
rest="${rest#*/}"
container="${rest%%/*}"
blob="${rest#*/}"
if [[ -z "$account" || -z "$container" || -z "$blob" || "$blob" == "$rest" ]]; then
echo "Invalid Azure Blob URI. Expected az://<account>/<container>/<blob>." >&2
exit 1
fi
mkdir -p "$(dirname "$destination")"
rm -f "$destination"
if ! az storage blob download \
--account-name "$account" \
--container-name "$container" \
--name "$blob" \
--file "$destination" \
--auth-mode login \
--only-show-errors \
>/dev/null 2>&1; then
echo "Failed to download a private signing artifact from Azure Blob Storage." >&2
exit 1
fi
}
verify_sha256() {
local path="$1"
local expected="$2"
local actual
actual="$(shasum -a 256 "$path" | awk '{ print $1 }')"
if [[ "$actual" != "$expected" ]]; then
echo "SHA-256 verification failed for '$path'." >&2
exit 1
fi
}
echo "Downloading prebuilt rcodesign."
download_az_blob_uri "$RCODESIGN_BLOB_URI" "$RCODESIGN"
verify_sha256 "$RCODESIGN" "$RCODESIGN_SHA256"
chmod 0755 "$RCODESIGN"
echo "Downloading prebuilt AKV PKCS11 provider."
download_az_blob_uri "$AKV_PKCS11_LIBRARY_BLOB_URI" "$PKCS11_LIBRARY"
verify_sha256 "$PKCS11_LIBRARY" "$AKV_PKCS11_LIBRARY_SHA256"
chmod 0644 "$PKCS11_LIBRARY"
{
echo "runner_os=$RUNNER_OS"
echo "runner_arch=$RUNNER_ARCH"
echo "library_name=$(basename "$PKCS11_LIBRARY")"
} > "$PKCS11_MANIFEST"
- name: Verify downloaded signing tools
shell: bash
env:
RCODESIGN: ${{ steps.paths.outputs.rcodesign }}
RCODESIGN_SHA256: ${{ inputs.rcodesign-sha256 }}
PKCS11_LIBRARY: ${{ steps.paths.outputs.pkcs11_library }}
AKV_PKCS11_LIBRARY_SHA256: ${{ inputs.akv-pkcs11-library-sha256 }}
PKCS11_MANIFEST: ${{ steps.paths.outputs.pkcs11_manifest }}
run: |
set -euo pipefail
verify_sha256() {
local path="$1"
local expected="$2"
local actual
actual="$(shasum -a 256 "$path" | awk '{ print $1 }')"
if [[ "$actual" != "$expected" ]]; then
echo "SHA-256 verification failed for '$path'." >&2
exit 1
fi
}
if [[ ! -x "$RCODESIGN" ]]; then
echo "rcodesign is missing or not executable at '$RCODESIGN'." >&2
exit 1
fi
if [[ ! -f "$PKCS11_LIBRARY" ]]; then
echo "AKV PKCS11 provider library is missing at '$PKCS11_LIBRARY'." >&2
exit 1
fi
verify_sha256 "$RCODESIGN" "$RCODESIGN_SHA256"
verify_sha256 "$PKCS11_LIBRARY" "$AKV_PKCS11_LIBRARY_SHA256"
"$RCODESIGN" --version
"$RCODESIGN" notarize --help > /dev/null
if [[ -f "$PKCS11_MANIFEST" ]]; then
echo "AKV PKCS11 provider artifact manifest is present."
else
echo "AKV PKCS11 provider artifact manifest is absent." >&2
exit 1
fi
- name: Download signing certificate from Key Vault
if: ${{ inputs.setup-mode == 'signing' }}
shell: bash
env:
KEY_VAULT_NAME: ${{ inputs.key-vault-name }}
KEY_NAME: ${{ inputs.key-name }}
KEY_VERSION: ${{ inputs.key-version }}
CERTIFICATE_SHA256: ${{ inputs.certificate-sha256 }}
SIGNING_CERTIFICATE_PEM: ${{ steps.paths.outputs.signing_certificate_pem }}
run: |
set -euo pipefail
certificate_version_args=()
if [[ -n "$KEY_VERSION" ]]; then
certificate_version_args+=(--version "$KEY_VERSION")
fi
if ! az keyvault certificate download \
--vault-name "$KEY_VAULT_NAME" \
--name "$KEY_NAME" \
"${certificate_version_args[@]}" \
--file "$SIGNING_CERTIFICATE_PEM" \
--encoding PEM \
--only-show-errors \
>/dev/null 2>&1; then
echo "Failed to download the public signing certificate from Azure Key Vault." >&2
exit 1
fi
if [[ -n "$CERTIFICATE_SHA256" ]]; then
actual_sha256="$(
openssl x509 -in "$SIGNING_CERTIFICATE_PEM" -noout -fingerprint -sha256 |
awk -F= '{ print toupper($2) }' |
tr -d ':\r\n'
)"
expected_sha256="$(printf '%s' "$CERTIFICATE_SHA256" | tr '[:lower:]' '[:upper:]' | tr -d ':\r\n ')"
if [[ "$actual_sha256" != "$expected_sha256" ]]; then
echo "Downloaded signing certificate SHA-256 did not match the expected fingerprint." >&2
exit 1
fi
fi
- name: Export AKV PKCS11 signing environment
if: ${{ inputs.setup-mode == 'signing' }}
shell: bash
env:
RCODESIGN_ROOT: ${{ steps.paths.outputs.rcodesign_root }}
PKCS11_LIBRARY: ${{ steps.paths.outputs.pkcs11_library }}
SIGNING_CERTIFICATE_PEM: ${{ steps.paths.outputs.signing_certificate_pem }}
KEY_VAULT_NAME: ${{ inputs.key-vault-name }}
KEY_NAME: ${{ inputs.key-name }}
KEY_VERSION: ${{ inputs.key-version }}
run: |
set -euo pipefail
{
echo "$RCODESIGN_ROOT/bin"
} >> "$GITHUB_PATH"
{
echo "OAI_CODESIGN_BACKEND=akv-pkcs11"
echo "OAI_AKV_PKCS11_LIBRARY=$PKCS11_LIBRARY"
echo "OAI_AKV_SIGNING_CERTIFICATE_PEM=$SIGNING_CERTIFICATE_PEM"
echo "OAI_AKV_KEY_LABEL=$KEY_NAME"
echo "AZURE_CREDENTIAL_KIND=azurecli"
echo "AZURE_KEYVAULT_NAME=$KEY_VAULT_NAME"
if [[ -n "$KEY_VERSION" ]]; then
echo "AZURE_KEYVAULT_KEY_VERSION=$KEY_VERSION"
fi
} >> "$GITHUB_ENV"
@@ -1,74 +0,0 @@
name: windows-code-sign
description: Sign Windows binaries with Azure Trusted Signing.
inputs:
target:
description: Target triple for the artifacts to sign.
required: true
binaries:
description: Space-delimited binary basenames to sign.
default: "codex codex-responses-api-proxy codex-windows-sandbox-setup codex-command-runner"
client-id:
description: Azure Trusted Signing client ID.
required: true
tenant-id:
description: Azure tenant ID for Trusted Signing.
required: true
subscription-id:
description: Azure subscription ID for Trusted Signing.
required: true
endpoint:
description: Azure Trusted Signing endpoint.
required: true
account-name:
description: Azure Trusted Signing account name.
required: true
certificate-profile-name:
description: Certificate profile name for signing.
required: true
runs:
using: composite
steps:
- name: Azure login for Trusted Signing (OIDC)
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
with:
client-id: ${{ inputs.client-id }}
tenant-id: ${{ inputs.tenant-id }}
subscription-id: ${{ inputs.subscription-id }}
- name: Prepare file list
id: prepare
shell: bash
env:
TARGET: ${{ inputs.target }}
BINARIES: ${{ inputs.binaries }}
run: |
set -euo pipefail
{
echo "files<<EOF"
for binary in ${BINARIES}; do
echo "${GITHUB_WORKSPACE}/vendor/codex/target/${TARGET}/release/${binary}.exe"
done
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Sign Windows binaries with Azure Trusted Signing
uses: azure/trusted-signing-action@1d365fec12862c4aa68fcac418143d73f0cea293 # v0.5.11
with:
endpoint: ${{ inputs.endpoint }}
trusted-signing-account-name: ${{ inputs.account-name }}
certificate-profile-name: ${{ inputs.certificate-profile-name }}
exclude-environment-credential: true
exclude-workload-identity-credential: true
exclude-managed-identity-credential: true
exclude-shared-token-cache-credential: true
exclude-visual-studio-credential: true
exclude-visual-studio-code-credential: true
exclude-azure-cli-credential: false
exclude-azure-powershell-credential: true
exclude-azure-developer-cli-credential: true
exclude-interactive-browser-credential: true
cache-dependencies: false
files: ${{ steps.prepare.outputs.files }}
-23
View File
@@ -1,23 +0,0 @@
{
"outputs": {
"codex-zsh": {
"platforms": {
"macos-aarch64": {
"name": "codex-zsh-aarch64-apple-darwin.tar.gz",
"format": "tar.gz",
"path": "codex-zsh/bin/zsh"
},
"linux-x86_64": {
"name": "codex-zsh-x86_64-unknown-linux-musl.tar.gz",
"format": "tar.gz",
"path": "codex-zsh/bin/zsh"
},
"linux-aarch64": {
"name": "codex-zsh-aarch64-unknown-linux-musl.tar.gz",
"format": "tar.gz",
"path": "codex-zsh/bin/zsh"
}
}
}
}
}
@@ -1,61 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "$#" -ne 1 ]]; then
echo "usage: $0 <archive-path>" >&2
exit 1
fi
archive_path="$1"
workspace="${GITHUB_WORKSPACE:?missing GITHUB_WORKSPACE}"
zsh_commit="${ZSH_COMMIT:?missing ZSH_COMMIT}"
zsh_patch="${ZSH_PATCH:?missing ZSH_PATCH}"
temp_root="${RUNNER_TEMP:-/tmp}"
work_root="$(mktemp -d "${temp_root%/}/codex-zsh-release.XXXXXX")"
trap 'rm -rf "$work_root"' EXIT
source_root="${work_root}/zsh"
package_root="${work_root}/codex-zsh"
wrapper_path="${work_root}/exec-wrapper"
stdout_path="${work_root}/stdout.txt"
wrapper_log_path="${work_root}/wrapper.log"
git clone https://git.code.sf.net/p/zsh/code "$source_root"
cd "$source_root"
git checkout "$zsh_commit"
git apply "${workspace}/${zsh_patch}"
./Util/preconfig
./configure
cores="$(command -v nproc >/dev/null 2>&1 && nproc || getconf _NPROCESSORS_ONLN)"
make -j"${cores}"
cat > "$wrapper_path" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
: "${DEV_WRAPPER_LOG:?missing DEV_WRAPPER_LOG}"
printf '%s\n' "$@" > "$DEV_WRAPPER_LOG"
file="$1"
shift
if [[ "$#" -eq 0 ]]; then
exec "$file"
fi
arg0="$1"
shift
exec -a "$arg0" "$file" "$@"
EOF
chmod +x "$wrapper_path"
DEV_WRAPPER_LOG="$wrapper_log_path" \
EXEC_WRAPPER="$wrapper_path" \
"${source_root}/Src/zsh" -fc '/bin/echo smoke-zsh' > "$stdout_path"
grep -Fx "smoke-zsh" "$stdout_path"
grep -Fx "/bin/echo" "$wrapper_log_path"
mkdir -p "$package_root/bin" "$(dirname "${workspace}/${archive_path}")"
cp "${source_root}/Src/zsh" "$package_root/bin/zsh"
chmod +x "$package_root/bin/zsh"
(cd "$work_root" && tar -czf "${workspace}/${archive_path}" codex-zsh)
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>
@@ -1,111 +0,0 @@
#!/usr/bin/env bash
# Notarize a standalone binary and retain its diagnostic report.
set -euo pipefail
usage() {
cat >&2 <<'EOF'
Usage: notarize_macos_binary_with_akv.sh --binary PATH [--report-dir PATH] [--max-wait-seconds SECONDS]
Options:
--binary PATH Signed standalone macOS binary to notarize.
--report-dir PATH Directory for notarization logs.
--max-wait-seconds SECONDS Maximum Apple notarization wait time.
EOF
}
binary_path=""
report_dir="${RUNNER_TEMP:-/tmp}/macos-binary-notarization-verification"
max_wait_seconds="600"
while [[ $# -gt 0 ]]; do
case "$1" in
--binary)
binary_path="${2:-}"
shift 2
;;
--report-dir)
report_dir="${2:-}"
shift 2
;;
--max-wait-seconds)
max_wait_seconds="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown notarization argument: $1" >&2
usage
exit 2
;;
esac
done
if [[ -z "$binary_path" ]]; then
echo "--binary is required." >&2
usage
exit 2
fi
if [[ ! -f "$binary_path" ]]; then
echo "Binary does not exist: $binary_path" >&2
exit 1
fi
if [[ ! "$max_wait_seconds" =~ ^[0-9]+$ ]]; then
echo "--max-wait-seconds must be a non-negative integer." >&2
exit 2
fi
for command_name in python3 zip; do
if ! command -v "$command_name" >/dev/null 2>&1; then
echo "$command_name was not found on PATH." >&2
exit 1
fi
done
missing_environment=0
for variable_name in \
APPLE_NOTARIZATION_ISSUER_ID \
APPLE_NOTARIZATION_AKV_KEY_NAME \
AZURE_KEYVAULT_NAME
do
if [[ -z "${!variable_name:-}" ]]; then
echo "$variable_name must be configured before notarizing a binary." >&2
missing_environment=1
fi
done
if [[ "$missing_environment" -ne 0 ]]; then
exit 2
fi
mkdir -p "$report_dir"
notarization_temp_dir="$(mktemp -d)"
trap 'rm -rf "$notarization_temp_dir" >/dev/null' EXIT
binary_name="$(basename "$binary_path")"
archive_path="$notarization_temp_dir/${binary_name}.zip"
(
cd "$(dirname "$binary_path")"
zip -q "$archive_path" "$binary_name"
)
notarization_log="$report_dir/${binary_name}-notarization.log"
python3 "$(dirname "$0")/notarize_with_akv.py" \
--file "$archive_path" \
--report-log "$report_dir/${binary_name}-notarization-developer-log.json" \
--max-wait-seconds "$max_wait_seconds" \
2>&1 | tee "$notarization_log"
{
echo "binary_name=$binary_name"
echo "max_wait_seconds=$max_wait_seconds"
echo "binary_sha256=$(shasum -a 256 "$binary_path" | awk '{ print $1 }')"
echo "notarization=completed"
} >"$report_dir/${binary_name}-notarization-summary.txt"
@@ -1,101 +0,0 @@
#!/usr/bin/env bash
# Notarize a signed disk image and staple its ticket.
set -euo pipefail
usage() {
cat >&2 <<'EOF'
Usage: notarize_macos_dmg_with_akv.sh --dmg PATH [--report-dir PATH] [--max-wait-seconds SECONDS]
Options:
--dmg PATH Signed DMG to submit to Apple notarization.
--report-dir PATH Directory for notarization logs.
--max-wait-seconds SECONDS Maximum Apple notarization wait time.
EOF
}
dmg_path=""
report_dir="${RUNNER_TEMP:-/tmp}/macos-notarization-verification"
max_wait_seconds="600"
while [[ $# -gt 0 ]]; do
case "$1" in
--dmg)
dmg_path="${2:-}"
shift 2
;;
--report-dir)
report_dir="${2:-}"
shift 2
;;
--max-wait-seconds)
max_wait_seconds="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown notarization argument: $1" >&2
usage
exit 2
;;
esac
done
if [[ -z "$dmg_path" ]]; then
echo "--dmg is required." >&2
usage
exit 2
fi
if [[ ! -f "$dmg_path" ]]; then
echo "DMG does not exist: $dmg_path" >&2
exit 1
fi
if [[ ! "$max_wait_seconds" =~ ^[0-9]+$ ]]; then
echo "--max-wait-seconds must be a non-negative integer." >&2
exit 2
fi
if ! command -v rcodesign > /dev/null 2>&1; then
echo "rcodesign was not found on PATH." >&2
exit 1
fi
missing_environment=0
for variable_name in \
APPLE_NOTARIZATION_ISSUER_ID \
APPLE_NOTARIZATION_AKV_KEY_NAME \
AZURE_KEYVAULT_NAME
do
if [[ -z "${!variable_name:-}" ]]; then
echo "$variable_name must be configured before notarizing a DMG." >&2
missing_environment=1
fi
done
if [[ "$missing_environment" -ne 0 ]]; then
exit 2
fi
mkdir -p "$report_dir"
notarization_log="$report_dir/dmg-notarization.log"
python3 "$(dirname "$0")/notarize_with_akv.py" \
--file "$dmg_path" \
--report-log "$report_dir/dmg-notarization-developer-log.json" \
--max-wait-seconds "$max_wait_seconds" \
2>&1 | tee "$notarization_log"
rcodesign staple "$dmg_path" 2>&1 | tee -a "$notarization_log"
{
echo "dmg_path=$dmg_path"
echo "max_wait_seconds=$max_wait_seconds"
echo "dmg_sha256=$(shasum -a 256 "$dmg_path" | awk '{ print $1 }')"
echo "notarization_staple=completed"
} > "$report_dir/dmg-notarization-summary.txt"
@@ -1,460 +0,0 @@
#!/usr/bin/env python3
"""Submit release artifacts for notarization and wait for the result."""
import argparse
import base64
import datetime
import hashlib
import hmac
import json
import os
import re
import subprocess
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
from dataclasses import dataclass
from pathlib import Path
from typing import Any
APPLE_NOTARY_URL = "https://appstoreconnect.apple.com/notary/v2/submissions"
AZURE_KEYVAULT_API_VERSION = "7.4"
AWS_REGION = "us-west-2"
JWT_LIFETIME_SECONDS = 15 * 60
POLL_INTERVAL_SECONDS = 10
MAX_SINGLE_UPLOAD_BYTES = 5 * 1024 * 1024 * 1024
class NotarizationError(RuntimeError):
"""Describe a notarization failure."""
@dataclass(frozen=True)
class NotarizationConfiguration:
"""Hold the account and signing-key configuration."""
issuer_id: str
apple_key_id: str
vault_name: str
vault_key_name: str
vault_key_version: str
@classmethod
def from_environment(cls) -> "NotarizationConfiguration":
"""Load and validate the notarization configuration."""
required = {
"issuer_id": "APPLE_NOTARIZATION_ISSUER_ID",
"vault_name": "AZURE_KEYVAULT_NAME",
"vault_key_name": "APPLE_NOTARIZATION_AKV_KEY_NAME",
}
values = {field: os.environ.get(name, "").strip() for field, name in required.items()}
missing = [name for field, name in required.items() if not values[field]]
if missing:
raise NotarizationError("Missing notarization configuration: " + ", ".join(missing))
if not re.fullmatch(r"[A-Za-z][A-Za-z0-9-]{2,23}", values["vault_name"]):
raise NotarizationError("Invalid signing vault name")
if not re.fullmatch(r"[A-Za-z0-9-]+", values["vault_key_name"]):
raise NotarizationError("Invalid notarization key name")
key_version = os.environ.get("APPLE_NOTARIZATION_AKV_KEY_VERSION", "").strip()
if key_version and not re.fullmatch(r"[0-9a-fA-F]{32}", key_version):
raise NotarizationError(
"Notarization key must resolve to a 32-character pinned version"
)
command = [
"az",
"keyvault",
"key",
"show",
"--vault-name",
values["vault_name"],
"--name",
values["vault_key_name"],
]
if key_version:
command.extend(["--version", key_version])
command.extend(
[
"--query",
'{id:key.kid,apple_key_id:tags."apple-key-id"}',
"--output",
"json",
"--only-show-errors",
]
)
# Resolve the key identifier and version from the same lookup.
try:
result = subprocess.run(command, check=True, capture_output=True, text=True)
except (OSError, subprocess.CalledProcessError) as error:
raise NotarizationError("Notarization signing key could not be read") from error
try:
metadata = json.loads(result.stdout)
returned_key_id = metadata["id"]
apple_key_id = metadata["apple_key_id"]
except (json.JSONDecodeError, KeyError, TypeError) as error:
raise NotarizationError("Notarization key metadata is invalid") from error
key_id_prefix = (
f"https://{values['vault_name']}.vault.azure.net/keys/{values['vault_key_name']}/"
)
if not isinstance(returned_key_id, str) or not returned_key_id.startswith(key_id_prefix):
raise NotarizationError("Unexpected notarization signing key")
returned_key_version = returned_key_id[len(key_id_prefix) :]
if not re.fullmatch(r"[0-9a-fA-F]{32}", returned_key_version):
raise NotarizationError(
"Notarization key must resolve to a 32-character pinned version"
)
if key_version and returned_key_version.lower() != key_version.lower():
raise NotarizationError("Unexpected notarization key version")
if not isinstance(apple_key_id, str) or not apple_key_id.strip():
raise NotarizationError("Notarization key must have an apple-key-id tag")
values["apple_key_id"] = apple_key_id.strip()
values["vault_key_version"] = returned_key_version
return cls(**values)
@property
def versioned_key_id(self) -> str:
"""Return the versioned signing-key identifier."""
return (
f"https://{self.vault_name}.vault.azure.net/keys/"
f"{self.vault_key_name}/{self.vault_key_version}"
)
def base64url_encode(value: bytes) -> str:
"""Encode bytes using the unpadded base64url format required by JWTs."""
return base64.urlsafe_b64encode(value).decode("ascii").rstrip("=")
def base64url_decode(value: str) -> bytes:
"""Decode a base64url value and reject malformed input."""
try:
return base64.b64decode(value + "=" * (-len(value) % 4), altchars=b"-_", validate=True)
except (ValueError, UnicodeEncodeError) as error:
raise NotarizationError("Signing service returned an invalid signature") from error
def create_apple_jwt(configuration: NotarizationConfiguration, *, issued_at: int) -> str:
"""Create an authentication token for notarization requests."""
header = {"alg": "ES256", "kid": configuration.apple_key_id, "typ": "JWT"}
# Restrict the token to notarization requests.
claims = {
"iss": configuration.issuer_id,
"iat": issued_at,
"exp": issued_at + JWT_LIFETIME_SECONDS,
"aud": "appstoreconnect-v1",
"scope": ["/notary/v2"],
}
encoded_header = base64url_encode(json.dumps(header, separators=(",", ":")).encode("utf-8"))
encoded_claims = base64url_encode(json.dumps(claims, separators=(",", ":")).encode("utf-8"))
signing_input = f"{encoded_header}.{encoded_claims}"
digest = hashlib.sha256(signing_input.encode("ascii")).digest()
key_url = configuration.versioned_key_id
request_body = json.dumps({"alg": "ES256", "value": base64url_encode(digest)})
try:
result = subprocess.run(
[
"az",
"rest",
"--method",
"post",
"--url",
f"{key_url}/sign?api-version={AZURE_KEYVAULT_API_VERSION}",
"--resource",
"https://vault.azure.net",
"--headers",
"Content-Type=application/json",
"--body",
request_body,
"--output",
"json",
"--only-show-errors",
],
check=True,
capture_output=True,
text=True,
)
except (OSError, subprocess.CalledProcessError) as error:
detail = getattr(error, "stderr", "").strip()
message = "Signing service could not sign the authentication token"
if detail:
message = f"{message}: {detail}"
raise NotarizationError(message) from error
try:
response = json.loads(result.stdout)
returned_key_id = response["kid"]
encoded_signature = response["value"]
except (json.JSONDecodeError, KeyError, TypeError) as error:
raise NotarizationError("Signing service returned an invalid response") from error
# Reject signatures generated with a different key version.
if returned_key_id != key_url:
raise NotarizationError("Signing service returned an unexpected key version")
if not isinstance(encoded_signature, str):
raise NotarizationError("Signing service returned an invalid signature")
signature = base64url_decode(encoded_signature)
# ES256 JWT signatures are 64 raw R || S bytes, not ASN.1/DER-encoded ECDSA signatures.
if len(signature) != 64:
raise NotarizationError("ES256 signature must contain 64 JOSE R || S bytes")
return f"{signing_input}.{base64url_encode(signature)}"
def json_request(url: str, token: str, *, body: dict[str, Any] | None = None) -> dict[str, Any]:
"""Send an authenticated JSON request to Apple's notarization service."""
data = None if body is None else json.dumps(body).encode("utf-8")
request = urllib.request.Request(
url,
data=data,
method="GET" if body is None else "POST",
headers={
"Accept": "application/json",
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
)
try:
with urllib.request.urlopen(request, timeout=60) as response:
result = json.load(response)
except urllib.error.HTTPError as error:
detail = error.read(1024).decode("utf-8", errors="replace")
raise NotarizationError(f"Apple Notary API returned HTTP {error.code}: {detail}") from error
except (OSError, json.JSONDecodeError) as error:
raise NotarizationError("Apple Notary API request failed") from error
if not isinstance(result, dict):
raise NotarizationError("Apple Notary API returned an invalid response")
return result
def sha256_file(path: Path) -> str:
"""Hash an artifact incrementally so large release files stay off the heap."""
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def hmac_sha256(key: bytes, value: str) -> bytes:
"""Derive one AWS Signature Version 4 signing-key component."""
return hmac.new(key, value.encode("utf-8"), hashlib.sha256).digest()
def upload_to_apple_s3(
path: Path,
file_digest: str,
credentials: dict[str, Any],
*,
timestamp: datetime.datetime | None = None,
) -> None:
"""Upload an artifact using the provided temporary credentials."""
required = (
"awsAccessKeyId",
"awsSecretAccessKey",
"awsSessionToken",
"bucket",
"object",
)
if any(
not isinstance(credentials.get(name), str) or not credentials[name] for name in required
):
raise NotarizationError("Apple returned incomplete temporary upload credentials")
size = path.stat().st_size
if not 0 < size <= MAX_SINGLE_UPLOAD_BYTES:
raise NotarizationError("Notarization payload must be between 1 byte and 5 GiB")
now = timestamp or datetime.datetime.now(datetime.timezone.utc)
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
date_stamp = now.strftime("%Y%m%d")
host = f"{credentials['bucket']}.s3.{AWS_REGION}.amazonaws.com"
object_path = "/" + urllib.parse.quote(credentials["object"].lstrip("/"), safe="/-_.~")
headers = {
"content-type": "application/octet-stream",
"host": host,
"x-amz-content-sha256": file_digest,
"x-amz-date": amz_date,
"x-amz-security-token": credentials["awsSessionToken"],
}
# Build the signature required by the upload endpoint.
signed_headers = ";".join(sorted(headers))
canonical_headers = "".join(f"{name}:{headers[name]}\n" for name in sorted(headers))
canonical_request = "\n".join(
["PUT", object_path, "", canonical_headers, signed_headers, file_digest]
)
credential_scope = f"{date_stamp}/{AWS_REGION}/s3/aws4_request"
string_to_sign = "\n".join(
[
"AWS4-HMAC-SHA256",
amz_date,
credential_scope,
hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(),
]
)
signing_key = ("AWS4" + credentials["awsSecretAccessKey"]).encode("utf-8")
for component in (date_stamp, AWS_REGION, "s3", "aws4_request"):
signing_key = hmac_sha256(signing_key, component)
signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
authorization = (
"AWS4-HMAC-SHA256 "
f"Credential={credentials['awsAccessKeyId']}/{credential_scope}, "
f"SignedHeaders={signed_headers}, Signature={signature}"
)
# Stream the artifact to avoid buffering its full contents.
with path.open("rb") as source:
request = urllib.request.Request(
f"https://{host}{object_path}",
data=source,
method="PUT",
headers={
**headers,
"Authorization": authorization,
"Content-Length": str(size),
},
)
try:
with urllib.request.urlopen(request, timeout=300):
pass
except urllib.error.HTTPError as error:
raise NotarizationError(
f"Apple notarization payload upload failed with HTTP {error.code}"
) from error
except OSError as error:
raise NotarizationError("Apple notarization payload upload failed") from error
def write_developer_log(submission_id: str, token: str, destination: Path | None) -> None:
"""Write the submission's optional diagnostic report."""
if destination is None:
return
response = json_request(f"{APPLE_NOTARY_URL}/{submission_id}/logs", token)
try:
log_url = response["data"]["attributes"]["developerLogUrl"]
except (KeyError, TypeError) as error:
raise NotarizationError("Apple did not provide a notarization developer log") from error
if not isinstance(log_url, str) or not log_url.startswith("https://"):
raise NotarizationError("Apple returned an invalid notarization developer log URL")
try:
# Do not forward unrelated authorization headers to the download URL.
with urllib.request.urlopen(log_url, timeout=60) as response:
destination.write_bytes(response.read())
except urllib.error.HTTPError as error:
raise NotarizationError(
f"Apple notarization developer log download failed with HTTP {error.code}"
) from error
except OSError as error:
raise NotarizationError("Apple notarization developer log download failed") from error
def notarize(
path: Path,
configuration: NotarizationConfiguration,
*,
max_wait_seconds: int,
report_log: Path | None,
) -> str:
"""Submit an artifact, upload it to Apple, and wait for a terminal result."""
issued_at = int(time.time())
token = create_apple_jwt(configuration, issued_at=issued_at)
file_digest = sha256_file(path)
# Create the submission before uploading the artifact.
response = json_request(
APPLE_NOTARY_URL,
token,
body={"sha256": file_digest, "submissionName": path.name},
)
try:
submission_id = response["data"]["id"]
upload_credentials = response["data"]["attributes"]
except (KeyError, TypeError) as error:
raise NotarizationError("Apple returned an invalid notarization submission") from error
if not isinstance(submission_id, str) or not submission_id:
raise NotarizationError("Apple did not return a notarization submission ID")
if not isinstance(upload_credentials, dict):
raise NotarizationError("Apple returned invalid notarization upload credentials")
print(f"Uploading notarization submission {submission_id} for {path.name}")
upload_to_apple_s3(path, file_digest, upload_credentials)
# A monotonic deadline keeps the wait limit stable if the system clock changes.
deadline = time.monotonic() + max_wait_seconds
# Wait for completion, equivalent to `notarytool submit --wait`.
while True:
# Refresh the token before it expires.
if int(time.time()) >= issued_at + JWT_LIFETIME_SECONDS - 60:
issued_at = int(time.time())
token = create_apple_jwt(configuration, issued_at=issued_at)
result = json_request(f"{APPLE_NOTARY_URL}/{submission_id}", token)
try:
status = result["data"]["attributes"]["status"]
except (KeyError, TypeError) as error:
raise NotarizationError("Apple returned an invalid notarization status") from error
if status in {"Accepted", "Invalid", "Rejected"}:
# Save diagnostics for both accepted and rejected submissions.
write_developer_log(submission_id, token, report_log)
message = f"Notarization submission {submission_id} completed with status {status}"
if status != "Accepted":
raise NotarizationError(message)
print(message)
return submission_id
if status != "In Progress":
raise NotarizationError(f"Unexpected notarization status: {status!r}")
remaining = deadline - time.monotonic()
if remaining <= 0:
raise NotarizationError(
f"Notarization submission {submission_id} exceeded {max_wait_seconds} seconds"
)
print(f"Notarization submission {submission_id} is still in progress")
time.sleep(min(POLL_INTERVAL_SECONDS, remaining))
def main() -> int:
"""Run notarization using command-line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument("--file", type=Path, required=True)
parser.add_argument("--report-log", type=Path)
parser.add_argument("--max-wait-seconds", type=int, default=600)
arguments = parser.parse_args()
try:
if not arguments.file.is_file():
raise NotarizationError(f"Notarization payload does not exist: {arguments.file}")
if arguments.max_wait_seconds < 0:
raise NotarizationError("--max-wait-seconds must be non-negative")
if arguments.report_log is not None:
arguments.report_log.parent.mkdir(parents=True, exist_ok=True)
notarize(
arguments.file,
NotarizationConfiguration.from_environment(),
max_wait_seconds=arguments.max_wait_seconds,
report_log=arguments.report_log,
)
except NotarizationError as error:
print(f"Notarization failed: {error}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,245 +0,0 @@
#!/usr/bin/env bash
# Small compatibility wrapper around native codesign and rcodesign.
#
# Existing packaging scripts call this instead of choosing a signing backend
# directly. OAI_CODESIGN_BACKEND=akv-pkcs11 routes signing through rcodesign
# while preserving the option, entitlement, identifier, timestamp, and deep
# signing surface used by the native codesign path.
set -euo pipefail
usage() {
cat >&2 <<'EOF'
Usage: sign_macos_code.sh --target PATH --identity IDENTITY [options]
Options:
--deep true|false
--entitlements PATH
--identifier IDENTIFIER
--identity IDENTITY
--options FLAGS
--target PATH
--timestamp true|false|none
EOF
}
target=""
identity=""
options=""
entitlements_file=""
identifier=""
deep="false"
timestamp="true"
while [[ $# -gt 0 ]]; do
case "$1" in
--deep)
deep="${2:-}"
shift 2
;;
--entitlements)
entitlements_file="${2:-}"
shift 2
;;
--identifier)
identifier="${2:-}"
shift 2
;;
--identity)
identity="${2:-}"
shift 2
;;
--options)
options="${2:-}"
shift 2
;;
--target)
target="${2:-}"
shift 2
;;
--timestamp)
timestamp="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown signing argument: $1" >&2
usage
exit 2
;;
esac
done
if [[ -z "$target" ]]; then
echo "--target is required." >&2
usage
exit 2
fi
if [[ ! -e "$target" ]]; then
echo "Signing target does not exist: $target" >&2
exit 1
fi
case "$deep" in
true|false) ;;
*)
echo "--deep must be true or false, got '$deep'." >&2
exit 2
;;
esac
case "$timestamp" in
true|false|none) ;;
*)
echo "--timestamp must be true, false, or none, got '$timestamp'." >&2
exit 2
;;
esac
sign_with_codesign() {
if [[ -z "$identity" ]]; then
echo "Native codesign requires --identity." >&2
exit 2
fi
local -a args
args=(--force)
if [[ "$deep" == "true" ]]; then
args+=(--deep)
fi
if [[ -n "$options" ]]; then
args+=(--options "$options")
fi
case "$timestamp" in
true)
args+=(--timestamp)
;;
false|none)
args+=(--timestamp=none)
;;
esac
if [[ -n "$entitlements_file" ]]; then
args+=(--entitlements "$entitlements_file")
fi
if [[ -n "$identifier" ]]; then
args+=(--identifier "$identifier")
fi
args+=(--sign "$identity" "$target")
codesign "${args[@]}"
}
append_rcodesign_flags() {
local raw_options="$1"
local option=""
if [[ -z "$raw_options" ]]; then
return 0
fi
IFS=',' read -ra split_options <<< "$raw_options"
for option in "${split_options[@]}"; do
option="${option//[[:space:]]/}"
[[ -z "$option" ]] && continue
case "$option" in
host|hard|kill|expires|restrict|library|runtime|linker-signed)
rcodesign_args+=(--code-signature-flags "$option")
;;
*)
echo "Unsupported rcodesign code signature option: $option" >&2
exit 2
;;
esac
done
}
rcodesign_options_require_notarization() {
local raw_options="$1"
local option=""
if [[ -z "$raw_options" || "$timestamp" != "true" ]]; then
return 1
fi
IFS=',' read -ra split_options <<< "$raw_options"
for option in "${split_options[@]}"; do
option="${option//[[:space:]]/}"
if [[ "$option" == "runtime" ]]; then
return 0
fi
done
return 1
}
sign_with_rcodesign() {
: "${OAI_AKV_PKCS11_LIBRARY:?OAI_AKV_PKCS11_LIBRARY is required for AKV PKCS11 signing.}"
: "${OAI_AKV_SIGNING_CERTIFICATE_PEM:?OAI_AKV_SIGNING_CERTIFICATE_PEM is required for AKV PKCS11 signing.}"
: "${OAI_AKV_KEY_LABEL:?OAI_AKV_KEY_LABEL is required for AKV PKCS11 signing.}"
if ! command -v rcodesign >/dev/null 2>&1; then
echo "rcodesign was not found on PATH." >&2
exit 1
fi
local -a rcodesign_args
rcodesign_args=(
sign
--config-file /dev/null
--pkcs11-library "$OAI_AKV_PKCS11_LIBRARY"
--pkcs11-certificate-file "$OAI_AKV_SIGNING_CERTIFICATE_PEM"
--pkcs11-key-label "$OAI_AKV_KEY_LABEL"
)
if [[ "$deep" == "false" ]]; then
rcodesign_args+=(--shallow)
fi
case "$timestamp" in
true)
;;
false|none)
rcodesign_args+=(--timestamp-url none)
;;
esac
append_rcodesign_flags "$options"
if rcodesign_options_require_notarization "$options"; then
rcodesign_args+=(--for-notarization)
fi
if [[ -n "$entitlements_file" ]]; then
rcodesign_args+=(--entitlements-xml-file "$entitlements_file")
fi
if [[ -n "$identifier" ]]; then
rcodesign_args+=(--binary-identifier "$identifier")
fi
rcodesign_args+=("$target")
rcodesign "${rcodesign_args[@]}"
}
case "${OAI_CODESIGN_BACKEND:-codesign}" in
codesign|"")
sign_with_codesign
;;
akv-pkcs11)
sign_with_rcodesign
;;
*)
echo "Unsupported OAI_CODESIGN_BACKEND: ${OAI_CODESIGN_BACKEND}" >&2
exit 2
;;
esac
@@ -1,202 +0,0 @@
import contextlib
import hashlib
import io
import json
import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
SIGNING_DIRECTORY = Path(__file__).resolve().parent
sys.path.insert(0, str(SIGNING_DIRECTORY))
import notarize_with_akv as notary # noqa: E402
CONFIGURATION = notary.NotarizationConfiguration(
"issuer-id",
"APPLEKEY01",
"notary-vault",
"example-signing-key",
"0123456789abcdef" * 2,
)
ENVIRONMENT = {
"APPLE_NOTARIZATION_ISSUER_ID": CONFIGURATION.issuer_id,
"AZURE_KEYVAULT_NAME": CONFIGURATION.vault_name,
"APPLE_NOTARIZATION_AKV_KEY_NAME": CONFIGURATION.vault_key_name,
}
CREDENTIALS = {
"awsAccessKeyId": "temporary-access-key",
"awsSecretAccessKey": "temporary-secret-key",
"awsSessionToken": "temporary-session-token",
"bucket": "apple-notary-bucket",
"object": "uploads/release artifact.dmg",
}
def response(payload):
return io.BytesIO(json.dumps(payload).encode() if isinstance(payload, dict) else payload)
def azure_response(payload):
return subprocess.CompletedProcess([], 0, stdout=json.dumps(payload), stderr="")
class NotarizationTest(unittest.TestCase):
def test_validates_the_apple_key_tag_and_pins_its_version(self) -> None:
metadata = {"id": CONFIGURATION.versioned_key_id, "apple_key_id": "APPLEKEY01"}
with (
patch.dict(os.environ, ENVIRONMENT, clear=True),
patch.object(
notary.subprocess,
"run",
return_value=azure_response(metadata),
) as show,
):
self.assertEqual(
notary.NotarizationConfiguration.from_environment(),
CONFIGURATION,
)
show.return_value = azure_response({**metadata, "apple_key_id": None})
with self.assertRaisesRegex(notary.NotarizationError, "apple-key-id tag"):
notary.NotarizationConfiguration.from_environment()
self.assertIn('{id:key.kid,apple_key_id:tags."apple-key-id"}', show.call_args.args[0])
invalid_environment = {**ENVIRONMENT, "APPLE_NOTARIZATION_AKV_KEY_NAME": "../bad"}
with (
patch.dict(os.environ, invalid_environment, clear=True),
self.assertRaisesRegex(notary.NotarizationError, "key name"),
):
notary.NotarizationConfiguration.from_environment()
def test_signs_the_notary_scoped_digest_with_the_pinned_key(self) -> None:
signature = bytes(range(64))
payload = {
"kid": CONFIGURATION.versioned_key_id,
"value": notary.base64url_encode(signature),
}
with patch.object(notary.subprocess, "run", return_value=azure_response(payload)) as sign:
token = notary.create_apple_jwt(CONFIGURATION, issued_at=1_780_000_000)
header, claims, encoded_signature = token.split(".")
self.assertEqual(json.loads(notary.base64url_decode(header))["kid"], "APPLEKEY01")
self.assertEqual(
json.loads(notary.base64url_decode(claims))["scope"],
["/notary/v2"],
)
self.assertEqual(notary.base64url_decode(encoded_signature), signature)
command = sign.call_args.args[0]
body = json.loads(command[command.index("--body") + 1])
self.assertEqual(body["alg"], "ES256")
self.assertEqual(
notary.base64url_decode(body["value"]),
hashlib.sha256(f"{header}.{claims}".encode()).digest(),
)
def test_rejects_wrong_key_versions_and_invalid_signatures(self) -> None:
cases = (
(CONFIGURATION.versioned_key_id + "wrong", bytes(64), "unexpected key version"),
(CONFIGURATION.versioned_key_id, b"short", "64 JOSE"),
)
for key_id, signature, message in cases:
payload = {"kid": key_id, "value": notary.base64url_encode(signature)}
with (
self.subTest(message=message),
patch.object(
notary.subprocess,
"run",
return_value=azure_response(payload),
),
self.assertRaisesRegex(notary.NotarizationError, message),
):
notary.create_apple_jwt(CONFIGURATION, issued_at=1)
def test_uploads_polls_and_retains_sanitized_apple_diagnostics(self) -> None:
signature = azure_response(
{
"kid": CONFIGURATION.versioned_key_id,
"value": notary.base64url_encode(bytes(64)),
}
)
responses = [
response({"data": {"id": "submission-1", "attributes": CREDENTIALS}}),
response(b""),
response({"data": {"attributes": {"status": "In Progress"}}}),
response({"data": {"attributes": {"status": "Accepted"}}}),
response({"data": {"attributes": {"developerLogUrl": "https://logs.example.com/log"}}}),
response(b'{"status":"Accepted"}'),
]
with tempfile.TemporaryDirectory() as directory:
artifact, log = (
Path(directory) / "codex.zip",
Path(directory) / "notary-log.json",
)
artifact.write_bytes(b"signed release binary")
output = io.StringIO()
with (
patch.object(notary.subprocess, "run", return_value=signature),
patch.object(notary.urllib.request, "urlopen", side_effect=responses) as requests,
patch.object(notary.time, "sleep") as sleep,
contextlib.redirect_stdout(output),
):
submission = notary.notarize(
artifact, CONFIGURATION, max_wait_seconds=600, report_log=log
)
self.assertEqual(submission, "submission-1")
self.assertEqual(json.loads(log.read_text()), {"status": "Accepted"})
sleep.assert_called_once_with(10)
upload = requests.call_args_list[1].args[0]
self.assertEqual(upload.get_method(), "PUT")
self.assertNotIn("temporary-secret-key", upload.get_header("Authorization"))
self.assertNotIn("temporary-secret-key", output.getvalue())
class NotarizationWrapperTest(unittest.TestCase):
def test_binary_and_dmg_wrappers_preserve_notarization_contracts(
self,
) -> None:
with tempfile.TemporaryDirectory() as directory:
root, tools = Path(directory), Path(directory) / "tools"
tools.mkdir()
call_log = root / "calls.txt"
environment = {
**os.environ,
**ENVIRONMENT,
"PATH": f"{tools}:{os.environ['PATH']}",
"RUNNER_TEMP": str(root),
"CALL_LOG": str(call_log),
}
for name in ("az", "python3", "rcodesign"):
body = "exit 0" if name == "az" else f'printf "{name} %s\\n" "$*" >> "$CALL_LOG"'
executable = tools / name
executable.write_text(f"#!/usr/bin/env bash\nset -euo pipefail\n{body}\n")
executable.chmod(0o755)
for kind in ("binary", "dmg"):
with self.subTest(kind=kind):
artifact = root / ("codex.dmg" if kind == "dmg" else "codex")
artifact.write_bytes(b"signed release artifact")
subprocess.run(
[
str(SIGNING_DIRECTORY / f"notarize_macos_{kind}_with_akv.sh"),
f"--{kind}",
str(artifact),
"--report-dir",
str(root / "report"),
],
env=environment,
check=True,
capture_output=True,
text=True,
)
calls = call_log.read_text().splitlines()
self.assertIn("notarize_with_akv.py --file", calls[0])
if kind == "dmg":
self.assertEqual(calls[1], f"rcodesign staple {artifact}")
else:
self.assertEqual(len(calls), 1)
self.assertEqual(list(root.rglob("*.p8")), [])
call_log.unlink()
if __name__ == "__main__":
unittest.main()
@@ -1,391 +0,0 @@
#!/usr/bin/env python3
"""Verify that codex-rs Cargo manifests follow workspace manifest policy.
Checks:
- Crates inherit `[workspace.package]` metadata.
- Crates opt into `[lints] workspace = true`.
- Crate names follow the codex-rs directory naming conventions.
- Workspace manifests do not introduce workspace crate feature toggles.
"""
from __future__ import annotations
import sys
import tomllib
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
CARGO_RS_ROOT = ROOT / "codex-rs"
WORKSPACE_PACKAGE_FIELDS = ("version", "edition", "license")
TOP_LEVEL_NAME_EXCEPTIONS = {
"windows-sandbox-rs": "codex-windows-sandbox",
}
UTILITY_NAME_EXCEPTIONS = {
"path-utils": "codex-utils-path",
}
MANIFEST_FEATURE_EXCEPTIONS = {
"codex-rs/code-mode/Cargo.toml": {"sandbox": ("v8/v8_enable_sandbox",)},
"codex-rs/v8-poc/Cargo.toml": {"sandbox": ("v8/v8_enable_sandbox",)},
}
OPTIONAL_DEPENDENCY_EXCEPTIONS = set()
INTERNAL_DEPENDENCY_FEATURE_EXCEPTIONS = {}
def main() -> int:
internal_package_names = workspace_package_names()
used_manifest_feature_exceptions: set[str] = set()
used_optional_dependency_exceptions: set[tuple[str, str, str]] = set()
used_internal_dependency_feature_exceptions: set[tuple[str, str, str]] = set()
failures_by_path: dict[str, list[str]] = {}
for path in manifests_to_verify():
if errors := manifest_errors(
path,
internal_package_names,
used_manifest_feature_exceptions,
used_optional_dependency_exceptions,
used_internal_dependency_feature_exceptions,
):
failures_by_path[manifest_key(path)] = errors
add_unused_exception_errors(
failures_by_path,
used_manifest_feature_exceptions,
used_optional_dependency_exceptions,
used_internal_dependency_feature_exceptions,
)
if not failures_by_path:
return 0
print(
"Cargo manifests under codex-rs must inherit workspace package metadata, "
"opt into workspace lints, and avoid introducing new workspace crate "
"features."
)
print(
"Workspace crate features are disallowed because our Bazel build setup "
"does not honor them today, which can let issues hidden behind feature "
"gates go unnoticed, and because they add extra crate build "
"permutations we want to avoid."
)
print(
"Cargo only applies `codex-rs/Cargo.toml` `[workspace.lints.clippy]` "
"entries to a crate when that crate declares:"
)
print()
print("[lints]")
print("workspace = true")
print()
print(
"Without that opt-in, `cargo clippy` can miss violations that Bazel clippy "
"catches."
)
print()
print(
"Package-name checks apply to `codex-rs/<crate>/Cargo.toml` and "
"`codex-rs/utils/<crate>/Cargo.toml`."
)
print(
"Workspace crate features are forbidden; add a targeted exception here "
"only if there is a deliberate temporary migration in flight."
)
print()
for path in sorted(failures_by_path):
errors = failures_by_path[path]
print(f"{path}:")
for error in errors:
print(f" - {error}")
return 1
def manifest_errors(
path: Path,
internal_package_names: set[str],
used_manifest_feature_exceptions: set[str],
used_optional_dependency_exceptions: set[tuple[str, str, str]],
used_internal_dependency_feature_exceptions: set[tuple[str, str, str]],
) -> list[str]:
manifest = load_manifest(path)
package = manifest.get("package")
if not isinstance(package, dict) and path != CARGO_RS_ROOT / "Cargo.toml":
return []
errors = []
if isinstance(package, dict):
for field in WORKSPACE_PACKAGE_FIELDS:
if not is_workspace_reference(package.get(field)):
errors.append(f"set `{field}.workspace = true` in `[package]`")
lints = manifest.get("lints")
if not (isinstance(lints, dict) and lints.get("workspace") is True):
errors.append("add `[lints]` with `workspace = true`")
expected_name = expected_package_name(path)
if expected_name is not None:
actual_name = package.get("name")
if actual_name != expected_name:
errors.append(
f"set `[package].name` to `{expected_name}` (found `{actual_name}`)"
)
path_key = manifest_key(path)
features = manifest.get("features")
if features is not None:
normalized_features = normalize_feature_mapping(features)
expected_features = MANIFEST_FEATURE_EXCEPTIONS.get(path_key)
if expected_features is None:
errors.append(
"remove `[features]`; new workspace crate features are not allowed"
)
else:
used_manifest_feature_exceptions.add(path_key)
if normalized_features != expected_features:
errors.append(
"limit `[features]` to the existing exception list while "
"workspace crate features are being removed "
f"(expected {render_feature_mapping(expected_features)})"
)
for section_name, dependencies in dependency_sections(manifest):
for dependency_name, dependency in dependencies.items():
if not isinstance(dependency, dict):
continue
if dependency.get("optional") is True:
exception_key = (path_key, section_name, dependency_name)
if exception_key in OPTIONAL_DEPENDENCY_EXCEPTIONS:
used_optional_dependency_exceptions.add(exception_key)
else:
errors.append(
"remove `optional = true` from "
f"`{dependency_entry_label(section_name, dependency_name)}`; "
"new optional dependencies are not allowed because they "
"create crate features"
)
if not is_internal_dependency(path, dependency_name, dependency, internal_package_names):
continue
dependency_features = dependency.get("features")
if dependency_features is not None:
normalized_dependency_features = normalize_string_list(
dependency_features
)
exception_key = (path_key, section_name, dependency_name)
expected_dependency_features = (
INTERNAL_DEPENDENCY_FEATURE_EXCEPTIONS.get(exception_key)
)
if expected_dependency_features is None:
errors.append(
"remove `features = [...]` from workspace dependency "
f"`{dependency_entry_label(section_name, dependency_name)}`; "
"new workspace crate feature activations are not allowed"
)
else:
used_internal_dependency_feature_exceptions.add(exception_key)
if normalized_dependency_features != expected_dependency_features:
errors.append(
"limit workspace dependency features on "
f"`{dependency_entry_label(section_name, dependency_name)}` "
"to the existing exception list while workspace crate "
"features are being removed "
f"(expected {render_string_list(expected_dependency_features)})"
)
if dependency.get("default-features") is False:
errors.append(
"remove `default-features = false` from workspace dependency "
f"`{dependency_entry_label(section_name, dependency_name)}`; "
"new workspace crate feature toggles are not allowed"
)
return errors
def expected_package_name(path: Path) -> str | None:
parts = path.relative_to(CARGO_RS_ROOT).parts
if len(parts) == 2 and parts[1] == "Cargo.toml":
directory = parts[0]
return TOP_LEVEL_NAME_EXCEPTIONS.get(
directory,
directory if directory.startswith("codex-") else f"codex-{directory}",
)
if len(parts) == 3 and parts[0] == "utils" and parts[2] == "Cargo.toml":
directory = parts[1]
return UTILITY_NAME_EXCEPTIONS.get(directory, f"codex-utils-{directory}")
return None
def is_workspace_reference(value: object) -> bool:
return isinstance(value, dict) and value.get("workspace") is True
def manifest_key(path: Path) -> str:
return str(path.relative_to(ROOT))
def normalize_feature_mapping(value: object) -> dict[str, tuple[str, ...]] | None:
if not isinstance(value, dict):
return None
normalized = {}
for key, features in value.items():
if not isinstance(key, str):
return None
normalized_features = normalize_string_list(features)
if normalized_features is None:
return None
normalized[key] = normalized_features
return normalized
def normalize_string_list(value: object) -> tuple[str, ...] | None:
if not isinstance(value, list) or not all(isinstance(item, str) for item in value):
return None
return tuple(value)
def render_feature_mapping(features: dict[str, tuple[str, ...]]) -> str:
entries = [
f"{name} = {render_string_list(items)}" for name, items in features.items()
]
return ", ".join(entries)
def render_string_list(items: tuple[str, ...]) -> str:
return "[" + ", ".join(f'"{item}"' for item in items) + "]"
def dependency_sections(manifest: dict) -> list[tuple[str, dict]]:
sections = []
for section_name in ("dependencies", "dev-dependencies", "build-dependencies"):
dependencies = manifest.get(section_name)
if isinstance(dependencies, dict):
sections.append((section_name, dependencies))
workspace = manifest.get("workspace")
if isinstance(workspace, dict):
workspace_dependencies = workspace.get("dependencies")
if isinstance(workspace_dependencies, dict):
sections.append(("workspace.dependencies", workspace_dependencies))
target = manifest.get("target")
if not isinstance(target, dict):
return sections
for target_name, tables in target.items():
if not isinstance(tables, dict):
continue
for section_name in ("dependencies", "dev-dependencies", "build-dependencies"):
dependencies = tables.get(section_name)
if isinstance(dependencies, dict):
sections.append((f"target.{target_name}.{section_name}", dependencies))
return sections
def dependency_entry_label(section_name: str, dependency_name: str) -> str:
return f"[{section_name}].{dependency_name}"
def is_internal_dependency(
manifest_path: Path,
dependency_name: str,
dependency: dict,
internal_package_names: set[str],
) -> bool:
package_name = dependency.get("package", dependency_name)
if isinstance(package_name, str) and package_name in internal_package_names:
return True
dependency_path = dependency.get("path")
if not isinstance(dependency_path, str):
return False
resolved_dependency_path = (manifest_path.parent / dependency_path).resolve()
try:
resolved_dependency_path.relative_to(CARGO_RS_ROOT)
except ValueError:
return False
return True
def add_unused_exception_errors(
failures_by_path: dict[str, list[str]],
used_manifest_feature_exceptions: set[str],
used_optional_dependency_exceptions: set[tuple[str, str, str]],
used_internal_dependency_feature_exceptions: set[tuple[str, str, str]],
) -> None:
for path_key in sorted(
set(MANIFEST_FEATURE_EXCEPTIONS) - used_manifest_feature_exceptions
):
add_failure(
failures_by_path,
path_key,
"remove the stale `[features]` exception from "
"`MANIFEST_FEATURE_EXCEPTIONS`",
)
for path_key, section_name, dependency_name in sorted(
OPTIONAL_DEPENDENCY_EXCEPTIONS - used_optional_dependency_exceptions
):
add_failure(
failures_by_path,
path_key,
"remove the stale optional-dependency exception for "
f"`{dependency_entry_label(section_name, dependency_name)}` from "
"`OPTIONAL_DEPENDENCY_EXCEPTIONS`",
)
for path_key, section_name, dependency_name in sorted(
set(INTERNAL_DEPENDENCY_FEATURE_EXCEPTIONS)
- used_internal_dependency_feature_exceptions
):
add_failure(
failures_by_path,
path_key,
"remove the stale internal dependency feature exception for "
f"`{dependency_entry_label(section_name, dependency_name)}` from "
"`INTERNAL_DEPENDENCY_FEATURE_EXCEPTIONS`",
)
def add_failure(failures_by_path: dict[str, list[str]], path_key: str, error: str) -> None:
failures_by_path.setdefault(path_key, []).append(error)
def workspace_package_names() -> set[str]:
package_names = set()
for path in cargo_manifests():
manifest = load_manifest(path)
package = manifest.get("package")
if not isinstance(package, dict):
continue
package_name = package.get("name")
if isinstance(package_name, str):
package_names.add(package_name)
return package_names
def load_manifest(path: Path) -> dict:
return tomllib.loads(path.read_text())
def cargo_manifests() -> list[Path]:
return sorted(
path
for path in CARGO_RS_ROOT.rglob("Cargo.toml")
if path != CARGO_RS_ROOT / "Cargo.toml"
)
def manifests_to_verify() -> list[Path]:
return [CARGO_RS_ROOT / "Cargo.toml", *cargo_manifests()]
if __name__ == "__main__":
sys.exit(main())
@@ -1,89 +0,0 @@
#!/usr/bin/env python3
"""Verify codex-tui does not depend on or import codex-core directly."""
from __future__ import annotations
import re
import sys
import tomllib
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
TUI_ROOT = ROOT / "codex-rs" / "tui"
TUI_MANIFEST = TUI_ROOT / "Cargo.toml"
FORBIDDEN_PACKAGE = "codex-core"
FORBIDDEN_SOURCE_PATTERNS = (
re.compile(r"\bcodex_core::"),
re.compile(r"\buse\s+codex_core\b"),
re.compile(r"\bextern\s+crate\s+codex_core\b"),
)
def main() -> int:
failures = []
failures.extend(manifest_failures())
failures.extend(source_failures())
if not failures:
return 0
print("codex-tui must not depend on or import codex-core directly.")
print(
"Use the app-server protocol/client boundary instead; temporary embedded "
"startup gaps belong behind codex_app_server_client::legacy_core."
)
print()
for failure in failures:
print(f"- {failure}")
return 1
def manifest_failures() -> list[str]:
manifest = tomllib.loads(TUI_MANIFEST.read_text())
failures = []
for section_name, dependencies in dependency_sections(manifest):
if FORBIDDEN_PACKAGE in dependencies:
failures.append(
f"{relative_path(TUI_MANIFEST)} declares `{FORBIDDEN_PACKAGE}` "
f"in `[{section_name}]`"
)
return failures
def dependency_sections(manifest: dict) -> list[tuple[str, dict]]:
sections: list[tuple[str, dict]] = []
for section_name in ("dependencies", "dev-dependencies", "build-dependencies"):
dependencies = manifest.get(section_name)
if isinstance(dependencies, dict):
sections.append((section_name, dependencies))
for target_name, target in manifest.get("target", {}).items():
if not isinstance(target, dict):
continue
for section_name in ("dependencies", "dev-dependencies", "build-dependencies"):
dependencies = target.get(section_name)
if isinstance(dependencies, dict):
sections.append((f'target.{target_name}.{section_name}', dependencies))
return sections
def source_failures() -> list[str]:
failures = []
for path in sorted(TUI_ROOT.glob("**/*.rs")):
text = path.read_text()
for line_number, line in enumerate(text.splitlines(), start=1):
if any(pattern.search(line) for pattern in FORBIDDEN_SOURCE_PATTERNS):
failures.append(f"{relative_path(path)}:{line_number} imports `codex_core`")
return failures
def relative_path(path: Path) -> str:
return str(path.relative_to(ROOT))
if __name__ == "__main__":
sys.exit(main())