Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
cd05aab
feat(cf-v2): delivery decision library (targets, smoke modes, D1 dedu…
ClaudiaFang Oct 30, 2026
a69fd62
feat(cf-v2): engine learns delivery kinds, Native Dev, smoke modes (W…
ClaudiaFang Oct 30, 2026
5030f8f
feat(cf-v2): unified target-driven delivery (Gate, Plan, matrix Migra…
ClaudiaFang Oct 30, 2026
d9847a5
fix(cf-v2): recovery rebuild carries the delivery kind and target URL
ClaudiaFang Oct 30, 2026
6675f38
fix(cf-v2): per-kind delivery calls, URL pre-resolution, D1 preflight…
ClaudiaFang Oct 30, 2026
e72ae91
test(cf-v2): test-jobs green on the per-kind topology; rollback job s…
ClaudiaFang Oct 30, 2026
c6423d0
docs(cf-v2): final DAG, public contract, limitations; tick verified o…
ClaudiaFang Oct 30, 2026
88d9777
fix(cf-v2): Preflight/Migration calls grant the permission ceiling of…
ClaudiaFang Oct 30, 2026
3842a45
docs(cf-v2): record real-runner scratch evidence, startup_failure fin…
ClaudiaFang Oct 30, 2026
55c16eb
perf(cf-preview): instrument jobrunner subprocesses; engine reads inp…
ClaudiaFang Oct 30, 2026
025d2ed
test(cf-preview): Fast/Full profiles, parallel delivery-chain groups,…
ClaudiaFang Oct 30, 2026
4d7ffa1
fix(cf-preview): test.sh creates its LOGDIR
ClaudiaFang Oct 30, 2026
df89ca3
fix(cf-preview): guard cd in test.sh (shellcheck SC2164)
ClaudiaFang Oct 30, 2026
85e34fe
refactor(cf-v2): one validated target descriptor replaces per-call ta…
ClaudiaFang Oct 30, 2026
79b6946
refactor(cf-v2): migration gates move from inline workflow bash/jq in…
ClaudiaFang Oct 30, 2026
becf013
refactor(cf-preview): one build and one publish primitive for the nor…
ClaudiaFang Oct 30, 2026
db62453
refactor(cf-preview): verification and rollback decisions move out of…
ClaudiaFang Oct 30, 2026
a9fd174
docs+test: measured performance, test profiles; per-phase perf catego…
ClaudiaFang Oct 30, 2026
e85de2a
fix(cf-v2): deferred verification keeps the deploy call's descriptors…
ClaudiaFang Oct 30, 2026
1b7ad93
perf(cf-preview): engine init is one validated Node process (init-che…
ClaudiaFang Oct 30, 2026
0fa5bb1
test(cf-preview): explicit named scenario groups replace AST source r…
ClaudiaFang Oct 30, 2026
506c101
refactor(cf-v2): the delivery kind is read only from the descriptor
ClaudiaFang Oct 30, 2026
e8bdd43
docs(cf-v2): measured results, structure table, real-runner evidence …
ClaudiaFang Oct 30, 2026
8ad9f5f
fix(cf-v2): lock-identity integrity, CLI stderr annotations, legacy D…
ClaudiaFang Oct 30, 2026
aa19cf6
docs(cf-v2): lock-identity trust boundary, CLI contract, final-SHA re…
ClaudiaFang Oct 30, 2026
0958398
refactor(cf-v2): one Database Migration job replaces four Preflight a…
ClaudiaFang Nov 3, 2026
e69365e
perf+docs(cf-v2): install tooling/deps once per job, anchors, single-…
ClaudiaFang Nov 3, 2026
010a990
docs(cf-v2): real-runner evidence for the single Migration job (e69365e)
ClaudiaFang Nov 3, 2026
efe8bc0
refactor(cf-v2): $/ self-repository references; CI, Preview lifecycle…
ClaudiaFang Nov 3, 2026
bc0b174
ci(cf-v2): actionlint ignores only the two $/ messages it cannot know…
ClaudiaFang Nov 3, 2026
3bda5fb
test(cf-v2): call-interface + actionlint-narrowness contract checks; …
ClaudiaFang Nov 3, 2026
f3325f5
docs(cf-v2): Phase 1 simplification - structure, $/ coverage, matrix …
ClaudiaFang Nov 3, 2026
8ad4d40
fix(cf-v2): a stale-skipped migration never authorizes a publish (P1)
ClaudiaFang Nov 3, 2026
fd6ab85
docs(cf-v2): record final-SHA 8ad4d40 validation, consumer compatibil…
ClaudiaFang Nov 3, 2026
f86062d
docs(cf-v2): confirm the undeclared CLOUDFLARE_PREVIEW_* secret failu…
ClaudiaFang Nov 3, 2026
a53f546
docs(cf-v2): v2 tag promotion preconditions, procedure and rollback; …
ClaudiaFang Nov 3, 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
12 changes: 10 additions & 2 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# actionlint 1.7.12 predates `concurrency.queue` (GitHub changelog 2026-05-07) and reports it as an unknown key.
# Only that one message is ignored, only for the shared Worker workflow; everything else is still linted.
# actionlint 1.7.12 predates two GitHub features this pipeline uses; ONLY these messages are ignored, everything else is still linted.
# * `concurrency.queue` (GitHub changelog 2026-05-07), reported as an unknown key; only the shared Worker workflow uses it.
# * the self-repository reference `uses: $/...` (GitHub changelog 2026-07-30, runner >= 2.336.0): actionlint still demands
# `owner/repo@ref` for actions and `owner/repo/path.yml@ref` for reusable workflows. The contract test (test-workflow-contract.py)
# validates every `$/` reference itself: static, no @ref, the target exists in this repository. Drop these entries once actionlint
# learns `$/`.
paths:
.github/workflows/_cf-worker-v2.yml:
ignore:
- 'unexpected key "queue" for "concurrency" section'
.github/workflows/_cf-worker-*.yml:
ignore:
- 'specifying action "\$/[a-z/-]+" in invalid format because ref is missing'
- 'reusable workflow call "\$/\.github/workflows/_cf-worker-[a-z-]+\.yml" at "uses" is not following the format'
263 changes: 263 additions & 0 deletions .github/workflows/_cf-worker-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
name: CF Worker CI (validation)

# ---------------------------------------------------------------------------
# INTERNAL — called ONLY by _cf-worker-v2.yml (one `uses:` per run). Not a public entrypoint. Contract: docs/cf-worker-v2-refactor.md
#
# Lint Typecheck Unit Integration Build Validation E2E six independent, PARALLEL jobs (no `needs` between them)
# |
# Validation Gate the ONE aggregation: every CONFIGURED job succeeded, every unconfigured one was skipped
#
# Bodies are actions/cf-ci-check (E2E has its own setup and own TEST build). None receives Cloudflare credentials and none uploads a
# build artifact: Build Validation builds for validation only, the delivery engine builds again for its real target.
# No workflow-level `concurrency` here (the public workflow owns the run-level group; reusing it would deadlock).
# ---------------------------------------------------------------------------

on:
workflow_call:
inputs:
app_name:
type: string
required: true
app_path:
type: string
default: .
app_version:
type: string
default: ''
runner:
type: string
default: linux
lint_cmd:
type: string
default: ''
typecheck_cmd:
type: string
default: ''
test_unit_cmd:
type: string
default: ''
test_integration_cmd:
type: string
default: ''
build_cmd:
type: string
required: true
ci_env:
type: string
default: ''
use_mailpit:
type: boolean
default: false
build_env_prod:
type: string
default: ''
build_env_preview:
type: string
default: ''
test_e2e_cmd:
type: string
default: ''
test_e2e_setup_cmd:
type: string
default: ''
test_e2e_build_cmd:
type: string
default: ''
test_e2e_variants:
type: string
default: '[{"name":"","args":"","artifact_suffix":""}]'
test_e2e_env:
type: string
default: ''
outputs:
validation:
description: passed ONLY when every configured validation job succeeded (the Validation Gate); empty otherwise
value: ${{ jobs.gate.outputs.validation }}

permissions:
contents: read

defaults:
run:
shell: bash

env:
PLAYWRIGHT_BROWSERS_PATH: /opt/playwright-browsers

jobs:
lint:
name: Lint
if: inputs.lint_cmd != ''
runs-on: ${{ inputs.runner }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: $/actions/cf-ci-check
with:
title: Lint
cmd: ${{ inputs.lint_cmd }}
app_path: ${{ inputs.app_path }}
build_env_prod: ${{ inputs.build_env_prod }}
build_env_preview: ${{ inputs.build_env_preview }}
ci_env: ${{ inputs.ci_env }}
app_version: ${{ inputs.app_version }}
typecheck:
name: Typecheck
if: inputs.typecheck_cmd != ''
runs-on: ${{ inputs.runner }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: $/actions/cf-ci-check
with:
title: Typecheck
cmd: ${{ inputs.typecheck_cmd }}
app_path: ${{ inputs.app_path }}
build_env_prod: ${{ inputs.build_env_prod }}
build_env_preview: ${{ inputs.build_env_preview }}
ci_env: ${{ inputs.ci_env }}
app_version: ${{ inputs.app_version }}
unit:
name: Unit Tests
if: inputs.test_unit_cmd != ''
runs-on: ${{ inputs.runner }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: $/actions/cf-ci-check
with:
title: Unit Tests
cmd: ${{ inputs.test_unit_cmd }}
app_path: ${{ inputs.app_path }}
build_env_prod: ${{ inputs.build_env_prod }}
build_env_preview: ${{ inputs.build_env_preview }}
ci_env: ${{ inputs.ci_env }}
app_version: ${{ inputs.app_version }}
use_mailpit: ${{ inputs.use_mailpit }}
integration:
name: Integration Tests
if: inputs.test_integration_cmd != ''
runs-on: ${{ inputs.runner }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: $/actions/cf-ci-check
with:
title: Integration Tests
cmd: ${{ inputs.test_integration_cmd }}
app_path: ${{ inputs.app_path }}
build_env_prod: ${{ inputs.build_env_prod }}
build_env_preview: ${{ inputs.build_env_preview }}
ci_env: ${{ inputs.ci_env }}
app_version: ${{ inputs.app_version }}
use_mailpit: ${{ inputs.use_mailpit }}
build:
name: Build Validation
runs-on: ${{ inputs.runner }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: $/actions/cf-ci-check
with:
title: Build Validation
cmd: ${{ inputs.build_cmd }}
app_path: ${{ inputs.app_path }}
build_env_prod: ${{ inputs.build_env_prod }}
build_env_preview: ${{ inputs.build_env_preview }}
ci_env: ${{ inputs.ci_env }}
app_version: ${{ inputs.app_version }}

# E2E — own setup and own TEST build (local URLs, mock services); never reused for a deployment.
e2e:
name: E2E
if: inputs.test_e2e_cmd != ''
strategy:
fail-fast: false
matrix:
e2e: ${{ fromJSON(inputs.test_e2e_variants) }}
runs-on: ${{ inputs.runner }}
timeout-minutes: 45
env:
E2E_ENV: ${{ inputs.test_e2e_env }}
steps:
- uses: actions/checkout@v6
- uses: $/actions/cf-tools
- name: Import E2E env
run: node "$TOOLS_DIR/parse-env.mjs" E2E_ENV "$GITHUB_ENV"
- uses: $/actions/setup
with:
setup-node: 'false'
setup-pnpm: 'false'
- name: Start Mailpit
if: inputs.use_mailpit
uses: $/actions/mailpit
- name: E2E setup
if: inputs.test_e2e_setup_cmd != ''
working-directory: ${{ inputs.app_path }}
env:
CMD: ${{ inputs.test_e2e_setup_cmd }}
run: bash -c "$CMD"
- name: E2E test build
if: inputs.test_e2e_build_cmd != ''
working-directory: ${{ inputs.app_path }}
env:
CMD: ${{ inputs.test_e2e_build_cmd }}
run: bash -c "$CMD"
- name: E2E tests
working-directory: ${{ inputs.app_path }}
env:
CMD: ${{ inputs.test_e2e_cmd }} ${{ matrix.e2e.args }}
run: bash -c "$CMD"
# Failure-only evidence with short retention; never a delivery dependency.
- name: Upload E2E report (failure only)
uses: actions/upload-artifact@v7
if: failure()
continue-on-error: true
with:
name: ${{ matrix.e2e.artifact_suffix != '' && format('e2e-report-{0}-{1}-{2}', inputs.app_name, matrix.e2e.artifact_suffix, github.run_id) || format('e2e-report-{0}-{1}', inputs.app_name, github.run_id) }}
path: |
${{ inputs.app_path }}/playwright-report/
${{ inputs.app_path }}/test-results/
if-no-files-found: ignore
retention-days: 3

# Validation Gate — fail closed. A configured job must be `success`; an unconfigured one must be `skipped`; anything else (failure,
# cancelled, an unexpected skip) fails the gate. `always()` makes the gate run (and fail) even when a job it needs failed, so the
# called workflow's conclusion is never a skipped-looking green.
gate:
name: Validation Gate
needs: [lint, typecheck, unit, integration, build, e2e]
if: always()
runs-on: ${{ inputs.runner }}
timeout-minutes: 5
outputs:
validation: ${{ steps.gate.outputs.validation }}
steps:
- id: gate
name: Every configured validation job succeeded
env:
LINT: ${{ needs.lint.result }}
LINT_CONFIGURED: ${{ inputs.lint_cmd != '' }}
TYPECHECK: ${{ needs.typecheck.result }}
TYPECHECK_CONFIGURED: ${{ inputs.typecheck_cmd != '' }}
UNIT: ${{ needs.unit.result }}
UNIT_CONFIGURED: ${{ inputs.test_unit_cmd != '' }}
INTEGRATION: ${{ needs.integration.result }}
INTEGRATION_CONFIGURED: ${{ inputs.test_integration_cmd != '' }}
BUILD: ${{ needs.build.result }}
BUILD_CONFIGURED: 'true'
E2E: ${{ needs.e2e.result }}
E2E_CONFIGURED: ${{ inputs.test_e2e_cmd != '' }}
run: |
bad=0
for j in LINT TYPECHECK UNIT INTEGRATION BUILD E2E; do
r="${j}"; c="${j}_CONFIGURED"
if [ "${!c}" = true ]; then
[ "${!r}" = success ] || { echo "::error::$j finished '${!r}' (configured: must succeed)"; bad=1; }
else
[ "${!r}" = skipped ] || { echo "::error::$j is not configured but finished '${!r}'"; bad=1; }
fi
done
[ "$bad" = 0 ] || exit 1
echo "validation=passed" >> "$GITHUB_OUTPUT"
echo "Validation Gate passed."
Loading
Loading