feat: production run — Q61 ramp, /404/, CloudFront router, cutover runbook
Build and deploy / build-and-deploy (push) Failing after 4s

Five items of Pouya's production run, 2026-09-01.

Q61 — scroll-padding-top becomes a max() ramp on `10lh - 83px`, with the
plain calc() first as the fallback for engines without `lh`. Hidden focus
stops under minimumFontSize=32: 290 of 1,455 -> 0, control build still
290. Default settings byte-identical (0 differences over 352 page-widths x
17 fields). The 12 residual cells at minimumFontSize=16/20 are pre-existing
and unchanged-or-better; reported, not widened, per instruction.

Intake backend + CloudFront — docs/09-cutover-runbook.md is the
copy-paste sequence for admin execution: every command followed by its
verification and expected output, rollback per part, and Part 10 is Q60's
TTL test. infra/cloudfront/router.js is the trailing-slash function
(30-case suite; 8 fail against the pre-review version, incl. a
protocol-relative open redirect). infra/cloudfront/configure.mjs is
dry-run-by-default and idempotent. scripts/intake-env.mjs emits the six
Lambda env vars from src/data/site.ts.

Four launch blockers found by reading the running system:
  - handler.mjs wrote pk/sk; the live table's key is submissionId with no
    sort key, so every submission would have failed validation silently
  - the Lambda invoke permission is scoped to the old route path
  - 22 of 23 pages 403 without the router function
  - there was no 404 page; src/pages/404.astro adds it

Claims audit (D20 cutover pass) — five gloss over-reaches corrected on
/practice/energy/, /practice/insurance/ (x2), /practice/technology/ and
/med-arb/. Three findings left open for Pouya: Q62, the /med-arb/ gloss,
and Q60.

Q62 — one frozen-tripwire pattern added under the freeze's own breach
exception, with a probe and four negative fixtures. check:claims exits 1
until the false /legal/privacy/ sentence is corrected, so both deploy
paths are blocked by a mechanism rather than by memory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
This commit is contained in:
Pouya Lajevardi
2026-09-02 06:52:20 -04:00
co-authored by Claude Opus 5
parent ca1c2524e1
commit bd282aa47d
30 changed files with 3256 additions and 143 deletions
+103
View File
@@ -0,0 +1,103 @@
/**
* Prints the intake Lambda's six environment variables as the JSON that
* `aws lambda update-function-configuration --environment` takes.
*
* ⚠️ THIS EXISTS SO THAT TWO PUBLISHED COMMITMENTS ARE NEVER RETYPED INTO A
* SHELL COMMAND. `RESPONSE_TIME` and `NO_RETAINER_NOTICE` are read from
* `src/data/site.ts` — the same constants `/contact/` renders — because a
* hand-typed copy of the notice inside the handler had already dropped a clause
* once (`docs/05`, and the handler's own comment on the constant). A deploy
* procedure that asks an operator to paste a sentence is the same defect one
* step further out, and the notice contains an EN DASH in "mediatorparty",
* which is exactly the character a retype loses.
*
* Resource names come from `AGENTS.md` §7 and are passed in, not defaulted from
* a second copy here — except the two that are pure site facts.
*
* usage: node scripts/intake-env.mjs --table <name> --notify <addr> --from <addr>
* node scripts/intake-env.mjs ... --shell # export lines instead
*/
import { CONTACT, NO_RETAINER_NOTICE, SITE } from '../src/data/site.ts';
const args = process.argv.slice(2);
const flag = (name) => {
const i = args.indexOf(`--${name}`);
return i === -1 ? undefined : args[i + 1];
};
const table = flag('table');
const notify = flag('notify');
const from = flag('from');
const missing = [
['--table', table],
['--notify', notify],
['--from', from],
]
.filter(([, v]) => !v)
.map(([k]) => k);
if (missing.length > 0) {
console.error(`missing: ${missing.join(' ')}`);
console.error(
'usage: node scripts/intake-env.mjs --table <dynamodb-table> ' +
'--notify <address> --from <ses-verified-address> [--shell]',
);
console.error('Resource names are in AGENTS.md §7.');
process.exit(2);
}
/* The site origin is not a deploy-time choice: the handler compares the request
Origin against it and redirects to pages ON it, so it must be the canonical
origin `astro.config.mjs` builds against. */
const origin = SITE.url.replace(/\/$/, '');
const vars = {
INTAKE_TABLE: table,
SITE_ORIGIN: origin,
NOTIFY_TO: notify,
MAIL_FROM: from,
RESPONSE_TIME: CONTACT.responseTime,
NO_RETAINER_NOTICE,
};
/* Guards, not decoration. Each one is a failure this project has already had or
has written down as the next one. */
for (const [k, v] of Object.entries(vars)) {
if (typeof v !== 'string' || v.trim() === '') {
throw new Error(
`${k} resolved empty — the handler throws at cold start on that`,
);
}
}
if (!/^https:\/\//.test(origin)) {
throw new Error(`SITE_ORIGIN must be an https origin, got ${origin}`);
}
/* The clause a hand-copy dropped. `docs/01` §/contact/ requires it, so its
absence is a published-disclosure defect rather than a typo. */
if (!NO_RETAINER_NOTICE.includes('create a conflict check')) {
throw new Error(
'NO_RETAINER_NOTICE is missing its fourth clause about not itself creating ' +
'a conflict check — docs/01 §/contact/ requires it. Do not deploy this.',
);
}
if (!//.test(NO_RETAINER_NOTICE)) {
throw new Error(
'NO_RETAINER_NOTICE no longer contains the en dash in "mediatorparty". ' +
'Either the constant changed deliberately, or something re-typed it.',
);
}
if (!/\btwo business days\b/.test(CONTACT.responseTime)) {
throw new Error(
`RESPONSE_TIME is "${CONTACT.responseTime}" — AGENTS.md §4/Q27 is a ` +
'two-business-day commitment. If the commitment changed, /contact/, the ' +
'bio and this all move together.',
);
}
if (args.includes('--shell')) {
for (const [k, v] of Object.entries(vars)) {
console.log(`export ${k}=${JSON.stringify(v)}`);
}
} else {
console.log(JSON.stringify({ Variables: vars }));
}