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
31 changes: 16 additions & 15 deletions .github/actions/run-kipp-automation/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ inputs:
required: false
default: ''
scrub-oidc-env:
description: 'Clear the GitHub OIDC request variables before the automation runs and for the remainder of the job. Enable only when no provider or later step needs OIDC. Default: `false`.'
description: 'Remove GitHub OIDC request variables from the automation process and clear them for later expression evaluation. Later steps may still receive fresh values when the job grants `id-token: write`. Enable only when the automation does not need OIDC. Default: `false`.'
required: false
default: 'false'

Expand Down Expand Up @@ -81,9 +81,9 @@ runs:
echo "Running automation '$AUTOMATION_ID' in a single step."
fi

# GITHUB_ENV applies the empty values to the automation and every later
# step in the job. This is opt-in because some providers and downstream
# steps legitimately exchange the job's OIDC identity.
# Clearing these values through GITHUB_ENV affects later workflow expressions. When the job
# grants `id-token: write`, the runner still injects fresh values into each step process, so the
# automation process removes them again below.
- name: Scrub OIDC request variables
if: inputs.scrub-oidc-env == 'true'
shell: bash
Expand All @@ -94,17 +94,6 @@ runs:
echo "ACTIONS_ID_TOKEN_REQUEST_TOKEN="
} >> "$GITHUB_ENV"

- name: Confirm OIDC request variables are scrubbed
if: inputs.scrub-oidc-env == 'true'
shell: bash
run: |
set -euo pipefail
if [ -n "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ] || [ -n "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ]; then
echo "::error::The OIDC request variables are still set for the automation step. Refusing to run."
exit 1
fi
echo "OIDC request variables are not visible to the automation step."

# Use github-script v9 or later because it runs on Node 24. The runtime
# imports TypeScript automation modules and relies on Node 24's type stripping.
#
Expand All @@ -117,13 +106,25 @@ runs:
KIPP_AUTOMATION_ID: ${{ inputs.automation-id }}
KIPP_STAGE: ${{ inputs.stage }}
KIPP_SUBJECT_PATH: ${{ inputs.subject-path }}
KIPP_SCRUB_OIDC_ENV: ${{ inputs.scrub-oidc-env }}
# github-token configures github-script's Octokit client but does not
# expose the token value. The runtime also needs the value for HTTPS Git
# pushes and authenticated `gh` commands in the Pi subprocess.
KIPP_GITHUB_TOKEN: ${{ inputs.github-token }}
with:
github-token: ${{ inputs.github-token }}
script: |
// Scrub before importing: the runtime reads this process environment, and the Pi
// subprocess inherits it.
const OIDC_REQUEST_VARIABLES = ["ACTIONS_ID_TOKEN_REQUEST_URL", "ACTIONS_ID_TOKEN_REQUEST_TOKEN"]
if (process.env.KIPP_SCRUB_OIDC_ENV === "true") {
for (const name of OIDC_REQUEST_VARIABLES) delete process.env[name]
const remaining = OIDC_REQUEST_VARIABLES.filter((name) => process.env[name] !== undefined)
if (remaining.length > 0) {
throw new Error("The OIDC request variables are still set for the automation step. Refusing to run.")
}
core.info("OIDC request variables are not visible to the automation step.")
}
const { pathToFileURL } = await import("node:url")
const runtime = await import(pathToFileURL(process.env.KIPP_RUNTIME_PATH).href)
await runtime.runAutomation({ github, context, core, exec })
72 changes: 64 additions & 8 deletions .github/actions/setup-kipp/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ description: Install Kipp Managed Services and configure an isolated Pi environm

inputs:
kipp-version:
description: 'Exact Kipp version or `latest`.'
description: 'Exact Kipp version or `latest`. Ignored when `package-path` is set.'
required: true
package-path:
description: 'Path to a local Kipp package, absolute or relative to `GITHUB_WORKSPACE`. When set, the action skips downloading Kipp from the registry and ignores `kipp-version`.'
required: false
default: ''
ai-provider:
description: 'AI provider to configure: `openai-compatible` or `azure-foundry`.'
required: true
Expand Down Expand Up @@ -42,10 +46,10 @@ inputs:

outputs:
kipp-version:
description: 'Exact version of the installed Kipp package.'
description: 'Exact version read from the selected Kipp package.'
value: ${{ steps.download.outputs.kipp-version }}
package-path:
description: 'Absolute path to the installed Kipp package root.'
description: 'Absolute path to the selected Kipp package root.'
value: ${{ steps.download.outputs.package-path }}
runtime-path:
description: 'Absolute path to the Kipp automation runtime entry point.'
Expand All @@ -64,12 +68,12 @@ runs:
using: "composite"

steps:
# Validate configuration before performing any installs.
- name: Validate inputs
id: validate
shell: bash
env:
KIPP_VERSION: ${{ inputs.kipp-version }}
PACKAGE_PATH_INPUT: ${{ inputs.package-path }}
AI_PROVIDER: ${{ inputs.ai-provider }}
AI_MODEL: ${{ inputs.ai-model }}
AI_API_KEY: ${{ inputs.ai-api-key }}
Expand All @@ -94,6 +98,7 @@ runs:
}

reject_line_breaks "kipp-version" "$KIPP_VERSION"
reject_line_breaks "package-path" "$PACKAGE_PATH_INPUT"
reject_line_breaks "ai-provider" "$AI_PROVIDER"
reject_line_breaks "ai-model" "$AI_MODEL"
reject_line_breaks "ai-api-key" "$AI_API_KEY"
Expand Down Expand Up @@ -131,8 +136,19 @@ runs:
*) fail "Unsupported ai-provider '$AI_PROVIDER'. Supported providers are: openai-compatible, azure-foundry." ;;
esac

# Normal workflows pin an exact version. The self-maintenance workflows use `latest` to discover and propose version updates.
if [ "$KIPP_VERSION" != "latest" ]; then
PACKAGE_PATH=''
if [ -n "$PACKAGE_PATH_INPUT" ]; then
case "$PACKAGE_PATH_INPUT" in
/*) PACKAGE_PATH="$PACKAGE_PATH_INPUT" ;;
*) PACKAGE_PATH="$GITHUB_WORKSPACE/$PACKAGE_PATH_INPUT" ;;
esac
[ -d "$PACKAGE_PATH" ] || fail "package-path '$PACKAGE_PATH_INPUT' is not a directory at $PACKAGE_PATH."
[ -f "$PACKAGE_PATH/package.json" ] || fail "package-path '$PACKAGE_PATH_INPUT' has no package.json at $PACKAGE_PATH/package.json."
fi

# Registry installs accept an exact version or `latest`. A local package supplies its own
# version through package.json, so kipp-version is ignored when package-path is set.
if [ -z "$PACKAGE_PATH" ] && [ "$KIPP_VERSION" != "latest" ]; then
[[ "$KIPP_VERSION" =~ $EXACT_VERSION ]] || fail "kipp-version must be an exact version such as 1.2.3, or the literal 'latest', received '$KIPP_VERSION'."
fi
[ -n "$AI_MODEL" ] || fail "ai-model is required."
Expand All @@ -157,6 +173,8 @@ runs:
;;
esac

echo "package-path=$PACKAGE_PATH" >> "$GITHUB_OUTPUT"

echo "Provider '$AI_PROVIDER' selected with model '$AI_MODEL'."

- name: Use Node.js ${{ inputs.node-version }}
Expand Down Expand Up @@ -226,11 +244,50 @@ runs:
PACKAGE_SCOPE: '@makerx-trusted-access'
PACKAGE_NAME: '@makerx-trusted-access/kipp'
KIPP_VERSION: ${{ inputs.kipp-version }}
PACKAGE_PATH_OVERRIDE: ${{ steps.validate.outputs.package-path }}
run: | # zizmor: ignore[adhoc-packages]
set -euo pipefail

if [ -n "$PACKAGE_PATH_OVERRIDE" ]; then
LOCAL_MANIFEST="$PACKAGE_PATH_OVERRIDE/package.json"
EXACT_VERSION='^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$'

fail_manifest() {
echo "::error::The local Kipp package at $PACKAGE_PATH_OVERRIDE $1."
exit 1
}

# Normalize invalid field types so validation below reports consistent errors.
read_manifest_field() {
FIELD="$1" MANIFEST_PATH="$LOCAL_MANIFEST" node -e '
const manifest = require(process.env.MANIFEST_PATH);
const value = manifest[process.env.FIELD];
process.stdout.write(typeof value === "string" ? value : "");
'
}

LOCAL_NAME="$(read_manifest_field name)"
LOCAL_VERSION="$(read_manifest_field version)"

# Validate caller-controlled metadata before writing it to $GITHUB_OUTPUT or including it
# in a workflow command. The exact-version check also rejects line breaks.
[ -n "$LOCAL_NAME" ] || fail_manifest "declares no package name"
case "$LOCAL_NAME" in
*$'\r'*|*$'\n'*) fail_manifest "declares a package name containing a carriage return or newline" ;;
esac
[[ "$LOCAL_VERSION" =~ $EXACT_VERSION ]] ||
fail_manifest "must declare an exact version such as 1.2.3, received '$LOCAL_VERSION'"

{
echo "package-path=$PACKAGE_PATH_OVERRIDE"
echo "kipp-version=$LOCAL_VERSION"
} >> "$GITHUB_OUTPUT"

echo "::notice::Using the local Kipp package $LOCAL_NAME@$LOCAL_VERSION at $PACKAGE_PATH_OVERRIDE. Nothing was installed from $REGISTRY_URL."
exit 0
fi

NPMRC="$RUNNER_TEMP/kipp-trusted-access.npmrc"
# Delete the temporary npm credentials whether the step succeeds or fails.
trap 'rm -f "$NPMRC"' EXIT

if [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ]; then
Expand Down Expand Up @@ -266,7 +323,6 @@ runs:
"$PACKAGE_SCOPE" "$REGISTRY_URL" "$HOST" "$TOKEN" > "$NPMRC" )
export NPM_CONFIG_USERCONFIG="$NPMRC"

# If `latest` was requested, resolve it to an exact version before installing.
INSTALL_VERSION="$KIPP_VERSION"
if [ "$INSTALL_VERSION" = "latest" ]; then
INSTALL_VERSION=$(npm view "$PACKAGE_NAME" version --registry "$REGISTRY_URL") || INSTALL_VERSION=""
Expand Down