openvibers/openvibe.contracts Commits

ADR-036 (Run is an execution authority) and ADR-045 (the Node is the universal runtime), proposed (#45)

OP OpenVibers committed 5 Oct 2026 · e2b2ae7b9d · 3 files, +301 −0

* ADR-036 (Run is an execution authority) and ADR-045 (the Node is the universal runtime), proposed

**Changes**
- `docs/adr/ADR-036-run-execution-authority.md:1` — new ADR: Run is an execution authority, not a Host detail (runtime classes, provider adapters as Fabric offers, `openvibe-sdk/placement` `plan()` + Node descriptor→offer mapping, cost ladder, the guarantees).
- `docs/adr/ADR-045-node-universal-runtime.md:1` — new ADR: the Node is OpenVibe's universal runtime (one signed binary, execution worker `worker:<class>`, computer control under local control levels + kill switch, cache/storage/delivery, local policy overrides cloud).
- `docs/adr/README.md:42` — ADR-036 row, beside ADR-034/035; `docs/adr/README.md:44` — ADR-045 row, beside ADR-042/046.

**Tests**
- `ov test test/platform-run.test.js test/platform-fabric.test.js` → `2 passed in 23 s` (it auto-selected exactly the 2 of 12 files the change reaches).
- `node scripts/docs-currency.js ~/OpenVibers --only contracts` → `✓ contracts current`, `1/1 current`, exit 0.

**Where the code/plan differed from the brief**
- No diff: `docs/adr` had no 036/045; `origin/main` has no ADR-045 in any repo; README rows 40–41 were ADR-034/035; plan §8 line 991 (ADR-036 "to write before their code"), line 986 (ADR-045 "to revise") and T14 lines 559–583 all matched.
- Two things worth flagging: the plan's cross-reference to "§2.1.11" has no such heading in the plan file, so I resolved it to the Fabric section as written (T1 / ADR-046); and the Node's own docs conflict on `code` — `docs/worker.md` documents `function` and `code`, while `docs/protocol.md`'s nack table says every class but `function` is unavailable. I phrased it as "`function` (first implemented) and `code` are the classes the worker declares" rather than claiming both ship.
- The docs-currency gate checks service README/STATUS, not ADRs; as noted, `test/run.js` has no ADR-index check, so CI's `openvibe-contracts-check` is the gate for these docs.

* ADR-045 states the Network/Bot credential split (amends ADR-043) and a checksummed binary; ADR-036 names ADR-048 resource control; ADR-043 indexed (review)

All four findings are fixed. The change is docs-only (three ADR files), no code paths touched.

**Findings**

1. **ADR-045/ADR-043 credential split fixed.** ADR-045 now states the split explicitly — Network owns the node/device principal and issues the pairing credential, Bot keeps the robot binding and the control link — and says this amends ADR-043's credential paragraph (decisions 1–2), while the rest of ADR-043 stands: `docs/adr/ADR-045-node-universal-runtime.md:6` (status), `:52-55` (§1), `:58-61` (§2, "Network owns identity and pairing").
2. **"signed, updatable static binary" corrected to "checksummed release binary",** with code-signing and self-update listed under "What this ADR does not claim" (releases publish `SHA256SUMS` only): `docs/adr/ADR-045-node-universal-runtime.md:60`, `:140`; the §2 heading at `:51` and the README summary at `docs/adr/README.md:45` were aligned. The `:16` "one signed binary" is inside the quoted plan text and stays as a quote.
3. **ADR-036 §1 now names ADR-048:** Run's jobs and sandboxes are the authority's resources, named by OVRN and changed through `common.resource-control-request@1` calls to Run, which appears to Services as an authority like any other: `docs/adr/ADR-036-run-execution-authority.md:52-53`.
4. **Missing ADR-043 row added** between ADR-042 and ADR-045, from the ADR-043 file's own title and status: `docs/adr/README.md:44` (its status cell is `Accepted 2026-09-29 (plan track T15, "Bot complete"; …)`).

**Tests**

- `node scripts/docs-currency.js ~/OpenVibers --only contracts` → `✓ contracts current`, `1/1 current`, exit 0.
- No test applies: the diff reaches only `docs/adr/ADR-036-*.md`, `docs/adr/ADR-045-*.md` and `docs/adr/README.md`, and nothing in `test/` reads the ADR index. Not committed or pushed, per instructions.

Changes

ddiff --git a/docs/adr/ADR-036-run-execution-authority.md b/docs/adr/ADR-036-run-execution-authority.md
nnew file mode 100644
iindex 00000000..f340800d
---- /dev/null
++++ b/docs/adr/ADR-036-run-execution-authority.md
@@@ -0,0 +1,152 @@
++# ADR-036: Run is an execution authority, not a Host detail
++
++**Status:** Proposed 2026-10-05 (plan track T14, "Run is not a Host detail"; plan §8 lists ADR-036 as one of the records
++"to write before their code", and Run's own code is owner-blocked). For the owner's review. Builds on ADR-034 §5
++(control plane and data plane; cells), ADR-032 (containers: platform services stay systemd units; tenant code
++isolation belongs to Stage C) and ADR-046 (offers, requirements, placement, signed plans and trust classes).
++
++## Context and current evidence
++
++- **Plan T14** (lines 559–583): *"Finish line: any workload — a function, code, a browser session, a Linux or desktop
++ machine, a GPU job — runs in a sandbox with metering, limits, cancellation and streaming, placed by the Fabric on
++ the cheapest capability that works; the Node runtime runs the same workloads on official servers and on users' own
++ machines under their trust policy."* And: *"**Run is not a Host detail.** Host deploys persistent applications; Run
++ executes isolated jobs/environments. They can share machines; they are separate authorities (ADR-036 rewritten
++ around this expanded purpose)."*
++- **Plan §8** lists ADR-036 as a rewrite: *"ADR-036 Run (**rewrite** around the expanded Run)"*. The line numbers
++ above are from a read of `origin/main`; the file has no ADR-036 today, and the plan's T14 wording is the only
++ statement of the decision.
++- **What the code already does.** Contracts carry the vocabulary but not the service:
++ `platform.runtime-class@1` (exactly six classes, in order: `function`, `code`, `browser`, `linux`, `desktop`,
++ `gpu`), `platform.job@1`, `platform.job-frame@1`, and the `run.*` request/result/event contracts plus capabilities
++ `run.job.submit`/`read`/`list`/`cancel`/`stream`/`admin` and events `run.job.queued|started|succeeded|failed|cancelled|expired`.
++ Every one of those contracts is marked PLANNED, `run` is a `placeholder` service, and `openvibe.run` has no
++ repository (`manifests/services/run.json`; `test/platform-run.test.js:90`). No placer call, no Run API.
++- **Host is the other authority.** Host already has its own contracts (`host.deploy@1`, `host.site@1`,
++ `host.project@1`, `host.domain@1`, and the create/read/manage request and result pairs). Nothing in them executes a
++ tenant workload. ADR-032 keeps platform services as systemd units and puts tenant-code isolation in Stage C; that
++ isolation is Run's, not Host's.
++- **The Fabric half is written down.** ADR-046 defines `platform.resource-offer@1`,
++ `platform.workload-requirements@1`, `platform.placement-result@1` and the signed `placement-plan@1`, and reserves
++ the `worker:<class>` capability names. `openvibe-sdk/placement` (`plan()`) is the one placer.
++ `platform.runtime-offer.v1.json` is the execution-capacity offer (its `kind` is `container`/`process`/`wasm`/`vm`/`function`,
++ with `limits`, `price`, `availability` and `constraints`); `platform.resource-offer.v1.json` carries the
++ capabilities, trust, latency and health the placer filters and ranks on.
++- **The first executor exists.** OpenVibe.Node's job worker runs `platform.job@1` jobs and emits per-second
++ `job_usage`, folded into `platform.usage-sample@1` readings keyed `run:<job id>:<n>` (`test/platform-run.test.js`;
++ OpenVibe.Node `docs/protocol.md` and `docs/worker.md`). The Node advertises `worker:function` (and `worker:code`)
++
++## Decision
++
++### 1. Run is an execution authority; Host is a deployment authority
++
++- **Host** deploys and operates persistent applications: a project's sites, domains, deploys and their config
++ (`contracts/host/*`). Its resources are long-lived and its lifecycle is a deploy.
++- **Run** executes isolated jobs and environments: a single run of a function, code, a browser session, a Linux or
++ desktop machine, or a GPU job, with its own sandbox, its own metering and its own end states. Its resources are
++ jobs and sandboxes, not sites.
++- The two are separate authorities: separate service principals, capabilities, contracts, events and data. They **may
++ share machines** — a cell's host can run both — but sharing a machine is not sharing authority. A Run outage does
++ not stop Host deploys and a Host deploy does not create, own or bill a Run job.
++- Run's jobs and sandboxes are the authority's resources: each is named by OVRN and every change goes through a
++ `common.resource-control-request@1` call to Run, which appears to Services as an authority like any other (ADR-048).
++
++### 2. Runtime classes
++
++- A job names one `platform.runtime-class@1` class: `function`, `code`, `browser`, `linux`, `desktop` or `gpu`.
++ `function` is the first class implemented; the others are reserved names (the class contract says so).
++- A worker advertises one reserved Fabric capability per class it runs, `worker:<class>`
++ (`platform.resource-offer@1.$defs.reservedWorkerCapabilities`, one name per class, in class order). A class it does
++ not advertise is refused (`nack`, `unsupported`); Run places a job only on a node that advertises
++ `worker:<class>`, so a class no node runs ends `expired` (`run.job.unplaceable`).
++
++### 3. Provider adapters are Fabric offers, not code paths in Run
++
++The adapters the plan names (T14, lines 567–569) — OpenVibe official worker, OpenVibe Node worker, self-hosted Run
++worker, managed browser providers, external sandbox providers, external GPU providers — are **offers**, not branches
++inside Run. Each reports:
++
++- `platform.resource-offer@1` for the machine or provider (kind, region/cell, trust, `capabilities`, capacity,
++ latency, health, pricing), and
++- `platform.runtime-offer.v1.json` for the execution capacity it sells (its `kind`, `limits`, `price`, `availability`
++ and `constraints`).
++
++Run states `platform.workload-requirements@1` and reads the `platform.placement-result@1`. Placement is **always**
++`openvibe-sdk/placement` `plan()`: hard constraints first, then the objective and hysteresis, with an explanation.
++Run writes no placer of its own (ADR-046: "A service states requirements and reads results; it does not rank
++offers."). The plan's §2.1.11 cross-reference is the Fabric section as written in ADR-046/T1, not a separate scheme.
++
++**Node descriptor → offers.** When Run turns an OpenVibe.Node into Fabric offers it applies the mapping the Node
++protocol already fixes (OpenVibe.Node `docs/protocol.md:310–321`):
++
++| Node descriptor field | Fabric offer | Example |
++| --- | --- | --- |
++| `capabilities.worker.runtime_classes` | `resource-offer.capabilities` item `worker:<class>` | `worker:function`, `worker:code` |
++| `capabilities.worker.max_jobs` | `runtime-offer.limits.concurrency` | 1 |
++| `capabilities.worker.max_ttl_ms` | `runtime-offer.limits.duration_seconds` | 600 |
++| device `node_id` / cell | `resource-offer.node_id` / `resource-offer.cell` | `node_01H…` / `wnam-1` |
++| worker trust (local policy) | `resource-offer.trust` | `user-owned` for a user's own Node |
++
++### 4. The cost ladder
++
++The scheduler always uses the **cheapest capability that works**: API/MCP → HTTP/structured → Playwright/CDP browser
++→ AI browser → visual desktop → full machine (plan T14, line 570). This ladder is what keeps a caller such as Actor
++cheap. Each rung is a candidate offer class the placer can choose; the objective (`cheapest`, `balanced`,
++`lowest-latency`, `private`) decides among offers that pass the hard constraints.
++
++### 5. Run guarantees
++
++Every job, on every adapter, is held to the same guarantees (plan T14, lines 572–574):
++
++- **Per-second metering** — one idempotent `platform.usage-sample@1` per elapsed wall second, keyed
++ `run:<job id>:<n>`; a resend derives the same reading. `job_exit.usage` is authoritative and never corrects a
++ metered second downward.
++- **TTL, idle suspend, cancel, snapshot/resume** — a job has a lifetime from creation (wait plus run); an idle
++ environment may be suspended and later resumed; `job_cancel` kills the process group and a job cancelled before it
++ starts never starts.
++- **Streaming stdout** — stdout is streamed as chunks (`job_stdout` on the Node link; `run.job-stream-event@1` and a
++ stream ticket over the API), best effort, bounded.
++- **Artifacts via Media** — inputs are Media objects pinned by sha256; outputs are Media objects. Run never fetches a
++ caller-chosen URL.
++- **Resource limits** — wall, CPU and memory caps, merged stricter with the node's own local caps; a request over the
++ project's tier cap is refused, never silently lowered.
++- **Network policy** — `none` (default), `public` or `openvibe-only`; a node whose local policy is stricter runs the
++ stricter one.
++- **Strong isolation** — "container-only isolation is not sufficient for hostile arbitrary code" (plan T14, line 574).
++ The class and adapter of the sandbox is Run's to enforce; the exact mechanism is open (see below).
++
++## Consequences
++
++- One execution authority and one placement vocabulary. A new runtime class or provider is a new offer (and, for a
++ class, a reserved capability name), not new code in Run.
++- Host and Run can share a cell's machines without sharing authority; deleting either leaves the other's rows alone.
++- The Node job worker is the first execution worker Run places; its per-second frames are already the metering
++ contract Billing consumes.
++- The `run.*` contracts are the authority's API; they stay PLANNED until the owner unblocks the service.
++
++## Open questions for the owner
++
++1. **Isolation mechanism.** The plan requires more than a container for hostile arbitrary code but names no
++ mechanism. Micro-VM (Firecracker/Kata), a userspace kernel (gVisor) and namespace/seccomp sandboxes are all open;
++ the Node worker's current `function` isolation is namespaces plus a seccomp allowlist.
++2. **Snapshot/resume backend.** Whether a resumed environment's state lives in Media, a Node-local disk, or a
++ provider's own snapshot is open.
++3. **Idle-suspend policy.** Who decides to suspend — Run, the node's local policy, or a placement objective — is
++ open.
++4. **Self-hosted Run workers.** Whether one may run other projects' jobs, or only its owner's, mirrors ADR-046's
++ `user-owned` question and is open.
++5. **Browser/desktop semantics.** Headed versus headless, and what a session's lifetime is, is deferred to the code
++ for those classes.
++
++## What this ADR does not claim
++
++- No OpenVibe.Run repository exists on `origin/main`; `run` is a placeholder service and `openvibe.run` has no
++ repository. Every `run.*` contract is PLANNED and nothing serves them.
++- Only `function` (and, per the worker's own docs, `code`) exists as a worker class, in OpenVibe.Node; `browser`,
++ `linux`, `desktop` and `gpu` are reserved names with no worker.
++- Cells are a single host (`cell = wnam-1`) today; O24's second physical cell, the WireGuard mesh and geo placement
++ are not built (plan T14, lines 580–581).
++- Per-second metering is proven only for Node jobs; no Billing consumer reads Run usage on `origin/main` yet.
++- The provider adapters above are the plan's list, not deployed adapters; no managed-browser, external-sandbox or
++ external-GPU offer producer exists.
ddiff --git a/docs/adr/ADR-045-node-universal-runtime.md b/docs/adr/ADR-045-node-universal-runtime.md
nnew file mode 100644
iindex 00000000..9e3cec68
---- /dev/null
++++ b/docs/adr/ADR-045-node-universal-runtime.md
@@@ -0,0 +1,146 @@
++# ADR-045: The Node is OpenVibe's universal runtime
++
++**Status:** Proposed 2026-10-05 (plan §8: *"ADR-045 (Node; **revise: Node is the universal runtime, not a
++Bot/Actor helper**)"*; plan T14, lines 575–579). For the owner's review. Builds on ADR-043 (Bot — devices, pairing,
++control and safety), ADR-046 §5 (the `worker:<class>` capability names) and ADR-048 (OVRN and authority boundaries).
++ADR-043 stays the robot/device half of the same Node; this record is the runtime half, and amends ADR-043's credential
++paragraph: Network owns the node/device principal and the pairing credential, Bot keeps the robot binding and control
++link.
++
++## Context and current evidence
++
++- **The plan revises ADR-045.** Plan §8 (line 986) calls ADR-045 written but to be revised: *"Node is the universal
++ runtime, not a Bot/Actor helper"*. `origin/main` has no ADR-045 file in any repository, so the revision is unstated
++ in the contract repository.
++- **What the plan says the Node is.** Plan T14, lines 575–579: *"**Node runtime** (ADR-045 revised: Node is the
++ universal runtime, not a Bot/Actor helper). **Core built on `origin/main`:** one signed binary, pairing, control
++ link, WHIP publisher, plugin runtime, the Adeept and Cozmo plugins, installer and CI. Still to add for its other
++ roles: computer control (terminal, files, screen, input, browser) under control levels, local kill switch and
++ recording (Actor's need, T17); execution-worker capability (Run's need); cache/storage/delivery capability (Media's
++ need, T4). **Local policy always overrides cloud commands.**"*
++- **Products bind capabilities, not device code.** Plan lines 321–324: *"Node identity is a Network principal.
++ Network owns the node/device principal and pairing credentials. Services manages the resource; Node presents
++ capabilities (T14); Bot binds robot resources to Node capabilities (T15); Actor binds computer control (T17); Run
++ binds execution workers (T14); Media binds cache/storage (T4). Bot keeps robots, robot profiles, operators, command
++ leases and physical safety — not universal device identity."*
++- **What the code already does** (OpenVibe.Node, `origin/main`): one Go static binary per platform — pairing
++ (device credential or a Network node principal), one outbound WebSocket to `wss://openvibe.bot/device`, heartbeats
++ and a deadman, owner limits clamped before any driver sees a value, a persisted e-stop and a local kill switch, a
++ plugin supervisor, WHIP video publishing (pion), and the system service (systemd, launchd, Windows). Plugins are
++ separate processes over JSON lines: `dryrun`, `adeept_adr036`, `cozmo`. The job worker runs `platform.job@1`
++ `function`/`code` jobs from locally declared artifacts only, with namespaces, a seccomp allowlist, CPU/memory/wall
++ caps and per-second `job_usage` (`docs/worker.md`, `docs/protocol.md`). Its `status.capabilities.worker` lists the
++ reserved `worker:<class>` names it runs; Run maps that descriptor to Fabric offers
++ (`docs/protocol.md:303–321`).
++- **The capability names are already agreed.** ADR-046 §5 reserves `worker:function`, `worker:code`,
++ `worker:browser`, `worker:linux`, `worker:desktop` and `worker:gpu` for the Node job worker, one per
++ `platform.runtime-class@1` class, in `platform.resource-offer@1.$defs.reservedWorkerCapabilities`.
++- **The control levels are plan wording.** Plan lines 745–747: *"each Node has a control level set on the device
++ (Observe / Ask / Trusted / Full control), a local indicator, a local kill switch, optional container/VM
++ confinement, replayable transcripts and file changes, and local secrets used through handles."* Of that, the local
++ kill switch (the persisted stop latch) exists; the four named levels and recording do not.
++- **Media's need is stated too.** Plan lines 366–368: *"Node is a storage/delivery candidate too
++ (`media.cache`, `storage.hot`, prepaid disk and bandwidth), so the placement engine can use unused capacity on
++ already-paid OpenVibe nodes before buying CDN traffic."*
++
++## Decision
++
++### 1. One Node, the universal runtime
++
++The Node is OpenVibe's universal runtime — one agent for a person's computers, servers, Raspberry Pis, phones and
++robots. It is **not** a Bot helper or an Actor helper: Bot, Actor, Run and Media are all clients of the same Node,
++and each binds the capabilities it needs. ADR-043 remains the robot/device half (robots, robot profiles, operators,
++command leases and physical safety); this ADR is the runtime half. The one split this record draws inside ADR-043 is
++the credential: **Network owns the node/device principal and issues the pairing credential; Bot keeps the robot
++binding and the control link.** That amends ADR-043's credential paragraph (its decisions 1-2); the rest of ADR-043
++stands.
++
++### 2. One release binary, one pairing, one control link, one local policy
++
++- The core is one checksummed release binary per platform with one pairing path and one outbound control link.
++ A role is **a capability the Node advertises plus local configuration**, never a second agent or a second install.
++- The Node is a Network principal (its node/device principal and pairing credential are Network's), and its resource
++ is named by OVRN (ADR-048). Network owns identity and pairing; Bot keeps the robot binding and the control link
++ (amending ADR-043's credential paragraph); Node presents capabilities.
++
++### 3. Execution worker (Run's need)
++
++- The Node advertises one `worker:<class>` capability per `platform.runtime-class@1` class it runs, per ADR-046 §5,
++ and refuses every class it does not advertise. `function` (the first class implemented) and `code` are the classes
++ the worker declares; `browser`, `linux`, `desktop` and `gpu` are reserved names.
++- Run places jobs on the Node by converting its descriptor into `platform.resource-offer@1` /
++ `platform.runtime-offer@1` (OpenVibe.Node `docs/protocol.md:310–321`) and calling `openvibe-sdk/placement` `plan()`.
++ The Node runs only locally declared artifacts, at an exact version, for the class they declare; nothing is fetched
++ and no path comes from the server.
++- Metering, TTL, cancel, limits, egress and stdout streaming are the job worker's own behaviour (`docs/worker.md`),
++ and are the Run guarantees ADR-036 states.
++
++### 4. Computer control (Actor's need, T17)
++
++- The Node exposes terminal, files, screen, input and browser control to an authorised client under **control levels
++ set on the device** (the plan names Observe / Ask / Trusted / Full control), with a local indicator, a **local kill
++ switch**, optional container/VM confinement, replayable transcripts and file changes, and local secrets used
++ through handles.
++- This control is off by default and approved per app (plan line 713); Actor reaches a person's own devices only
++ through the Node under a delegated grant.
++
++### 5. Cache/storage/delivery (Media's need, T4)
++
++- The Node is a storage/delivery **candidate**: it may offer `media.cache`, `storage.hot` and prepaid disk and
++ bandwidth as Fabric offers, so placement can use unused capacity on already-paid nodes before buying CDN traffic.
++- This makes the Node an offer holder, not a second byte authority: Media keeps the object catalog, the location
++ policy and the only deletion path (ADR-031/ADR-046).
++
++### 6. Local policy always overrides cloud commands
++
++- The device's own configuration decides which roles are enabled, which artifacts and classes run, which control
++ level applies, which egress is allowed, and whether the stop latch is set. A cloud command can never widen local
++ policy.
++- The egress rule is the model the other roles follow: a job asking for `public` or `openvibe-only` is placed only on
++ a node whose `worker.egress` allows it, and a worker whose host policy is stricter runs the stricter one
++ (`run.job-create-request@1`; OpenVibe.Node `docs/protocol.md`).
++
++## Alternatives considered
++
++- **Node as a Bot-only helper (the plan's earlier framing):** rejected by the revision. Bot, Actor, Run and Media
++ would each grow a device agent, and an owner would install and pair several.
++- **A separate agent per product (an Actor agent, a Run worker, a Media cache daemon):** rejected. One binary, one
++ control link and one local policy are what make a single device safe to reason about; capabilities distinguish the
++ roles.
++- **Cloud-authoritative control levels:** rejected. The device enforces local policy and the kill switch; generalising
++ ADR-043's device-authoritative safety is the point of the runtime half.
++- **The Node as a second resource authority:** rejected. Products own their resource rows; the Node presents
++ capabilities and local policy (plan lines 321–324, ADR-048).
++
++## Consequences
++
++- One device surface for every product; a new role is a capability plus local configuration, not a new agent.
++- Local policy is the security boundary: no cloud command can enable a role, widen egress or clear the stop latch.
++- The Node class work (`worker:<class>`, ADR-046 §5) and the future Actor and storage/delivery capabilities follow
++ these semantics, so they need no separate device model.
++- Bot keeps robots, robot profiles, operators, command leases and physical safety (ADR-043); the Node keeps execution,
++ computer control and cache/storage/delivery.
++
++## Open questions for the owner
++
++1. **Control levels.** Whether the set is exactly Observe / Ask / Trusted / Full control, how each maps to a local
++ indicator and to recording, and the capability names they use, is plan wording; the contract is open.
++2. **One principal or many.** Whether a Node serving several products/projects is one Network principal with scoped
++ grants or one principal per project is Network's decision and is open (ADR-048 OVRN).
++3. **Cache/storage contracts.** Which offer contracts carry the Node's cache/storage role — the existing
++ `platform.storage-offer@1` / `platform.delivery-offer@1` or a Node-specific one — and whether placement policy
++ stays Media's, is open.
++4. **Other users' workloads.** Whether a Node may run other users' jobs or serve other users' computer control, or
++ only its owner's, mirrors ADR-046's `user-owned` question and is open.
++
++## What this ADR does not claim
++
++- Computer control, recording, cache/storage/delivery and the `browser`/`linux`/`desktop`/`gpu` classes are **not
++ built**; only the job worker's `function`/`code` classes exist.
++- The release binary is not code-signed and has no self-update: releases publish `SHA256SUMS` only.
++- The four control-level names and the local indicator are plan wording; the code today has a local stop latch, not
++ the four levels.
++- No Actor, Run or Media consumer binds these Node capabilities on `origin/main`; Run is a placeholder service
++ (ADR-036).
++- The Node worker is reached over the Bot control link today; it is not yet an offer producer, because the Run that
++ would convert its descriptor is not built.
ddiff --git a/docs/adr/README.md b/docs/adr/README.md
iindex b29f26e9..f5d0c980 100644
---- a/docs/adr/README.md
++++ b/docs/adr/README.md
@@@ -39,6 +39,9 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
| [ADR-033](ADR-033-account-export-and-deletion.md) | Account export and deletion: services push their export parts to Network; deletion after a 30-day grace, each service erases its rows and confirms; money rows kept pseudonymised | Accepted 2026-09-27; implementation in progress |
| [ADR-034](ADR-034-platform-north-star.md) | Platform north star, an open affordable cloud: boundaries now (projects, `ovrn:` resource names, scoped grants, generated contracts, metering), infrastructure when measured; control/data plane and cells; regions, edge nodes, load balancing and a geo API; pulled, gated deploys; four extension levels under one manifest | Proposed 2026-09-28, for the owner's review |
| [ADR-035](ADR-035-postgresql-and-valkey.md) | PostgreSQL 18 as every service's system of record (database + roles per service, PgBouncer transaction pooling, pgBackRest PITR to B2) and Valkey as the shared non-authoritative store; the SDK data layer; services migrate one at a time, smallest first; supersedes ADR-007's trigger rule; amendment: PostgreSQL dialect only (PGlite in tests), a verified SQLite importer, every service async and migrated | Accepted 2026-09-28 (owner decision), amended the same day |
++| [ADR-036](ADR-036-run-execution-authority.md) | Run is an execution authority, not a Host detail: Host deploys persistent applications, Run executes isolated jobs/environments (`function`, `code`, `browser`, `linux`, `desktop`, `gpu`), placed by the Fabric on the cheapest capable offer; provider adapters are offers, not code paths; per-second metering, TTL, idle suspend, cancel, snapshot/resume, stdout streaming, artifacts via Media, limits, network policy and strong isolation | Proposed 2026-10-05 (plan T14; plan §8 lists it to write before its code, and Run's code is owner-blocked), for the owner's review |
| [ADR-042](ADR-042-events-fabric.md) | The events fabric: the delivery-policy class is the one vocabulary and TOPIC/QUEUE/STREAM a derived carrier class; PostgreSQL stays the record, carriers are transports that fail open to `pg-v1`; a delivery lease before any second worker; per-key ordering; the SDK planner places deliveries and explains them; JetStream for STREAM only; opaque cursors replace the global seq after one overlap release; hot/replay/archive tiers; amends ADR-004's NATS rejection | Accepted 2026-09-29 (plan T7) |
++| [ADR-043](ADR-043-bot-devices-and-control.md) | Bot — devices, pairing, control and safety: robots, devices and credentials are separate (one hashed, rotatable, revocable device credential); pairing is a one-time code; one outbound WebSocket per device with absolute deadlines, device-side stops and a latched e-stop; robot profiles rather than rows; owner/operators/viewers with a timed public queue; everything audited; OpenVibe.Bot is its own service | Accepted 2026-09-29 (plan track T15, "Bot complete"; owner direction the same evening: openvibe.bot is an open control panel for robots, streaming optional; the first robots are the owner's Adeept 4WD Smart Car for Raspberry Pi and a Cozmo-based robot) |
++| [ADR-045](ADR-045-node-universal-runtime.md) | The Node is OpenVibe's universal runtime, not a Bot/Actor helper: one checksummed release binary and one control link serving the execution worker (`worker:<class>`, per ADR-046 §5), computer control under device-set control levels with a local kill switch and recording, and cache/storage/delivery; local policy always overrides cloud commands; ADR-043 stays the robot/device half | Proposed 2026-10-05 (plan T14 and §8 revision; consistent with ADR-046 and ADR-048, amends ADR-043's credential paragraph), for the owner's review |
| [ADR-046](ADR-046-universal-adaptive-fabric.md) | The universal adaptive fabric: offers, requirements, placement results and signed plans through one placer (`openvibe-sdk/placement`); health, cost, telemetry and usage samples; five trust classes, with `user-owned` eligible only when a workload names it; `worker:<class>` Node capabilities; the data plane keeps running on the last valid signed plan; T6 AI the reference consumer | Proposed 2026-10-03 (plan T1 step 3), for the owner's review |
| [ADR-048](ADR-048-services-control-plane.md) | The Services control plane: Services aggregates authorities and owns no other service's rows; every console action is a `common.resource-control-request@1` to the owning authority, answered with `common.resource-control-result@1` (idempotency keys, `confirmation_id` for sensitive actions); OVRN `ovrn:<service>:<project_id>:<type>/<id>` (`common.resource-name@1`) is the one cross-service resource name | Accepted 2026-10-04 (plan T13) |