fix: refute (ar)'s intake finding; fix the D20 gloss class; add X-Robots-Tag on *.pdf
Build and deploy / build-and-deploy (push) Failing after 4s
Build and deploy / build-and-deploy (push) Failing after 4s
Pouya's rulings of 2026-09-03, in five parts. 1. THE INTAKE FORM IS NOT BROKEN. (ar) was wrong. docs/09 §7.1 verbatim — POST /api/intake with an Origin header — returns 303 to /contact/could-not-send/ with access-control-allow-origin echoed; the same probe without Origin returns 403. A bare POST 403s BY DESIGN and §7.1 says so three lines below the probe it prescribes: "403 means the Origin header did not arrive". The earlier finding read a status code without reading the document that defines it. Second time in two days. CLAUDE.md's instrument list goes eight to nine. D20 findings 12 and 19 fall with it; §7.2 (that both emails arrive) is still owed. The correction is APPENDED as entry (as); (ar) stands unedited. 2. The privacy retention comment was stale, not a defect — superseded by his decision to publish and confirm after launch, reading from 2026-09-04. Reworded; the TODO(pouya) came off with the gate it enforced. The mechanism finding survives: it was a JSX comment, stripped by Astro, so no build or deploy path could see it. A publication gate that lives only in a stripped comment is not a gate. §9 Q60 corrected. 3. The gloss class is fixed — 15 of the 20 D20 findings, 14 distinct edits across 9 files, under the rule "the gloss may say no more than the extract says; no new claims, no new sources". Swept three unpublished insights drafts too, and corrected the wrong CAA attribution at its source in docs/reference/, which is where a fixed page re-seeds. /bio/ changed, so the committed PDF is regenerated (89,549 B, 1 page asserted). Three findings outstanding: 10 needs a ruling, 11 is ruled and owed via Q60, 13 needs him to have said it. R1 is not one of the twenty. 4. X-Robots-Tag cannot be done with S3 object metadata — --metadata writes user metadata, returned as x-amz-meta-x-robots-tag, which no crawler reads. Built as the CloudFront response-headers policy docs/06 has specified all along: configure.mjs section 4. It needs a --apply run, not a deploy. The policy is cloned from whatever is attached at run time and reconciled on every run, because a response-headers policy replaces rather than merges. 5. Headshot deferred as an open non-defect. The master and the srcset ladder are both fine; Astro passes no quality, so AVIF encodes at sharp's default 50 and is served first. Two review rounds, 29 findings, all resolved, none declined; stopped at two per D19. NINE of round 2's fourteen were defects in round 1's own repairs — including a fix that harmonised both /fees/ rows onto wording that was itself unregistered, publishing an unsourced fee term twice where it had been once. Gates, exit status read for each: check 0 (0 errors, 0 warnings, 0 hints), build 0 (23 pages), check:claims 0, check:intake 0, og:proof 0, lint 0, minifier grep exit 1, router.test.mjs 30/30. Lighthouse NOT run. Nothing deployed and nothing applied to the distribution. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
This commit is contained in:
co-authored by
Claude Opus 5
parent
b9523817e2
commit
02739adac9
@@ -309,7 +309,7 @@ status, not the absence of an error.
|
||||
|
||||
---
|
||||
|
||||
## Part 3 — Apply the three distribution changes
|
||||
## Part 3 — Apply the four distribution changes
|
||||
|
||||
One script, `infra/cloudfront/configure.mjs`, because the alternative is
|
||||
hand-editing a 300-line JSON document and posting it back with an `IfMatch` ETag.
|
||||
@@ -328,18 +328,39 @@ Part 0.3 records is exactly:
|
||||
resolved Managed-CachingDisabled = 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
|
||||
resolved Managed-AllViewerExceptHostHeader = b689b0a8-53d0-40ab-baf2-68738e2966ac
|
||||
|
||||
4 change(s) to distribution E1OK7G98KNKUTA (ETag …):
|
||||
6 change(s) to distribution E1OK7G98KNKUTA (ETag …):
|
||||
+ DefaultCacheBehavior.FunctionAssociations viewer-request -> arn:…:function/adr-sml-router
|
||||
+ CustomErrorResponses += 404 -> /404.html with status 404
|
||||
+ Origins += intake-api -> …execute-api… (https-only, TLSv1.2)
|
||||
+ CacheBehaviors += /api/* -> intake-api, CachingDisabled, AllViewerExceptHostHeader, POST allowed
|
||||
+ create response-headers policy adr-sml-pdf-noindex (SecurityHeadersConfig cloned from … + X-Robots-Tag: noindex)
|
||||
+ CacheBehaviors += *.pdf -> <s3-origin>, default cache policy, adr-sml-pdf-noindex (policy id created in the same --apply pass)
|
||||
|
||||
DRY RUN — nothing was sent. Re-run with --apply to write it.
|
||||
```
|
||||
|
||||
Fewer than four changes means part of this is already done — read which lines are
|
||||
prefixed `·` (already present) and carry on. More than four, or a different set,
|
||||
means the distribution is not in the state 0.3 recorded: stop and re-read it.
|
||||
⚠️ **SECTION 4 CANNOT SHOW THE POLICY ID IN A DRY RUN, AND SAYS SO — IT IS
|
||||
STILL ONE `--apply`.** The `*.pdf` behaviour has to carry the response-headers
|
||||
policy's id, and on a first run that policy does not exist yet, so the dry run
|
||||
prints the behaviour it *would* add with `(policy id created in the same --apply
|
||||
pass)` where the id goes. **A single `--apply` creates the policy and adds the
|
||||
behaviour in one call — do not run it twice.** The dry run reports both changes
|
||||
either way; one that listed only the policy would hide the half that touches a
|
||||
distribution serving 23 pages.
|
||||
|
||||
Fewer than six changes means part of this is already done — read which lines are
|
||||
prefixed `·` (already present) and carry on. **On the live distribution as at
|
||||
2026-09-03, changes 1–3 are applied and you should see exactly the last two.**
|
||||
More than six, or a different set, means the distribution is not in the state 0.3
|
||||
recorded: stop and re-read it.
|
||||
|
||||
⚠️ **AND `adr-sml-pdf-noindex` IS RECONCILED ON EVERY RUN, NOT ONLY CREATED.** A
|
||||
response-headers policy **replaces** rather than merges, so the PDF policy has to
|
||||
carry everything the default behaviour's policy carries. If they have diverged —
|
||||
someone adds the `Content-Security-Policy` or `Permissions-Policy` that
|
||||
`docs/05` specifies to one and not the other — the script **throws and names the
|
||||
diff** rather than passing. That is deliberate: the failure it guards is the PDF
|
||||
being served different headers from the pages, which is silent.
|
||||
|
||||
```bash
|
||||
node infra/cloudfront/configure.mjs --dist "$DIST_ID" --api-domain "$API_DOMAIN" \
|
||||
@@ -352,11 +373,43 @@ echo "deployed: $?"
|
||||
|
||||
```bash
|
||||
aws cloudfront get-distribution-config --id "$DIST_ID" \
|
||||
--query 'DistributionConfig.{Fn:DefaultCacheBehavior.FunctionAssociations.Items[].EventType,Err:CustomErrorResponses.Items[].{Code:ErrorCode,Page:ResponsePagePath,Status:ResponseCode},Beh:CacheBehaviors.Items[].{P:PathPattern,O:TargetOriginId,Methods:AllowedMethods.Items},Origins:Origins.Items[].Id}'
|
||||
--query 'DistributionConfig.{Fn:DefaultCacheBehavior.FunctionAssociations.Items[].EventType,Err:CustomErrorResponses.Items[].{Code:ErrorCode,Page:ResponsePagePath,Status:ResponseCode},Beh:CacheBehaviors.Items[].{P:PathPattern,O:TargetOriginId,RHP:ResponseHeadersPolicyId,Fn2:FunctionAssociations.Items[].EventType,Methods:AllowedMethods.Items},Origins:Origins.Items[].Id}'
|
||||
```
|
||||
|
||||
**Expect:** `Fn: ["viewer-request"]`; one error response `404 → /404.html → 404`;
|
||||
one behaviour `/api/*` → `intake-api` with POST in its method list; two origins.
|
||||
**two** behaviours — `/api/*` → `intake-api`, POST in its method list, **no
|
||||
`RHP` and `Fn2: null`** (the association is withheld there deliberately: a 301
|
||||
would turn the form's POST into a GET and drop the body), and `*.pdf` → the S3
|
||||
origin **with an `RHP` id and `Fn2: ["viewer-request"]`**; two origins.
|
||||
|
||||
**Then verify the header actually arrives, because the config landing is not the
|
||||
same fact:**
|
||||
|
||||
```bash
|
||||
curl -D /tmp/pdf.h -o /dev/null "$SITE/pouya-lajevardi-bio.pdf"
|
||||
echo "curl_exit=$?" # curl's OWN status, on its own line
|
||||
for h in x-robots-tag strict-transport-security x-content-type-options \
|
||||
x-frame-options x-xss-protection referrer-policy; do
|
||||
printf '%-28s %s\n' "$h" "$(grep -ic "^$h:" /tmp/pdf.h)"
|
||||
done
|
||||
```
|
||||
|
||||
**Expect** `curl_exit=0` and **`1` against every one of the six** — the five
|
||||
security headers *and* `x-robots-tag`.
|
||||
|
||||
⚠️ **THE SHAPE OF THIS BLOCK IS THE POINT, and its first version got all three
|
||||
wrong.** It piped `curl -sI` into one `grep -E` with six alternatives and read
|
||||
`$?`. That reports **grep's** status, not curl's, so a DNS failure, a TLS failure
|
||||
and a 5xx all read as `exit=1` — indistinguishable from "the headers are
|
||||
missing", with `-s` deleting the message that would have told them apart. And an
|
||||
alternation exits **0 if ANY ONE** matches, so `exit=0` would not have meant the
|
||||
five arrived, which is the only regression the block exists to catch. Counting
|
||||
each header separately is what makes a partial clone visible. (`CLAUDE.md`: never
|
||||
suppress stderr, never read a pipeline's status as its first command's, and a
|
||||
uniform pass is the result that ends a check rather than starting one.)
|
||||
|
||||
⚠️ **If any of the five security headers reads `0`, the policy did not clone them
|
||||
and the PDF has LOST headers it had before this change.**
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user