Build and deploy / build-and-deploy (push) Failing after 5s
Step 5 ships /practice/ and the six practice-area pages (construction,
technology, energy, insurance, shareholder, cross-border) from one route, and
adds the mechanical §4 gate Pouya ruled for.
check:claims — §4 Forbidden becomes a build error
scripts/check-claims.mjs greps dist/**/*.html for 10 patterns, each carrying
the incident that put it there. It strips <style> and non-JSON-LD <script>
first (a bare sweep for "leading" returned 26 hits, 25 of them
var(--leading-body)), self-tests every pattern against fixtures before
sweeping, and refuses a missing, empty or stale dist/. Wired into /build
Phase 5 and both deploy paths.
Q54 — six conduct undertakings publish, and §4 gains a third class
Conduct undertakings sit apart from credentials and offerings: the gate is
that Pouya said it in terms. The strings live in CONDUCT_UNDERTAKINGS so a
softening is one visible diff. (e) and (f) replace the third-person sentences
already on /arbitration/ rather than joining them.
Q49, Q50 recorded as rulings. §7 records the SES us-east-1 stray identity's
deletion. R11 holds typescript at its current major, with the peer-range
reason recorded.
Three facts corrected, two of them already shipped
- The LAT gloss said mediation "before filing and continuing after filing";
the Tribunal names mediation for "Before you apply" only and its second
sentence is about negotiation. An ellipsis in docs/01 had deleted it.
- "Connection allocation" is not an Ontario term.
- "The 2026 privacy statute" does not exist — Bill C-27 died without royal
assent. Struck from docs/03 rather than corrected in place.
ADR Chambers struck from /arbitration/ and from docs/01 item 3 (Pouya,
2026-08-30): the source establishes what the firm publishes, not that an
outside neutral can be appointed under its rules.
claims-auditor gains a second lens — for every quoted source, whether the
sentence beneath stays inside what the quotation establishes. Four shipped
defects had that shape and none of them is greppable.
CLAUDE.md gains a convention: never truncate the output of a check you intend
to believe. `npm run check | tail -3` returns warnings, hints and a blank line
and drops the errors line; it was reported as passing four times while
astro check was exiting 1 with 10 type errors.
Gates, exit status read directly, not through a pipe:
npm run check exit=0
npm run lint exit=0
npm run build exit=0
npm run check:claims exit=0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
160 lines
6.5 KiB
YAML
160 lines
6.5 KiB
YAML
# Gitea Actions — the live pipeline for this repository.
|
|
#
|
|
# Gitea Actions speaks GitHub Actions syntax, so this is a near-direct port of
|
|
# docs/reference/github-actions-oidc.yml.example (kept as the OIDC reference in
|
|
# case the repo ever moves to GitHub; it lives under docs/ rather than
|
|
# .github/workflows/ so Gitea can never fall back to it).
|
|
#
|
|
# ONE REAL DIFFERENCE: Gitea is not an AWS OIDC provider, so there is no role to
|
|
# assume. Deploys are designed to authenticate with a SCOPED IAM USER whose key
|
|
# lives only in this repository's Gitea secrets. Whether that user and key have
|
|
# actually been created is AGENTS.md Q22 — unanswered as of 2026-08-26.
|
|
#
|
|
# See docs/06-deployment.md for the exact IAM policy — it grants four actions on
|
|
# one bucket and one distribution, nothing more. Rotate the key quarterly; OIDC
|
|
# would have made that unnecessary.
|
|
#
|
|
# Requires a Gitea Actions runner registered to this repo or its organisation.
|
|
|
|
name: Build and deploy
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: deploy-production
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
build-and-deploy:
|
|
runs-on: ubuntu-latest
|
|
|
|
env:
|
|
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
AWS_DEFAULT_REGION: ${{ vars.AWS_REGION }}
|
|
S3_BUCKET: ${{ vars.S3_BUCKET }}
|
|
CLOUDFRONT_DISTRIBUTION_ID: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}
|
|
# Job-level so the guard can see it. An empty INTAKE_ENDPOINT does not
|
|
# fail the build - it ships a live contact form posting to nothing.
|
|
INTAKE_ENDPOINT: ${{ vars.INTAKE_ENDPOINT }}
|
|
|
|
steps:
|
|
# Runs first, before checkout and before any AWS call, so a
|
|
# misconfiguration costs one second instead of a full build.
|
|
#
|
|
# Repository variables live at Settings -> Actions -> Variables. Gitea
|
|
# only added the `vars` context in 1.21; this instance reports 1.27.2
|
|
# [verified 2026-08-26 - /api/v1/version, AGENTS.md §7], so the guard is
|
|
# belt-and-braces rather than load-bearing. It stays because an unset or
|
|
# mistyped variable degrades the sync target to "s3://" and the run dies
|
|
# obscurely somewhere in the middle, whatever the Gitea version.
|
|
#
|
|
# Covers the deploy-target variables, the intake endpoint, AND the two
|
|
# secrets. The secrets matter most: AGENTS.md Q22 records that nobody has
|
|
# confirmed the IAM user or its key exists, so an unset key is the single
|
|
# likeliest first-run failure - and without this it would burn a whole
|
|
# build before dying at `aws sts get-caller-identity`.
|
|
#
|
|
# Only emptiness is ever tested. No value is echoed, so nothing here can
|
|
# leak a secret into the run log.
|
|
- name: Guard - required variables and secrets are set
|
|
run: |
|
|
missing=''
|
|
[ -n "$AWS_DEFAULT_REGION" ] || missing="$missing AWS_REGION(var)"
|
|
[ -n "$S3_BUCKET" ] || missing="$missing S3_BUCKET(var)"
|
|
[ -n "$CLOUDFRONT_DISTRIBUTION_ID" ] || missing="$missing CLOUDFRONT_DISTRIBUTION_ID(var)"
|
|
[ -n "$INTAKE_ENDPOINT" ] || missing="$missing INTAKE_ENDPOINT(var)"
|
|
[ -n "$AWS_ACCESS_KEY_ID" ] || missing="$missing AWS_ACCESS_KEY_ID(secret)"
|
|
[ -n "$AWS_SECRET_ACCESS_KEY" ] || missing="$missing AWS_SECRET_ACCESS_KEY(secret)"
|
|
if [ -n "$missing" ]; then
|
|
echo "Not set:$missing"
|
|
echo
|
|
echo 'Variables: Settings -> Actions -> Variables.'
|
|
echo 'Secrets: Settings -> Actions -> Secrets.'
|
|
echo 'See docs/06-deployment.md.'
|
|
echo 'If the variables ARE set, this Gitea predates the vars context (1.21+).'
|
|
exit 1
|
|
fi
|
|
echo 'All required variables and secrets are set.'
|
|
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: npm
|
|
|
|
- name: Install
|
|
run: npm ci
|
|
|
|
- name: Type and template check
|
|
run: npm run check
|
|
|
|
- name: Build
|
|
run: npm run build
|
|
env:
|
|
PUBLIC_SITE_URL: https://adr.smlcompany.ca
|
|
# vars, not env — Gitea expression-context support is the very thing
|
|
# the guard above exists to not depend on.
|
|
PUBLIC_INTAKE_ENDPOINT: ${{ vars.INTAKE_ENDPOINT }}
|
|
PUBLIC_BOOKING_URL: ${{ vars.BOOKING_URL }}
|
|
|
|
# AGENTS.md §4 Forbidden, enforced on the built output before a single
|
|
# byte is uploaded. Runs here rather than in `npm run check` because it
|
|
# reads dist/, and it refuses a stale or empty dist for the same reason
|
|
# this workflow guards its variables: an empty sweep reads exactly like a
|
|
# clean one. Mirrored in scripts/deploy-local.sh.
|
|
- name: Claim check
|
|
run: npm run check:claims
|
|
|
|
# Some Gitea runner images ship without the AWS CLI. Install if missing.
|
|
- name: Ensure AWS CLI
|
|
run: |
|
|
if ! command -v aws >/dev/null 2>&1; then
|
|
curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip
|
|
unzip -q /tmp/awscliv2.zip -d /tmp
|
|
sudo /tmp/aws/install --update
|
|
fi
|
|
aws --version
|
|
|
|
- name: Verify credentials
|
|
run: aws sts get-caller-identity
|
|
|
|
# Three passes: hashed immutable assets first, then images, HTML last.
|
|
# A visitor must never fetch a new page whose assets have not landed yet.
|
|
- name: Sync hashed assets
|
|
run: |
|
|
aws s3 sync ./dist "s3://${S3_BUCKET}" \
|
|
--exclude "*" \
|
|
--include "_astro/*" --include "fonts/*" \
|
|
--cache-control "public, max-age=31536000, immutable" \
|
|
--no-progress
|
|
|
|
- name: Sync images
|
|
run: |
|
|
aws s3 sync ./dist "s3://${S3_BUCKET}" \
|
|
--exclude "*" \
|
|
--include "*.avif" --include "*.webp" --include "*.jpg" \
|
|
--include "*.png" --include "*.svg" \
|
|
--cache-control "public, max-age=604800" \
|
|
--no-progress
|
|
|
|
- name: Sync HTML and the rest
|
|
run: |
|
|
aws s3 sync ./dist "s3://${S3_BUCKET}" \
|
|
--exclude "_astro/*" --exclude "fonts/*" \
|
|
--cache-control "public, max-age=0, must-revalidate" \
|
|
--delete --no-progress
|
|
|
|
- name: Invalidate CloudFront
|
|
run: |
|
|
aws cloudfront create-invalidation \
|
|
--distribution-id "${CLOUDFRONT_DISTRIBUTION_ID}" \
|
|
--paths "/*"
|
|
|
|
- name: Summary
|
|
run: echo "Deployed to https://adr.smlcompany.ca — commit ${GITHUB_SHA:0:7}"
|