openvibers/openvibe.contracts Commits
ADR-032: containers for platform services (roadmap WS-N task 12). Platform services stay systemd units (dedicated users, hardening, slices, socket activation for zero-downtime restarts); ovhost keeps one deploy adapter with a reserved inventory runtime field; untrusted tenant code is the Stage C isolation ADR's. Revisit on a second host, a conflicting system dependency, Stage C, or a unit systemd cannot harden enough.
Changes
nnew file mode 100644@@@ -0,0 +1,64 @@++# ADR-032: Containers for platform services++++**Status:** Accepted 2026-09-26 (roadmap WS-N task 12). Revisit on any trigger below.++++## Context and current evidence++++- Every OpenVibe service runs as a systemd unit on one host, each as its own Unix user, deployed by++ `ovhost deploy <service>` (OpenVibe.Host) or a per-repository `deploy.sh`. That is 25 services on 2026-09-26.++- Deploys are checkout moves with dependency install, readiness polling and automatic rollback. Live++ uses a release layout (`releases/<time>-<sha>`, `current` symlink) and **systemd socket activation**,++ so a restart never refuses a connection (`127.0.0.1:3000` held by pid 1).++- Units already carry hardening: `ProtectSystem`, `NoNewPrivileges`, `PrivateTmp` and `ReadWritePaths`++ per data directory. `ovhost validate` checks each unit against the service's `lifecycle` declaration++ (Contracts 0.55.0).++- Backups, restore drills, the browser check and the alert relay are systemd timers that `ovhost` drives.++- Stage C of Host (sandboxed **user** code) will need isolation stronger than a Unix user. It is not++ started, and it has its own isolation ADR.++- The host has 15 GB of memory and 11 GB available at peak; no service has asked for a system dependency++ that conflicts with another's.++++## Decision++++- **Platform services stay systemd units.** There is no container runtime for first-party services. The++ isolation they need comes from what systemd gives per unit: a dedicated user, a read-only system,++ private /tmp, capability and syscall filters where a unit can take them, and slices for memory and++ CPU limits.++- **ovhost keeps one deploy adapter: systemd.** The inventory's service entry keeps `unit` and `socket`,++ and a future `runtime` field is reserved: an absent value means `systemd`. A container adapter, if one++ is ever added, is a second strategy behind the same deploy, readiness, rollback and lifecycle checks,++ never a separate path.++- **Tenant and user code (Stage C) is out of scope here.** Running untrusted code needs a sandbox++ (gVisor, Firecracker or equivalent), with budgets, egress policy and kill/revoke. That is the Stage C++ isolation ADR, and it may well use containers or micro-VMs.++++## Alternatives considered++++- **Docker (or Podman) Compose for every service.** Rejected for now:++ - it rewrites deploy, rollback, backups and readiness for no measured gain;++ - socket activation, which gives zero-downtime restarts, would need re-engineering;++ - it adds a daemon (Docker) or a new unit model (Podman quadlets) to operate;++ - image builds and registries add a supply chain and storage we do not need on one host.++- **Kubernetes.** Rejected: one host, no scheduling problem to solve.++- **Containers for some services only.** Rejected until a service needs it: two runtimes double the++ operator's surface.++++## Revisit when any of these is measured++++- A second host joins, and services move between hosts: portability then pays for images.++- A service needs a system dependency or runtime version that conflicts with another's on the host.++- Stage C lands. Its sandbox runtime may be reused for platform workers that process untrusted media,++ such as ffmpeg on uploads.++- A unit cannot be hardened enough under systemd, for example it needs its own network namespace with++ an egress policy that systemd's `IPAddressAllow`/`RestrictAddressFamilies` cannot express.++++## Consequences++++- Deploy tooling stays as it is (`ovhost deploy`, Live's release layout, socket activation).++- New services ship a unit, a `lifecycle` block and `ReadWritePaths`, never a Dockerfile.++- WS-N task 11 (`ovhost` absorbs the per-repository `deploy.sh` scripts) proceeds as systemd strategies.++++## Acceptance++++- `ovhost validate` passes for every service with `runtime` absent, which means systemd.++- No first-party repository carries a Dockerfile or compose file that production uses.@@@ -35,3 +35,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each| [ADR-029](ADR-029-account-merge.md) | Merging two accounts: survivor keeps the subject, the other becomes an alias, services follow `network.subject.merged` | Accepted 2026-09-26; implementation pending || [ADR-030](ADR-030-follow-graph.md) | Follow graph: Network owns follows by subject, Live keeps a projection; expand/migrate/contract with reconciliation | Accepted 2026-09-26; migration pending || [ADR-031](ADR-031-s3-compatible-surface.md) | S3-compatible subset for Media: bucket = project namespace, object CRUD + ListObjectsV2 + presign, per-project keys | Accepted 2026-09-26; implementation pending |++| [ADR-032](ADR-032-containers.md) | Containers: platform services stay systemd units (one adapter in ovhost, a reserved `runtime` field); tenant code isolation belongs to Stage C | Accepted 2026-09-26 |
new file mode 100644
new file mode 100644
@@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
+# ADR-032: Containers for platform services
+
+**Status:** Accepted 2026-09-26 (roadmap WS-N task 12). Revisit on any trigger below.
+
+## Context and current evidence
+
+- Every OpenVibe service runs as a systemd unit on one host, each as its own Unix user, deployed by
+ `ovhost deploy <service>` (OpenVibe.Host) or a per-repository `deploy.sh`. That is 25 services on 2026-09-26.
+- Deploys are checkout moves with dependency install, readiness polling and automatic rollback. Live
+ uses a release layout (`releases/<time>-<sha>`, `current` symlink) and **systemd socket activation**,
+ so a restart never refuses a connection (`127.0.0.1:3000` held by pid 1).
+- Units already carry hardening: `ProtectSystem`, `NoNewPrivileges`, `PrivateTmp` and `ReadWritePaths`
+ per data directory. `ovhost validate` checks each unit against the service's `lifecycle` declaration
+ (Contracts 0.55.0).
+- Backups, restore drills, the browser check and the alert relay are systemd timers that `ovhost` drives.
+- Stage C of Host (sandboxed **user** code) will need isolation stronger than a Unix user. It is not
+ started, and it has its own isolation ADR.
+- The host has 15 GB of memory and 11 GB available at peak; no service has asked for a system dependency
+ that conflicts with another's.
+
+## Decision
+
+- **Platform services stay systemd units.** There is no container runtime for first-party services. The
+ isolation they need comes from what systemd gives per unit: a dedicated user, a read-only system,
+ private /tmp, capability and syscall filters where a unit can take them, and slices for memory and
+ CPU limits.
+- **ovhost keeps one deploy adapter: systemd.** The inventory's service entry keeps `unit` and `socket`,
+ and a future `runtime` field is reserved: an absent value means `systemd`. A container adapter, if one
+ is ever added, is a second strategy behind the same deploy, readiness, rollback and lifecycle checks,
+ never a separate path.
+- **Tenant and user code (Stage C) is out of scope here.** Running untrusted code needs a sandbox
+ (gVisor, Firecracker or equivalent), with budgets, egress policy and kill/revoke. That is the Stage C
+ isolation ADR, and it may well use containers or micro-VMs.
+
+## Alternatives considered
+
+- **Docker (or Podman) Compose for every service.** Rejected for now:
+ - it rewrites deploy, rollback, backups and readiness for no measured gain;
+ - socket activation, which gives zero-downtime restarts, would need re-engineering;
+ - it adds a daemon (Docker) or a new unit model (Podman quadlets) to operate;
+ - image builds and registries add a supply chain and storage we do not need on one host.
+- **Kubernetes.** Rejected: one host, no scheduling problem to solve.
+- **Containers for some services only.** Rejected until a service needs it: two runtimes double the
+ operator's surface.
+
+## Revisit when any of these is measured
+
+- A second host joins, and services move between hosts: portability then pays for images.
+- A service needs a system dependency or runtime version that conflicts with another's on the host.
+- Stage C lands. Its sandbox runtime may be reused for platform workers that process untrusted media,
+ such as ffmpeg on uploads.
+- A unit cannot be hardened enough under systemd, for example it needs its own network namespace with
+ an egress policy that systemd's `IPAddressAllow`/`RestrictAddressFamilies` cannot express.
+
+## Consequences
+
+- Deploy tooling stays as it is (`ovhost deploy`, Live's release layout, socket activation).
+- New services ship a unit, a `lifecycle` block and `ReadWritePaths`, never a Dockerfile.
+- WS-N task 11 (`ovhost` absorbs the per-repository `deploy.sh` scripts) proceeds as systemd strategies.
+
+## Acceptance
+
+- `ovhost validate` passes for every service with `runtime` absent, which means systemd.
+- No first-party repository carries a Dockerfile or compose file that production uses.
@@ -35,3 +35,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
@@ -35,3 +35,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
| [ADR-029](ADR-029-account-merge.md) | Merging two accounts: survivor keeps the subject, the other becomes an alias, services follow `network.subject.merged` | Accepted 2026-09-26; implementation pending |
| [ADR-029](ADR-029-account-merge.md) | Merging two accounts: survivor keeps the subject, the other becomes an alias, services follow `network.subject.merged` | Accepted 2026-09-26; implementation pending |
| [ADR-030](ADR-030-follow-graph.md) | Follow graph: Network owns follows by subject, Live keeps a projection; expand/migrate/contract with reconciliation | Accepted 2026-09-26; migration pending |
| [ADR-030](ADR-030-follow-graph.md) | Follow graph: Network owns follows by subject, Live keeps a projection; expand/migrate/contract with reconciliation | Accepted 2026-09-26; migration pending |
| [ADR-031](ADR-031-s3-compatible-surface.md) | S3-compatible subset for Media: bucket = project namespace, object CRUD + ListObjectsV2 + presign, per-project keys | Accepted 2026-09-26; implementation pending |
| [ADR-031](ADR-031-s3-compatible-surface.md) | S3-compatible subset for Media: bucket = project namespace, object CRUD + ListObjectsV2 + presign, per-project keys | Accepted 2026-09-26; implementation pending |
+| [ADR-032](ADR-032-containers.md) | Containers: platform services stay systemd units (one adapter in ovhost, a reserved `runtime` field); tenant code isolation belongs to Stage C | Accepted 2026-09-26 |