Compare commits

...

15 Commits

Author SHA1 Message Date
Barcode Betty f504807467 fix(cartsnitch): deploy-dev/deploy-uat PR base = dev/uat not main (CAR-1370)
CI / test (pull_request) Successful in 14s
CI / lint (pull_request) Successful in 15s
CI / audit (pull_request) Successful in 15s
CI / e2e (pull_request) Successful in 45s
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) Successful in 1m1s
Deploy jobs in ci.yml were opening image-tag-bump PRs against cartsnitch/infra: main
regardless of which branch triggered the deploy. The deploy-dev job should target
dev, deploy-uat should target uat.

Two-line swap in .gitea/workflows/ci.yml:
- Line 582 (deploy-dev): --arg base main -> --arg base dev
- Line 728 (deploy-uat): --arg base main -> --arg base uat

Verified by inspecting both curl payloads; no other --arg base occurrences.

CAR-1370 / CAR-1371
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-10 20:40:48 +00:00
Barcode Betty 3aa6459bed Merge pull request 'fix(api): widen alembic_version.version_num in migration 001 (CAR-1302)' (#289) from barcode-betty/car-1303-widen-alembic-via-migration into dev
CI / lint (push) Successful in 14s
CI / build-and-push-receiptwitness (push) Successful in 3m8s
CI / e2e (push) Successful in 45s
CI / audit (push) Successful in 12s
CI / test (push) Successful in 13s
CI / lighthouse (push) Successful in 58s
CI / build-and-push-api (push) Successful in 1m45s
CI / build-and-push-auth (push) Successful in 1m55s
CI / lint (pull_request) Successful in 16s
CI / test (pull_request) Successful in 43s
CI / build-and-push (push) Successful in 1m57s
CI / e2e (pull_request) Successful in 1m0s
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) Successful in 1m15s
CI / deploy-dev (pull_request) Has been skipped
CI / deploy-uat (pull_request) Has been skipped
CI / audit (pull_request) Successful in 15s
CI / deploy-dev (push) Successful in 14s
CI / deploy-uat (push) Has been skipped
fix(api): widen alembic_version.version_num in migration 001 (CAR-1302)

Rebased onto current dev head ad18a43b5 per CAR-1365. Drops 71e3b81 (already in dev via #281). Resolves ci.yml conflict by keeping dev's CAR-1316/1318 fixed version. Self-merge per SDLC Phase 1 (CI green on run #3470).
2026-06-10 04:53:34 +00:00
Barcode Betty 446cf6642b fix(ci): bind vite preview to 127.0.0.1, not localhost (CAR-1218)
CI / lint (pull_request) Successful in 15s
CI / test (pull_request) Successful in 13s
CI / audit (pull_request) Successful in 12s
CI / e2e (pull_request) Successful in 44s
CI / build-and-push-receiptwitness (pull_request) Has been skipped
CI / build-and-push-auth (pull_request) Has been skipped
CI / build-and-push-api (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) Successful in 57s
The act runner resolves 'localhost' to ::1 (IPv6) and the preview
server does not get a reachable IPv4 socket, so wait-on times out
and the 'Start preview server' step fails the lighthouse job. Bind
explicitly to 127.0.0.1 (IPv4).

Refs CAR-1218, CAR-1302, CAR-1334
2026-06-10 04:50:12 +00:00
Barcode Betty b0cb2b7a9e ci: retrigger CI for CAR-1334 (CAR-1302) 2026-06-10 04:49:33 +00:00
Barcode Betty a54ea423ef fix(api): widen alembic_version.version_num in migration 001 (CAR-1302)
Alembic hardcodes alembic_version.version_num to VARCHAR(32) in
DefaultImpl.version_table_impl, and version_table_column_width is NOT a
real kwarg that context.configure() honors — it's silently ignored, so
the env.py change alone was never going to take effect on a fresh DB.

Our descriptive revision ids exceed 32 chars (e.g. 003_make_users_hashed_
password_nullable = 39, common 002_add_normalized_products_upc_variants_
index = 46), so the 003 / common 002 stamp fails with StringDataRight-
Truncation, the whole chain rolls back, and the column is recreated at
VARCHAR(32) on the next attempt.

Fix:
- api/alembic/versions/001_encrypt_session_data.py: insert ALTER TABLE
  alembic_version ALTER COLUMN version_num TYPE VARCHAR(128) as the very
  first statement of upgrade(), before any early-return path. Idempotent
  when the column is already wider (e.g. the CAR-1298 one-shot Job).
- common/alembic/versions/001_add_email_inbound_token.py: same defensive
  ALTER as the first statement of upgrade() (common is a library, not
  deployed, but the 46-char 002 id would have hit the same trap).
- api/alembic/env.py: remove the phantom version_table_column_width=128
  kwarg from both context.configure() call sites — it was a no-op and
  misled the original investigation.

No downgrade() changes: a matching narrowing could truncate.

Refs CAR-1302 (durable root fix), CAR-1298 (prod workaround this
replaces). Verified against a fresh PostgreSQL — all 9 api migrations
upgrade head with no StringDataRightTruncation, and common 001/002 stamp
the 46-char id cleanly. Cluster has pgcrypto enabled by the operator.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-10 04:49:33 +00:00
Savannah Savings ad18a43b57 Merge pull request 'fix(ci): let lhci serve static dist for lighthouse gate (CAR-1218)' (#281) from betty/car-1218-lighthouse-ci into dev
CI / lint (push) Successful in 12s
CI / test (push) Successful in 11s
CI / e2e (push) Failing after 3s
CI / audit (push) Successful in 12s
CI / lighthouse (push) Successful in 56s
CI / build-and-push-api (push) Successful in 2m16s
CI / build-and-push-receiptwitness (push) Successful in 2m48s
CI / build-and-push (push) Has been skipped
CI / build-and-push-auth (push) Successful in 1m1s
CI / deploy-uat (push) Has been skipped
CI / deploy-dev (push) Successful in 7s
2026-06-10 04:16:49 +00:00
Savannah Savings 6abbc2f04e Merge pull request 'fix(ci): deploy jobs compute sha tag from $GITHUB_SHA (CAR-1316, CAR-1195)' (#292) from betty/car-1319-sha-tag-fix into dev
CI / test (push) Successful in 13s
CI / lint (push) Successful in 13s
CI / audit (push) Successful in 10s
CI / e2e (push) Successful in 54s
CI / lighthouse (push) Failing after 1m23s
CI / build-and-push-api (push) Successful in 1m34s
CI / build-and-push-receiptwitness (push) Successful in 2m14s
CI / build-and-push-auth (push) Successful in 38s
CI / build-and-push (push) Successful in 1m4s
CI / deploy-uat (push) Has been skipped
CI / deploy-dev (push) Successful in 9s
CI / lint (pull_request) Successful in 10s
CI / test (pull_request) Successful in 12s
CI / audit (pull_request) Successful in 11s
CI / e2e (pull_request) Successful in 43s
CI / build-and-push-api (pull_request) Has been skipped
CI / build-and-push-receiptwitness (pull_request) Has been skipped
CI / build-and-push-auth (pull_request) Has been skipped
CI / lighthouse (pull_request) Failing after 1m20s
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
2026-06-08 12:34:06 +00:00
Barcode Betty afe8f7b7f9 fix(ci): align deploy frontend image-bump to app entry name (CAR-1318)
CI / build-and-push-receiptwitness (pull_request) Has been skipped
CI / lint (push) Successful in 12s
CI / test (push) Successful in 12s
CI / audit (push) Successful in 12s
CI / e2e (push) Successful in 44s
CI / build-and-push-api (push) Successful in 1m47s
CI / e2e (pull_request) Successful in 39s
CI / lighthouse (push) Failing after 1m13s
CI / lint (pull_request) Successful in 10s
CI / test (pull_request) Successful in 10s
CI / audit (pull_request) Successful in 9s
CI / build-and-push-auth (push) Successful in 1m15s
CI / build-and-push-api (pull_request) Has been skipped
CI / build-and-push-auth (pull_request) Has been skipped
CI / build-and-push (push) Successful in 51s
CI / lighthouse (pull_request) Failing after 1m25s
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 / build-and-push-receiptwitness (push) Failing after 22m44s
CI / deploy-uat (push) Has been skipped
CI / deploy-dev (push) Successful in 2m50s
Co-authored-by: Barcode Betty <betty@cartsnitch.com>
Co-committed-by: Barcode Betty <betty@cartsnitch.com>
2026-06-07 11:51:42 +00:00
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
Savannah Savings 39804135a4 fix(auth): log /health 503 error and surface message in body (#283, CAR-1276)
CI / audit (push) Successful in 13s
CI / test (push) Successful in 13s
CI / lint (pull_request) Successful in 14s
CI / test (pull_request) Successful in 13s
CI / lighthouse (push) Failing after 1m22s
CI / e2e (pull_request) Successful in 49s
CI / build-and-push-receiptwitness (pull_request) Has been skipped
CI / e2e (push) Successful in 44s
CI / audit (pull_request) Successful in 13s
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 1m23s
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 / lint (push) Successful in 16m17s
CI / build-and-push-auth (push) Successful in 38s
CI / build-and-push-api (push) Successful in 1m34s
CI / build-and-push (push) Successful in 2m44s
CI / build-and-push-receiptwitness (push) Successful in 3m52s
CI / deploy-uat (push) Has been skipped
CI / deploy-dev (push) Failing after 6s
2026-06-06 00:02:17 +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
Barcode Betty b2c4692400 fix(auth): log /health 503 error and surface message in body (CAR-1276)
CI / deploy-uat (pull_request) Has been skipped
CI / test (pull_request) Successful in 12s
CI / lint (pull_request) Successful in 13s
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 40s
CI / e2e (pull_request) Successful in 1m11s
CI / build-and-push (pull_request) Has been skipped
CI / deploy-dev (pull_request) Has been skipped
CI / lighthouse (pull_request) Failing after 1m15s
The /health handler's catch block was empty, so when the DB probe
failed we had no log line to diagnose from. UAT auth was crashlooping
on /health 503s for that exact reason — pod logs only showed
'CartSnitch auth service listening on port 3001' and nothing else.

Add console.error with the error name/message and include the message
in the 503 response body so the next time this fails we can read the
actual error from `kubectl logs` without re-deploying.

This is the dev-side observability half of CAR-1276. The underlying
DB failure still needs investigation (likely better-auth schema
missing from the cartsnitch DB; see CAR-1276 for the analysis).

Tests updated to assert the new error field is present and a string.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-05 07:05:46 +00:00
7 changed files with 91 additions and 42 deletions
+39 -32
View File
@@ -91,6 +91,9 @@ jobs:
npm install -g playwright
npx playwright install --with-deps chromium
- name: Start preview server
# CAR-1218: bind to 127.0.0.1 (IPv4) not localhost. The act runner
# resolves 'localhost' to ::1 (IPv6) and the preview server does not
# get a reachable IPv4 socket, so wait-on times out.
run: |
npx vite preview --host 127.0.0.1 --port 4173 &
npx wait-on http://127.0.0.1:4173/ --timeout 30000
@@ -505,14 +508,14 @@ 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
if: needs.build-and-push.result == 'success'
run: |
cd infra/apps/overlays/dev
kustomize edit set image ghcr.io/cartsnitch/cartsnitch=git.farh.net/cartsnitch/cartsnitch:${{ steps.frontend_tag.outputs.tag }}
kustomize edit set image ghcr.io/cartsnitch/app=git.farh.net/cartsnitch/cartsnitch:${{ steps.frontend_tag.outputs.tag }}
- name: Determine image tag for receiptwitness
id: receiptwitness_tag
@@ -520,7 +523,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
@@ -535,7 +538,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
@@ -550,7 +553,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
@@ -576,7 +579,7 @@ jobs:
PR_JSON=$(curl -sS -X POST \
-H "Authorization: token ${CI_GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg head "cartsnitch:${BRANCH}" --arg base main --arg title "ci(dev): update overlay image tags (${GITHUB_SHA::12})" --arg body "$PR_BODY" '{head:$head,base:$base,title:$title,body:$body}')" \
-d "$(jq -n --arg head "cartsnitch:${BRANCH}" --arg base dev --arg title "ci(dev): update overlay image tags (${GITHUB_SHA::12})" --arg body "$PR_BODY" '{head:$head,base:$base,title:$title,body:$body}')" \
"https://git.farh.net/api/v1/repos/cartsnitch/infra/pulls")
PR_NUM=$(echo "$PR_JSON" | jq -r '.number // empty')
if [ -z "$PR_NUM" ]; then
@@ -594,6 +597,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" \
@@ -602,17 +615,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:
@@ -649,14 +654,14 @@ 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
if: needs.build-and-push.result == 'success'
run: |
cd infra/apps/overlays/uat
kustomize edit set image ghcr.io/cartsnitch/cartsnitch=git.farh.net/cartsnitch/cartsnitch:${{ steps.frontend_tag.outputs.tag }}
kustomize edit set image ghcr.io/cartsnitch/app=git.farh.net/cartsnitch/cartsnitch:${{ steps.frontend_tag.outputs.tag }}
- name: Determine image tag for receiptwitness
id: receiptwitness_tag
@@ -664,7 +669,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
@@ -679,7 +684,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
@@ -694,7 +699,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
@@ -720,7 +725,7 @@ jobs:
PR_JSON=$(curl -sS -X POST \
-H "Authorization: token ${CI_GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg head "cartsnitch:${BRANCH}" --arg base main --arg title "ci(uat): update overlay image tags (${GITHUB_SHA::12})" --arg body "$PR_BODY" '{head:$head,base:$base,title:$title,body:$body}')" \
-d "$(jq -n --arg head "cartsnitch:${BRANCH}" --arg base uat --arg title "ci(uat): update overlay image tags (${GITHUB_SHA::12})" --arg body "$PR_BODY" '{head:$head,base:$base,title:$title,body:$body}')" \
"https://git.farh.net/api/v1/repos/cartsnitch/infra/pulls")
PR_NUM=$(echo "$PR_JSON" | jq -r '.number // empty')
if [ -z "$PR_NUM" ]; then
@@ -738,6 +743,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" \
@@ -746,15 +761,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
+1 -2
View File
@@ -31,7 +31,6 @@ def run_migrations_offline() -> None:
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
version_table_column_width=128,
)
with context.begin_transaction():
context.run_migrations()
@@ -45,7 +44,7 @@ def run_migrations_online() -> None:
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(connection=connection, target_metadata=target_metadata, version_table_column_width=128)
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()
# Create any tables defined in models but not yet created by migrations.
@@ -33,6 +33,15 @@ def _is_fernet_token(value: str) -> bool:
def upgrade() -> None:
# Alembic hardcodes alembic_version.version_num to VARCHAR(32)
# (DefaultImpl.version_table_impl) and exposes no option to widen it
# (version_table_column_width is NOT a real kwarg — it is silently ignored).
# Our descriptive revision ids exceed 32 chars (e.g.
# 003_make_users_hashed_password_nullable = 39), so widen the column as the
# very first migration statement, before any early-return path below.
# Idempotent: a no-op when already wider (e.g. pre-created by the CAR-1298 Job).
op.execute("ALTER TABLE alembic_version ALTER COLUMN version_num TYPE VARCHAR(128)")
conn = op.get_bind()
inspector = sa.inspect(conn)
+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
+23 -4
View File
@@ -19,9 +19,18 @@ describe('Auth health endpoint', () => {
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', db: 'reachable' }));
} catch {
} catch (err) {
// Mirror src/index.ts: log the error and include the message in the
// response body so /health 503s are diagnosable from pod logs.
console.error(
'[auth /health] DB probe failed:',
err instanceof Error ? `${err.name}: ${err.message}` : err,
);
const detail = err instanceof Error ? err.message : 'unknown error';
res.writeHead(503, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'error', db: 'unreachable' }));
res.end(
JSON.stringify({ status: 'error', db: 'unreachable', error: detail }),
);
}
return;
}
@@ -76,7 +85,10 @@ describe('Auth health endpoint', () => {
close();
equal(status, 503);
equal(body, '{"status":"error","db":"unreachable"}');
const parsed = JSON.parse(body);
equal(parsed.status, 'error');
equal(parsed.db, 'unreachable');
equal(parsed.error, 'connection refused');
});
it('returns 503 with db=unreachable when query times out', async () => {
@@ -95,7 +107,14 @@ describe('Auth health endpoint', () => {
close();
equal(status, 503);
equal(body, '{"status":"error","db":"unreachable"}');
const parsed = JSON.parse(body);
equal(parsed.status, 'error');
equal(parsed.db, 'unreachable');
// The query promise rejects with a synthetic 'timeout' error; the
// Promise.race wrapper also rejects with 'DB timeout'. The body should
// surface whichever error was thrown — accept either to stay robust.
equal(typeof parsed.error, 'string');
equal(parsed.error.length > 0, true);
});
it('returns a terminal response for unknown paths (no hang)', async () => {
+12 -2
View File
@@ -21,9 +21,19 @@ const server = createServer(async (req, res) => {
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok", db: "reachable" }));
} catch {
} catch (err) {
// Log the actual error so /health 503s are diagnosable from pod logs
// (CAR-1276: UAT auth was crashlooping with no log output beyond the
// initial "listening on port 3001" line because this catch was empty).
console.error(
"[auth /health] DB probe failed:",
err instanceof Error ? `${err.name}: ${err.message}` : err,
);
const detail = err instanceof Error ? err.message : "unknown error";
res.writeHead(503, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "error", db: "unreachable" }));
res.end(
JSON.stringify({ status: "error", db: "unreachable", error: detail }),
);
}
return;
}
@@ -18,6 +18,11 @@ depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
# Same VARCHAR(32) alembic_version limitation as the api migrations; the
# common 002 revision id is 46 chars. Widen first so a fresh-DB upgrade can
# stamp it. Idempotent.
op.execute("ALTER TABLE alembic_version ALTER COLUMN version_num TYPE VARCHAR(128)")
op.add_column("users", sa.Column("email_inbound_token", sa.String(22), nullable=True))
op.create_unique_constraint("uq_users_email_inbound_token", "users", ["email_inbound_token"])