diff --git a/deploy/RUNBOOK.md b/deploy/RUNBOOK.md index 0d77cfd..e9ccb40 100644 --- a/deploy/RUNBOOK.md +++ b/deploy/RUNBOOK.md @@ -173,6 +173,13 @@ recovered. Without that step a drill where the volume quietly survived would pas would one where the restore did nothing — the seeded database looks much like the backed-up one, which is exactly how this check was found to be necessary. +### Artifact grants are bounded + +A presigned upload is issued for a specific key, content type and **size**. A body that does +not match the declared `sizeBytes` is refused with `403`, so a grant meant for a screenshot +cannot be spent on a gigabyte — read a `403` there as the limit working, not as a +misconfiguration. + ## Platform observability Flakemetry is OpenTelemetry-native and should dogfood its own telemetry: api and worker diff --git a/deploy/compose/README.md b/deploy/compose/README.md index 4ce76e5..434be4e 100644 --- a/deploy/compose/README.md +++ b/deploy/compose/README.md @@ -144,13 +144,27 @@ the whole path is drilled in CI on every change: it destroys the database volume the instance back from a dump. See the [runbook](../RUNBOOK.md#backups--disaster-recovery) for RPO/RTO and the reasoning. -## One thing to verify yourself - -Artifact upload end to end — reporter → presigned PUT → browser download — is **not covered by -CI**, so treat it as unproven until you have watched a screenshot appear in the dashboard. - -The failure mode to expect is a `403` on the presigned URL. SigV4 signs the `Host` header, so -a URL signed for `minio:9000` is rejected when a browser presents `artifacts.example.com`. -That is why every service here signs against the public artifact host even for traffic that -never leaves the machine. If you move to real S3 or R2, set `FLAKEMETRY_S3_ENDPOINT` to the -same hostname your CI runners and browsers will actually connect to. +## Verified, and what that covers + +This stack has been run end to end — not only `docker compose config`: + +| | | +| --- | --- | +| Only Caddy publishes | 80 and 443; Postgres and MinIO are reachable only inside the network | +| Both apps answer `/health` | `{"service":"api"}` and `{"service":"web"}` on their own hostnames, which is why they are separate hostnames | +| Artifacts round-trip | presign → `PUT` → presigned `GET` returns the same bytes | +| The object store refuses unsigned requests | `403 AccessDenied` on a plain `GET` | + +**A presigned upload is bound to the size it was issued for.** A body larger than the +declared `sizeBytes` is refused with `403`, so a grant for a screenshot cannot be spent on a +gigabyte. Worth knowing before you read a `403` as a misconfiguration. + +The one thing local testing cannot cover is certificate issuance: Caddy uses an internal +certificate for a `.localhost` name and only talks to Let's Encrypt for a real domain. Point +the DNS records at the host before the first start, or the initial order fails. + +If you move to real S3 or R2, set `FLAKEMETRY_S3_ENDPOINT` to the hostname your CI runners +and browsers actually connect to. SigV4 signs the `Host` header, so a URL signed for +`minio:9000` is rejected the moment a browser presents the public name — which is why every +service here signs against the public artifact host even for traffic that never leaves the +machine.