Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 20 additions & 25 deletions cmd/ob-docgen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ var blocks = []block{
"Setting replicas, resources, volumes, published ports or drain behaviour",
}},
{Key: "services", Title: "services", Order: 20, Status: statusShipped,
Summary: "Supporting services Onebox runs for you — the eleven built-in drivers, their versions, settings and persistence.",
Summary: "Supporting services Onebox runs for you: the eleven built-in drivers, their versions, settings and persistence.",
ReadWhen: []string{
"Adding a database, cache or queue",
"Deciding between a managed service and a daemon workload",
Expand Down Expand Up @@ -327,7 +327,7 @@ var blocks = []block{
Summary: "Who owns the ingress proxy, which image runs it, and how TLS is resolved.",
ReadWhen: []string{"Taking over the Traefik configuration, or turning routing off entirely"}},
{Key: "hooks", Title: "hooks", Order: 80, Status: statusShipped,
Summary: "Commands run at a lifecycle seam — bootstrap, pre_release, post_release, post_deploy — or keyed by a job name to replace that job's command.",
Summary: "Commands run at a lifecycle seam (bootstrap, pre_release, post_release, post_deploy) or keyed by a job name to replace that job's command.",
ReadWhen: []string{"Running a command around a deploy or a rollback"}},
{Key: "registries", Title: "registries", Order: 90, Status: statusShipped,
Summary: "Named container registries and the local environment variables holding their credentials.",
Expand All @@ -336,7 +336,7 @@ var blocks = []block{
Summary: "Named webhooks that receive selected operation and scheduled-job outcomes.",
ReadWhen: []string{"Sending deploy outcomes to Slack, Discord or an incident tool"}},
{Key: "backupTargets", Slug: "backup-targets", Title: "backupTargets", Order: 200, Status: statusShipped,
Summary: "User-owned off-host S3-compatible repositories a protected service writes its backups to. Executable for the postgres driver; every other driver refuses a policy rather than accepting one it cannot honour.",
Summary: "User-owned off-host S3-compatible repositories a protected service writes its backups to. Executable for the postgres driver; every other driver refuses a policy it cannot honour.",
ReadWhen: []string{"Declaring where a database's backups go", "Understanding why Onebox refuses a backup target that shares the protected host"}},
{Key: "externalServices", Slug: "external-services", Title: "externalServices", Order: 210, Status: statusSchemaOnly,
Summary: "Typed dependencies operated outside Onebox, whose lifecycle and backups stay external. Accepted by the loader; not yet executable.",
Expand Down Expand Up @@ -897,13 +897,9 @@ func renderDriverPage() string {
fmt.Fprintln(&buf, "with `unknown_service_driver`, listing the drivers Onebox can run and directing")
fmt.Fprintln(&buf, "you to a `daemon` workload.")
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "That is deliberate: inventing an image from a name produces a container that")
fmt.Fprintln(&buf, "Inventing an image from a name produces a container that")
fmt.Fprintln(&buf, "starts and stores nothing durable.")
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "The runtime and connection tables are generated from the private driver catalogue.")
fmt.Fprintln(&buf, "The guidance and limitations below remain deliberately authored in the generator:")
fmt.Fprintln(&buf, "they are product explanations, not facts the catalogue can prove.")
fmt.Fprintln(&buf)

fmt.Fprintln(&buf, "## Runtime catalogue")
fmt.Fprintln(&buf)
Expand Down Expand Up @@ -932,7 +928,7 @@ func renderDriverPage() string {
driver.Name, escapeCell(serviceDriverTypicalUses[driver.Name]), connectionPartsCell(driver.ConnectionParts))
}
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "A part the driver does not have — a database on a cache — is omitted rather than")
fmt.Fprintln(&buf, "A part the driver does not have (a database on a cache) is omitted, never")
fmt.Fprintln(&buf, "written empty. Every driver has a password part; `redis` and `valkey` have a")
fmt.Fprintln(&buf, "user part because Redis 6+ authenticates the built-in `default` user, and a URL")
fmt.Fprintln(&buf, "with an empty username fails AUTH outright.")
Expand Down Expand Up @@ -963,8 +959,8 @@ func renderDriverPage() string {
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "- **The service outlives every release.** Its own Compose project; no deploy and")
fmt.Fprintln(&buf, " no rollback stops it or removes its volume.")
fmt.Fprintln(&buf, "- **The credential is generated on the server, once.** Not in your project, the")
fmt.Fprintln(&buf, " generated runtime, or the digest. Never rotated by a re-apply.")
fmt.Fprintln(&buf, "- **The credential is generated on the server, once.** It is absent from your project,")
fmt.Fprintln(&buf, " the generated runtime, and the digest. A re-apply never rotates it.")
fmt.Fprintln(&buf, "- **The version binds into the release digest**, so a database upgrade under an")
fmt.Fprintln(&buf, " untouched application cannot pass unnoticed.")
fmt.Fprintln(&buf)
Expand All @@ -988,28 +984,27 @@ func renderDriverPage() string {
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, ":::caution[`mongodb` runs a standalone server, not a replica set]")
fmt.Fprintln(&buf, "Change streams and multi-document transactions need a replica set. An application")
fmt.Fprintln(&buf, "using either will connect, authenticate, and *then* fail — Rocket.Chat reports")
fmt.Fprintln(&buf, "using either will connect, authenticate, and then fail. Rocket.Chat reports")
fmt.Fprintln(&buf, "`The $changeStream stage is only supported on replica sets`.")
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "Onebox does not configure a replica set, so an application that needs one wants a")
fmt.Fprintln(&buf, "`daemon` workload it owns. This is a limitation of the driver rather than of the")
fmt.Fprintln(&buf, "contract, and it is stated here because the failure appears in the application's")
fmt.Fprintln(&buf, "logs rather than in anything Onebox says.")
fmt.Fprintln(&buf, "`daemon` workload it owns. The failure appears in the application's logs,")
fmt.Fprintln(&buf, "not in Onebox output.")
fmt.Fprintln(&buf, ":::")
fmt.Fprintln(&buf)

fmt.Fprintln(&buf, ":::caution[Only `postgres` is backed up]")
fmt.Fprintln(&buf, "`postgres` is the one driver with an executable backup contract: declaring")
fmt.Fprintln(&buf, "`backup` on it gives a continuous off-host archive and point-in-time recovery.")
fmt.Fprintln(&buf, "Every other driver declares `policy_qualified: false`, so a backup policy on it")
fmt.Fprintln(&buf, "is refused at `ob validate` rather than accepted and left to do nothing — and its")
fmt.Fprintln(&buf, "is refused at `ob validate`; its")
fmt.Fprintln(&buf, "data lives only on this host until you copy it off yourself.")
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "Onebox also refuses a major version change a driver cannot perform in place,")
fmt.Fprintln(&buf, "rather than replacing the container and leaving the data intact and unreachable.")
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "For `postgres`, declaring `backup` changes this — see")
fmt.Fprintln(&buf, "[Shipped vs proposed](/docs/reference/capabilities).")
fmt.Fprintln(&buf, "For `postgres`, a `backup` block changes this once `ob backup enable` succeeds;")
fmt.Fprintln(&buf, "see [Declaring is not enabling](/docs/guides/backups#declaring-is-not-enabling).")
fmt.Fprintln(&buf, ":::")
fmt.Fprintln(&buf)

Expand Down Expand Up @@ -1055,12 +1050,12 @@ func renderErrorPage() string {
fmt.Fprintln(&buf, generatedMarker)
fmt.Fprintln(&buf)

fmt.Fprintln(&buf, "A code is a promise: it names a kind of failure that keeps meaning the same thing")
fmt.Fprintln(&buf, "A code names a kind of failure that keeps meaning the same thing")
fmt.Fprintln(&buf, "across releases. Branch on the code, never on the sentence.")
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "## Validation codes")
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "Adding one is a deliberate act. A test refuses any code the loader emits that is")
fmt.Fprintln(&buf, "A test refuses any code the loader emits that is")
fmt.Fprintln(&buf, "not enumerated, and any enumerated code that nothing emits, so this table cannot")
fmt.Fprintln(&buf, "drift in either direction.")
fmt.Fprintln(&buf)
Expand All @@ -1078,14 +1073,14 @@ func renderErrorPage() string {

fmt.Fprintln(&buf, "## Operation codes")
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "Raised while a command runs, rather than while the project file is read. These")
fmt.Fprintln(&buf, "Raised during command execution, not project-file loading. These")
fmt.Fprintln(&buf, "are the codes a deploy, a rollback, a secret rotation or an audited exec puts in")
fmt.Fprintln(&buf, "the `error.code` field, so they are the set to branch on when automating.")
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "A blank command is deliberate: some failures have no honest Onebox command that")
fmt.Fprintln(&buf, "resolves them. A local artifact that could not be written is fixed on the")
fmt.Fprintln(&buf, "A blank command means no Onebox command resolves the failure. A local artifact")
fmt.Fprintln(&buf, "that could not be written is fixed on the")
fmt.Fprintln(&buf, "filesystem, not by running the tool again. An angle-bracketed command names a")
fmt.Fprintln(&buf, "step to complete rather than a line to run verbatim.")
fmt.Fprintln(&buf, "step to complete, not a line to run verbatim.")
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "| Code | Means | Guidance role | Command |")
fmt.Fprintln(&buf, "| --- | --- | --- | --- |")
Expand Down Expand Up @@ -1178,7 +1173,7 @@ func renderCLIPage(ctx context.Context, obBin string) (string, error) {
// actually asking when they pick between them.
fmt.Fprintln(&buf, "## Which command changes nothing")
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "Many commands change nothing — `status`, `logs`, `audit`, `schema`, `version` and `canonical` all")
fmt.Fprintln(&buf, "Many commands change nothing: `status`, `logs`, `audit`, `schema`, `version` and `canonical` all")
fmt.Fprintln(&buf, "just report. These five are the ones that get confused with each other, because each one")
fmt.Fprintln(&buf, "answers \"would this deploy work?\" from a different place.")
fmt.Fprintln(&buf)
Expand Down
41 changes: 16 additions & 25 deletions site/src/content/docs/docs/concepts/evidence-not-declaration.mdx
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
---
title: Evidence, not declaration
description: Why a field in a file is never allowed to assert that something is true on the host.
summary: The principle that separates what you declared from what is observed — and how it shapes approval, tiers, drift, and structured output.
summary: "The principle that separates what you declared from what is observed, and how it shapes approval, tiers, drift, and structured output."
sidebar:
order: 2
read_when:
- "Understanding why an approval is separate from a plan"
- "Understanding why declaring protection would not establish it"
- "Designing something that has to report status honestly"
- "Designing something that has to report status accurately"
---

A configuration file records what you **want**. It cannot record what is **true**.
Most tools blur that line, and the blur is where operators get hurt: a field
called `backup: true` reads as a fact about the host when it is only a sentence
in a file.

Onebox keeps the two apart everywhere.
A configuration file records what you want, not what is true on the host;
`backup: true` is only a line in a file. Onebox keeps the two apart.

## Approval is not model intent

> A statement that the user approved is data, not authority.

The current CLI requires a deliberate local human ceremony bound to the exact
The current CLI requires a local human ceremony bound to the exact
sealed plan, operator label, server, observed state, inputs, risk, and expiry.
`ob plan` and `ob approve` are separate commands so the reviewed object and the
answer cannot drift unnoticed. The resulting `local_cli` artifact is
Expand Down Expand Up @@ -52,13 +48,12 @@ while, **all at once**:
Any one of those expiring returns the service to `Run` immediately. Historical
proof stays visible but cannot be mistaken for current protection.

That is a deliberately expensive bar. A cheaper one — trusting the policy block —
would produce a green tier for a service whose backups have been failing for a
month.
Trusting the policy block alone would report a protected service even if its
backups had been failing for a month.

:::note[Tier and envelope are separate]
`Managed` would always be accompanied by the recovery kind — `snapshot`, `pitr`
or `cold` — plus the observed recovery point and measured restore time. Otherwise
`Managed` would always be accompanied by the recovery kind (`snapshot`, `pitr`
or `cold`) plus the observed recovery point and measured restore time. Otherwise
"managed Redis" reads as point-in-time recovery it does not have.
:::

Expand All @@ -67,17 +62,15 @@ or `cold` — plus the observed recovery point and measured restore time. Otherw
Read commands do not repair, converge, prune or create. `ob status` and
`ob doctor` read evidence; they never trigger the check that produces it.

In the proposed assurance layer this is structural rather than promised: the
check process has no converge methods in its operation graph at all.
In the proposed assurance layer, the check process has no converge methods
in its operation graph.

## Origins are published

`ob canonical` annotates every value you did not write — `# default`,
`# shorthand`, `# override`. The same idea appears in structured output, which
labels authored, default, environment-override, observed and derived values.

Knowing that `retainReleases: 5` was Onebox's choice rather than yours is the
difference between reviewing a configuration and reading one.
See [origin annotations](/docs/get-started/reading-it-back#ob-canonical-is-the-one-to-read-in-review)
for `ob canonical`, including `# default`, `# shorthand`, `# override` and defaults
such as `retainReleases: 5`. Structured output labels authored, default,
environment-override, observed and derived values.

## Checkout provenance

Expand All @@ -99,9 +92,7 @@ worthless once that state may have moved.

## What this costs

Honest evidence produces worse-looking status pages. A service that oscillates
Observed evidence can produce worse-looking status pages. A service that oscillates
between `Managed` and `Run` because a drill keeps being deferred for disk space
looks broken, where a tool that trusted the declaration would look fine.

The alternative is worse. A green light that does not depend on anything being
true is not a green light.
30 changes: 12 additions & 18 deletions site/src/content/docs/docs/concepts/generated-compose.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Why Compose is generated
description: Why the runtime is derived from your declaration rather than read from a file you wrote.
description: Why Onebox derives the runtime from your declaration.
summary: Compose is an artifact Onebox generates, not an input it reads. What that buys, and how you get out.
sidebar:
order: 3
Expand All @@ -18,18 +18,15 @@ Under the [ownership boundary](/docs/concepts/ownership-boundary), Onebox owns t
runtime. Reading a runtime you wrote inverts that: you own it, and Onebox merely
operates something it did not design and cannot fully reason about.

Concretely, that costs three things:
The Compose file and Onebox annotations could disagree, with nothing to
arbitrate between them.

**Two descriptions that can disagree.** The Compose file would say one thing,
the Onebox annotations another, and nothing would arbitrate.

**No derivation.** Onebox could not choose the container name, the volume name,
Onebox could not choose the container name, the volume name,
the router rule or the network, because you already would have. So it could not
guarantee they are consistent, collision-free, or stable across releases.

**No honest defaults.** `strategy: rolling` needs a health check to gate it. When
the health check lives in a file Onebox merely reads, "is this rollout gated?"
becomes a question rather than a fact.
`strategy: rolling` needs a health check to gate it. Reading a health check from
an authored file would leave Onebox unable to guarantee that the rollout is gated.

## What generation buys

Expand All @@ -43,15 +40,15 @@ becomes a question rather than a fact.
them; if an unmanaged endpoint remains attached, destruction stops without
deleting Onebox's recovery state or releasing host ownership. Once a resource
exists its name cannot change without migration, and a foreign resource already
holding a derived name is refused rather than adopted.
holding a derived name is refused, not adopted.
- **Digest binding.** The rendered Compose is bound into the plan, so what was
reviewed is what executes.
- **Refusals that mean something.** `strategy_ungated`, `route_collision`,
`stateful_replicas` are all statements about the generated runtime, which is
the only thing that will actually run.
- **Typed routing.** Two workloads claiming the same entrypoint, protocol, hostname
and path is `route_collision`, rather than a proxy that accepts both and routes
to one with nothing saying which.
and path produce `route_collision`. This prevents the proxy from accepting both
and routing to one without saying which.

## A workload name is its identity

Expand All @@ -75,9 +72,7 @@ ob preview # print the generated runtime, change nothing
ob eject # write it out and hand it over, permanently
```

`ob eject` strips the overlay, so what lands in your repository is ordinary
Compose — not Compose carrying Onebox labels you would then have to unpick — and
your workloads are repointed at it with your comments intact.
See [Eject](/docs/guides/eject) for the output and what remains on the host.

## And the escape hatch remains

Expand All @@ -91,8 +86,7 @@ workloads:
```

Refusals apply only where the referenced service contradicts something Onebox
owns — `extends`, a fixed `container_name`, `network_mode`, labels in the `onebox.`
owns: `extends`, a fixed `container_name`, `network_mode`, labels in the `onebox.`
or `traefik.` namespaces. See [Adopt an existing Compose file](/docs/guides/compose).

The result is a contract that generates by default and defers on request, rather
than one that can only do one of the two.
The contract generates by default and defers on request.
Loading
Loading