feat(llm): Amazon Bedrock provider over a standard-library SigV4 signer

Kind `bedrock` speaks the Converse API for chat roles and InvokeModel for
Titan and Cohere embeddings. Requests are signed with AWS Signature
Version 4 implemented with crypto/hmac and crypto/sha256 only; the signer
is pinned by tests to signatures botocore produces for the same requests,
with and without a session token, so drift fails the build.

The credential is one secret reference, ACCESS_KEY_ID:SECRET_ACCESS_KEY
[:SESSION_TOKEN], encrypted together like every other provider key. The
region is read from the endpoint host, which must be a Bedrock runtime
host over HTTPS unless private endpoints are explicitly allowed; other
hosts are refused. Provider errors are scrubbed of the secret and token.

Migration 0024 widens the provider kind constraint; existing rows are
untouched and the down migration refuses while bedrock rows exist rather
than dropping configuration. UI kind selects, docs/llm-providers.md,
ADR-0010, ARCHITECTURE and the changelog are updated.
This commit is contained in:
Andy Dixon
2026-08-24 22:03:48 +01:00
parent 7d608c2783
commit f165adc5f2
14 changed files with 497 additions and 6 deletions
+16
View File
@@ -0,0 +1,16 @@
# ADR-0010: Amazon Bedrock over a standard-library SigV4 signer
Date: 2026-09-15
Status: Accepted
## Context
Operators asked for Amazon Bedrock. ADR-0004 kept the LLM layer replaceable and deferred Bedrock until a request signer was justified. The AWS SDK for Go is large, pulls in many transitive modules, and would be the only non-database dependency in the runtime; the surface Noema needs is one signed POST per call.
## Decision
Implement AWS Signature Version 4 in `internal/llm/providers/sigv4.go` with `crypto/hmac` and `crypto/sha256` only, restricted to what Bedrock uses (POST, JSON body, host, date, content type, optional session token, no query string). The signer is pinned by tests to signatures produced by botocore for the same requests, with and without a session token, so drift from the reference implementation fails the build. Chat uses the Converse API, embeddings use InvokeModel with the Titan and Cohere shapes. The credential is one secret reference, `ACCESS_KEY_ID:SECRET_ACCESS_KEY[:SESSION_TOKEN]`, encrypted together like every other provider key. The region is read from the endpoint host, which must be a `bedrock-runtime.<region>.amazonaws.com` URL over HTTPS; other hosts are refused unless the provider explicitly allows private endpoints (tests and local gateways). Provider errors are scrubbed of the secret and token before they reach logs or the UI.
## Consequences
- No new module dependency; the module graph stays auditable.
- Only the signed request shapes Bedrock needs are supported; presigned URLs, S3-style single encoding and chunked signing are out of scope and would need new tests against the reference signer.
- Converse has no JSON-mode switch, so JSON roles rely on the instruction plus Noema's own parsing and validation, as with every provider.
- Temporary credentials expire; rotation is the operator's job through the ordinary secret-reference update path.