Compare commits

...

5 Commits

Author SHA1 Message Date
Barcode Betty 04529666fc fix(ci): deploy jobs compute sha tag from $GITHUB_SHA (CAR-1316, CAR-1195)
CI / audit (pull_request) Successful in 10s
CI / lint (pull_request) Successful in 14s
CI / test (pull_request) Successful in 15s
CI / build-and-push-receiptwitness (pull_request) Has been skipped
CI / build-and-push-api (pull_request) Has been skipped
CI / build-and-push-auth (pull_request) Has been skipped
CI / e2e (pull_request) Successful in 40s
CI / build-and-push (pull_request) Has been skipped
CI / deploy-dev (pull_request) Has been skipped
CI / deploy-uat (pull_request) Has been skipped
CI / lighthouse (pull_request) Failing after 1m14s
The four `build-and-push*` jobs declared a job-level output
`sha_tag: sha-${{ github.sha }}` (literal prefix concatenated with
an expression). Gitea Actions does NOT substitute ${{ github.sha }}
inside that concatenated value, so the literal string
`sha-${{ github.sha }}` propagated into needs.<job>.outputs.sha_tag.

Each deploy job's 'Determine image tag' step then expanded
`echo "tag=${{ needs.<job>.outputs.sha_tag }}" >> "$GITHUB_OUTPUT"`
into `echo "tag=sha-${{ github.sha }}"`, and bash parsed ${{ }}
as a parameter expansion -> bad substitution (CAR-1316, run #2994).

Switch the consumer-side fix: read $GITHUB_SHA (bash env var, no
template) directly inside the 8 'else' branches in deploy-dev and
deploy-uat. Leave the 4 build-and-push* outputs alone — they're only
consumed by these 8 steps, so the consumer fix fully resolves the
failure with the smallest blast radius.

Refs: CAR-1316, CAR-1195, CAR-1194.
2026-06-07 11:28:41 +00:00
Savannah Savings 515631987b Merge pull request 'ci(deploy): never hard-fail on infra-PR merge outcome (CAR-1216)' (#284) from betty/car-1216-deploy-never-fail-merge into dev
CI / audit (push) Successful in 11s
CI / lint (push) Successful in 18s
CI / audit (pull_request) Successful in 9s
CI / test (pull_request) Successful in 21s
CI / build-and-push-api (push) Failing after 1m2s
CI / test (push) Successful in 45s
CI / lint (pull_request) Successful in 11s
CI / e2e (push) Successful in 40s
CI / e2e (pull_request) Successful in 40s
CI / lighthouse (push) Failing after 1m13s
CI / build-and-push-auth (push) Successful in 30s
CI / build-and-push-receiptwitness (pull_request) Has been skipped
CI / build-and-push-api (pull_request) Has been skipped
CI / build-and-push-auth (pull_request) Has been skipped
CI / build-and-push (pull_request) Has been skipped
CI / deploy-dev (pull_request) Has been skipped
CI / deploy-uat (pull_request) Has been skipped
CI / lighthouse (pull_request) Failing after 1m14s
CI / build-and-push (push) Successful in 2m59s
CI / build-and-push-receiptwitness (push) Successful in 4m35s
CI / deploy-uat (push) Has been skipped
CI / deploy-dev (push) Successful in 15s
2026-06-07 10:20:28 +00:00
Savannah Savings 993302c72c fix(auth): pin base image to node 22.22.2 digest (CAR-1279 Phase 2) (#287)
CI / audit (push) Successful in 10s
CI / test (push) Successful in 14s
CI / e2e (push) Successful in 42s
CI / lighthouse (push) Failing after 1m16s
CI / test (pull_request) Successful in 12s
CI / lint (push) Successful in 14s
CI / lint (pull_request) Successful in 11s
CI / e2e (pull_request) Successful in 47s
CI / audit (pull_request) Successful in 10s
CI / build-and-push-auth (push) Successful in 2m20s
CI / build-and-push-api (push) Successful in 3m12s
CI / build-and-push-receiptwitness (pull_request) Has been skipped
CI / build-and-push-api (pull_request) Has been skipped
CI / build-and-push-auth (pull_request) Has been skipped
CI / build-and-push (pull_request) Has been skipped
CI / lighthouse (pull_request) Failing after 1m18s
CI / build-and-push-receiptwitness (push) Successful in 5m16s
CI / build-and-push (push) Successful in 2m21s
CI / deploy-dev (pull_request) Has been skipped
CI / deploy-uat (pull_request) Has been skipped
CI / deploy-dev (push) Failing after 14s
CI / deploy-uat (push) Has been skipped
2026-06-06 06:22:35 +00:00
Savannah Savings 7803d229eb fix(auth): pin base image to node 22.22.2 digest (CAR-1279 Phase 2)
CI / lint (pull_request) Successful in 40s
CI / test (pull_request) Successful in 1m15s
CI / build-and-push-receiptwitness (pull_request) Has been skipped
CI / build-and-push-api (pull_request) Has been skipped
CI / build-and-push-auth (pull_request) Has been skipped
CI / lighthouse (pull_request) Failing after 1m12s
CI / audit (pull_request) Successful in 2m47s
CI / e2e (pull_request) Successful in 3m18s
CI / build-and-push (pull_request) Has been skipped
CI / deploy-dev (pull_request) Has been skipped
CI / deploy-uat (pull_request) Has been skipped
Pin both build and runtime stages of auth/Dockerfile to
node:22-alpine@sha256:8ea2348b068a9544dae7317b4f3aafcdc032df1647bb7d768a05a5cad1a7683f
— the Docker Hub manifest digest for node:22.22.2-alpine (verified against
the registry by CTO).

This is the digest pulled in by the previously-healthy ghcr auth image, which
connects fine to the dev Postgres with the same pg 8.20.0 driver and
byte-identical source. The Gitea-built image, which bundles node 22.22.3
(via the floating 'node:22-alpine' tag), deterministically resets the
Postgres connection during the /health DB probe (read ECONNRESET →
Connection terminated unexpectedly).

Pinning both stages to the manifest digest restores the exact node runtime
that the healthy ghcr image used and fixes the dev auth crashloop. The
'RUN apk update && apk upgrade --no-cache' lines are kept as-is per task
spec.

Refs CAR-1279, CAR-1276 (CAR-1287)
2026-06-06 02:26:54 +00:00
Barcode Betty 81b19b9072 ci(deploy): never hard-fail on infra-PR merge outcome (CAR-1216)
CI / lint (pull_request) Successful in 12s
CI / test (pull_request) Successful in 12s
CI / build-and-push-receiptwitness (pull_request) Has been skipped
CI / build-and-push-api (pull_request) Has been skipped
CI / build-and-push-auth (pull_request) Has been skipped
CI / audit (pull_request) Successful in 18s
CI / e2e (pull_request) Successful in 43s
CI / build-and-push (pull_request) Has been skipped
CI / deploy-dev (pull_request) Has been skipped
CI / deploy-uat (pull_request) Has been skipped
CI / lighthouse (pull_request) Failing after 17m15s
The in-job merge attempt against `cartsnitch/infra` main is a best-effort
fast-path only. `infra` main requires a human approving review and the CI
bot (`CI_GITEA_TOKEN`) can never self-approve, so the merge call
structurally cannot succeed in the general case.

Replace the special-cased `does not have enough approvals` branch and the
final `else -> exit 1` branch in both `deploy-dev` and `deploy-uat` with a
single non-failing outcome: surface the Gitea response as a `::notice::`
and `exit 0`. The PR is already opened and `cs_savannah` is requested as
reviewer above, so the GitOps hand-off is intact.

The only hard-fail (`exit 1`) in this step remains the empty-`PR_NUM`
check (PR could not be created at all).

Related: CAR-1195 (PR-bump pattern), CAR-1194, CAR-1212.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-05 23:57:11 +00:00
2 changed files with 34 additions and 30 deletions
+32 -28
View File
@@ -488,7 +488,7 @@ jobs:
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "tag=${{ needs.build-and-push.outputs.calver_tag }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ needs.build-and-push.outputs.sha_tag }}" >> "$GITHUB_OUTPUT"
echo "tag=sha-${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
fi
- name: Update frontend image tag
@@ -503,7 +503,7 @@ jobs:
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "tag=${{ needs.build-and-push-receiptwitness.outputs.calver_tag }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ needs.build-and-push-receiptwitness.outputs.sha_tag }}" >> "$GITHUB_OUTPUT"
echo "tag=sha-${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
fi
- name: Update receiptwitness image tag
@@ -518,7 +518,7 @@ jobs:
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "tag=${{ needs.build-and-push-api.outputs.calver_tag }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ needs.build-and-push-api.outputs.sha_tag }}" >> "$GITHUB_OUTPUT"
echo "tag=sha-${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
fi
- name: Update api image tag
@@ -533,7 +533,7 @@ jobs:
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "tag=${{ needs.build-and-push-auth.outputs.calver_tag }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ needs.build-and-push-auth.outputs.sha_tag }}" >> "$GITHUB_OUTPUT"
echo "tag=sha-${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
fi
- name: Update auth image tag
@@ -577,6 +577,16 @@ jobs:
if [ "${REVIEW_HTTP}" -lt 200 ] || [ "${REVIEW_HTTP}" -ge 300 ]; then
echo "::notice::Failed to request reviewers for cartsnitch/infra PR #${PR_NUM} (HTTP ${REVIEW_HTTP}); continuing"
fi
# CAR-1216: the in-job merge attempt is a best-effort fast-path only.
# `cartsnitch/infra` main requires a human approving review (immutable
# branch protection); the CI bot (`CI_GITEA_TOKEN`) can never self-
# approve, so this merge call structurally cannot succeed in the
# general case. Any non-merged outcome (approvals pending, checks
# pending, any other Gitea message) is the GitOps approval gate, not
# a CI failure — the PR is already opened and `cs_savannah` is
# requested as reviewer above. Surface the response as a notice and
# exit success. The only hard-fail (`exit 1`) in this step remains
# the empty-`PR_NUM` check (PR could not be created at all).
MERGE_RESP=$(curl -sS -X POST \
-H "Authorization: token ${CI_GITEA_TOKEN}" \
-H "Content-Type: application/json" \
@@ -585,17 +595,9 @@ jobs:
MERGED=$(echo "$MERGE_RESP" | jq -r '.merged // false')
if [ "$MERGED" = "true" ]; then
echo "PR #${PR_NUM} merged into cartsnitch/infra main"
elif echo "$MERGE_RESP" | grep -qi 'does not have enough approvals'; then
# GitOps approval gate: the PR is correctly opened and surfaces in
# the CTO queue via the reviewers request above. Treat as success
# (exit 0) so the deploy job does not hard-fail on the approvals
# requirement that only a human maintainer can satisfy.
echo "::notice::infra PR #${PR_NUM} opened and awaiting CTO (cs_savannah) approve+merge — GitOps approval gate, not a failure"
exit 0
else
echo "::error::Auto-merge of cartsnitch/infra PR #${PR_NUM} failed: $MERGE_RESP"
echo "::error::Reassign to cs_savannah (authorized merger for cartsnitch/infra main) for backstop merge."
exit 1
echo "::notice::infra PR #${PR_NUM} opened and awaiting CTO (cs_savannah) approve+merge — GitOps approval gate, not a failure: $MERGE_RESP"
exit 0
fi
deploy-uat:
@@ -632,7 +634,7 @@ jobs:
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "tag=${{ needs.build-and-push.outputs.calver_tag }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ needs.build-and-push.outputs.sha_tag }}" >> "$GITHUB_OUTPUT"
echo "tag=sha-${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
fi
- name: Update frontend image tag
@@ -647,7 +649,7 @@ jobs:
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "tag=${{ needs.build-and-push-receiptwitness.outputs.calver_tag }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ needs.build-and-push-receiptwitness.outputs.sha_tag }}" >> "$GITHUB_OUTPUT"
echo "tag=sha-${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
fi
- name: Update receiptwitness image tag
@@ -662,7 +664,7 @@ jobs:
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "tag=${{ needs.build-and-push-api.outputs.calver_tag }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ needs.build-and-push-api.outputs.sha_tag }}" >> "$GITHUB_OUTPUT"
echo "tag=sha-${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
fi
- name: Update api image tag
@@ -677,7 +679,7 @@ jobs:
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "tag=${{ needs.build-and-push-auth.outputs.calver_tag }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ needs.build-and-push-auth.outputs.sha_tag }}" >> "$GITHUB_OUTPUT"
echo "tag=sha-${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
fi
- name: Update auth image tag
@@ -721,6 +723,16 @@ jobs:
if [ "${REVIEW_HTTP}" -lt 200 ] || [ "${REVIEW_HTTP}" -ge 300 ]; then
echo "::notice::Failed to request reviewers for cartsnitch/infra PR #${PR_NUM} (HTTP ${REVIEW_HTTP}); continuing"
fi
# CAR-1216: the in-job merge attempt is a best-effort fast-path only.
# `cartsnitch/infra` main requires a human approving review (immutable
# branch protection); the CI bot (`CI_GITEA_TOKEN`) can never self-
# approve, so this merge call structurally cannot succeed in the
# general case. Any non-merged outcome (approvals pending, checks
# pending, any other Gitea message) is the GitOps approval gate, not
# a CI failure — the PR is already opened and `cs_savannah` is
# requested as reviewer above. Surface the response as a notice and
# exit success. The only hard-fail (`exit 1`) in this step remains
# the empty-`PR_NUM` check (PR could not be created at all).
MERGE_RESP=$(curl -sS -X POST \
-H "Authorization: token ${CI_GITEA_TOKEN}" \
-H "Content-Type: application/json" \
@@ -729,15 +741,7 @@ jobs:
MERGED=$(echo "$MERGE_RESP" | jq -r '.merged // false')
if [ "$MERGED" = "true" ]; then
echo "PR #${PR_NUM} merged into cartsnitch/infra main"
elif echo "$MERGE_RESP" | grep -qi 'does not have enough approvals'; then
# GitOps approval gate: the PR is correctly opened and surfaces in
# the CTO queue via the reviewers request above. Treat as success
# (exit 0) so the deploy job does not hard-fail on the approvals
# requirement that only a human maintainer can satisfy.
echo "::notice::infra PR #${PR_NUM} opened and awaiting CTO (cs_savannah) approve+merge — GitOps approval gate, not a failure"
exit 0
else
echo "::error::Auto-merge of cartsnitch/infra PR #${PR_NUM} failed: $MERGE_RESP"
echo "::error::Reassign to cs_savannah (authorized merger for cartsnitch/infra main) for backstop merge."
exit 1
echo "::notice::infra PR #${PR_NUM} opened and awaiting CTO (cs_savannah) approve+merge — GitOps approval gate, not a failure: $MERGE_RESP"
exit 0
fi
+2 -2
View File
@@ -1,4 +1,4 @@
FROM node:22-alpine AS builder
FROM node:22-alpine@sha256:8ea2348b068a9544dae7317b4f3aafcdc032df1647bb7d768a05a5cad1a7683f AS builder
RUN apk update && apk upgrade --no-cache
WORKDIR /app
COPY package.json package-lock.json* ./
@@ -7,7 +7,7 @@ COPY tsconfig.json ./
COPY src/ src/
RUN npm run build
FROM node:22-alpine
FROM node:22-alpine@sha256:8ea2348b068a9544dae7317b4f3aafcdc032df1647bb7d768a05a5cad1a7683f
RUN apk update && apk upgrade --no-cache
WORKDIR /app
ENV NODE_ENV=production