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
The re-audit of the deploy-guard change surfaced defects well outside the
diff, including one that would have broken production mail.
docs/05-backend-spec.md had the two SES DKIM sets exactly inverted, labelling
the three records that resolve as "orphans" and the three NXDOMAIN records as
"Live. Never delete". Entry (j) corrected this in AGENTS.md §7 and the
correction never reached docs/05. Since SES has no custom MAIL FROM, DKIM is
the only thing satisfying DMARC, so acting on that table would have silently
broken intake mail authentication.
Also in this change:
- .gitea/workflows/deploy.yml gains a guard as steps[0] that fails the run,
naming the variable, if AWS_REGION, S3_BUCKET or CLOUDFRONT_DISTRIBUTION_ID
is empty — how a Gitea too old for the vars context manifests. Verified
fail-closed under bash -e, sh -e and bash -euo pipefail.
- AGENTS.md Current Truth: SPF and DMARC recorded as present (Q20), the
matching §10 High risk row retired, three duplicate Q rows removed.
- docs/reference/AWS-Hosting-Guide.md tracked and given a do-not-execute
banner; it was an executable procedure for the architecture D1/D3 replace.
- Copy decks: "a working litigator" and "an active litigation practice"
replaced with the register's own wording; LegalService JSON-LD replaced with
ProfessionalService; tribunal-secretary offers removed per D14; nine stale
question blockers swept.
- astro.config.mjs: prefetchAll disabled — it injected JS into every page
against the zero-JS convention with no decision recorded.
- src/data/site.ts: unregistered response-time commitment nulled (Q27);
OBA section names downgraded to [assumed] (Q28).
- s3:AbortMultipartUpload reasoning corrected to measure ./dist, not the repo.
Opens Q27, Q28, Q29. AGENTS.md entry (q) records the full resolution,
including the findings declined and why.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XquaEq4BgWMCwUqLEyNkF