feat(datastore): blob_storage_attachments foundation table and helper

Why

The blob_storage_attachments table is outside every format spec and outside S06's storage-layer scope, leaving OCI/Maven/npm Step 1s blocked on foundation work no one owned. This MR ships the unowned half: the partitioned table (64 hash partitions on sha256), the explicit sequence, the namespaces FK, the lookup index, and a shared BlobStorageAttachmentStore datastore. Format Step 1 schemas can now declare their composite FK against a real parent.

Closes #92 (closed). Plan: docs/plans/2026-05-21-blob-storage-attachments-foundation.md. Related: ADR-007, ADR-022, S06 Step 6 (FK lands with blob_storage_blobs), S12.

What

  • FK to blob_storage_blobs deferred to S06 Step 6 (parent table not yet on trunk). The wrap matches both SQLSTATE 23503 and the blob FK constraint name, so a namespaces FK violation (caller passed a non-existent namespaceID) takes the generic wrap path, not ErrParentBlobMissing. The blob-FK integration test appends to this file in S06 Step 6.
  • Named-query metric carries a TODO(#92-followup-S03) marker. A one-line follow-up MR after S03 ships the helper.
  • Folds in S12 line 280 + 284 spec corrections (id-column shape and PK shape) so spec and migration agree same-day.
Context for LLM agents

Rationale

  • Explicit sequence (DEFAULT nextval(...) + OWNED BY), not GENERATED ALWAYS AS IDENTITY. ADR-022 namespace migration re-inserts rows with source-deployment id values without OVERRIDING SYSTEM VALUE.
  • ErrParentBlobMissing wrap omits PgError.Detail/Where. These can echo attacker-supplied namespace_id and sha256 from the violating key. The default fmt.Errorf("%w", pgErr) already excludes them per pgx/v5; the helper structure keeps the omission explicit.

Non-goals

  • Delete helper. Every delete callsite is conditional and format-specific; a by-id delete would mislead callers.
Edited by Hayley Swimelar

Merge request reports

Loading
Loading