Files
adr-sml/README.md
T
Pouya LajevardiandClaude Opus 5 6bf1167624 fix: sweep D3 amendment through the specs; correct inverted DKIM table
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
2026-08-26 11:28:42 -04:00

98 lines
4.6 KiB
Markdown

# adr.smlcompany.ca
The dispute resolution practice of Pouya Lajevardi — Toronto.
A static site built with [Astro](https://astro.build), built for deployment to
Amazon S3 behind CloudFront by Gitea Actions — see Deployment; the pipeline is
not yet proven.
## Quick start
```bash
nvm use # Node 22
npm install
npm run dev # http://localhost:4321
```
## Scripts
| Command | Does |
|---|---|
| `npm run dev` | Development server with hot reload |
| `npm run build` | Static build to `./dist` |
| `npm run preview` | Serve the built site locally |
| `npm run check` | `astro check` — type and template errors |
| `npm run lint` | ESLint + Prettier check — **not yet wired**, no ESLint config exists |
| `npm run format` | Prettier — rewrite files in place |
| `npm run lighthouse` | Lighthouse CI — **not yet wired**, no `lighthouserc` exists |
## Before you contribute
Read **`AGENTS.md`** first, and maintain it as you work — it is the living
record of what this project is, what was decided, and why. Then read
**`CLAUDE.md`** for the working rules, and the specs in `docs/`.
The single hardest rule: **no factual claim about Pouya, his credentials, his
experience, or his practice ships unless it appears in the verified register in
`AGENTS.md` §4.** This is a public marketing surface, and the site it replaces
contained fabricated credentials.
## How work is done here
Pouya decides; Claude Code implements and then adversarially reviews its own
work. Run **`/build <task>`** for any substantive change — it plans, implements,
runs two independent review agents on the diff (the claims audit wherever copy
changed), resolves the findings, verifies the build, and records the session in
`AGENTS.md`. `/review` runs the review pass alone; `/wrap` closes a session.
Full protocol and prompt guidance: `docs/08-execution-protocol.md`.
## Deployment
`.gitea/workflows/deploy.yml` is the deploy pipeline — **Gitea Actions**, not
GitHub Actions. On a push to `main` it builds, syncs to S3, and invalidates
CloudFront.
**It has never run green.** There is no `package-lock.json`, so `npm ci` exits
at step one; `src/pages/` is empty, so there is nothing to build; and whether an
`act_runner` is registered is recorded nowhere. The workflow is written; the
pipeline is unproven.
The workflow's **first** step guards against the other way this fails quietly:
it aborts the run, naming the variable, if `AWS_REGION`, `S3_BUCKET`, or
`CLOUDFRONT_DISTRIBUTION_ID` is empty — which is how a Gitea too old for the
`vars` context manifests.
The GitHub Actions equivalent, which uses OIDC role assumption, is kept as
`docs/reference/github-actions-oidc.yml.example` in case the project ever moves
to a forge that supports it. It sits outside `.github/workflows/` on purpose:
Gitea falls back to that directory when `.gitea/workflows` is absent, so a
workflow file left there with a `push` trigger would be only conditionally
inert. As an `.example` under `docs/` it cannot be picked up at all.
**The pipeline is designed around a long-lived AWS credential.** Gitea is not an
AWS OIDC provider, so there is no role to assume: deploys are to authenticate as
a scoped IAM user, `adr-sml-deploy`, with its access key held in the
repository's Gitea Actions secrets. Nothing in `AGENTS.md` records that user as
created or that key as issued — `docs/06-deployment.md` is a procedure to
perform, not a record of one performed. Two things are meant to bound the risk,
and neither is confirmed in place:
- **The policy must stay narrow.** Four actions: `s3:ListBucket` on one bucket,
`s3:PutObject` and `s3:DeleteObject` on that bucket's contents, and
`cloudfront:CreateInvalidation` on one distribution. No `Action: "*"`, no
`Resource: "*"`, nothing outside that one bucket and that one distribution.
The AWS account is shared with unrelated projects, including a bucket whose
name indicates another business's production database backups — that
narrowness is what keeps a compromised runner away from it, and it is
load-bearing rather than hygiene. See `AGENTS.md` §10. If a deploy step needs
a permission the policy lacks, question the step; do not widen the policy.
- **The key must be rotated quarterly, and nobody owns that yet.** Create a
second access key, update the Gitea secrets, confirm a deploy succeeds, then
delete the old one — rotation that leaves the old key active is not
rotation. OIDC would have removed the obligation entirely; it is
unavailable, so this is a standing calendar task still waiting on an owner.
Full procedure, IAM policy, runner setup, and cutover checklist:
`docs/06-deployment.md`.