Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
524b8d0
fix(fastly): apply canonical store selectors at deploy
aram356 Sep 16, 2026
07ffc29
test(actions): model Fastly resource link IDs
aram356 Sep 16, 2026
8efad3c
docs: move deploy specs under superpowers
aram356 Sep 16, 2026
1c1e8c3
docs: align deploy design artifact layout
aram356 Sep 16, 2026
263f7b2
docs: design version-scoped Fastly selectors
aram356 Sep 16, 2026
2da415e
docs: plan version-scoped Fastly selectors
aram356 Sep 16, 2026
1c2ba63
refactor(deploy): let adapters claim managed deployment
aram356 Sep 16, 2026
63eaaba
feat(fastly): define versioned runtime descriptors
aram356 Sep 16, 2026
b3d5a94
feat(fastly): load runtime config by service version
aram356 Sep 16, 2026
578524f
docs: drop legacy Fastly selector migration
aram356 Sep 16, 2026
96ea0ac
fix(fastly): validate managed deploy inputs before dispatch
aram356 Sep 16, 2026
b905ca9
docs: require immutable Fastly application releases
aram356 Sep 16, 2026
d281592
feat(fastly): plan descriptors and resource links before deploy
aram356 Sep 16, 2026
18de384
fix(fastly): prepare selectors before publishing versions
aram356 Sep 17, 2026
cb69341
fix(actions): retain failed Fastly deploy versions
aram356 Sep 17, 2026
17db1c0
test(actions): model version-scoped Fastly descriptors
aram356 Sep 17, 2026
7ed31eb
fix(fastly): compile host-only tests for host targets
aram356 Sep 17, 2026
1eea68f
docs(fastly): explain version-scoped runtime selectors
aram356 Sep 17, 2026
ea8937d
chore: generalize lifecycle examples
aram356 Sep 17, 2026
4ba48c3
fix(actions): tighten Fastly lifecycle contracts
aram356 Sep 17, 2026
b5fef1a
fix(config): align staging keys with runtime descriptors
aram356 Sep 17, 2026
1ff1222
fix(config): apply manifest store defaults consistently
aram356 Sep 17, 2026
ae75f9b
test(fastly): redact sensitive assertion diagnostics
aram356 Sep 17, 2026
d21e9bc
fix(fastly): close managed deployment review gaps
aram356 Sep 17, 2026
7b4260c
test(fastly): gate package hash fixture by target
aram356 Sep 17, 2026
3411ada
test(actions): satisfy shellcheck for literal domains
aram356 Sep 17, 2026
d22b8fa
docs: replace Fastly runtime selector design
aram356 Sep 17, 2026
0159acb
fix(config): reject invalid store selectors before provider access
aram356 Sep 17, 2026
9d320c2
refactor(fastly): use logical version resource links
aram356 Sep 17, 2026
bb0f881
test(actions): align staging link mutation order
aram356 Sep 17, 2026
657bfdc
test(fastly): avoid logging selector errors
aram356 Sep 17, 2026
bcc89fa
fix(fastly): keep runtime store keys target independent
aram356 Sep 17, 2026
2b0cec8
fix(manifest): resolve adapter logging case insensitively
aram356 Sep 17, 2026
f88ab52
fix(fastly): harden managed deployment lifecycle
aram356 Sep 17, 2026
7420786
test(actions): align smokes with logical store keys
aram356 Sep 17, 2026
dbb9501
fix(fastly): harden immutable release publication
aram356 Sep 17, 2026
fba2076
fix(actions): align immutable release contracts
aram356 Sep 18, 2026
c40ae8d
fix(actions): scope Fastly releases to Fastly
aram356 Sep 18, 2026
24e9a7c
docs(actions): design provider-neutral action cores
aram356 Sep 18, 2026
a0ebc0f
docs(actions): define lifecycle capability contract
aram356 Sep 18, 2026
9051d7e
docs(actions): preserve repeated lifecycle outputs
aram356 Sep 18, 2026
6127a2c
docs(actions): preserve lifecycle parsing semantics
aram356 Sep 18, 2026
9de7b0b
docs(actions): tighten modular core contracts
aram356 Sep 18, 2026
9b5d4ba
fix(fastly): parse live resource link types
aram356 Sep 18, 2026
d09502e
docs(fastly): document runtime selector migration
aram356 Sep 18, 2026
4e52d41
docs(actions): plan provider-neutral action cores
aram356 Sep 18, 2026
4531aee
refactor(actions): modularize application release lifecycle
aram356 Sep 18, 2026
f865aa9
fix(actions): preserve caller script directory
aram356 Sep 18, 2026
7162b7e
fix(ci): refresh app demo lockfile
aram356 Sep 18, 2026
8577341
test(ci): run every adapter CLI suite
aram356 Sep 18, 2026
8b50678
ci: split host test suites by surface
aram356 Sep 18, 2026
7c2d098
ci: retain uploaded artifacts for 14 days
aram356 Sep 18, 2026
fd45db1
fix(fastly): avoid unsupported compute version diff
aram356 Sep 18, 2026
6258b6b
fix(fastly): use pubsub logging API path
aram356 Sep 18, 2026
bb4e004
fix(fastly): use current service commands
aram356 Sep 18, 2026
a57d7a0
ci: retain full workspace test baseline
aram356 Sep 19, 2026
d06de9d
ci: add reusable Rust build cache
aram356 Sep 19, 2026
cf9a96a
fix(fastly): canonicalize configuration snapshots
aram356 Sep 19, 2026
119fbf8
ci: cache application Cargo target artifacts
aram356 Sep 19, 2026
5878eb7
test(ci): satisfy cache action shellcheck
aram356 Sep 19, 2026
12c3215
Merge main into fix/fastly-environment-store-selectors
aram356 Sep 20, 2026
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
9 changes: 5 additions & 4 deletions .github/actions/build-app-cli/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ outputs:
description: The binary name inside the artifact.
value: ${{ steps.build.outputs['app-cli-bin'] }}
app-cli-artifact:
description: Artifact name the deploy, healthcheck, rollback, and config-push-fastly actions consume.
description: Artifact name consumed when assembling an immutable application release.
value: ${{ steps.build.outputs['app-cli-artifact'] }}

runs:
Expand Down Expand Up @@ -105,6 +105,9 @@ runs:
EDGEZERO__PROJECT__WORKING_DIRECTORY: ${{ inputs['working-directory'] }}
EDGEZERO__PROJECT__RUST_TOOLCHAIN: ${{ inputs['rust-toolchain'] }}
EDGEZERO__APP__CLI__ARTIFACT: ${{ inputs['app-cli-artifact'] }}
# setup-rust-build-cache publishes this through GITHUB_ENV only when its
# opt-in target cache is enabled. build-app-cli confines it to RUNNER_TEMP.
EDGEZERO__RUST_CACHE__TARGET_DIR: ${{ env.EDGEZERO__RUST_CACHE__TARGET_DIR }}
# This step runs app-controlled code, so it emits NO outputs through the
# runner's GITHUB_OUTPUT (blanked below). It collects them into this
# action-owned file; the separate "Publish CLI outputs" step re-emits them.
Expand Down Expand Up @@ -276,9 +279,7 @@ runs:
name: ${{ steps.build.outputs['app-cli-artifact'] }}
path: ${{ steps.build.outputs['tarball-path'] }}
if-no-files-found: error
# No forced retention: the repository/org default applies, so the artifact
# survives an environment-approval gate that delays the deploy job past a day
# (a forced `retention-days: 1` would expire the CLI before it downloads).
retention-days: 14

# Remove the per-invocation workspace (build/stage dirs, the outputs handoff)
# now that the artifact is uploaded. Runs no application code.
Expand Down
26 changes: 22 additions & 4 deletions .github/actions/build-app-cli/scripts/build-app-cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
set -euo pipefail

# Compiles the CLI package the APPLICATION provides — a crate in the app's own
# workspace — into an action-owned CARGO_TARGET_DIR, then packages the binary
# workspace — into an application-scoped cached CARGO_TARGET_DIR when configured,
# or an action-owned scratch directory otherwise. It then packages the binary
# plus a self-describing app-cli-meta.json into a tar so the executable bit
# survives actions/upload-artifact. Never builds the EdgeZero monorepo CLI.
#
Expand All @@ -17,6 +18,7 @@ set -euo pipefail
# RUNNER_TEMP optional action-owned scratch root (default: /tmp)
# EDGEZERO__PROVIDER__ENV_CLEAR optional JSON array of provider aliases to scrub before the build (default: [])
# EDGEZERO__ACTION__WORKSPACE optional per-invocation scratch root (default: mktemp under RUNNER_TEMP)
# EDGEZERO__RUST_CACHE__TARGET_DIR optional shared Cargo target directory beneath RUNNER_TEMP
# EDGEZERO__ACTION__OUTPUT_FILE optional outputs handoff file, started fresh (default: GITHUB_OUTPUT)
# Writes (outputs):
# app-cli-package the package that was built
Expand Down Expand Up @@ -194,6 +196,19 @@ main() {
[[ -n "$action_ws" ]] || action_ws=$(mktemp -d "$runner_temp/edgezero-cli.XXXXXX")
local stage_root="$action_ws/artifact"
local build_target_dir="$action_ws/build"
local cached_target_dir="${EDGEZERO__RUST_CACHE__TARGET_DIR:-}"

if [[ -n "$cached_target_dir" ]]; then
[[ -d "$runner_temp" ]] || fail "RUNNER_TEMP does not exist or is not a directory"
[[ -d "$cached_target_dir" ]] ||
fail "cached Cargo target directory does not exist or is not a directory"
local runner_temp_real cached_target_real
runner_temp_real=$(canonical_path "$runner_temp")
cached_target_real=$(canonical_path "$cached_target_dir")
is_under "$runner_temp_real" "$cached_target_real" ||
fail "cached Cargo target directory must resolve inside RUNNER_TEMP"
build_target_dir="$cached_target_real"
fi

require_linux_x86_64
require_cmd cargo
Expand Down Expand Up @@ -260,8 +275,11 @@ main() {
[[ "$has_bin" == "true" ]] || fail "app-cli-package '$cli_package' declares no binary target named '$cli_bin'"
cli_version=$(jq -r '.version' <<<"$package_json")

# Build into an action-owned target dir so the checkout stays clean.
reset_owned_dir "$build_target_dir" "$runner_temp"
# Keep restored artifacts when setup-rust-build-cache supplied the target.
# The uncached fallback remains isolated to this invocation and starts clean.
if [[ -z "$cached_target_dir" ]]; then
reset_owned_dir "$build_target_dir" "$runner_temp"
fi
run_untrusted CARGO_TARGET_DIR="$build_target_dir" cargo +"$rust_toolchain" build \
--locked --release -p "$cli_package" --bin "$cli_bin"

Expand All @@ -278,7 +296,7 @@ main() {
>"$stage_root/app-cli-meta.json"

# Fixed tarball name — never derive a path component from caller input.
local tarball="$stage_root/../edgezero-cli.tar"
local tarball="$stage_root/../app-cli.tar"
tar -C "$stage_root" -cf "$tarball" "$cli_bin" app-cli-meta.json
tarball=$(canonical_path "$tarball")

Expand Down
93 changes: 43 additions & 50 deletions .github/actions/config-push-fastly/action.yml
Original file line number Diff line number Diff line change
@@ -1,76 +1,68 @@
name: EdgeZero config-push-fastly
description: Push a checked-out EdgeZero application's typed config to a Fastly config store using a prebuilt app CLI artifact.
description: Push publisher runtime config using the CLI and manifest from a verified immutable application release.

inputs:
app-cli-artifact:
description: Name of the build-app-cli artifact to download and run.
app-release-archive:
description: Path to the pinned application release archive.
required: true
app-release-sha256:
description: Expected lowercase SHA-256 digest of the application release archive.
required: true
expected-source-revision:
description: Full source revision that release.json must record.
required: true
app-cli-bin:
description: Binary name inside the artifact. Defaults to the artifact metadata.
required: false
default: ""
fastly-api-token:
description: Fastly API token. Injected only into the push step.
description: Fastly API token, scoped only to provider operations.
required: true
working-directory:
description: Application directory relative to github.workspace (holds the manifest + typed config).
description: Directory relative to github.workspace containing a publisher-owned app-config file.
required: false
default: .
manifest:
description: Optional edgezero.toml path relative to working-directory.
required: false
default: ""
app-config:
description: "Optional typed config file path relative to working-directory (default: resolved from the manifest). Mutually exclusive with app-config-inline."
description: Typed runtime config file relative to working-directory; exactly one config input is required.
required: false
default: ""
app-config-inline:
description: "Optional raw typed-config content (TOML) supplied inline instead of from a checked-out file — for config that lives in a GitHub variable with no file on disk. Written to an action-owned temp file and passed to the CLI. Mutually exclusive with app-config."
description: Inline typed runtime config; exactly one config input is required.
required: false
default: ""
no-env:
description: "When 'true', pass --no-env so the CLI does NOT overlay <APP_NAME>__…__<KEY> environment variables onto the typed config before pushing. Defaults to 'false'."
description: "When 'true', skip the typed runtime environment overlay."
required: false
default: "false"
store:
description: "Optional logical config-store id (default: the manifest's resolved id)."
description: Optional logical Config Store ID declared by the bundled application manifest.
required: false
default: ""
key:
description: "Optional explicit base key for a PRODUCTION push (default: the logical store id). Not allowed with deploy-to: staging, whose key is derived."
description: "Deprecated and rejected when nonempty; use EDGEZERO__STORES__CONFIG__<ID>__KEY."
required: false
default: ""
deploy-to:
description: "'production' writes the base key; 'staging' writes the <logical-store-id>_staging variant in the same store (the key the staging selector points at)."
description: "Select the canonical environment key, with production/staging fallback when absent."
required: false
default: production

outputs:
pushed-key:
description: The key that was written (the base key, or the derived _staging variant).
description: Key written by the application CLI.
value: ${{ steps.push.outputs['pushed-key'] }}
store:
description: The logical config-store id the CLI resolved (always emitted, not only when the `store` input was supplied).
description: Logical Config Store ID resolved by the bundled manifest.
value: ${{ steps.push.outputs.store }}
mutation-attempted:
description: "'true', emitted immediately BEFORE the config-push CLI runs (so a cancel/timeout mid-mutation can preserve it; a hard runner loss can still drop it, so absence is not proof the store is unchanged, and a cancel in the tiny pre-run window is a conservative false positive). On failure, read this via `if: always()` and reconcile — do not assume the config store is unchanged."
description: "'true' when the config-push CLI was invoked."
value: ${{ steps.push.outputs['mutation-attempted'] }}
provider-cli-version:
description: The pinned Fastly CLI version this action installed and ran.
description: Pinned Fastly CLI version installed by this action.
value: ${{ steps.install-fastly.outputs['provider-cli-version'] }}

runs:
using: composite
steps:
# A UNIQUE per-invocation workspace root under RUNNER_TEMP, so two concurrent
# invocations in one job (e.g. `background: true`) never collide on fixed temp
# paths (CLI download, extracted tools). The cleanup step removes it.
- name: Prepare action workspace
id: ws
shell: bash
# Runs before validation, so it scrubs like every other step: blank the
# shipped aliases and BASH_ENV/ENV (a caller's job env could otherwise point
# BASH_ENV at checkout code that runs at bash startup with a token in scope).
env:
BASH_ENV: ""
ENV: ""
Expand All @@ -97,10 +89,13 @@ runs:
env:
BASH_ENV: ""
ENV: ""
EDGEZERO__APP__CLI__ARTIFACT_PRESENT: ${{ inputs['app-cli-artifact'] != '' && 'true' || 'false' }}
EDGEZERO__APP__RELEASE__ARCHIVE_PRESENT: ${{ inputs['app-release-archive'] != '' && 'true' || 'false' }}
EDGEZERO__APP__RELEASE__SHA256_PRESENT: ${{ inputs['app-release-sha256'] != '' && 'true' || 'false' }}
EDGEZERO__FASTLY__API_TOKEN_PRESENT: ${{ inputs['fastly-api-token'] != '' && 'true' || 'false' }}
EDGEZERO__DEPLOY__TO: ${{ inputs['deploy-to'] }}
EDGEZERO__CONFIG_PUSH__KEY_PRESENT: ${{ inputs.key != '' && 'true' || 'false' }}
EDGEZERO__CONFIG_PUSH__APP_CONFIG_PRESENT: ${{ inputs['app-config'] != '' && 'true' || 'false' }}
EDGEZERO__CONFIG_PUSH__APP_CONFIG_INLINE_PRESENT: ${{ inputs['app-config-inline'] != '' && 'true' || 'false' }}
EDGEZERO__CONFIG_PUSH__KEY: ${{ inputs.key }}
FASTLY_API_TOKEN: ""
FASTLY_SERVICE_ID: ""
FASTLY_TOKEN: ""
Expand All @@ -119,12 +114,18 @@ runs:
FASTLY_HOME: ""
run: exec "$GITHUB_ACTION_PATH/scripts/validate.sh"

- name: Download CLI artifact
uses: actions/download-artifact@v8
with:
name: ${{ inputs['app-cli-artifact'] }}
path: ${{ steps.ws.outputs.root }}/cli-download
- name: Verify application release
id: release
shell: bash
env:
BASH_ENV: ""
ENV: ""
EDGEZERO__APP__RELEASE__ARCHIVE: ${{ inputs['app-release-archive'] }}
EDGEZERO__APP__RELEASE__SHA256: ${{ inputs['app-release-sha256'] }}
EDGEZERO__APP__RELEASE__EXPECTED_SOURCE_REVISION: ${{ inputs['expected-source-revision'] }}
EDGEZERO__APP__RELEASE__EXPECTED_ADAPTER: fastly
EDGEZERO__APP__RELEASE__EXPECTED_LIFECYCLE_PROTOCOL: "1"
EDGEZERO__APP__RELEASE__ROOT: ${{ steps.ws.outputs.root }}/release
FASTLY_API_TOKEN: ""
FASTLY_SERVICE_ID: ""
FASTLY_TOKEN: ""
Expand All @@ -141,16 +142,16 @@ runs:
FASTLY_CONFIG_FILE: ""
FASTLY_CARGO_PROFILE: ""
FASTLY_HOME: ""
run: exec "$GITHUB_ACTION_PATH/../release-core/scripts/prepare-release.sh"

- name: Extract CLI
- name: Extract application CLI
id: cli
shell: bash
env:
BASH_ENV: ""
ENV: ""
EDGEZERO__APP__CLI__ARTIFACT_DIR: ${{ steps.ws.outputs.root }}/cli-download
EDGEZERO__APP__CLI__ARCHIVE: ${{ steps.release.outputs['app-cli-archive'] }}
EDGEZERO__ACTION__TOOL_ROOT: ${{ steps.ws.outputs.root }}/tools
EDGEZERO__APP__CLI__BIN: ${{ inputs['app-cli-bin'] }}
FASTLY_API_TOKEN: ""
FASTLY_SERVICE_ID: ""
FASTLY_TOKEN: ""
Expand Down Expand Up @@ -199,27 +200,19 @@ runs:
id: push
shell: bash
env:
# BASH_ENV/ENV are sourced at bash startup, before this script can scrub —
# blank them so a caller's job env cannot run code here with the token.
BASH_ENV: ""
ENV: ""
# Mint the sensitive lifecycle log under the per-invocation workspace so the
# Cleanup step removes it wholesale even if the in-process EXIT trap cannot fire.
EDGEZERO__ACTION__WORKSPACE: ${{ steps.ws.outputs.root }}
EDGEZERO__APP__CLI__BIN: ${{ steps.cli.outputs['app-cli-bin'] }}
EDGEZERO__APP__CLI__PATH: ${{ steps.cli.outputs['app-cli-path'] }}
EDGEZERO__PROJECT__WORKING_DIRECTORY: ${{ inputs['working-directory'] }}
EDGEZERO__DEPLOY__TO: ${{ inputs['deploy-to'] }}
EDGEZERO__CONFIG_PUSH__STORE: ${{ inputs.store }}
EDGEZERO__CONFIG_PUSH__KEY: ${{ inputs.key }}
EDGEZERO__CONFIG_PUSH__MANIFEST: ${{ inputs.manifest }}
EDGEZERO__CONFIG_PUSH__MANIFEST: ${{ steps.release.outputs['application-manifest'] }}
EDGEZERO__CONFIG_PUSH__APP_CONFIG: ${{ inputs['app-config'] }}
EDGEZERO__CONFIG_PUSH__APP_CONFIG_INLINE: ${{ inputs['app-config-inline'] }}
EDGEZERO__CONFIG_PUSH__NO_ENV: ${{ inputs['no-env'] }}
# Only the typed token reaches the CLI under the adapter's own convention
# (FASTLY_API_TOKEN, what `fastly config-store-entry update` reads); every
# other inherited FASTLY_* alias is blanked so none can redirect the push.
FASTLY_API_TOKEN: ${{ inputs['fastly-api-token'] }}
EDGEZERO__FASTLY__API_TOKEN: ${{ inputs['fastly-api-token'] }}
FASTLY_API_TOKEN: ""
FASTLY_SERVICE_ID: ""
FASTLY_TOKEN: ""
FASTLY_KEY: ""
Expand Down
Loading
Loading