feat: SES production access and monitoring; §7 as single source of operational truth
Q19 is closed — SES production access granted in ca-central-1, confirmed in writing. Nothing now blocks /contact/. The structural change is the important one. Specs in docs/ carried their own copies of resource IDs, regions, DNS records and service state. AGENTS.md §7 is now the single source of truth for operational facts and docs/ cite it rather than restating it, with the rule recorded in CLAUDE.md under Conventions. The reason is the previous commit's DKIM inversion, generalised: the same fact lived in §7 and docs/05, a correction reached one of them, and the stale copy told an operator to delete the records that authenticate outbound mail. A duplicated fact is one that will eventually be wrong in one place, and the copy that goes stale is the one nobody re-reads. Verified by grep over docs/*.md — no operational identifier remains. Also in this change: - §7 records the SES monitoring: SNS topic ses-alerts, alarms SES-BounceRate-High (>= 0.03) and SES-ComplaintRate-High (>= 0.001), and the deliberate choice of email feedback forwarding over an SNS feedback topic at this volume. The ses-alerts email subscription is stamped PENDING CONFIRMATION — the alarms currently notify nobody, now tracked as R9 and on the cutover checklist. - docs/05 records why those alarms are a real control: SES suspends above roughly a 5% bounce rate, and under 100 messages a month five bounces crosses it. - Q29: the deploy guard now covers AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY (emptiness only, never echoed) and INTAKE_ENDPOINT, promoted to job-level env. An empty intake endpoint ships a live form posting to nothing, which is worse than a failed build. Executed under sh -e across four input states; fails closed, leaks nothing. - docs/06: account ID removed from the backup-bucket callout, pointing at §10 instead, as README already does. - astro.config.mjs: prefetch removed entirely. Any setting ships Astro's prefetch script to every page against the zero-JS convention. Recorded as a decision; revisit against real Lighthouse numbers. AGENTS.md entry (r) records the full reasoning. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012XquaEq4BgWMCwUqLEyNkF
This commit is contained in:
co-authored by
Claude Opus 5
parent
6bf1167624
commit
2b6176e4d7
+26
-14
@@ -37,6 +37,9 @@ jobs:
|
||||
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
|
||||
@@ -48,26 +51,33 @@ jobs:
|
||||
# to interpolate to an empty string, the sync target below degrades to
|
||||
# "s3://", and the run dies obscurely somewhere in the middle.
|
||||
#
|
||||
# SCOPE: this guard covers the three DEPLOY-TARGET variables only. It does
|
||||
# NOT cover AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY (an unset secret
|
||||
# still fails later, at `aws sts get-caller-identity`), nor
|
||||
# vars.INTAKE_ENDPOINT, which is step-scoped on the Build step and whose
|
||||
# absence would ship a form posting to an empty endpoint. See AGENTS.md
|
||||
# Q23 - extending the guard to both is an open decision, not an oversight.
|
||||
- name: Guard - required repository variables are set
|
||||
# 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"
|
||||
[ -n "$S3_BUCKET" ] || missing="$missing S3_BUCKET"
|
||||
[ -n "$CLOUDFRONT_DISTRIBUTION_ID" ] || missing="$missing CLOUDFRONT_DISTRIBUTION_ID"
|
||||
[ -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 "Missing repository variables:$missing"
|
||||
echo "Not set:$missing"
|
||||
echo
|
||||
echo 'Set them at Settings -> Actions -> Variables (see docs/06-deployment.md).'
|
||||
echo 'If they ARE set, this Gitea instance predates the vars context (1.21+).'
|
||||
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 'Required repository variables are present.'
|
||||
echo 'All required variables and secrets are set.'
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -86,6 +96,8 @@ jobs:
|
||||
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 }}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user