Files
BountyBoard/DECISIONS.md
T
etalon f2c534f636 feat: store layer with indexes, optimistic concurrency, GridFS, credential crypto (phase 2)
- mongo-driver v2 connection with bounded startup retry + /readyz gate
- idempotent creation of all §4.9 indexes
- UpdateVersioned: version-filtered updates, conflict vs not-found errors
- AES-256-GCM Seal/Open for ticketing credentials, base64(nonce|ct)
- GridFS file store: MIME sniffing, MAX_UPLOAD_MB cap, sha256 metadata
- HMAC-signed short-lived file URL tokens (§5.1, 1h TTL)
- integration tests against compose Mongo via docker-compose.test.yml overlay

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:17:26 +02:00

60 lines
3.5 KiB
Markdown

# Decisions
Spec-silent choices, recorded as required by the build instructions.
## Phase 1
- **Go toolchain 1.26.4** (latest stable; spec requires ≥ 1.22). Installed at
`/usr/local/go`.
- **Module name `bountyboard`** — no remote repository exists; a single-word
module keeps import paths short (`bountyboard/internal/...`).
- **Package in `internal/http` is named `httpx`** to avoid clashing with
stdlib `net/http` inside its own files. The directory stays `internal/http`
per spec §14; importers alias it (`httpx "bountyboard/internal/http"`).
- **`/readyz` uses a pluggable checker registry** (`AddReadinessCheck`):
required checks (Mongo, Phase 2) gate readiness with 503; optional checks
(atomizer / work performer, Phase 11) are reported as `degraded` but stay
200, matching §6's "non-fatal, reported".
- **Runtime image is `alpine:3.21`, not distroless** — busybox `wget` enables
the compose healthcheck without adding tooling, and `ca-certificates` is
needed for outbound HTTPS to Jira/ADO/YouTrack. Non-root user.
- **Config validation is strict and fail-fast**: `CREDENTIALS_ENC_KEY` is
mandatory at startup (base64, exactly 32 bytes) and `SESSION_SECRET` must be
≥ 16 chars, even before the features using them land — a misconfigured
deployment should die at boot, not at first use. All validation errors are
reported together via `errors.Join`.
- **Trusted-proxy resolution walks X-Forwarded-For right-to-left**, skipping
addresses inside `TRUSTED_PROXY_CIDRS`; the first untrusted address is the
client. A malformed entry stops the walk (everything left of it is
attacker-controllable). If the whole chain is trusted, the leftmost entry is
used. Headers are ignored entirely when the direct peer is untrusted.
- **ULID**: canonical 48-bit ms timestamp + 80-bit crypto randomness, no
intra-millisecond monotonicity (spec only needs sortability at ms
resolution; randomness makes collisions negligible).
- **`.env` loader semantics**: real environment variables win over `.env`
values; unquoted values support trailing ` # comment`; missing `.env` file
is not an error (containers receive env via compose `env_file`).
- **Makefile `seed`/`backup`/`restore` are failing stubs until Phase 12** so
the targets exist but cannot be mistaken for working.
## Phase 2
- **Integration tests reach Mongo via `docker-compose.test.yml`**, an explicit
test-only overlay publishing Mongo on `127.0.0.1:27017`. The normal compose
file still never publishes Mongo (§12); the spec's integration-test
requirement (§2.1) needs host access, and a loopback-only opt-in overlay is
the smallest hole. Each run uses a `bountyboard_test_<ulid>` database and
drops it on cleanup.
- **`UpdateVersioned` rejects updates that touch `version`** and merges
`updatedAt`/`$inc version` into the caller's operators; it distinguishes
`ErrNotFound` from `ErrVersionConflict` with a follow-up existence check.
- **File MIME type = sniffed (`http.DetectContentType`) unless inconclusive**
(`application/octet-stream`), in which case the client-declared type is
kept. A confident sniff overrides a lying declaration.
- **sha256 of uploads is recorded post-upload** via an update on `fs.files`
metadata (GridFS metadata must be supplied before streaming; the hash is
only known after).
- **Signed file tokens** are `base64url(exp || hmac-sha256(fileID|exp))` with
a key derived as `sha256("bountyboard/file-url/v1" + SESSION_SECRET)` so
file tokens can never collide with other uses of the session secret.