Skip to content

Commit 0258d09

Browse files
committed
docs: publish v0.1.1 from main
Add the v0.1.1 Docusaurus snapshot to the Pages source branch and keep v0.1.0 available. Restrict Pages deployment to main because the github-pages environment correctly rejects tag deployments.
1 parent 87ec8b4 commit 0258d09

40 files changed

Lines changed: 4033 additions & 2 deletions

.github/workflows/docs.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ name: Docs
33
on:
44
push:
55
branches: [main]
6-
tags: ['v*']
76
paths:
87
- 'web/**'
98
- '.github/workflows/docs.yml'

web/docs/release-notes.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ sidebar_position: 7
44

55
# Release notes
66

7+
## v0.1.1 — 2026-07-30
8+
9+
This maintenance release updates the AWS and Google Cloud Go dependencies and
10+
the GitHub Pages actions used by CI. It contains no functional or evidence-model
11+
changes from v0.1.0.
12+
713
## v0.1.0 — 2026-07-30
814

915
The first frozen maintenance release includes the currently shipped Barman

web/docusaurus.config.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,18 @@ const config: Config = {
3939
path: 'docs',
4040
sidebarPath: './sidebars.ts',
4141
includeCurrentVersion: true,
42-
lastVersion: '0.1.0',
42+
lastVersion: '0.1.1',
4343
versions: {
4444
current: {
4545
label: 'Dev',
4646
badge: true,
4747
banner: 'unreleased',
4848
},
49+
'0.1.1': {
50+
label: 'v0.1.1',
51+
badge: true,
52+
banner: 'none',
53+
},
4954
'0.1.0': {
5055
label: 'v0.1.0',
5156
badge: true,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"label": "Architecture",
3+
"position": 3,
4+
"link": {
5+
"type": "doc",
6+
"id": "architecture/index"
7+
}
8+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
---
2+
sidebar_position: 5
3+
---
4+
5+
# The evidence channel
6+
7+
The sidecar producer exists so pgConsole can show repository evidence without
8+
re-implementing it, and without either project inheriting the other's
9+
dependencies.
10+
11+
## The boundary
12+
13+
```mermaid
14+
flowchart LR
15+
subgraph pod["One CloudNativePG-adjacent Pod"]
16+
OSV["objectstoreviewer<br/>(producer, RUNTIME_MODE=pgconsole-sidecar)"]
17+
PC["pgconsole<br/>(consumer)"]
18+
PX["auth proxy<br/>(no evidence volumes)"]
19+
end
20+
STORE[("S3 repository<br/>read-only credential")]
21+
22+
STORE --> OSV
23+
OSV -- "HTTP/1.1 over private Unix socket<br/>+ pod-local bearer token" --> PC
24+
PX -- "no access" --- OSV
25+
```
26+
27+
Responsibilities do not overlap:
28+
29+
| Owner | Responsibility |
30+
|---|---|
31+
| ObjectStoreViewer | scanning, format semantics, evidence states, the wire vocabulary, the socket lifecycle |
32+
| pgConsole | consuming and displaying the evidence; it holds the only Kubernetes API token |
33+
| pgtoolbox | composing the Pod, creating the socket and token volumes, generating the token |
34+
35+
## Why a Unix socket
36+
37+
The transport is HTTP/1.1 over `/var/run/objectstoreviewer/evidence.sock`. The
38+
bearer token authenticates the *caller*; the fixed socket path — in a volume
39+
mounted only into the two intended containers — authenticates the *server
40+
boundary*. Loopback TCP, cluster Services, and non-local sockets are not
41+
accepted, so no NetworkPolicy mistake can expose the channel and the auth proxy
42+
cannot reach it at all.
43+
44+
The producer refuses a symlink or non-socket at the path, and removes a stale
45+
socket only after a dial confirms nothing is listening. See the [required
46+
filesystem profile](../operations/sidecar.md#required-filesystem-profile).
47+
48+
## The dependency-light module
49+
50+
The wire vocabulary lives in a separate Go module,
51+
`github.com/fyannk/pgObjectStoreViewer/api`, which imports **no** main
52+
ObjectStoreViewer package, cloud SDK, compression library, repository parser,
53+
HTTP server, or credential implementation. `make check-api` asserts
54+
that it has zero external module dependencies.
55+
56+
It provides:
57+
58+
- the exact health, readiness, snapshot, error, and Barman collection types;
59+
- conservative validators for states, nullable evidence, deterministic order,
60+
bounds, and the initial S3/Barman profile;
61+
- unknown-details-tag handling that **discards** unrecognized payloads;
62+
- the shared S3 canonicalization and destination fingerprint algorithm, with
63+
cross-project golden vectors;
64+
- a generated Draft 2020-12 `schema.json`; and
65+
- deterministic JSON wire goldens.
66+
67+
The main module's provider-free `internal/evidenceapi` projects its validated
68+
immutable inventory into those types. It stays outside the module so the
69+
cross-project contract can never import a scanner, a parser, or a runtime.
70+
71+
## Immutable publication and pagination
72+
73+
A publication is immutable once visible. Collections are cursor-paginated, and a
74+
cursor is bound to a publication identity: a reader either finishes a collection
75+
consistently within one generation, or is told the publication changed — it can
76+
never silently interleave two generations. Cursors are opaque, bounded to
77+
4096 bytes, process-local, and unrelated to the bearer token.
78+
79+
## Identity and correlation
80+
81+
A snapshot carries the CNPG namespace, the immutable Cluster UID, an optional
82+
display name, and a destination fingerprint derived by the shared
83+
canonicalization algorithm. Correlation is **observed-UID-only**: the producer
84+
never reads the Kubernetes API, so it reports the identity it was configured
85+
with and lets the consumer decide whether that matches the cluster it is
86+
displaying.
87+
88+
## Compatibility
89+
90+
`v1alpha1` is additive-only within the version: new fields may appear, and a
91+
consumer must ignore what it does not recognize. Unrecognized typed-details tags
92+
are discarded rather than guessed at. A semantic version tag, a pgConsole
93+
consumer, a pgtoolbox Pod profile, and a listed runtime pair belong to
94+
cross-project integration — the executable producer alone does not claim a
95+
qualified or shippable pair.
96+
97+
## Verification
98+
99+
```bash
100+
make check-api # module dependency boundary and generated drift
101+
make test # schema, goldens, projection, channel, runtime, and probe
102+
make test-stress # repeat lifecycle-sensitive cases under the race detector
103+
make test-container # exercise the real image under restricted profiles
104+
```
105+
106+
The wire details are in the [evidence API
107+
reference](../reference/evidence-api.md) and its [resource
108+
reference](../reference/evidence-api-resources.md); the reasoning behind the
109+
channel is in [Decisions](decisions.md).

0 commit comments

Comments
 (0)