Files
adr-sml/scripts/intake-env.mjs
T
Pouya LajevardiandClaude Opus 5 bd282aa47d
Build and deploy / build-and-deploy (push) Failing after 4s
feat: production run — Q61 ramp, /404/, CloudFront router, cutover runbook
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
2026-09-02 06:52:20 -04:00

104 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 }));
}