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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,26 @@ jobs:
bun run storybook:build
bun run test:storybook

# Probes Apple notary + signing credentials without notarizing.
preflight-apple:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: macos-14
steps:
- uses: actions/checkout@v7

- name: Check Apple notary + signing credentials
env:
APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: scripts/ci/preflight-apple.sh

# Compute the next version once, before anything is built, so the compiled
# binaries embed the same version that the release tag + Docker tag will use.
version:
needs: lint-test
needs: [lint-test, preflight-apple]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
outputs:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ docs/_site/

# generated types copied from bindings/ (bindings/ itself is committed)
vscode-extension/src/generated/
vscode-extension/.nyc_output/
webcomponents/src/generated/
vscode-extension/out/
vscode-extension/.vscode-test/
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "operator"
version = "0.2.13"
version = "0.2.15"
edition = "2021"
rust-version = "1.95"
description = "Multi-agent orchestration dashboard for kanban shaped software development"
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.13
0.2.15
2 changes: 1 addition & 1 deletion agnt-plugin/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "operator-plugin",
"version": "0.2.13",
"version": "0.2.15",
"description": "Orchestrate Operator! ticket-driven coding agents from AGNT workflows",
"author": "untra",
"displayName": "Operator!",
Expand Down
2 changes: 1 addition & 1 deletion agnt-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "operator-plugin",
"version": "0.2.13",
"version": "0.2.15",
"description": "Orchestrate Operator! ticket-driven coding agents from AGNT workflows",
"author": "untra",
"license": "MIT",
Expand Down
4 changes: 2 additions & 2 deletions charts/operator/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ apiVersion: v2
name: operator
description: Run Operator as a single-writer agent orchestration service
type: application
version: 0.2.13
appVersion: "0.2.13"
version: 0.2.15
appVersion: "0.2.15"
kubeVersion: ">=1.25.0-0"
home: https://operator.untra.io
sources:
Expand Down
2 changes: 1 addition & 1 deletion coder-module/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ variable "slug" {
variable "install_version" {
type = string
description = "The version of operator to install (must match a GitHub release tag)."
default = "0.2.13"
default = "0.2.15"
}

variable "install_prefix" {
Expand Down
2 changes: 1 addition & 1 deletion docs/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ collections_dir: .

# Permalink structure
permalink: pretty
version: 0.2.13
version: 0.2.15

# Google Analytics
ga_tag: G-5JZPJWWT7S # Replace with actual GA4 measurement ID from analytics.google.com
2 changes: 1 addition & 1 deletion docs/schemas/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"license": {
"name": "MIT"
},
"version": "0.2.13"
"version": "0.2.15"
},
"paths": {
"/api/v1/agents/active": {
Expand Down
2 changes: 1 addition & 1 deletion opr8r/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion opr8r/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "opr8r"
version = "0.2.13"
version = "0.2.15"
edition = "2021"
description = "Minimal CLI wrapper for LLM commands in multi-step ticket workflows"
license = "MIT"
Expand Down
117 changes: 117 additions & 0 deletions scripts/ci/preflight-apple.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/bin/bash
set -euo pipefail

# Validates Apple notary + Developer ID signing credentials without notarizing
# anything, so an expired agreement/key/cert fails before release builds fan out.
# Usage: preflight-apple.sh
#
# Environment variables (same as notarize.sh / codesign.sh):
# APPLE_NOTARY_KEY_BASE64 - base64-encoded .p8 API key
# APPLE_NOTARY_KEY_ID - API key ID
# APPLE_NOTARY_ISSUER_ID - API issuer UUID
# APPLE_CERTIFICATE_P12_BASE64 - base64-encoded .p12 certificate
# APPLE_CERTIFICATE_PASSWORD - password for the .p12 file

readonly CERT_EXPIRY_WARN_DAYS=30
readonly SECONDS_PER_DAY=86400
readonly SIGNING_IDENTITY="Developer ID Application"
readonly PULL_REQUEST_EVENT="pull_request"

WORK_DIR="$(mktemp -d)"
SUMMARY="${GITHUB_STEP_SUMMARY:-/dev/null}"
FAILED=0

trap 'rm -rf "$WORK_DIR"' EXIT

report() {
echo "- $1 $2" >> "$SUMMARY"
}

fail() {
echo "::error::$1: $2"
report "❌" "$1: $2"
FAILED=1
}

# Fork and Dependabot PRs receive no secrets; anything else must have them.
require_env() {
local check="$1"
shift
local missing=()
local name
for name in "$@"; do
[ -n "${!name:-}" ] || missing+=("$name")
done
[ ${#missing[@]} -eq 0 ] && return 0

if [ "${GITHUB_EVENT_NAME:-}" = "$PULL_REQUEST_EVENT" ]; then
echo "::warning::$check skipped: ${missing[*]} unavailable to this PR"
report "⚠️" "$check skipped: secrets unavailable to this PR"
else
fail "$check" "${missing[*]} unset"
fi
return 1
}

check_notary() {
local check="Notary API"
require_env "$check" APPLE_NOTARY_KEY_BASE64 APPLE_NOTARY_KEY_ID APPLE_NOTARY_ISSUER_ID || return 0

local key_path="$WORK_DIR/AuthKey_${APPLE_NOTARY_KEY_ID}.p8"
echo "$APPLE_NOTARY_KEY_BASE64" | base64 --decode > "$key_path"

# `history` is a read-only call to the same team-scoped API as `submit`, so a
# missing agreement or revoked key 403s here exactly as it would on submit.
local output
if output=$(xcrun notarytool history \
--key "$key_path" \
--key-id "$APPLE_NOTARY_KEY_ID" \
--issuer "$APPLE_NOTARY_ISSUER_ID" \
--output-format json 2>&1); then
echo "$check: credentials accepted"
report "✅" "$check: credentials accepted"
else
echo "$output" >&2
fail "$check" "$(echo "$output" | grep -m1 -i 'error' || echo 'request failed') — check developer.apple.com/account for unsigned agreements and that the API key is not revoked"
fi
}

p12_to_pem() {
openssl pkcs12 -in "$1" -nokeys -clcerts -passin env:APPLE_CERTIFICATE_PASSWORD 2>/dev/null \
|| openssl pkcs12 -legacy -in "$1" -nokeys -clcerts -passin env:APPLE_CERTIFICATE_PASSWORD 2>/dev/null
}

check_certificate() {
local check="Signing certificate"
require_env "$check" APPLE_CERTIFICATE_P12_BASE64 APPLE_CERTIFICATE_PASSWORD || return 0

local p12_path="$WORK_DIR/cert.p12"
local pem_path="$WORK_DIR/cert.pem"
echo "$APPLE_CERTIFICATE_P12_BASE64" | base64 --decode > "$p12_path"

if ! p12_to_pem "$p12_path" > "$pem_path" || [ ! -s "$pem_path" ]; then
fail "$check" "could not decrypt .p12 (wrong APPLE_CERTIFICATE_PASSWORD or corrupt certificate)"
return 0
fi

local subject expiry
subject=$(openssl x509 -in "$pem_path" -noout -subject)
expiry=$(openssl x509 -in "$pem_path" -noout -enddate | cut -d= -f2)

if [[ "$subject" != *"$SIGNING_IDENTITY"* ]]; then
fail "$check" "not a '$SIGNING_IDENTITY' certificate ($subject)"
elif ! openssl x509 -in "$pem_path" -noout -checkend 0 > /dev/null; then
fail "$check" "expired on $expiry"
elif ! openssl x509 -in "$pem_path" -noout -checkend $((CERT_EXPIRY_WARN_DAYS * SECONDS_PER_DAY)) > /dev/null; then
echo "::warning::$check expires on $expiry (within $CERT_EXPIRY_WARN_DAYS days)"
report "⚠️" "$check: expires on $expiry"
else
echo "$check: valid until $expiry"
report "✅" "$check: valid until $expiry"
fi
}

echo "### Apple release credentials" >> "$SUMMARY"
check_notary
check_certificate
exit "$FAILED"
33 changes: 33 additions & 0 deletions tests/release_workflow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use std::fs;
use std::path::PathBuf;

use serde_yaml::Value;

const MAIN_PUSH: &str = "github.event_name == 'push' && github.ref == 'refs/heads/main'";

fn workflow() -> Value {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".github/workflows/build.yaml");
let content = fs::read_to_string(&path)
.unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display()));
serde_yaml::from_str(&content)
.unwrap_or_else(|error| panic!("failed to parse {}: {error}", path.display()))
}

fn needs(job: &Value, dependency: &str) -> bool {
job["needs"]
.as_sequence()
.is_some_and(|needs| needs.iter().any(|need| need.as_str() == Some(dependency)))
}

#[test]
fn test_apple_preflight_starts_on_merge_and_gates_release_builds() {
let workflow = workflow();
let jobs = &workflow["jobs"];
let preflight = &jobs["preflight-apple"];

assert_eq!(preflight["if"].as_str(), Some(MAIN_PUSH));
assert!(preflight.get("needs").is_none());
assert!(needs(&jobs["version"], "preflight-apple"));
assert!(needs(&jobs["build"], "version"));
assert!(needs(&jobs["build-opr8r"], "version"));
}
44 changes: 25 additions & 19 deletions vscode-extension/images/icons/dist/operator-icons.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@font-face {
font-family: "operator-icons";
src: url("./operator-icons.woff?173f3284801b532eebc3a5569872bf66") format("woff");
src: url("./operator-icons.woff?9f799214e04923a1c16f6ee3f717bebf") format("woff");
}

i[class^="opi-"]:before, i[class*=" opi-"]:before {
Expand All @@ -20,57 +20,63 @@ i[class^="opi-"]:before, i[class*=" opi-"]:before {
.opi-zedindustries:before {
content: "\f102";
}
.opi-webhook:before {
.opi-xai:before {
content: "\f103";
}
.opi-vscode:before {
.opi-webhook:before {
content: "\f104";
}
.opi-tmux:before {
.opi-vscode:before {
content: "\f105";
}
.opi-openrouter:before {
.opi-tmux:before {
content: "\f106";
}
.opi-ollama:before {
.opi-operator:before {
content: "\f107";
}
.opi-notification:before {
.opi-openrouter:before {
content: "\f108";
}
.opi-linear:before {
.opi-ollama:before {
content: "\f109";
}
.opi-google:before {
.opi-notification:before {
content: "\f10a";
}
.opi-gitlab:before {
.opi-linear:before {
content: "\f10b";
}
.opi-github:before {
.opi-google:before {
content: "\f10c";
}
.opi-gitea:before {
.opi-gitlab:before {
content: "\f10d";
}
.opi-gemini:before {
.opi-github:before {
content: "\f10e";
}
.opi-codex:before {
.opi-gitea:before {
content: "\f10f";
}
.opi-coder:before {
.opi-gemini:before {
content: "\f110";
}
.opi-cmux:before {
.opi-codex:before {
content: "\f111";
}
.opi-claude:before {
.opi-coder:before {
content: "\f112";
}
.opi-atlassian:before {
.opi-cmux:before {
content: "\f113";
}
.opi-anthropic:before {
.opi-claude:before {
content: "\f114";
}
.opi-atlassian:before {
content: "\f115";
}
.opi-anthropic:before {
content: "\f116";
}
Loading
Loading