openvibers/openvibe.contracts Commits
ADR-036 (Run is an execution authority) and ADR-045 (the Node is the universal runtime), proposed (#45)
Changes
nnew file mode 100644@@@ -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`)++ in `[email protected]`.++++## 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.nnew file mode 100644@@@ -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.@@@ -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) |
new file mode 100644
new file mode 100644
@@ -0,0 +1,152 @@
@@ -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`)
+ in `[email protected]`.
+
+## 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.
new file mode 100644
new file mode 100644
@@ -0,0 +1,146 @@
@@ -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.
@@ -39,6 +39,9 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
@@ -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-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-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-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-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-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) |
| [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) |