docs(contracts): add CONTRACT_001-004 — T00
Interface contracts unblocking the parallel fan-out (T01-T07): - 001 config schema (single stack, passphrase + VERSIONS + Pulumi config) - 002 Vault path layout (foundation/<service>/<type>-credentials, camelCase) - 003 container network/DNS/ports/volumes (foundation-net, named volumes) - 004 backup artifact format + restore order (Vault->PG->RustFS->Forgejo) ADR_F001 (layered platform) already satisfied by ADR-004. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f18676e6b3
commit
188e30e23e
5 changed files with 299 additions and 0 deletions
103
documentation/contracts/CONTRACT_001_config_schema.md
Normal file
103
documentation/contracts/CONTRACT_001_config_schema.md
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
# Contract — CONTRACT_001 — Bootstrap Config Schema
|
||||
|
||||
**Between**: `bootstrap/config.ts` (producer) ↔ every component in `bootstrap/components/*` (consumers)
|
||||
**Status**: Agreed (pending implementation validation)
|
||||
**Realizes**: PLAN-002 §3, §4.2 · **Depends on**: ADR-004, ADR-005
|
||||
|
||||
## Interface
|
||||
|
||||
The single Pulumi stack `foundation` is configured through three channels. **No other inputs exist.**
|
||||
|
||||
```
|
||||
1. ENV PULUMI_CONFIG_PASSPHRASE (the master passphrase — the only external secret)
|
||||
SSH_PRIVATE_KEY_PATH (path to the key that reaches the VM; default ~/.ssh/id_rsa)
|
||||
2. VERSIONS foundation/VERSIONS (image digests + tool versions — determinism, not in Pulumi config)
|
||||
3. Pulumi config Pulumi.foundation.yaml (typed, non-secret) + secrets (secure: v1:…, passphrase-encrypted)
|
||||
```
|
||||
|
||||
### 1.1 Typed config shape (`config.ts` MUST export this)
|
||||
|
||||
```ts
|
||||
export interface FoundationConfig {
|
||||
// --- identity / networking ---
|
||||
baseDomain: string; // "olsitec.de"
|
||||
hosts: { // public FQDNs terminated by Caddy
|
||||
forge: string; // "forge.olsitec.de" (Forgejo web/API/registry)
|
||||
vault: string; // "vault.olsitec.de" (Vault UI/API — internal-restricted)
|
||||
s3: string; // "s3.olsitec.de" (RustFS API, optional public)
|
||||
};
|
||||
forgeSshPort: number; // 2222 (git-over-ssh, published directly, not via Caddy)
|
||||
|
||||
// --- deployment target (Docker-over-SSH provider) ---
|
||||
vm: {
|
||||
host: string; // IP or DNS of the foundation VM
|
||||
user: string; // ssh user (e.g. "root" or "deploy")
|
||||
// private key path comes from ENV SSH_PRIVATE_KEY_PATH, never config
|
||||
};
|
||||
|
||||
// --- container plane (see CONTRACT_003 for names/ports) ---
|
||||
network: { name: string; subnet: string }; // "foundation-net", "172.30.0.0/24"
|
||||
dataRoot: string; // host path for bind mounts / named-volume root (e.g. "/srv/foundation")
|
||||
|
||||
// --- TLS strategy ---
|
||||
tls: {
|
||||
mode: "letsencrypt-dns01" | "internal-ca"; // day-zero may start internal-ca, switch later
|
||||
acmeEmail: string;
|
||||
// cloudflareApiToken is a SECRET (see §1.3)
|
||||
};
|
||||
|
||||
// --- service sizing / fixed names (derived, non-secret) ---
|
||||
postgres: { db: string; forgejoDb: string }; // names only; creds are generated → Vault
|
||||
rustfs: { buckets: string[] }; // ["forgejo-packages","forgejo-artifacts","forgejo-lfs","foundation-backups"]
|
||||
forgejo: { adminUser: string; orgName: string };// "platform-admin", "olsitec"
|
||||
runner: { labels: string[] }; // ["docker:docker://…","dind:docker://-"] (PLAN-001 §4a)
|
||||
|
||||
// --- credential feature flags (ADR-002 style; selects what @pulumi/random generates) ---
|
||||
features: {
|
||||
postgres: boolean; rustfs: boolean; forgejo: boolean;
|
||||
runner: boolean; backup: boolean; registry: boolean;
|
||||
};
|
||||
|
||||
// --- backup ---
|
||||
backup: {
|
||||
bucket: string; // "foundation-backups" (in RustFS)
|
||||
offsiteEndpoint: string; // self-hosted second location (CONTRACT_004); creds are SECRET
|
||||
retentionDaily: number; retentionWeekly: number;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 Non-secret config keys (`Pulumi.foundation.yaml` → `config:`)
|
||||
Namespace **`foundation:`**. Examples: `foundation:baseDomain`, `foundation:vm.host`,
|
||||
`foundation:tls.mode`, `foundation:rustfs.buckets` (array), `foundation:features.forgejo`.
|
||||
All are reproducible and safe to commit in plaintext.
|
||||
|
||||
### 1.3 Secret config keys (`secure: v1:…`, passphrase-encrypted, committable)
|
||||
Namespace **`vaultCredentials:`** and **`foundation:`** as appropriate:
|
||||
|
||||
| Key | Source | Notes |
|
||||
|-----|--------|-------|
|
||||
| `vaultCredentials:rootToken` | captured after `vault operator init` | EXACT pattern from `olsitec-core/run.sh` |
|
||||
| `vaultCredentials:unsealKeys` | captured after init (JSON array) | used by the passphrase-gated unseal helper (D2/ADR-004) |
|
||||
| `foundation:cloudflareApiToken` | seeded once (manual) | DNS-01 ACME; also mirrored to Vault for renewal |
|
||||
| `foundation:backup.offsiteAccessKey` / `…offsiteSecretKey` | seeded once | offsite target creds; mirrored to Vault (`foundation/backup/backup-credentials`) |
|
||||
|
||||
> **Everything else is generated** by `@pulumi/random` and written to Vault (CONTRACT_002) — never
|
||||
> placed in config. The passphrase is **never** stored anywhere (ENV only).
|
||||
|
||||
## Ownership
|
||||
- **Producer**: `bootstrap/config.ts` parses + validates (fails closed on missing required keys).
|
||||
- **Consumers**: components read typed config; they MUST NOT read raw `pulumi.Config` ad hoc.
|
||||
|
||||
## Assumptions
|
||||
- One stack, one environment ("foundation") at Layer 0. Multi-stage is a Layer-1 concern.
|
||||
- Image digests live in `VERSIONS`, not config, so an upgrade is a `VERSIONS` diff (PLAN-002 §7.1).
|
||||
|
||||
## Validation
|
||||
- `preflight/` asserts ENV + `VERSIONS` present and well-formed before `pulumi up`.
|
||||
- `pulumi preview` on an empty stack must report missing required config clearly (acceptance T02).
|
||||
|
||||
## Change Process
|
||||
Adding a service = add its `features.<x>` flag + its fixed names here, then its Vault keys in
|
||||
CONTRACT_002 and its container in CONTRACT_003. Breaking key renames require a minor version note in
|
||||
this contract and a coordinated update across consumers.
|
||||
Loading…
Add table
Add a link
Reference in a new issue