|
| 1 | +--- |
| 2 | +sidebar_position: 6 |
| 3 | +--- |
| 4 | + |
| 5 | +# Decisions |
| 6 | + |
| 7 | +Why the code looks the way it does. Each entry records what was decided, when, |
| 8 | +and what it rules out — the reasoning that a reader cannot recover from the code |
| 9 | +alone. |
| 10 | + |
| 11 | +The code is the authority on *what happens*. This page is the authority on |
| 12 | +*why*, and nothing more. |
| 13 | + |
| 14 | +## One application, many formats |
| 15 | + |
| 16 | +**Decided with the initial architecture.** ObjectStoreViewer models two |
| 17 | +independent axes — object-store provider and repository format — instead of |
| 18 | +shipping sibling `barmanviewer` and `pgbackrestviewer` applications. |
| 19 | + |
| 20 | +Splitting per format would duplicate the security boundary, provider clients, |
| 21 | +scanner, cache, UI, metrics, container, and operator integration. Adding Azure |
| 22 | +must not change Barman semantics; adding pgBackRest must not duplicate HTTP, |
| 23 | +cache, redaction, or provider adapters. |
| 24 | + |
| 25 | +Splitting a format into its own application would require a new decision showing |
| 26 | +a materially different deployment or security boundary, team lifecycle, or |
| 27 | +runtime that cannot safely coexist here. |
| 28 | + |
| 29 | +## No shared deep backup model |
| 30 | + |
| 31 | +**Decided with the format boundary.** The shared envelope carries only concepts |
| 32 | +with identical meaning in every format: format and compatibility status, scope |
| 33 | +kind and display name, scan generation/completeness/freshness, normalized |
| 34 | +evidence state plus format-native details, WAL positions, observed coverage with |
| 35 | +assumptions and stop reason, capability flags, and the observed/unknown/verified |
| 36 | +distinction. |
| 37 | + |
| 38 | +Everything richer stays behind the format's own analyzer. A pgBackRest reference |
| 39 | +chain is not a Barman parent field; Barman server layout is not a pgBackRest |
| 40 | +stanza. Conversion into the envelope is conservative: a fact that cannot be |
| 41 | +represented without losing an invariant stays format-owned or becomes `unknown`. |
| 42 | +It is never approximated into healthy common data. |
| 43 | + |
| 44 | +## Explicit format selection, no detection |
| 45 | + |
| 46 | +**Decided with the configuration contract.** `REPOSITORY_FORMAT` is required. |
| 47 | +Optional detection could only ever be a redacted diagnostic, because an empty, |
| 48 | +encrypted, partially retained, or mixed-prefix repository makes automatic |
| 49 | +detection unsafe. A mismatch between configured format and observed layout is |
| 50 | +`unknown`/configuration-invalid, never a fallback. |
| 51 | + |
| 52 | +## The configuration contract is frozen |
| 53 | + |
| 54 | +**Frozen 2026-07-27**, deliberately before the composing operator's CRD existed, |
| 55 | +so that both sides could be built against one stable contract. Changing it |
| 56 | +requires matching compatibility, documentation, and test updates in the same |
| 57 | +change. A removed or re-validated variable fails startup rather than being |
| 58 | +ignored — a misconfiguration must never become a silently wrong answer. |
| 59 | + |
| 60 | +## Static credentials only as files |
| 61 | + |
| 62 | +**Decided with the configuration contract.** Direct `AWS_ACCESS_KEY_ID`, |
| 63 | +`AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN` *values* are rejected. Secrets |
| 64 | +arrive as mounted files or ambient workload identity, are read with a size |
| 65 | +ceiling, and are held in an opaque type that cannot be printed. Environment |
| 66 | +variables leak through process listings, crash dumps, and child processes; a |
| 67 | +mounted file has a permission model. |
| 68 | + |
| 69 | +## Evidence as a sidecar, not a library |
| 70 | + |
| 71 | +**Decided by the consumer team, 2026-07-28.** Repository evidence reaches |
| 72 | +pgConsole from a **pod-private sidecar** holding the only object-store |
| 73 | +credentials, over a versioned read-only JSON API. |
| 74 | + |
| 75 | +In-process reuse was **permanently rejected**: it would have put object-store |
| 76 | +credentials and Kubernetes credentials in the same process, and coupled two |
| 77 | +release cycles through Go `internal` visibility. The sidecar keeps a hard rule |
| 78 | +intact — S3 credentials and Kubernetes credentials never coexist in one |
| 79 | +container, the sidecar holds no credential valid against the Kubernetes API, and |
| 80 | +the consumer holds no object-store credential. |
| 81 | + |
| 82 | +## A Unix socket, not loopback TCP |
| 83 | + |
| 84 | +**Decided in the evidence API contract.** `v1alpha1` uses HTTP/1.1 over a fixed |
| 85 | +pod-private Unix socket. Authenticated loopback TCP was considered as a fallback |
| 86 | +and is not part of the version. |
| 87 | + |
| 88 | +The bearer token authenticates the *caller*; the socket path — in a volume |
| 89 | +mounted only into the two intended containers — authenticates the *server |
| 90 | +boundary*. No NetworkPolicy mistake can expose the channel, and the auth proxy |
| 91 | +cannot reach it at all. Confinement comes from the mount set, never from a file |
| 92 | +mode. |
| 93 | + |
| 94 | +## Liveness only, never readiness |
| 95 | + |
| 96 | +**Decided 2026-07-28, after review.** The image ships a `probe` subcommand for an |
| 97 | +`exec` **liveness** probe. Configuring `/readyz` as a kubelet readiness probe is |
| 98 | +forbidden: a store failure would make the whole Pod unavailable, contradicting |
| 99 | +the consumer's source-independent readiness. Repository evidence is one source |
| 100 | +among several, not a dependency. |
| 101 | + |
| 102 | +## Correlation is observed-UID-only |
| 103 | + |
| 104 | +**Decided 2026-07-28, after review.** The producer never reads the Kubernetes |
| 105 | +API. It exports repository identity and exact backup IDs; the consumer correlates |
| 106 | +using only its own Kubernetes observation, and there is no injected fallback. |
| 107 | + |
| 108 | +Zero matches means "counterpart not observed". More than one match means |
| 109 | +"ambiguous". Neither is guessed or collapsed into agreement. Cluster name, |
| 110 | +Backup object name, scheduled-backup name, Barman server name, timestamps, and |
| 111 | +object prefixes are explicitly *not* substitute correlation keys. |
| 112 | + |
| 113 | +## A types-only module |
| 114 | + |
| 115 | +**Decided in the evidence API contract.** The wire vocabulary lives in |
| 116 | +`github.com/fyannk/pgObjectStoreViewer/api` with **zero external module |
| 117 | +dependencies**, so consumers can depend on the contract without inheriting a |
| 118 | +cloud SDK, a compression library, or a repository parser. Collector, publisher, |
| 119 | +engine, provider, and analyzer interfaces stay implementation details that |
| 120 | +consumers must not import. |
| 121 | + |
| 122 | +## Two counters, not one |
| 123 | + |
| 124 | +**Decided in the evidence API contract.** `evidence_generation` identifies the |
| 125 | +last complete immutable evidence; `revision` advances on every published attempt, |
| 126 | +including a failed refresh that retains previous evidence as stale. One counter |
| 127 | +could not express "the evidence is unchanged but the last attempt failed", which |
| 128 | +is exactly the state an operator most needs to see. |
| 129 | + |
| 130 | +## Typed failure over truncated success |
| 131 | + |
| 132 | +**Decided in the evidence API contract.** A safety-limit failure returns a typed |
| 133 | +error rather than a short page. A truncated success is indistinguishable from a |
| 134 | +complete small answer, which is how a bounded read turns into a false healthy |
| 135 | +conclusion. |
| 136 | + |
| 137 | +## Gap confirmation is not persisted |
| 138 | + |
| 139 | +**Decided with the gap lifecycle.** A gap needs two consecutive complete scans to |
| 140 | +be confirmed, and confirmation lives only in the process. After a restart, gaps |
| 141 | +begin as candidates again. |
| 142 | + |
| 143 | +Persisting confirmation would mean durable state — a volume, a schema, a |
| 144 | +migration path, and a new way to be wrong after a restore. Re-confirming costs |
| 145 | +one refresh interval. |
| 146 | + |
| 147 | +## Downloads last, and opt-in |
| 148 | + |
| 149 | +**Decided with the security posture.** Base backups and WALs contain every byte |
| 150 | +of every database while bypassing PostgreSQL authorization. `ALLOW_DOWNLOAD` |
| 151 | +defaults to `false`, no usable route exists until the feature ships with its own |
| 152 | +threat model, and no incidental raw-object endpoint may appear earlier. |
| 153 | + |
| 154 | +## Reimplement, never copy |
| 155 | + |
| 156 | +**Decided with the format profiles.** Barman and pgBackRest source, documentation, |
| 157 | +and pinned generated repositories are the compatibility *authority*, and the |
| 158 | +behavior is reimplemented in Go. Implementation code is not copied, which keeps |
| 159 | +this project's Apache-2.0 licensing intact. |
0 commit comments