3829b6add3
* feat(ai): resolve the Claude backend from the environment Tier 1 of #1406: a self-hosted deployment can now run every AI feature on a plain ANTHROPIC_API_KEY, with no AWS account. Hosted behaviour is unchanged. lib/ai/provider.ts resolves the backend once, from the environment: AI_PROVIDER explicit override, bedrock|anthropic AWS static key pair Bedrock ANTHROPIC_API_KEY the direct Anthropic API nothing set Bedrock, so the AWS credential provider chain (instance profile, IRSA) still resolves Bedrock deliberately wins when both credential sets are present. EU residency in eu-north-1 is a BFL/GDPR posture rather than a default, so adding an Anthropic key for an experiment must not silently move production inference out of the region. AI_PROVIDER is the way to say you meant it. Model ids are written bare in code and prefixed to eu.anthropic.* only for Bedrock, which needs the cross-region inference profile for on-demand throughput. An operator override that already carries a prefix passes through untouched, so BEDROCK_MODEL_ID and friends keep working as written. Converted call sites: the agent composer, invoice-inbox extraction, the document-extraction model label, and both receipt-hunt clients. The last two are not named in the issue, which predates receipt-hunt landing in main. @anthropic-ai/sdk is declared at 0.95.0, the version @anthropic-ai/bedrock-sdk 0.29.1 already pulled in transitively, so the lockfile dedupes to one copy with no new download. scripts/smoke-bedrock.ts becomes scripts/smoke-ai.ts and grows two steps. Unit tests can only prove which provider and model id get resolved; they cannot prove the resulting request is one the backend accepts. The script now sends real traffic over all three shapes the app uses: a plain create, a streamed turn carrying adaptive thinking, an effort level, an hour-long cache breakpoint and a tool, and document extraction end to end when given a file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Bjorn Bergenheim <29535152+bjornbergenheim@users.noreply.github.com> * docs(self-hosting): document the AI smoke test The script added alongside the provider split is what closes the #1406 acceptance criterion ("document extraction and the assistant both work"), so a self-hoster needs to know it exists. Covers both invocations and states that it exits non-zero, which is what makes it usable as a post-deploy check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Bjorn Bergenheim <29535152+bjornbergenheim@users.noreply.github.com> * test(ai): split the smoke test's thinking probe from its tool probe The combined probe could not falsify what it claimed to. It asked a question that needs a tool call, so the tool was used and adaptive thinking correctly declined to reason about it: the zero thinking-block count that came back was uninformative rather than a signal. 2a keeps the tool and drops thinking. 2b asks a question with several dependent steps (reverse charge, then a partial deduction, then the affected boxes) so that a model honouring the parameter must reason, and reports the thinking text length as well as the block count, since display:"summarized" can yield blocks with empty text. The cached system prompt is also padded past the 1024-token minimum cacheable prefix. Below that the API caches nothing and reports no error, so the old probe's cache counters read zero whether or not caching worked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Bjorn Bergenheim <29535152+bjornbergenheim@users.noreply.github.com> * fix(document-extraction): stop requiring AWS_REGION in the manifest The extension now needs one of two credential sets, AWS static keys or ANTHROPIC_API_KEY, and the manifest schema cannot express "one of". Since requiredEnvVars only drives a build-time warning and never gates anything, listing AWS_REGION told every self-hoster running the direct API to set a variable that has no effect for them. The description was also still promising Sonnet 4.6 via Bedrock specifically, which is no longer what the extension does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Bjorn Bergenheim <29535152+bjornbergenheim@users.noreply.github.com> * fix(ai): read documentKind defensively in the smoke test The field arrived with the receipt-aware extraction work, so referencing it directly stops the script compiling against any checkout from before that landed. tsconfig includes **/*.ts and next.config does not disable type checking, so on such a checkout this failed the production build rather than just the script: caught while preparing a test branch for a self-hosted instance that had not synced yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Bjorn Bergenheim <29535152+bjornbergenheim@users.noreply.github.com> * fix(deps): restore the nested @swc/helpers entry in the lockfile Declaring @anthropic-ai/sdk with `npm install --package-lock-only` also pruned node_modules/next-intl/node_modules/@swc/helpers@0.5.23, an optional peer entry the local npm 11 considers redundant and the image's npm 10.9.8 does not. The result passed every local check and failed `npm ci` inside the Docker build, which is the only place the lockfile is actually enforced. The lockfile is now the previous one plus the single root dependency line, verified with `npm ci --dry-run`. @anthropic-ai/sdk needed nothing else: it was already in the tree as a transitive dependency of @anthropic-ai/bedrock-sdk. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Bjorn Bergenheim <29535152+bjornbergenheim@users.noreply.github.com> * Update DECISIONS.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update Docker documentation for AI provider credentials Clarify the role of credentials in AI provider selection and document extraction requirements. * Update SELF-HOSTING.md with smoke-ai script details Clarify usage of smoke-ai script for credential checks and document extraction. * Improve error handling and logging in smoke-ai script * fix(ai): complete plain-key self-hosting path Signed-off-by: Emil <emilmattsson14@gmail.com> --------- Signed-off-by: Bjorn Bergenheim <29535152+bjornbergenheim@users.noreply.github.com> Signed-off-by: Emil <emilmattsson14@gmail.com> Co-authored-by: Bjorn Bergenheim <29535152+bjornbergenheim@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
174 lines
9.1 KiB
Bash
174 lines
9.1 KiB
Bash
# Local development environment variables.
|
|
# Copy to .env and fill in the values: cp .env.example .env
|
|
|
|
# ── Required ──────────────────────────────────────────────
|
|
# Supabase project credentials (Dashboard -> Settings -> API)
|
|
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-or-publishable-key
|
|
SUPABASE_SERVICE_ROLE_KEY=your-service-role-or-secret-key
|
|
|
|
# App base URL (local dev)
|
|
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|
|
|
# Secret for authenticating cron/scheduled requests.
|
|
# Any non-empty random string for local dev: openssl rand -hex 16
|
|
CRON_SECRET=generate-a-random-secret
|
|
|
|
# Receipt hunt (nightly matcher, 05:30 UTC): comma-separated company ids the
|
|
# hunt may stage proposals for. Unset means it runs for nobody, so enabling it
|
|
# is always a deliberate act rather than a side effect of deploying.
|
|
# Model used to resolve bank descriptors to merchants and to decide which mail
|
|
# is the receipt for which purchase. Falls back to BEDROCK_MODEL_ID.
|
|
RECEIPT_HUNT_MODEL_ID=
|
|
# Floor for accepting the model's pairing. Every proposal is human-reviewed, so
|
|
# this trades recall against review effort, not against correctness.
|
|
RECEIPT_HUNT_MIN_CONFIDENCE=
|
|
RECEIPT_HUNT_COMPANY_IDS=
|
|
|
|
# Session timeouts are opt-in per user (user_preferences.auto_logout, toggled
|
|
# in Settings > Security): users who have not opted in stay signed in for the
|
|
# full Supabase session lifetime. The variables below set the limits that
|
|
# apply to opted-in users: 30 minutes idle, 12 hours absolute, warning 2
|
|
# minutes before expiry. Set a timeout to 0 to disable that limit entirely.
|
|
# Self-hosted deployments default both limits to 0 unless overridden.
|
|
# NEXT_PUBLIC_SESSION_TIMEOUT_FORCE_ALL=true enforces the timeouts for every
|
|
# user regardless of their preference (emergency lever / strict deployments).
|
|
# The signing key falls back to SUPABASE_SERVICE_ROLE_KEY; set a dedicated
|
|
# random secret if session signing should rotate independently.
|
|
# NEXT_PUBLIC_SESSION_IDLE_TIMEOUT_MS=1800000
|
|
# NEXT_PUBLIC_SESSION_ABSOLUTE_TIMEOUT_MS=43200000
|
|
# NEXT_PUBLIC_SESSION_WARNING_MS=120000
|
|
# NEXT_PUBLIC_SESSION_TIMEOUT_FORCE_ALL=false
|
|
# SESSION_TIMEOUT_SECRET=
|
|
|
|
# Self-hosted only: set to true when public signup is turned off in your
|
|
# GoTrue/Supabase auth config (GOTRUE_DISABLE_SIGNUP / "Allow new users to
|
|
# sign up" off). GoTrue offers no clean server-side read of that setting, so
|
|
# this flag mirrors it. When true, inviting a teammate who has no account
|
|
# provisions the account server-side via the auth admin invite API (GoTrue
|
|
# must have SMTP configured to deliver that mail) instead of relying on
|
|
# public /register, which GoTrue would reject with "Signups not allowed".
|
|
# The GoTrue redirect URI allow-list (URI Allow List / GOTRUE_URI_ALLOW_LIST)
|
|
# must include /invite/* or the invite email's redirect silently falls back
|
|
# to SITE_URL.
|
|
# Hosted keeps this unset: public signup stays open there.
|
|
# AUTH_SIGNUPS_DISABLED=false
|
|
|
|
# Sign in with Google. Requires the Google provider to be configured in
|
|
# Supabase/GoTrue first (Google Cloud OAuth client + redirect URI):
|
|
# https://supabase.com/docs/guides/auth/social-login/auth-google
|
|
# The button stays hidden until this is true.
|
|
# NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=true
|
|
|
|
# ── Optional: extension features (core runs without these) ─
|
|
# AI features (document extraction + AI assistant). Two ways to provide a key;
|
|
# set one of them. AI_PROVIDER (bedrock|anthropic) forces the choice if both
|
|
# are present, which otherwise resolves to Bedrock.
|
|
#
|
|
# 1. Claude via AWS Bedrock. Needs an AWS account with Bedrock model access to
|
|
# Claude. Keeps inference in eu-north-1, which is what hosted runs.
|
|
# AWS_ACCESS_KEY_ID=
|
|
# AWS_SECRET_ACCESS_KEY=
|
|
# AWS_REGION=eu-north-1
|
|
# BEDROCK_MODEL_ID=
|
|
#
|
|
# 2. Claude via the direct Anthropic API. No AWS account needed, so this is
|
|
# usually the self-hosted option. Note that it has no EU-residency
|
|
# guarantee: use Bedrock if you need one.
|
|
# ANTHROPIC_API_KEY=
|
|
# AI_PROVIDER=
|
|
# Bank connections (Enable Banking)
|
|
# ENABLE_BANKING_APP_ID=
|
|
# ENABLE_BANKING_PRIVATE_KEY=
|
|
# Accounting integrations
|
|
# FORTNOX_CLIENT_ID=
|
|
# FORTNOX_CLIENT_SECRET=
|
|
# FORTNOX_REDIRECT_URI=
|
|
# Björn Lundén app credentials (OAuth2 client credentials; per-company
|
|
# User-Key is entered by the user in the migration wizard)
|
|
# BJORN_LUNDEN_CLIENT_ID=
|
|
# BJORN_LUNDEN_CLIENT_SECRET=
|
|
# WhatsApp receipt intake (whatsapp-inbox extension, Meta Cloud API).
|
|
# ACCESS_TOKEN: system-user permanent token with whatsapp_business_messaging
|
|
# scope only. PHONE_NUMBER_ID: the Graph object id of the sending number.
|
|
# APP_SECRET verifies X-Hub-Signature-256 on the webhook; VERIFY_TOKEN is the
|
|
# GET-handshake shared secret you also enter in the Meta app dashboard.
|
|
# PHONE_HASH_KEY: random pepper for phone lookup hashes (openssl rand -hex 32).
|
|
# PHONE_ENCRYPTION_KEY: 32-byte hex AES-256-GCM key (openssl rand -hex 32).
|
|
# WHATSAPP_ACCESS_TOKEN=
|
|
# WHATSAPP_PHONE_NUMBER_ID=
|
|
# WHATSAPP_APP_SECRET=
|
|
# WHATSAPP_VERIFY_TOKEN=
|
|
# WHATSAPP_PHONE_HASH_KEY=
|
|
# WHATSAPP_PHONE_ENCRYPTION_KEY=
|
|
# Optional: the public number as E.164 digits (e.g. 46766867041) for the
|
|
# wa.me deep link in settings. Unset = resolved from the Graph API instead.
|
|
# WHATSAPP_PUBLIC_NUMBER=
|
|
# Bolagsverket: digital inlämning av årsredovisning (bolagsverket extension).
|
|
# BOLAGSVERKET_ENV is test | accept | prod (default test) and also caps which
|
|
# environment a company may select in settings (test < accept < prod).
|
|
# Certificate material is read from env ONLY (PEM or base64-wrapped PEM):
|
|
# never from extension settings or the database.
|
|
#
|
|
# SECRET CUSTODY (prod): never keep the real mTLS private key in a plaintext
|
|
# .env file. Inject these at runtime from a secrets manager (Vercel encrypted
|
|
# env vars, AWS Secrets Manager, Vault, Doppler, …), restrict read access to
|
|
# the deploy pipeline, and rotate the client certificate/key on the cadence
|
|
# agreed with Bolagsverket (and immediately on suspected exposure). Outbound
|
|
# hosts are pinned per environment in extensions/general/bolagsverket/lib/
|
|
# client.ts (HOSTS): the endpoint is not configurable via env.
|
|
# BOLAGSVERKET_ENV=
|
|
# BOLAGSVERKET_CLIENT_CERT=
|
|
# BOLAGSVERKET_CLIENT_KEY=
|
|
# BOLAGSVERKET_CA=
|
|
# Safety gate: enable only after agreement, certificate, test-bank fixtures,
|
|
# acceptance testing, and production runbook approval are complete.
|
|
# BOLAGSVERKET_FILING_ENABLED=false
|
|
# NEXT_PUBLIC_BOLAGSVERKET_FILING_ENABLED=false
|
|
# BOLAGSVERKET_ARELLE_VALIDATOR_URL=
|
|
# BOLAGSVERKET_ARELLE_VALIDATOR_TOKEN=
|
|
|
|
# ── Optional: product analytics + error tracking (PostHog) ─
|
|
# Hosted only. Self-hosted deployments never load PostHog: isAnalyticsEnabled()
|
|
# (lib/analytics/enabled.ts) short-circuits on NEXT_PUBLIC_SELF_HOSTED=true, and
|
|
# no __NEXT_PUBLIC_POSTHOG_*__ sentinel is baked into the Docker image, so an
|
|
# operator cannot accidentally ship their users' behaviour to our project.
|
|
#
|
|
# The token is the PUBLIC project token (phc_...). It is embedded in the client
|
|
# bundle by design and is not a secret. Leave unset to run with analytics off.
|
|
# Browser traffic goes through the same-origin /rl rewrite in next.config.ts;
|
|
# NEXT_PUBLIC_POSTHOG_HOST is only used by the server-side SDK.
|
|
# NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN=
|
|
# NEXT_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com
|
|
#
|
|
# PostHog Support identity verification. A REAL SECRET (it also authenticates
|
|
# external API requests), so no NEXT_PUBLIC_ prefix: it must never reach the
|
|
# client bundle. Only the derived per-user HMAC crosses to the browser
|
|
# (lib/analytics/identity-hash.ts). Unset means support tickets are scoped to
|
|
# one browser session and users recover them by email link, which is the
|
|
# normal state for local dev, CI and self-hosted.
|
|
# POSTHOG_SECRET_API_KEY=
|
|
|
|
# ── Optional: error tracking / observability ──────────────
|
|
# The app routes every error-level log line, and anything flagged
|
|
# `alert: true`, to a provider-agnostic sink (lib/observability). When the
|
|
# PostHog token above is set, lib/init.ts registers the PostHog adapter
|
|
# (lib/analytics/posthog-observability.ts) as that sink; otherwise the sink
|
|
# stays a NO-OP, the PostHog client is never constructed and nothing is ever
|
|
# sent. (The SDK is still bundled in those builds, since the imports are
|
|
# static; it simply never initialises.) The variables below are for a
|
|
# DIFFERENT vendor adapter and still change nothing on their own.
|
|
#
|
|
# Names are generic placeholders. When a provider is picked, either keep these
|
|
# and read them in the adapter, or replace them with the vendor's own names.
|
|
# OBSERVABILITY_DSN= # server-side ingest endpoint / key
|
|
# NEXT_PUBLIC_OBSERVABILITY_DSN= # browser ingest endpoint / key, if used
|
|
# Any adapter reading these MUST forward only post-redaction payloads
|
|
# (lib/observability/redact.ts): see docs/security/logging-and-observability.md
|
|
# Optional overrides. Both have sensible defaults: the environment falls back
|
|
# to VERCEL_ENV then NODE_ENV, and the release falls back to
|
|
# NEXT_PUBLIC_BUILD_ID (the commit sha next.config.ts inlines at build time)
|
|
# then VERCEL_GIT_COMMIT_SHA. Set them only when tagging must differ.
|
|
# OBSERVABILITY_ENVIRONMENT=
|
|
# OBSERVABILITY_RELEASE=
|