feat: rule Q62 by stating the truth; strike the /med-arb/ gloss; re-stamp R18
Pouya's four rulings of 2026-09-01, applied 2026-09-02.
Q62 — RULED "state the truth", not "remove the access". /legal/privacy/
now says two people can read the intake table, names their role, and adds
the two stronger facts the false sentence had crowded out: the handler
role holds PutItem only, and adr-sml-deploy is implicitDeny on all seven
read and write actions. Wording is subject to Pouya's read-through —
Q63(a), with a TODO(pouya) beside the copy.
The ruling named one sentence; a vocabulary sweep found the falsehood in
three places, and the audit then found two more. Five paragraphs now
answer "who can see it" and change together.
The tripwire stays permanently, per ruling, and grew from two
alternatives to five. Every alternative is one string that reached dist/.
Proven both ways against the pre-correction page rebuilt from bd282aa:
exit 1 with 5 matches at dist/legal/privacy/index.html:54,67,67,68,72;
exit 0 on the corrected page, self-test 12 patterns / 36 approved
strings.
/med-arb/ — the gloss is struck with no replacement, per ruling. The
strike left "the section above" pointing at the ADRIC rule set and "the
agreement" with no antecedent; both fixed. The bare designations line
sitting under ADRIC's quoted competence requirement is also struck, which
goes beyond the ruling and is flagged for Pouya.
R18 — re-stamped, two-tier: (a)(c)(d) re-verified against a source,
(b)(e)(f)(g) held on a cadence judgement. All seven hold, no shipped
sentence changed. R18's trigger had NO cutover checklist item and had
stamped five extracts of seven; both fixed. Candidate limb (h) flagged.
R10 — fired and unsatisfied; left open on instruction.
The evidence behind the new privacy sentence was weaker than the
sentence. Re-measured: 33 of 33 roles simulated (23 of 26 carried inline
policies nobody had read; the two CDK lookup roles can read the table),
four trust policies, the CloudFormation escalation path for all five
users, 0 federated providers, root recorded. Every read path terminates
at the same two people.
Two review rounds, 36 findings. 35 fixed, 1 declined. Five of round 2's
were defects in round 1's own fixes; stopped at two per D19.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
This commit is contained in:
co-authored by
Claude Opus 5
parent
bd282aa47d
commit
6aaf089b05
@@ -211,7 +211,7 @@ against this row.
|
||||
| `submissionId` | `<uuid>` — **the partition key.** Fixed by the table; the notification email prints this value verbatim so it can be pasted into the console |
|
||||
| `submittedAt` | `<ISO-8601 timestamp>` — an ordinary attribute, not a sort key |
|
||||
| fields | as above |
|
||||
| `sourceIp`, `userAgent` | abuse investigation only |
|
||||
| `sourceIp`, `userAgent` | ~~abuse investigation only~~ — ⚠️ **AMENDED 2026-09-02: that purpose holds for `userAgent` and `submittedAt`, and NOT for `sourceIp`.** Behind the `/api/*` behaviour `requestContext.http.sourceIp` is a CloudFront edge, so it identifies the network rather than the sender and cannot serve an abuse investigation. `/legal/privacy/` now states the two purposes separately — timestamp and user-agent for abuse, the address as something that simply arrives with the request. **A spec and a page disagreeing about WHY data is held is the disclosure PIPEDA actually turns on**, and this row said one thing while the page said another for a day. `docs/09` Part 7.2 measures the field; if it holds the reader's own address, this row and that paragraph both change. `adversarial-reviewer`, round 2 |
|
||||
| `consentAt` | `<ISO-8601 timestamp>` — when the consent box was submitted |
|
||||
| `ttl` | epoch seconds — **the input to automatic deletion; see §Retention for why writing it is not the mechanism** |
|
||||
|
||||
@@ -446,7 +446,7 @@ Plausible or Fathom, cookieless, no consent banner.
|
||||
- [ ] **TTL set and verified by test record.** ⚠️ **THIS ONE BACKS A PUBLISHED PROMISE.** `/legal/privacy/` states that records are deleted automatically after 24 months, and it asserts the **mechanism**, not only the period. The handler writes the `ttl` attribute — epoch seconds, 24 months, confirmed against this spec `[verified 2026-08-31]` — and **writing the attribute is not the mechanism**: TTL must also be enabled on the table, which is a table setting the code cannot see. **`AGENTS.md` §7 holds that status and its stamp; this line does not restate it** — it restated it once, went stale within the day, and had to be pulled back (§12 R19). **The test record is what closes this item, not the status:** `ENABLED` proves the setting, a record written with a near-future `ttl` and observed to vanish proves the behaviour. Tracked as §9 Q60
|
||||
- [x] **PITR enabled** — `ENABLED`, 35-day window `[verified 2026-09-01 — describe-continuous-backups]`
|
||||
- [ ] KMS customer-managed key. **Not on the table: encryption at rest is with the AWS-owned key** `[verified 2026-09-01 — describe-table returns no SSEDescription]`. **Not claimed on `/legal/privacy/`** — the page says "encrypted at rest", which is unconditionally true of every DynamoDB table and does not mention a customer-managed key, so nothing published depends on it. An improvement, not a blocker
|
||||
- [ ] **Table access matches what `/legal/privacy/` says about it.** ⚠️ **IT DOES NOT, AS AT 2026-09-01.** The page says *"nobody else has access to the table… no external administrator"*; the account's `admins` group carries `AdministratorAccess` and has **two** members, and `simulate-principal-policy` returns **allowed** for `dynamodb:GetItem`/`Query`/`Scan` for both. Evidence and commands: `docs/reference/intake-table-access-verification.md`. §9 **Q62**, and it blocks that page going public
|
||||
- [x] ✅ **Table access matches what `/legal/privacy/` says about it — 2026-09-02.** **The access is unchanged; the page now states it.** Pouya ruled *state the truth* rather than *remove the access* (§9 Q62), so the page publishes two administrators of a shared account, and adds the two stronger facts the false sentence had crowded out: the handler role holds `PutItem` **only**, and `adr-sml-deploy` is `implicitDeny` on all seven read **and** write actions. Evidence and commands: `docs/reference/intake-table-access-verification.md`, whose enumeration was **extended on 2026-09-02** — the original screened roles by `list-attached-role-policies` alone, missing that 23 of 26 non-service-linked roles carry inline policies and that the two CDK `lookup` roles can read the table. Four roles can, not two; all four terminate at the same two people. ⚠️ **THIS LINE SAID "IT DOES NOT" FOR A DAY AFTER THE PAGE WAS CORRECTED, AND IT IS A DEFINITION-OF-DONE LIST SOMEONE FOLLOWS AT CUTOVER** — the Q62 sweep was run over `src/` only, so it could not reach a spec. `adversarial-reviewer`, round 1. The sweep across `docs/` is in the Change Log entry
|
||||
- [ ] Both emails send; SPF/DKIM/DMARC aligned; inbox-tested, not spam-tested
|
||||
- [ ] **CloudWatch alarms on Lambda `Errors` and API Gateway `5xx`** — replacing the DLQ item, which is struck: a DLQ on a **synchronously** invoked function never receives anything, so the alarm on its depth would have been permanently green. See §Notification. The handler writes to DynamoDB **before** sending mail, so the protection this item was pointing at is in the code rather than in a queue
|
||||
- [x] **Form usable by keyboard only.** Errors are announced by the browser's own validation, which with no script is the only thing that can announce them inline — `role="alert"` needs a live region and something to write into it
|
||||
|
||||
Reference in New Issue
Block a user