Tracing: always export error and slow traces, plus launch policy docs (#474) - #476
Merged
Conversation
- tracing_sampler.go: wraps the OTLP exporter and root sampler so every error span and every span slower than 2s is always exported, regardless of the configured head-sampling ratio, instead of a flat ratio that almost never picks the one failing request that matters - docs/API_STABILITY.md: what /v1 promises, what counts as a breaking vs non-breaking change, experimental-endpoint marking, and the deprecation mechanism (headers + changelog); flags /v1/admin/db as needing an explicit stable-vs-experimental decision before freeze - docs/LAUNCH_CHECKLIST.md + docs/ROLLBACK_RUNBOOK.md: checklist/ runbook templates, not claimed as executed — includes a real finding that all 25 existing migrations are forward-only (zero .down.sql files), so rollback across most schema changes has no automated path today Closes #457 Closes #458 Closes #459 Closes #460
Always-export error/slow traces, API stability policy, launch checklist and rollback runbook
#474 added recordOnlySampler and alwaysKeepExporter with no tests, and the logic is easy to break silently: a Drop decision cannot be reconsidered at export time, so reverting RecordOnly to Drop would restore the exact behaviour the change exists to prevent — errors and slow requests vanishing under a low sampling ratio — with nothing failing. Covers: - The sampler never returns Drop, tested at ratio 0 so any span surviving proves the RecordOnly path kept it rather than head sampling. - The exporter keeps error spans and spans slower than slowSpanThreshold, and discards fast successful unsampled ones. - A span exactly at the threshold is discarded, pinning the boundary so `>` cannot drift to `>=` unnoticed. Both assertions were verified against deliberately broken copies: switching RecordOnly back to Drop fails the first test, and relaxing the threshold comparison fails the boundary case. Without that check a passing test tells you nothing.
`SBOM + scan (go-api)` fails on CVE-2026-14456 — libcrypto3 3.5.7-r0, a fixable HIGH (OpenSSL denial of service via unbounded memory). It is a required status check, so this one finding blocks every PR in the repo, including ones that touch nothing related. A digest bump does not fix it: the pin here is already the current alpine:3.22 (3.22.5, verified with `docker buildx imagetools inspect`). The patched libcrypto3 3.5.8-r0 is published in the v3.22 package repo but no rebuilt base image carries it yet. `apk upgrade --no-cache` in the runtime stage picks up the patched package while leaving the base pinned by digest, so builds stay reproducible against a known image and still ship patched system libraries. Verified 3.5.8-r0 is the current libcrypto3 in the alpine v3.22 main repo. Not applied to the other Dockerfiles: only go-api is failing the scan, and the same change elsewhere should follow its own scan evidence rather than being copied on assumption.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Carries #474 (always-export error/slow traces, API stability policy, launch checklist, rollback runbook) with test coverage added for the sampler.
#474 was merged onto this branch rather than straight into
dev, matching how #469, #471 and #473 were handled.What lands
Always-export error and slow traces (#457) — the substantive piece, and the design is right.
Head sampling decides at span start, before it can know a request will fail. A low fixed ratio therefore means the one failing trace you actually need is almost never the one kept. This fixes that in two halves:
recordOnlySamplerreturnsRecordOnlyinstead ofDropfor spans the ratio sampler would discard, so the span is still recordedalwaysKeepExporterinspects each finished span and exports it if it errored or ran longer than 2s, discarding the restNet effect: every error and every slow request is exported regardless of ratio, while ordinary traffic still samples normally.
API stability policy (#458) —
docs/API_STABILITY.md: what/v1/promises, what may change freely, what forces a/v2/, how experimental endpoints are marked, and the deprecation mechanism.Launch checklist (#459) and rollback runbook (#460) — both as documents.
Added on top (
bf4ffa0)#474 shipped the sampler with no tests, and this logic breaks silently: a
Dropdecision cannot be reconsidered at export time, so revertingRecordOnlytoDropwould restore exactly the behaviour the change exists to prevent, with nothing failing.Added coverage for both halves:
TestAlwaysRecordSamplerNeverDropsDrop, checked at ratio 0 so a surviving span provesRecordOnlykept itTestAlwaysKeepExporter...>cannot drift to>=Both assertions were verified against deliberately broken copies of the sampler — switching
RecordOnlyback toDropfails the first, relaxing the comparison fails the boundary case. A passing test that cannot fail is worth nothing, so this was checked rather than assumed.Issue status
Closed #458 by hand — the policy covers every criterion. Auto-close does not fire when the base is not the default branch.
Left open with specifics recorded on each:
RecordOnlymeans every span is fully recorded in memory even when never exported, so memory cost is now paid on 100% of requests rather than the sampled fraction. Worth measuring under the soak test — it is the one real trade-off here.Worth flagging from the rollback runbook
The author checked and found
database/migrations/has 25 forward migrations and zero.down.sqlfiles — so no migration is reversible in any automated sense today, and a rollback across a schema change means hand-writing reverse SQL under incident pressure.They flagged it rather than papering over it, and recommend requiring a
.down.sqlper migration plus expand/contract for anything not cleanly reversible. That is arguably its own issue and blocks #460.Verification
go build,go vet,gofmtandgo test ./...all pass.Also cherry-picked the
apk upgradefix forCVE-2026-14456fromintegration/launch-fixes, since this branch is offdevand would otherwise fail the same required trivy check.