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
4 changes: 2 additions & 2 deletions .github/workflows/cloudflare-pages-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ jobs:
ref: ${{ github.event.workflow_run.head_sha || github.ref }}

- name: Setup pnpm + Node (+ install)
uses: hseshadr/ci/.github/actions/setup-pnpm@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
uses: hseshadr/ci/.github/actions/setup-pnpm@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
package-json-file: ${{ inputs.package-json-file }}
node-version: ${{ inputs.node-version }}
Expand All @@ -213,7 +213,7 @@ jobs:
run: bash --noprofile --norc -euo pipefail -c "$TRUSTED_COMMAND"

- name: Deploy to Cloudflare Pages
uses: hseshadr/ci/.github/actions/pages-deploy-dist@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
uses: hseshadr/ci/.github/actions/pages-deploy-dist@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
project-name: ${{ inputs.project-name }}
dist-dir: ${{ inputs.dist-dir }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/frontend-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ jobs:
persist-credentials: false

- name: Setup pnpm + Node (+ install)
uses: hseshadr/ci/.github/actions/setup-pnpm@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
uses: hseshadr/ci/.github/actions/setup-pnpm@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
package-json-file: ${{ inputs.package-json-file }}
node-version: ${{ inputs.node-version }}
Expand All @@ -99,7 +99,7 @@ jobs:

- name: Setup Playwright (cached)
if: ${{ inputs.install-playwright }}
uses: hseshadr/ci/.github/actions/setup-playwright@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
uses: hseshadr/ci/.github/actions/setup-playwright@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
browsers: ${{ inputs.playwright-browsers }}
working-directory: ${{ inputs.playwright-working-directory || inputs.working-directory }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/python-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ jobs:
persist-credentials: false

- name: Setup Python + uv (+ sync)
uses: hseshadr/ci/.github/actions/setup-python-uv@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
uses: hseshadr/ci/.github/actions/setup-python-uv@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
python-version: ${{ inputs.python-version }}
sync-args: ${{ inputs.sync-args }}
Expand Down
32 changes: 26 additions & 6 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ jobs:
persist-credentials: false

- name: Setup Python + uv (+ sync)
uses: hseshadr/ci/.github/actions/setup-python-uv@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
uses: hseshadr/ci/.github/actions/setup-python-uv@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
python-version: ${{ inputs.python-version }}
sync-args: ${{ inputs.sync-args }}
Expand Down Expand Up @@ -189,15 +189,35 @@ jobs:
echo "Verifying ${name}==${version} is served by PyPI..."

# Registries have propagation delay, so retry — but a timeout is a FAILURE,
# never a pass. Bounded: 6 attempts, 10s apart (~60s).
for attempt in 1 2 3 4 5 6; do
# never a pass.
#
# The old bound was 6 attempts 10s apart (~60s) and it was too tight: it
# failed publishes that had genuinely succeeded. A red run on a live release
# is not harmless — it teaches the reader to wave off red publish runs, which
# is exactly how the six-green-while-404 defect comes back. Measured
# 2026-08-03, time from a green upload until the registry served the version:
# PyPI edgeproc-core 0.4.0 >60s (failed this check while genuinely live)
# PyPI edge-proc 0.3.0 ~120s
# npm @edgeproc/errors 0.1.0 ~200s (first publish of a NEW package name)
# npm @edgeproc/avow 0.3.0 ~60s
# The first publish of a new name is the slowest case AND the case a real
# trusted-publisher misconfiguration is indistinguishable from, so the budget
# must clear propagation by a wide margin before it is allowed to accuse.
# Bound: 600s of sleep, 3x the slowest measured case. Backoff (5, 10, 15, 30,
# then 60s) so the common case still verifies in ~15s instead of paying it.
delays=(5 10 15 30 60 60 60 60 60 60 60 60 60) # 13 sleeps, exactly 600s
attempts=$((${#delays[@]} + 1))

for ((i = 0; i < attempts; i++)); do
if curl -fsS --max-time 15 -o /dev/null "$url"; then
echo "Verified: ${name}==${version} is live on PyPI."
exit 0
fi
echo "Attempt ${attempt}/6: not served yet; retrying in 10s..."
sleep 10
if ((i < ${#delays[@]})); then
echo "Attempt $((i + 1))/${attempts}: not served yet; retrying in ${delays[i]}s..."
sleep "${delays[i]}"
fi
done

echo "::error::PUBLISH VERIFICATION FAILED — the upload step reported success, but PyPI does not serve ${name}==${version} (${url}) after ~60s. A green upload is not proof of a release. Most likely cause: an incomplete PyPI trusted-publisher bootstrapthe OIDC exchange can look fine while no project is actually registered for this repo+workflow. Check the project's Publishing settings, or the account-level pending publisher, on PyPI."
echo "::error::PUBLISH VERIFICATION FAILED — the upload step reported success, but PyPI does not serve ${name}==${version} (${url}) after ~10 minutes of retrying. A green upload is not proof of a release. Two very different causes land here, so establish which one before touching this run: (1) STILL PROPAGATING — open ${url} yourself; the slowest propagation measured for this portfolio was ~200s, so 10 minutes should be ample, but if it loads now then the release DID happen and this is a false alarm worth reporting. (2) THE RELEASE NEVER HAPPENED — an incomplete PyPI trusted-publisher bootstrap, or a job_workflow_ref/publisher mismatch; the OIDC exchange can look fine while no project is actually registered for this repo+workflow. Check the project's Publishing settings, or the account-level pending publisher, on PyPI. Do not re-run this job merely to make it green."
exit 1
4 changes: 2 additions & 2 deletions .github/workflows/security-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ jobs:

# run-sync: false — we `uv export` the lock, not build a venv.
- name: Setup Python + uv
uses: hseshadr/ci/.github/actions/setup-python-uv@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
uses: hseshadr/ci/.github/actions/setup-python-uv@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
working-directory: ${{ inputs.python-working-directory }}
run-sync: "false"
Expand Down Expand Up @@ -163,7 +163,7 @@ jobs:
# install: false — `pnpm audit` reads the lockfile + queries the advisory DB
# directly; no node_modules needed.
- name: Setup pnpm + Node
uses: hseshadr/ci/.github/actions/setup-pnpm@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
uses: hseshadr/ci/.github/actions/setup-pnpm@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
package-json-file: ${{ inputs.frontend-working-directory }}/package.json
node-version: ${{ inputs.node-version }}
Expand Down
31 changes: 25 additions & 6 deletions .github/workflows/ts-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ jobs:
registry-url: ${{ inputs.registry-url }}

- name: Setup pnpm + Node (+ install)
uses: hseshadr/ci/.github/actions/setup-pnpm@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
uses: hseshadr/ci/.github/actions/setup-pnpm@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
package-json-file: ${{ inputs.package-json-file }}
node-version: ${{ inputs.node-version }}
Expand Down Expand Up @@ -201,15 +201,34 @@ jobs:
echo "Verifying ${name}@${version} is served by ${NPM_REGISTRY_URL}..."

# Retry for propagation delay, but a timeout is a FAILURE, never a pass.
# Bounded: 6 attempts, 10s apart (~60s).
for attempt in 1 2 3 4 5 6; do
#
# The old bound was 6 attempts 10s apart (~60s) and it was too tight: it
# failed publishes that had genuinely succeeded. A red run on a live release
# is not harmless — it teaches the reader to wave off red publish runs, which
# is exactly how the six-green-while-404 defect comes back. Measured
# 2026-08-03, time from a green publish until the registry served the version:
# PyPI edgeproc-core 0.4.0 >60s (failed this check while genuinely live)
# PyPI edge-proc 0.3.0 ~120s
# npm @edgeproc/errors 0.1.0 ~200s (first publish of a NEW package name)
# npm @edgeproc/avow 0.3.0 ~60s
# The first publish of a new name is the slowest case AND the case a real
# trusted-publisher misconfiguration is indistinguishable from, so the budget
# must clear propagation by a wide margin before it is allowed to accuse.
# Bound: 600s of sleep, 3x the slowest measured case. Backoff (5, 10, 15, 30,
# then 60s) so the common case still verifies in ~15s instead of paying it.
delays=(5 10 15 30 60 60 60 60 60 60 60 60 60) # 13 sleeps, exactly 600s
attempts=$((${#delays[@]} + 1))

for ((i = 0; i < attempts; i++)); do
if npm view "${name}@${version}" version --registry "$NPM_REGISTRY_URL" >/dev/null 2>&1; then
echo "Verified: ${name}@${version} is live on ${NPM_REGISTRY_URL}."
exit 0
fi
echo "Attempt ${attempt}/6: not served yet; retrying in 10s..."
sleep 10
if ((i < ${#delays[@]})); then
echo "Attempt $((i + 1))/${attempts}: not served yet; retrying in ${delays[i]}s..."
sleep "${delays[i]}"
fi
done

echo "::error::PUBLISH VERIFICATION FAILED — the publish step reported success, but ${NPM_REGISTRY_URL} does not serve ${name}@${version} after ~60s. A green publish is not proof of a release. Most likely cause: an incomplete npm trusted-publisher bootstrapnpm has no 'pending publisher', so the package must already exist before a trusted publisher can be attached, and the first publish of a brand-new name must be token/OTP-based. Confirm the trusted publisher is registered for this repo + workflow filename."
echo "::error::PUBLISH VERIFICATION FAILED — the publish step reported success, but ${NPM_REGISTRY_URL} does not serve ${name}@${version} after ~10 minutes of retrying. A green publish is not proof of a release. Two very different causes land here, so establish which one before touching this run: (1) STILL PROPAGATING — run 'npm view ${name}@${version} version' yourself; the slowest propagation measured for this portfolio was ~200s, on the first publish of a brand-new package name, so 10 minutes should be ample — but if it resolves now then the release DID happen and this is a false alarm worth reporting. (2) THE RELEASE NEVER HAPPENED — an incomplete npm trusted-publisher bootstrap; npm has no 'pending publisher', so the package must already exist before a trusted publisher can be attached, and the first publish of a brand-new name must be token/OTP-based. Confirm the trusted publisher is registered for this repo + workflow filename. Do not re-run this job merely to make it green."
exit 1
27 changes: 23 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,29 @@ included.

## Unreleased (on `main`, after ci-v3.0.0)

**No brick changed shape** — every entry here is a guard, a test, or a fix to the
copy-paste surface in `examples/`. Nothing below requires a re-pin; the `examples/`
changes require a re-copy.

**No brick changed shape** — no input, output or permission moved, so no caller needs
editing. One entry below changes brick *behaviour*: the publish-verification retry bound
in `python-publish.yml` and `ts-publish.yml`. That one needs a re-pin to reach a consumer.
Everything else is a guard, a test, or a fix to the copy-paste surface in `examples/`,
which needs a re-copy.

- **The publish-verification bound was too tight, and it failed a release that had
genuinely succeeded.** The check itself is right and stays: ask the registry whether the
version is served, never trust the uploader, and treat a timeout as a FAILURE. Its bound
was wrong — 6 attempts 10s apart (~60s) against measured propagation of ~120s
(PyPI `edge-proc` 0.3.0) and ~200s (npm `@edgeproc/errors` 0.1.0, the first publish of a
brand-new name). `edgeproc-core` 0.4.0 went live on PyPI and
[its publish run went red anyway](https://github.com/hseshadr/edgeproc-core/actions/runs/30842985605).
That false negative is not harmless: a red run on a live release teaches the reader to
wave off red publish runs, which is exactly how the six-green-while-404 defect returns.
New bound: 14 attempts with backoff (5, 10, 15, 30, then 60s) — 600s of sleep, 3x the
slowest case measured, while the common case still verifies in ~15s. The failure message
now separates "STILL PROPAGATING" from "THE RELEASE NEVER HAPPENED"; it previously listed
only the misconfiguration causes, which is misleading now that a timeout is more often
propagation. The guard keeps its teeth: run the step against a version PyPI/npm does not
serve and it still exits **1** after the full budget. The three `examples/*/publish.yml`
inline copies carry the same bound, so the surface consumers copy does not ship the
defect.
- **The drift detector caught its first new control, and the cause was partly this repo.**
On 2026-08-02 the scheduled sweep went red: `30 … 29 allowlisted; 1 new`
([run 30739082151](https://github.com/hseshadr/ci/actions/runs/30739082151)); the day
Expand Down
2 changes: 1 addition & 1 deletion examples/almamesh/security-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ permissions:
contents: read
jobs:
audit:
uses: hseshadr/ci/.github/workflows/security-audit.yml@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
uses: hseshadr/ci/.github/workflows/security-audit.yml@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
run-python-audit: true
python-working-directory: backend
Expand Down
8 changes: 4 additions & 4 deletions examples/aml-filter/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
permissions:
contents: read
pull-requests: read
uses: hseshadr/ci/.github/workflows/secret-scan.yml@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
uses: hseshadr/ci/.github/workflows/secret-scan.yml@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0

frontend:
runs-on: ubuntu-latest
Expand All @@ -42,7 +42,7 @@ jobs:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: hseshadr/ci/.github/actions/setup-pnpm@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
- uses: hseshadr/ci/.github/actions/setup-pnpm@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
package-json-file: frontend/package.json
node-version-file: frontend/.nvmrc
Expand All @@ -51,7 +51,7 @@ jobs:
# Weights come from a GitHub RELEASE, not a package registry, so the fetch
# needs GH_TOKEN. Job-level `env:` is what reaches a composite's steps —
# the composite deliberately takes no secret input.
- uses: hseshadr/ci/.github/actions/restore-model-cache@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
- uses: hseshadr/ci/.github/actions/restore-model-cache@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
env:
GH_TOKEN: ${{ github.token }}
with:
Expand All @@ -67,7 +67,7 @@ jobs:
gh release download model-weights-v1 --repo "$GITHUB_REPOSITORY" --dir "$dir/onnx" --clobber \
--pattern model_quantized.onnx
node scripts/download-model.mjs
- uses: hseshadr/ci/.github/actions/setup-playwright@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
- uses: hseshadr/ci/.github/actions/setup-playwright@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
working-directory: frontend
cache-key: playwright-${{ runner.os }}-${{ hashFiles('frontend/pnpm-lock.yaml') }}
Expand Down
4 changes: 2 additions & 2 deletions examples/aml-filter/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: hseshadr/ci/.github/actions/setup-pnpm@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
- uses: hseshadr/ci/.github/actions/setup-pnpm@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
package-json-file: frontend/package.json
node-version-file: frontend/.nvmrc
Expand Down Expand Up @@ -105,7 +105,7 @@ jobs:
fi

# --- shared deploy half: identical wrangler step used by every Pages repo ---
- uses: hseshadr/ci/.github/actions/pages-deploy-dist@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
- uses: hseshadr/ci/.github/actions/pages-deploy-dist@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
project-name: aml-filter
dist-dir: frontend/app/dist
Expand Down
4 changes: 2 additions & 2 deletions examples/aml-filter/security-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ permissions:
contents: read
jobs:
audit:
uses: hseshadr/ci/.github/workflows/security-audit.yml@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
uses: hseshadr/ci/.github/workflows/security-audit.yml@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
with:
run-pnpm-audit: true
frontend-working-directory: frontend
pnpm-audit-level: low
gitleaks:
uses: hseshadr/ci/.github/workflows/secret-scan.yml@33c5e5fa421210e6cc91ea30cad708bce29a2407 # ci-v3.1.0
uses: hseshadr/ci/.github/workflows/secret-scan.yml@7226072bd02e7aecc5b065b3eaf0bfbf4b3e1790 # ci-v3.2.0
Loading
Loading