From 2e010e6db4f70007302908cb927c251118401a32 Mon Sep 17 00:00:00 2001 From: Thom Lamb Date: Mon, 31 Aug 2026 19:08:26 -0500 Subject: [PATCH] build(intake): give the container an ANTHROPIC_API_KEY (#223) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The drafting worker needs a credential, and a Portainer stack variable alone does not reach the container — stack variables are interpolated into the compose file as ${VAR}, and a service receives exactly what its own environment block lists. That is how UPLOADS_DIR went missing in #118, and both files say so; this adds the line that makes the variable actually arrive. QA takes it from QA_ANTHROPIC_API_KEY, prefixed like the database and SMTP credentials so production's key cannot be pasted there and silently work. It is also worth a key of its own rather than sharing production's, because this is the only credential in either stack that spends money per call, on a path anybody holding an upload link can trigger. Absent is a working configuration in both, deliberately, which is why prod's line carries `:-` and neither variable joins the always-required list. A submission still arrives, keeps its photos and waits undrafted. Losing somebody's consignment to an expired key would be far worse than an item arriving without its description written, and the photos may be the only copy of something no longer in the sender's hands. USPS is the existing precedent for a credential whose absence degrades rather than fails. The comments say plainly that a spend limit belongs on the key in the Anthropic console, since nothing in this repository can enforce one and #227's submission ceiling bounds the volume rather than the bill. docs/ops/production-stack-cutover.md said the compose file interpolates thirteen names and listed them. It now says fourteen, because that document stakes its usefulness on being checked against the file rather than written from memory — a cutover working from a stale list is how a variable gets left behind, which is the failure the document exists to prevent. Counted from the file: exactly fourteen. composeEnvironment.test.ts passes, 24 tests. It checks that every deployment sets what the validator requires, so adding a variable ahead of a validator entry cannot break it — the entry itself comes with the worker. Ref #223 --- docker-compose.prod.yml | 23 +++++++++++++++++++++++ docker-compose.qa.yml | 19 +++++++++++++++++++ docs/ops/production-stack-cutover.md | 2 +- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 7c8341a..6851b87 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -90,6 +90,14 @@ # USPS_CLIENT_ID Optional. Leave unset to run without address # USPS_CLIENT_SECRET validation; the app degrades gracefully rather than # failing, so an empty value is a working configuration. +# ANTHROPIC_API_KEY Optional. Drafts a listing from a submitted photo +# (#223). Unset means submissions still arrive and wait +# undrafted, which is a working configuration for the +# same reason USPS is. The one credential here that +# spends money per call, and on a path anybody holding +# an upload link can trigger — put a spend limit on the +# key in the Anthropic console, because nothing in this +# repository can enforce one. # # The names above are what this file reads. A stack variable under any other # name is substituted nowhere and never reaches the container, so reconciling @@ -202,6 +210,21 @@ services: # this same container, and a certificate that covers it. Until then the # server warns at boot that the defence is off rather than staying silent. - UPLOADS_BASE_URL=${UPLOADS_BASE_URL:-} + + # Drafts a listing from a submitted photo (#223). + # + # Absent is a working configuration, deliberately. A submission still + # arrives, keeps its photos and waits in the queue undrafted — losing + # somebody's consignment to an expired key would be far worse than an + # item arriving without its description written, and the photos may be + # the only copy of an item no longer in the sender's hands. + # + # The one credential in this stack that spends money per call, on a path + # anybody holding an upload link can trigger. Set a spend limit on the + # key in the Anthropic console — nothing in this repository can enforce + # one, and #227 is the submission ceiling that bounds the volume rather + # than the bill. + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} volumes: # Production's own uploads directory. QA writes to # /volume1/configs/redefined-designs-qa/uploads; sharing this one would diff --git a/docker-compose.qa.yml b/docker-compose.qa.yml index 3fb87fd..161f728 100644 --- a/docker-compose.qa.yml +++ b/docker-compose.qa.yml @@ -40,6 +40,13 @@ # ADMIN_GATE_SECRET — the shared secret Nginx Proxy Manager injects as the # X-Admin-Gate header on the gated location. Both sides must # hold the same value or the admin API returns 403. See #63. +# QA_ANTHROPIC_API_KEY — optional. Drafts a listing from a submitted photo +# (#223). QA_ prefixed like the others so production's key +# cannot be pasted here by accident, and worth its own key +# rather than sharing production's: this is the only +# credential here that spends money per call, and it is +# reachable by anyone holding an upload link. Leave it unset +# and submissions still arrive, undrafted. services: redefined-designs-qa: @@ -114,6 +121,18 @@ services: # enough: stack variables are substituted into this file, not handed to # the container, so a variable with no line here never reaches the app. - ADMIN_GATE_SECRET=${ADMIN_GATE_SECRET} + + # Drafts a listing from a submitted photo (#223). Named QA_ in the stack + # for the same reason as the database and SMTP credentials: pasting + # production's variables in here must not silently work — and this is the + # one credential in the stack that costs money per call, on a path a + # stranger with a link can trigger. + # + # Absent is a working configuration. A submission still arrives, keeps + # its photos and waits in the queue undrafted, because losing somebody's + # consignment to an expired key would be far worse than an item arriving + # without its description written. + - ANTHROPIC_API_KEY=${QA_ANTHROPIC_API_KEY} volumes: # Separate uploads directory. Sharing production's would let a QA run # write into, and a QA teardown delete, real product images. diff --git a/docs/ops/production-stack-cutover.md b/docs/ops/production-stack-cutover.md index 81dedc3..90b2b92 100644 --- a/docs/ops/production-stack-cutover.md +++ b/docs/ops/production-stack-cutover.md @@ -60,7 +60,7 @@ Everything here is lost when the stack is deleted, and the rollback in step 8 is **The stack name**, exactly as Portainer shows it. If it is not `redefined-designs`, note that — the new stack must be created with that name, because the stack name becomes the compose project name and reusing QA's would make Compose reconcile the two against each other. -**Every stack environment variable, name and value.** They belong to the stack, and deleting it discards them. This is the step whose omission is felt hardest. The compose file interpolates thirteen names — `DEMO_MODE`, `DB_PASSWORD`, `SMTP_USER`, `SMTP_PASSWORD`, `SMTP_FROM`, `ADMIN_GATE_SECRET`, `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`, `USPS_CLIENT_ID`, `USPS_CLIENT_SECRET`, `UPLOADS_BASE_URL` and `BACKUP_PASSPHRASE` — and an unset one substitutes to an empty string rather than failing. None of it is recoverable from anything in this repository. Take everything the stack holds rather than working from this list; it is here to say how much there is, and it is checked against the file rather than from memory. +**Every stack environment variable, name and value.** They belong to the stack, and deleting it discards them. This is the step whose omission is felt hardest. The compose file interpolates fourteen names — `DEMO_MODE`, `DB_PASSWORD`, `SMTP_USER`, `SMTP_PASSWORD`, `SMTP_FROM`, `ADMIN_GATE_SECRET`, `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`, `USPS_CLIENT_ID`, `USPS_CLIENT_SECRET`, `UPLOADS_BASE_URL`, `BACKUP_PASSPHRASE` and `ANTHROPIC_API_KEY` — and an unset one substitutes to an empty string rather than failing. None of it is recoverable from anything in this repository. Take everything the stack holds rather than working from this list; it is here to say how much there is, and it is checked against the file rather than from memory. `USPS_CLIENT_ID` and `USPS_CLIENT_SECRET` deserve naming because losing them is the one failure here that is completely silent. Address validation is skipped when they are empty rather than failing, so checkout keeps working and quietly stops validating addresses. Nothing in step 7 catches it, and there is no crash loop to notice.