chore: project scaffold, specs, and working record
Build and deploy / build-and-deploy (push) Failing after 5s
Build and deploy / build-and-deploy (push) Failing after 5s
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
# 05 — Intake, booking, and data handling
|
||||
|
||||
Authority: `AGENTS.md` §3 D10 — rebuilt intake form plus calendar booking.
|
||||
Existing infrastructure is documented in `AWS-Hosting-Guide.md` Parts 8–10.
|
||||
**Read that guide before changing anything**; the resources already exist and
|
||||
were built by hand in the console.
|
||||
|
||||
---
|
||||
|
||||
## What exists today
|
||||
|
||||
API Gateway (HTTP API) → Lambda → DynamoDB, with SES for notification email and
|
||||
a verified sender on `smlcompany.ca`. `[verified 2026-08-25 — AWS-Hosting-Guide.md]`
|
||||
|
||||
The shape is right. This is a hardening and rework pass, not a replacement.
|
||||
|
||||
## What this data actually is
|
||||
|
||||
The form collects, in a live legal dispute: the inquirer's identity and contact
|
||||
details, the names of opposing parties and their counsel, the nature of the
|
||||
dispute, and often the amounts at issue.
|
||||
|
||||
That is **personal information about identifiable third parties who have not
|
||||
consented and do not know the submission happened.** It is more sensitive than a
|
||||
typical contact form by a wide margin, and it is potentially conflict-relevant.
|
||||
Design accordingly. Nothing in this section is optional.
|
||||
|
||||
---
|
||||
|
||||
## Form fields
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|---|---|---|---|
|
||||
| Name | text | yes | |
|
||||
| Email | email | yes | Validated server-side, not only in the browser |
|
||||
| Phone | tel | no | |
|
||||
| Role | select | yes | Counsel · In-house · Party · Institution · Other |
|
||||
| Firm / organisation | text | no | |
|
||||
| Process sought | select | yes | Mediation · Arbitration · Med-Arb · ENE · Not sure |
|
||||
| Practice area | select | yes | The six areas plus Other |
|
||||
| Other parties | text | no | Surfaced for conflicts screening |
|
||||
| Opposing counsel | text | no | Same |
|
||||
| Matter summary | textarea | yes | 2000 char cap. Hint: *no privileged or confidential detail* |
|
||||
| Timing | select | no | Urgent · 30 days · 90 days · Exploring |
|
||||
| Preferred contact | radio | no | Email · Phone |
|
||||
| Consent | checkbox | **yes** | Explicit, unchecked by default, links to `/legal/privacy/` |
|
||||
|
||||
**Do not collect** dollar amounts, document uploads, or anything the inquirer
|
||||
might reasonably treat as privileged. The intake call is for that.
|
||||
|
||||
### Consent text
|
||||
|
||||
> I consent to Pouya Lajevardi storing and using the information in this form to
|
||||
> respond to my inquiry and to run a conflicts check. I understand that
|
||||
> submitting this form does not create a retainer, does not appoint a neutral,
|
||||
> and does not itself establish a mediator–party relationship.
|
||||
|
||||
## Validation and abuse control
|
||||
|
||||
Client-side validation is a convenience. **The Lambda re-validates everything.**
|
||||
|
||||
- Required fields present; email well-formed; lengths within bounds
|
||||
- Reject any field over its cap rather than truncating silently
|
||||
- **Honeypot** field, hidden from sighted and screen-reader users, must be empty
|
||||
- **Timestamp check** — reject submissions completed in under 3 seconds
|
||||
- **Rate limit** by source IP at API Gateway: 5 requests / 5 minutes
|
||||
- No CAPTCHA. It is a third-party script on a page collecting legal information,
|
||||
and the two controls above stop the traffic that matters
|
||||
- CORS restricted to `https://adr.smlcompany.ca` — no wildcard
|
||||
- Strip HTML from every field before storage and before it enters an email body
|
||||
|
||||
## Storage
|
||||
|
||||
DynamoDB, `ca-central-1` — **Canadian data residency is a real selling point for
|
||||
a Canadian legal practice, and the privacy policy will say so.** Confirm the
|
||||
existing table's region and migrate if it is elsewhere (**Q10**).
|
||||
|
||||
| Attribute | |
|
||||
|---|---|
|
||||
| `pk` | `INTAKE#<uuid>` |
|
||||
| `sk` | `<ISO-8601 timestamp>` |
|
||||
| fields | as above |
|
||||
| `sourceIp`, `userAgent` | abuse investigation only |
|
||||
| `ttl` | epoch seconds — **automatic deletion** |
|
||||
|
||||
**Encryption at rest** with a customer-managed KMS key. **Point-in-time recovery
|
||||
on.** Table access limited to the Lambda role and one named administrative
|
||||
principal.
|
||||
|
||||
### Retention
|
||||
|
||||
**24 months, enforced by DynamoDB TTL.** Not a policy someone remembers — a
|
||||
mechanism that runs whether anyone remembers or not.
|
||||
|
||||
Rationale: long enough to serve conflicts screening across a normal matter
|
||||
lifecycle; short enough to be defensible under PIPEDA's requirement to retain
|
||||
personal information only as long as necessary. Whatever number ships must match
|
||||
`/legal/privacy/` exactly.
|
||||
|
||||
## Notification
|
||||
|
||||
SES on submission:
|
||||
|
||||
- **To Pouya:** the full submission, plainly formatted, replyable to the inquirer.
|
||||
- **To the inquirer:** confirmation of receipt, expected response time, a repeat
|
||||
of the no-retainer language, and a link to the privacy policy. This email is
|
||||
the reason the form beats a `mailto:` link.
|
||||
|
||||
SES must have SPF, DKIM, and DMARC aligned on `smlcompany.ca` or these land in
|
||||
spam. The guide covers domain verification; **DMARC needs confirming (Q3)**.
|
||||
|
||||
Failure handling: SES failure must never lose the submission. Write to DynamoDB
|
||||
first, then send. A dead-letter queue on the Lambda, and a CloudWatch alarm on
|
||||
DLQ depth ≥ 1.
|
||||
|
||||
## Booking
|
||||
|
||||
An embedded scheduler for the 30–45 minute confidential intake call
|
||||
(**Q5** — tool not yet chosen).
|
||||
|
||||
- Prefer a provider with Canadian or EU data residency and no advertising
|
||||
business. Cal.com self-hosted is the strongest privacy posture; Cal.com cloud
|
||||
or Calendly are acceptable.
|
||||
- **Lazy-load behind a click.** No third-party iframe on first paint, and no
|
||||
third-party script on any other page.
|
||||
- Provide a plain link fallback that works with JavaScript disabled.
|
||||
- The booking page must carry the same no-retainer language.
|
||||
- Disclose the provider by name in `/legal/privacy/`.
|
||||
|
||||
## Security headers
|
||||
|
||||
Set at CloudFront via a response-headers policy:
|
||||
|
||||
```
|
||||
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
|
||||
X-Content-Type-Options: nosniff
|
||||
Referrer-Policy: strict-origin-when-cross-origin
|
||||
Permissions-Policy: geolocation=(), microphone=(), camera=(), interest-cohort=()
|
||||
Content-Security-Policy: default-src 'self'; img-src 'self' data:;
|
||||
style-src 'self' 'unsafe-inline'; script-src 'self';
|
||||
frame-src <booking-provider>; form-action 'self' <api-endpoint>;
|
||||
base-uri 'self'; frame-ancestors 'none'
|
||||
```
|
||||
|
||||
Tighten CSP once the booking provider is chosen. `unsafe-inline` on styles is
|
||||
tolerable for critical CSS; `unsafe-inline` on scripts is not — use a hash or
|
||||
nonce for the reveal script.
|
||||
|
||||
## Privacy policy must state
|
||||
|
||||
Written to match what is actually built, not what is typical:
|
||||
|
||||
What is collected · why · lawful basis (consent) · where it is stored (DynamoDB,
|
||||
region, encrypted at rest) · **the retention period and that deletion is
|
||||
automatic** · who can access it · third parties involved (AWS, SES, the booking
|
||||
provider, analytics if any) · how to request access or deletion and the address
|
||||
to use · that submitting the form creates no retainer and no mediator–party
|
||||
relationship · cookie and analytics disclosure · last-updated date.
|
||||
|
||||
If analytics ship, prefer a cookieless privacy-preserving tool (Plausible,
|
||||
Fathom). GA4 on a page collecting legal-dispute information is a poor fit for a
|
||||
practice whose privacy posture is part of its offer (**Q11**).
|
||||
|
||||
## Definition of done
|
||||
|
||||
- [ ] Server-side validation independent of the client
|
||||
- [ ] Honeypot and timing checks live; rate limit configured
|
||||
- [ ] CORS restricted to the production origin
|
||||
- [ ] TTL set and verified by test record
|
||||
- [ ] KMS encryption and PITR enabled
|
||||
- [ ] Both emails send; SPF/DKIM/DMARC aligned; inbox-tested, not spam-tested
|
||||
- [ ] DLQ and CloudWatch alarm configured
|
||||
- [ ] Form usable by keyboard only; errors announced with `role="alert"`
|
||||
- [ ] Form degrades to a `mailto:` fallback with JavaScript disabled
|
||||
- [ ] Privacy policy matches the implementation line for line
|
||||
Reference in New Issue
Block a user