Skip to content

chore(deps-dev): bump hono from 4.12.10 to 4.12.25 #405

chore(deps-dev): bump hono from 4.12.10 to 4.12.25

chore(deps-dev): bump hono from 4.12.10 to 4.12.25 #405

Workflow file for this run

name: Release
on:
push:
branches:
- master
pull_request:
branches:
- '*'
workflow_dispatch:
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
check-deps:
continue-on-error: true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '22.x'
- name: Install dependencies
run: npm ci
- name: Check for mature dependency updates
run: npx dry-aged-deps --check
build-and-test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
opensearch_version: ['1.3.20', '2.19.5']
services:
opensearch:
image: opensearchproject/opensearch:${{ matrix.opensearch_version }}
env:
discovery.type: single-node
ES_JAVA_OPTS: '-Xms1g -Xmx1g'
plugins.security.disabled: 'true'
# Required for OpenSearch 2.12+ to skip the demo-config installer when
# the security plugin is disabled. No-op on 1.3.20 (env var unknown
# to that version's entrypoint). Without this, the 2.x entrypoint
# bails out demanding OPENSEARCH_INITIAL_ADMIN_PASSWORD even though
# plugins.security.disabled is set.
DISABLE_INSTALL_DEMO_CONFIG: 'true'
ports:
- 9200:9200
options: >-
--health-cmd "curl -sf http://localhost:9200"
--health-interval 10s
--health-timeout 5s
--health-retries 20
steps:
- uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '22.x'
- name: Cache npm dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm-
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Cache G-NAF zip
uses: actions/cache@v4
id: gnaf-cache
with:
path: target/gnaf/*.zip
key: gnaf-zip-${{ hashFiles('service/address-service.js') }}
restore-keys: |
gnaf-zip-
- name: Prepare OT test fixture
run: |
# Download G-NAF if not cached
npm run build
if [ ! -f target/gnaf/*.zip ]; then
node -e "
require('@babel/register');
require('@babel/polyfill');
require('./service/address-service').fetchGnafFile().then(f => console.log('Downloaded:', f));
"
fi
# Extract only OT + Authority Code files into a slim fixture
GNAF_ZIP=$(ls target/gnaf/*.zip | head -1)
FIXTURE_DIR=target/gnaf-fixture
mkdir -p "$FIXTURE_DIR"
unzip -o -j "$GNAF_ZIP" '*/Authority Code/*' -d "$FIXTURE_DIR/Authority Code/" 2>/dev/null || true
unzip -o -j "$GNAF_ZIP" '*/Standard/OT_*' -d "$FIXTURE_DIR/Standard/" 2>/dev/null || true
echo "Fixture contents:"
find "$FIXTURE_DIR" -type f | head -20
- name: Run tests (no geo)
env:
GNAF_TEST_FIXTURE_DIR: target/gnaf-fixture
run: npm run test:nogeo
- name: Run tests (geo)
env:
GNAF_TEST_FIXTURE_DIR: target/gnaf-fixture
# ADDRESSR_ENABLE_GEO=1 matches the production EB env at deploy/main.tf
# so this step closes the previous coverage gap where the geo code
# path (loadSiteGeo / loadDefaultGeo) only ran in production. No
# external API: geo data comes from the G-NAF dataset itself, so the
# OT fixture above is sufficient.
run: npm run test:geo
release:
if: github.ref == 'refs/heads/master'
needs: build-and-test
runs-on: ubuntu-latest
steps:
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '22.x'
- uses: actions/checkout@v5
- name: Install dependencies
run: |
npm ci
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@v1.4.5
with:
publish: npm run turbo:ci:publish
version: npm run turbo:ci:version
commit: 'chore: release'
title: 'chore: release'
createGithubReleases: true
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Deploy new version
if: steps.changesets.outputs.published == 'true'
uses: devcontainers/ci@v0.3
env:
TF_VAR_elastic_host: ${{ secrets.TF_VAR_elastic_host }}
TF_VAR_elastic_password: ${{ secrets.TF_VAR_elastic_password }}
TF_VAR_elastic_username: ${{ secrets.TF_VAR_elastic_username }}
# ADR 029 amendment 2026-04-29: distinct creds for v2 to fix P028's
# silent 401 (v2 master user no longer aliases v1's; cutover still
# flips only var.elastic_host).
TF_VAR_elastic_v2_username: ${{ secrets.TF_VAR_ELASTIC_V2_USERNAME }}
TF_VAR_elastic_v2_password: ${{ secrets.TF_VAR_ELASTIC_V2_PASSWORD }}
TF_VAR_aws_secret_key: ${{ secrets.TF_VAR_aws_secret_key }}
TF_VAR_aws_access_key: ${{ secrets.TF_VAR_aws_access_key }}
TF_VAR_proxy_auth_header: ${{ secrets.TF_VAR_proxy_auth_header }}
TF_VAR_proxy_auth_value: ${{ secrets.TF_VAR_proxy_auth_value }}
# ADR 032 / P042 — Cloudflare provider + worker module credentials.
# Token requires Workers Scripts Edit + Workers Routes Edit + Workers
# Secrets Edit scopes on the addressr account/zone. RapidAPI key is
# the existing dashboard value (no rotation during the P042 cutover,
# per ADR 032 Scope). Sourced via 1P Voder per reference_addressr_secrets.
TF_VAR_cloudflare_api_token: ${{ secrets.TF_VAR_cloudflare_api_token }}
TF_VAR_cloudflare_account_id: ${{ secrets.TF_VAR_cloudflare_account_id }}
TF_VAR_cloudflare_zone_id: ${{ secrets.TF_VAR_cloudflare_zone_id }}
TF_VAR_cloudflare_rapidapi_key: ${{ secrets.TF_VAR_cloudflare_rapidapi_key }}
TF_TOKEN_app_terraform_io: ${{ secrets.TERRAFORM_CLOUD_TOKEN }}
with:
runCmd: |
npm run deploy:prod
env: |
TF_IN_AUTOMATION=1
TF_VAR_elasticapp=mountainpass-addressr
TF_VAR_elastic_host
TF_VAR_elastic_password
TF_VAR_elastic_username
TF_VAR_elastic_v2_username
TF_VAR_elastic_v2_password
TF_VAR_aws_secret_key
TF_VAR_aws_access_key
TF_VAR_proxy_auth_header
TF_VAR_proxy_auth_value
TF_VAR_cloudflare_api_token
TF_VAR_cloudflare_account_id
TF_VAR_cloudflare_zone_id
TF_VAR_cloudflare_rapidapi_key
TF_TOKEN_app_terraform_io
- name: Wait for deployment to stabilize
if: steps.changesets.outputs.published == 'true'
run: sleep 120
- name: Smoke test production
if: steps.changesets.outputs.published == 'true'
run: |
set -e
echo "Testing health endpoint..."
curl -sf --retry 3 --retry-delay 10 https://backend.addressr.io/health
echo ""
echo "Testing /api-docs is reachable without gateway auth (ADR 024 allowlist)..."
curl -sf --retry 3 --retry-delay 10 -o /dev/null https://backend.addressr.io/api-docs
echo ""
# P035 / risk-scorer R3: /debug/shadow-config must be reachable
# without gateway auth (ADR 024 allowlist) and must return the
# documented JSON shape. Catches the silent-no-op P035 failure
# mode automatically on every deploy: if the running addressr-server
# process can't see ADDRESSR_SHADOW_HOST, hostSet will be false
# and we'll know within minutes of deploy instead of hours via
# operator probing. The endpoint must also respect the closed
# lastError.class enum — we assert it against null OR enum members.
echo "P035 R3: probing /debug/shadow-config..."
shadow_body=$(curl -sf --retry 3 --retry-delay 10 \
https://backend.addressr.io/debug/shadow-config)
echo "$shadow_body" | jq -e '.' > /dev/null || {
echo "/debug/shadow-config did not return valid JSON"; exit 1;
}
# ADR 029 Phase 1 was rolled back 2026-05-14 (v2 OpenSearch
# decommissioned, ADDRESSR_SHADOW_* env vars removed). Per the ADR 031
# default-off posture, hostSet=false is now the expected steady state
# (shadow capability shipped, no target configured, mirrorRequest
# no-ops). Assert false so an ACCIDENTAL shadow re-enable is caught.
# When ADR 029 Phase 1 is re-attempted, flip this back to "true".
host_set=$(echo "$shadow_body" | jq -r '.hostSet')
if [ "$host_set" != "false" ]; then
echo "/debug/shadow-config hostSet=$host_set, expected false (shadow is intentionally off post-ADR-029-rollback; flip to true when shadow is re-enabled)"
echo "body: $shadow_body"; exit 1
fi
last_error_class=$(echo "$shadow_body" | jq -r '.lastError.class // "null"')
case "$last_error_class" in
null|AbortError|ConnectionError|AuthError|UnknownError) ;;
*)
echo "/debug/shadow-config lastError.class=$last_error_class, expected null or one of the closed enum values"
exit 1 ;;
esac
echo "P035 R3 ok: hostSet=false (shadow off per ADR 029 rollback), lastError.class=$last_error_class"
echo ""
# ADR 024 bypass probe: direct-without-header must be rejected.
# Any status other than 401 fails the release — enforcement is the
# steady state (ADR 024 accepted, P009 closed).
echo "Probing direct bypass (ADR 024 Confirmation criterion 5)..."
status=$(curl -s -o /dev/null -w "%{http_code}" "https://backend.addressr.io/addresses?q=sydney")
if [ "$status" != "401" ]; then
echo "direct probe returned $status, expected 401 (enforcement regression)"; exit 1
fi
echo "direct probe status 401 (expected)"
# P040: Cloudflare Worker (ADR 018) auth boundary probes. These are
# distinct from the ADR 024 origin probe above — the worker rejects
# at a different layer with a different body shape. Probing both
# makes a future regression that conflates the two layers loud.
echo "Probing worker referer-accept (ADR 018 safeHosts allowlist)..."
status=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Referer: https://addressr.io/" \
"https://api.addressr.io/addresses/GANSW718804790")
if [ "$status" != "200" ]; then
echo "worker probe with Referer returned $status, expected 200 (safeHosts allowlist regression — P040 workaround broken)"; exit 1
fi
echo "worker probe with Referer status 200 (expected)"
echo "Probing worker no-referer rejection (ADR 018 default-deny)..."
worker_body=$(curl -s "https://api.addressr.io/addresses/GANSW718804790")
if ! echo "$worker_body" | grep -q "no-origin not permitted"; then
echo "worker no-Referer probe did not return ADR 018 rejection body; got: $worker_body"
echo "(this distinguishes the worker layer from the ADR 024 origin layer — body must start with 'no-origin not permitted')"
exit 1
fi
echo "worker no-Referer rejection body matches ADR 018 (expected)"
# ADR 025 / P007 ranking probes + P019 Link header rel probe: exact
# street-level address must rank first when sub-unit variants share
# the address; root / must advertise every rel in the contract.
auth_header="${{ secrets.TF_VAR_proxy_auth_header }}: ${{ secrets.TF_VAR_proxy_auth_value }}"
# P019: rel completeness on root /. The rel list is the HATEOAS
# contract from test/resources/features/addressv2.feature:8-15 (ADR
# 012). Root / requires ADR 024 auth, so we use $auth_header here.
# This probe replaces the pre-ADR-024 "curl -sf /" line that was
# latently broken under proxy-auth enforcement.
echo "Probing root / Link header rel completeness (P019)..."
link_header=$(curl -sf --retry 3 --retry-delay 10 -D - -o /dev/null \
-H "$auth_header" https://backend.addressr.io/ \
| grep -i '^link:' | tr -d '\r' || true)
expected_rels=(
"https://addressr.io/rels/address-search"
"https://addressr.io/rels/locality-search"
"https://addressr.io/rels/postcode-search"
"https://addressr.io/rels/state-search"
"https://addressr.io/rels/api-docs"
"https://addressr.io/rels/health"
)
for rel in "${expected_rels[@]}"; do
if ! echo "$link_header" | grep -qF "$rel"; then
echo "root Link header missing rel: $rel"
echo "actual link header: $link_header"
exit 1
fi
done
echo "root / Link header contains all ${#expected_rels[@]} expected rels."
echo "Probing ranking for 278 ROSS RIVER RD AITKENVALE QLD 4814 (issue #375)..."
top_sla=$(curl -sf --retry 3 --retry-delay 10 -H "$auth_header" \
"https://backend.addressr.io/addresses?q=278%20ROSS%20RIVER%20RD%20AITKENVALE%20QLD%204814" \
| jq -r '.[0].sla')
if [[ "$top_sla" != "278 ROSS RIVER RD,"* ]]; then
echo "ranking regression: expected top hit to start with '278 ROSS RIVER RD,', got '$top_sla'"
exit 1
fi
echo "top hit '$top_sla' (street-level, expected)"
echo "Probing ranking for 19 MURRAY RD CHRISTMAS ISLAND OT 6798..."
top_sla=$(curl -sf --retry 3 --retry-delay 10 -H "$auth_header" \
"https://backend.addressr.io/addresses?q=19%20MURRAY%20RD%20CHRISTMAS%20ISLAND%20OT%206798" \
| jq -r '.[0].sla')
if [[ "$top_sla" != "19 MURRAY RD, CHRISTMAS ISLAND OT 6798" ]]; then
echo "ranking regression: expected top hit '19 MURRAY RD, CHRISTMAS ISLAND OT 6798', got '$top_sla'"
exit 1
fi
echo "top hit '$top_sla' (street-level, expected)"
echo "Smoke tests passed."
# TODO: RapidAPI OpenAPI spec sync is deferred — see ADR 023.
# The REST provisioning API is defunct and the GitHub Action
# has no tagged versions. /api-docs is live; for now, use the
# "Import from URL" option in RapidAPI Studio pointing at
# https://backend.addressr.io/api-docs when new endpoints are added.