openvibers/openvibe.contracts Commits

ADR-053 (proposed): OpenVibe.Website authors, OpenVibe.Host serves (#51)

OP OpenVibers committed 5 Oct 2026 · c6584df9c4 · 2 files, +171 −0

* ADR-049 (proposed): OpenVibe.Website authors, OpenVibe.Host serves

Changes (file:line)
- `docs/adr/ADR-049-website-host-boundary.md:1` — new 163-line proposed ADR (title/status/Evidence/Decision/Authority-boundaries/Open-questions/What-this-does-not-claim/Out-of-scope/Consequences).
- `docs/adr/README.md:49` — ADR-049 row appended after ADR-048 (3-column table shape preserved).

Check results
- `nice -n 15 node scripts/estate.js --check` → `estate: current`, exit 0.
- D41 docs-status (`scripts/docs-status.js --check` from `OpenVibe.Shared` `origin/main`) over this checkout → `docs-status: docs are current`, exit 0.
- No test file reaches this change (only docs/adr markdown), so no `ov test` file applied; `test/run.js` reads ADR-013/ADR-022 but not the ADR index.

Re-read findings vs the task's line numbers
- The cited Host route numbers matched `origin/main` exactly on re-read: `server/http/api.js:123` (GET `/projects/:id/sites`), `:127` (POST), `:197` (POST `/sites/:id/deploys`), `:207` (POST `/deploys/:id/activate`), `:224` (POST `/sites/:id/domains`), `:237`/`:241`/`:245` (GET/PUT/DELETE `/sites/:id/config`). The router's real surface is larger than that list (takedowns `:105-:120`, `GET/DELETE /sites/:id` `:132`/`:136`, `GET /sites/:id/deploys` `:139`, `GET /deploys/:id` `/log` `:199`/`:203`, `/rollback` `:212`, `DELETE /deploys/:id` `:217`, domains `:220`/`:228`/`:233`, source `:253`/`:257`/`:261`/`:265`); the ADR uses the full surface. The header docstring is lines 2-21 (not 1-25).
- `OpenVibe.Website` has **no repository and no service manifest**, but it does exist as a product manifest (`manifests/products/openvibe.website.json`, `"noRepo": true`) — stated in the ADR, along with no `website.*` contract/namespace/capability and no `OpenVibe.Website` checkout under `~/OpenVibers`.
- "D41 docs-status check over `docs/adr/README.md`" is not what that check does: `scripts/docs-status.js` (OpenVibe.Shared) reconciles `package.json` pins with `README.md`'s versions block and `STATUS.json` only. No script/test validates the ADR index, so the row was verified by hand in addition to the estate check.
- Plan facts cited: Website "Not created" (line 96), T12 moves Website to T19 (line 531), T19 composition `Host + Codes + Actor + Run + Media` (line 783), D36 (line 972); the OpenVibe.Sites placeholder row is line 98 (not 95). The plan lists no Website ADR and no T19 step list; the step-1 framing is noted as the dossier's.

* The Website/Host ADR is ADR-053 (049 is reserved), calls Host's own API with the control contract and OVRNs open, and asks who serves tenant zones (review)

All four findings fixed in `docs/adr/ADR-053-website-host-boundary.md` and `docs/adr/README.md`; `node scripts/estate.js --check` → `estate: current`.

- **Finding 1 — renumber:** Plan §8 reserves ADR-046–052 and docs/adr holds none of 047–052, so 053 is the first unreserved number; renamed the file to `ADR-053-website-host-boundary.md`, its title (`docs/adr/ADR-053-website-host-boundary.md:1`), and the README row (`docs/adr/README.md:49`, still ascending after ADR-048).
- **Finding 2 — ADR-048 claim softened:** Decision 2 now reads "Website calls Host's own API under its own principal and grants" and marks the common control contract and Host OVRN names open (`docs/adr/ADR-053-website-host-boundary.md:78-80`), with open question 6 added (`docs/adr/ADR-053-website-host-boundary.md:135-138`).
- **Finding 3 — "step 1" dropped:** removed from the Status line, the intro sentence, and the evidence sentence in the ADR (`docs/adr/ADR-053-website-host-boundary.md:3,6,22,63`) and from the README row (`docs/adr/README.md:49`).
- **Finding 4 — zone/TLS open question:** added open question 7 on which zone serves Website tenant sites and who issues their TLS certificates, noting the `yourname.openvibe.website` promise versus Host owning only `openvibe.host` (`docs/adr/ADR-053-website-host-boundary.md:139-141`).

Docs only; nothing committed or pushed. No test file reaches this change (the only `docs/adr` readers in `test/run.js` load ADR-013 and ADR-022), so no `ov test` run was applicable.

Changes

ddiff --git a/docs/adr/ADR-053-website-host-boundary.md b/docs/adr/ADR-053-website-host-boundary.md
nnew file mode 100644
iindex 00000000..18f4e21f
---- /dev/null
++++ b/docs/adr/ADR-053-website-host-boundary.md
@@@ -0,0 +1,170 @@
++# ADR-053: The Website/Host boundary — OpenVibe.Website authors, OpenVibe.Host serves
++
++**Status:** Proposed 2026-10-05 (plan track T19), for the owner's review. Builds on ADR-014 (developer
++projects and tenancy), ADR-006 (Media object, location and namespace model), ADR-036 (Run is an execution authority,
++not a Host detail) and ADR-048 (one authority per resource; a control operation is a call to the owning authority).
++Records the boundary T19 needs; the plan lists no Website ADR.
++
++## Evidence
++
++- **Host is built and is the site, deploy, domain and serving authority today.** Its own surface, `OpenVibe.Host`
++ `origin/main` `server/http/api.js:2-21`, states it: sites (`GET/POST /projects/:id/sites`, `GET/DELETE /sites/:id`,
++ `host.site.manage`), config (`GET/PUT/DELETE /sites/:id/config`, `host.site.config`), source
++ (`GET/PUT/DELETE /sites/:id/source`, `host.site.manage`), deploys (`GET/POST /sites/:id/deploys`, `GET /deploys/:id`,
++ `GET /deploys/:id/log`, `POST /deploys/:id/activate`, `POST /sites/:id/rollback`, `DELETE /deploys/:id`,
++ `host.deploy.create` / `host.site.manage`), domains (`GET/POST /sites/:id/domains`, `POST /domains/:id/verify`,
++ `DELETE /domains/:id`, `host.domain.manage`) and staff takedowns (`host.site.manage`).
++- The routes, exactly as they read on `origin/main` (the header's prose is not the line numbers): sites
++ `GET /projects/:id/sites` `:123`, `POST /projects/:id/sites` `:127`, `GET /sites/:id` `:132`, `DELETE /sites/:id`
++ `:136`; deploys `GET /sites/:id/deploys` `:139`, `POST /sites/:id/deploys` `:197`, `GET /deploys/:id` `:199`,
++ `GET /deploys/:id/log` `:203`, `POST /deploys/:id/activate` `:207`, `POST /sites/:id/rollback` `:212`,
++ `DELETE /deploys/:id` `:217`, `POST /sites/:id/source/deploys` `:265`; domains `:220`, `:224`, `:228`, `:233`;
++ config `:237`, `:241`, `:245`; source `:253`, `:257`, `:261`; takedowns `:105-:120`. The initial read that named
++ `:123`, `:127`, `:197`, `:207`, `:224` and `:237-:245` **matched `origin/main` on re-read**; the surface is larger
++ than that list (takedowns, rollback, source, `GET /deploys/:id/log`), and this ADR uses the full surface.
++- **Host owns the rows.** `OpenVibe.Host` `origin/main` `migrations/0001_initial.sql` creates `host_sites` (`:43`),
++ `host_deploys` (`:58`), `host_deploy_files` (`:78`), `host_blobs` (`:88`), `host_activations` (`:96`),
++ `host_deploy_logs` (`:107`), `host_domains` (`:116`) and `host_takedowns` (`:139`); `host_sites.active_deploy_id`
++ is the one serving pointer, and `migrations/0003_preview.sql:15-16` adds `host_sites.preview_deploy_id` and
++ `preview_expires_at`. A deploy is immutable and content-addressed: `host.deploy@1` (`contracts/host/deploy.v1.json`)
++ carries `state` (`ready`/`failed`/`deleted`), `source` (`archive`/`files`/`preview`/`git`), its manifest hash and, for
++ a git deploy, the public `repo_url`/`ref`/`commit_sha` provenance. A site is `host.site@1`
++ (`contracts/host/site.v1.json`): one DNS label, the default hostname/URL and the single `active_deploy_id`.
++- **Host owns domain verification and serving.** `host.domain@1` (`contracts/host/domain.v1.json`) is a site's default
++ `<site>.openvibe.host` or a custom domain "served only once its DNS TXT record is verified (re-checked daily)", with
++ "TLS certificates … issued by an operator". A domain is bound to a **site**, not to an authoring project.
++- **Host's capabilities and manifest are registered.** `manifests/services/host.json` gives Host the capabilities
++ `host.site.manage`, `host.deploy.create`, `host.domain.manage`, `host.site.config`, `namespacesOwned: ["host.*"]`, the
++ domains `openvibe.host`, and the events `host.deploy.created`, `host.deploy.activated`, `host.deploy.failed`,
++ `host.domain.verified`, `host.release.published`. Its exposure is `internal` / `publicSite: placeholder` (the domain
++ serves the OpenVibe.Sites placeholder page) — **Stage B is alpha and not launched** (`OpenVibe.Host` `STATUS.json`:
++ `"deployed": "loopback only, not launched"`, and `stages.B` "running on the host on loopback :4910 … not launched").
++- **The host-side contracts already exist.** `contracts/host/`: `project`, `site`, `deploy`, `domain`, `site-config` and
++ the `site-manage-request/result`, `deploy-create-request/result`, `domain-manage-request/result`,
++ `site-config-request/result` pairs, all owned by `host` in `contracts/catalog.json`.
++- **Website is a product, not a service, and has no code.** The only artifact is
++ `manifests/products/openvibe.website.json` (this checkout): domain `openvibe.website`, name `OpenVibe.Website`,
++ `"relationships": { "noRepo": true, … }`, launch "tenant hosting on OpenVibe.Host (built, not launched) and the
++ editor". There is **no `OpenVibe.Website` checkout** under `~/OpenVibers`, **no `manifests/services/website.json`**,
++ **no `manifests/repositories/OpenVibe.Website.json`**, and **no `website.*` id** in `contracts/catalog.json` (grep:
++ zero). Website has no capability, no namespace and no contract.
++- **Website is not OpenVibe.Sites.** `OpenVibe.Sites` is a separate, deliberate component: `manifests/services/sites.json`
++ is the "static placeholder generator; not a runtime" (exposure `live`, no capabilities, no namespaces), and the plan
++ deletes it in T11 (plan line 98: "Sites (33 domains) | — | Placeholder generator | **deleted** in T11"). Website is
++ the product the plan puts on Host, not the placeholder page generator.
++- **The plan.** Plan line 96: "Actor, Services, Run, Watch, Website, Zone | — | Not created | T13/T14/T17/T18/T19".
++ T12 (line 531): "**Website** moves out of this track (T19): the builder/editor composes Host + Codes + Actor + Run +
++ Media, so it is built after those exist instead of being rewritten around them later." T19 (line 776) "Product
++ compositions (each complete when started)": "Every product gets its own authority only for genuinely unique domain
++ data; everything else is composed from existing platform services." The composition row (line 783): "Website
++ (openvibe.website) | Host + Codes + Actor + Run + Media". D36 (line 972): "Website on Host (D36, composed in T19)".
++ T12's Stage B finish line (lines 526-529) is Host's: "tenant vhosts, TLS via ACME for custom domains, objects on
++ Media with replication and backup, per-site headers/redirects/SPA fallback, preview deploys, Git deploys,
++ sitemap/robots". The plan lists **no Website ADR** and gives T19 compositions, not steps.
++
++## Decision
++
++1. **Website owns authoring; Host owns serving.** OpenVibe.Website owns the authoring record a person edits and
++ publishes: its **projects** (the authoring workspaces), **pages** (the page tree and content), **revisions** (the
++ source history a build is made from), **builds** (the artifact and manifest a revision produces), **Media asset
++ pointers** (references to `media.object@1` objects, ADR-006 — pointers, never a second copy of the bytes) and the
++ intended **domains** (the hostname(s) the authoring project targets, and the DNS instructions shown to its owner).
++ OpenVibe.Host owns the **site**, the **deploy** and its **activation**, **domain binding, DNS verification and TLS**,
++ and the actual **serving** of the bytes.
++2. **Host is the one site/deploy authority, and it already is.** Every create, read, change, activate, roll back,
++ delete, domain and config operation is the Host route named in Evidence: `GET/POST /projects/:id/sites`
++ (`server/http/api.js:123`, `:127`), `POST /sites/:id/deploys` (`:197`), `POST /deploys/:id/activate` (`:207`),
++ `POST /sites/:id/domains` (`:224`), `GET/PUT/DELETE /sites/:id/config` (`:237`/`:241`/`:245`) among them. Website
++ calls Host's own API under its own principal and grants; it does not reach into Host's tables. Whether Host also
++ exposes the common control contract (`common.resource-control-request@1`, ADR-048) and what its OVRN resource names
++ (`ovrn:host:<prj>:site/<id>`) are is open (below).
++3. **Website keeps no duplicate `host_sites` / `host_deploys` rows.** It may hold at most a rebuildable read model of
++ what it shows (a site id, an active deploy id), never the row of record, and never a second `active_deploy_id`
++ pointer: the one serving pointer stays `host_sites.active_deploy_id`, exposed as `[email protected]_deploy_id`
++ (`contracts/host/site.v1.json`). A Website publish means "upload a build, then activate it" through Host; the
++ answer is Host's `host.deploy@1`.
++4. **A Website build is not a Host deploy.** A revision and a build are authoring facts; a deploy is Host's immutable,
++ content-addressed record of an uploaded output. Website's build manifest names the files and hashes it intends to
++ publish; Host re-validates and stores them, and only a Host deploy can be served, activated or rolled back. The
++ same rule applies to the CI path: `POST /sites/:id/source/deploys` (`:265`) always lands as the site's preview
++ (`server/http/api.js` `noActivate`), and Website approves it through `POST /deploys/:id/activate` like any other
++ deploy.
++5. **Domains stay Host's rows.** Website owns the intended hostname and shows its owner the TXT instructions; Host owns
++ the `host_domains` row, `POST /sites/:id/domains`, the daily re-check and `POST /domains/:id/verify`, the
++ certificate and the serving (`host.domain@1`). A domain is bound to a Host **site** (`site_id`), not to a Website
++ authoring project. Whether Website keeps any domain row of its own, or reads Host's `host.domains`, is open
++ (below).
++6. **Media bytes stay Media's; Website holds pointers.** An authoring asset is a pointer to a `media.object@1` object
++ (ADR-006); a deploy's served bytes are Host's `host_blobs` / `host_deploy_files` (or, per T12, objects on Media with
++ replication and backup). Website never becomes a second object store.
++7. **Website is not OpenVibe.Sites, and it is not a new serving stack.** It composes Host + Codes + Actor + Run +
++ Media (plan line 783); it never mounts a second Host API, a second `host.*` namespace or its own tenant vhosts.
++
++## Authority boundaries
++
++| Concern | OpenVibe.Website (authoring) | OpenVibe.Host (serving) |
++|---|---|---|
++| Project | the authoring workspace (files, settings, collaborators) it owns | `host.project@1` (`contracts/host/project.v1.json`): the Host tenancy keyed by Network `prj_` (`network_project_id`), quota (`GET/PUT /projects/:id/quota`, `:83`/`:87`) and members (`:92`/`:97`) |
++| Pages | the page tree and content the editor reads/writes | none: a page is not a Host row; it becomes files in a deploy |
++| Revisions | the source history a build is made from (from the editor or a Codes repository) | none: deploys are immutable content-addressed outputs, not revisions |
++| Builds | the artifact and manifest a revision produces | a deploy is the uploaded output: `POST /sites/:id/deploys` (`:197`), or `POST /sites/:id/source/deploys` (`:265`, always preview) |
++| Media assets | pointers to `media.object@1` objects (ADR-006) | the deploy's files (`host_deploy_files`, `host_blobs`); Host serves what it holds |
++| Domains | the intended hostname(s) and the DNS instructions shown to the owner | the served domain: `host.domain@1`, `GET/POST /sites/:id/domains` (`:220`/`:224`), `POST /domains/:id/verify` (`:228`), `DELETE /domains/:id` (`:233`); TLS by the operator |
++| Site | no row; it reads Host's site | `host.site@1`: `GET/POST /projects/:id/sites` (`:123`/`:127`), `GET/DELETE /sites/:id` (`:132`/`:136`); the one serving pointer `host_sites.active_deploy_id` |
++| Deploy / activation | asks Host and shows the answer; keeps no deploy row | `GET /sites/:id/deploys` (`:139`), `GET /deploys/:id` (`:199`), `/log` (`:203`), `POST /deploys/:id/activate` (`:207`), `POST /sites/:id/rollback` (`:212`), `DELETE /deploys/:id` (`:217`) |
++| Site config | none | `GET/PUT/DELETE /sites/:id/config` (`:237`/`:241`/`:245`): headers, redirects, SPA fallback (`host.site.config`) |
++| Git source | the project's code home (Codes, T19) | `GET/PUT/DELETE /sites/:id/source` (`:253`/`:257`/`:261`): the repo and branch the project's CI deploys from; Host never clones, fetches or builds it |
++| Takedown / serve | none | staff takedowns (`:105-:120`) and serving of the active deploy |
++
++## Open questions for the owner
++
++1. **The authoring contracts.** Whether Website's projects, pages, revisions and builds are new `website.*` contracts
++ (and their shapes), or whether pages/revisions map onto Codes' repository objects, is open. Nothing exists today:
++ no `website.*` id, no namespace and no service manifest.
++2. **The Website project versus the Host project.** Whether a Website authoring project is a distinct row that links a
++ Network `prj_` to a Host `host.project@1`, or is the Host project itself with authoring data beside it, is open. The
++ plan's rule (T19, line 778) is only that the product keeps its own authority for genuinely unique domain data.
++3. **Domains.** Whether Website keeps a domain row of its own (the intended hostname and its approval state) or reads
++ Host's `host.domains` directly is open; what is fixed is that the binding, verification, certificate and serving are
++ Host's.
++4. **The build artifact.** Whether a build's output is stored as an authoring artifact (and where), or exists only as
++ the Host deploy it is uploaded into, is open. T12 says Host keeps "objects on Media with replication and backup", so
++ this may be Media, not Website.
++5. **A Website service manifest.** Whether Website registers as a service (a `manifests/services/website.json` with a
++ capability and namespace) before its authoring contracts, or ships as a product that only calls Host, is open.
++6. **Host's control contract and OVRNs.** ADR-048 never mentions Host; this ADR records the call as Host's own API
++ under Website's principal and grants. Whether Host also exposes the common control contract
++ (`common.resource-control-request@1`, ADR-048), and what its OVRN resource names look like
++ (`ovrn:host:<prj>:site/<id>`), is open.
++7. **The serving zone and TLS.** The Website product manifest promises `yourname.openvibe.website`
++ (`manifests/products/openvibe.website.json`), but Host's manifest owns only `openvibe.host`. Which zone serves
++ Website tenant sites and who issues their TLS certificates is open.
++
++## What this ADR does not claim
++
++- No OpenVibe.Website repository exists on `origin/main`; there is no `OpenVibe.Website` checkout, no repository
++ manifest and no service manifest. Every product manifest statement above is `manifests/products/openvibe.website.json`
++ in this checkout, `"noRepo": true`.
++- No `website.*` contract exists in `contracts/catalog.json`, no `website` namespace and no Website capability; the
++ authoring model in Decision 1 is the boundary being fixed, not built code.
++- Host's serving side is alpha and **not launched**: `manifests/services/host.json` exposure is `internal` /
++ `publicSite: placeholder`, and `OpenVibe.Host` `STATUS.json` says Stage B "not launched". The routes in Evidence are
++ code on `origin/main`, not a public service.
++- No contract schema is added or changed by this ADR; it is prose and cites the existing `host.*` contracts.
++
++## Out of scope
++
++- Website's own repository, its editor/UI, its service manifest, its contracts and its deploy (T19).
++- Codes' repository/file model and the T16 work; Actor's and Run's generation/execution paths (T17/T14); Media's object
++ API (ADR-006/ADR-031) — Website only holds pointers.
++- Host's Stage B launch, Public Suffix List submission and its Stage C integration with Run (T12).
++- The `host.*` contracts and routes themselves: this ADR cites them, it does not redesign them.
++
++## Consequences
++
++- Additive and documentation-only: one new ADR and its row in `docs/adr/README.md`. No schema, no manifest and no code
++ change, so `docs/ESTATE.md` and `manifests/repositories/` are untouched and `node scripts/estate.js --check` stays
++ clean.
++- Website can be coded once T12 (Host Stage B), T14 (Run) and T17 (Actor) land, against Host's existing routes, without
++ a second site/deploy model.
++- Rollback: the ADR is a document; removing it changes no runtime.
ddiff --git a/docs/adr/README.md b/docs/adr/README.md
iindex 4a82a27c..57000b61 100644
---- a/docs/adr/README.md
++++ b/docs/adr/README.md
@@@ -46,3 +46,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
| [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) |
++| [ADR-053](ADR-053-website-host-boundary.md) | The Website/Host boundary: OpenVibe.Website owns authoring (projects, pages, revisions, builds, Media asset pointers and intended domains) and OpenVibe.Host owns site, deploy, activation, domain binding/verification/TLS and serving; Website keeps no duplicate `host_sites`/`host_deploys` rows and calls Host's routes; a Website build is not a Host deploy; Website composes Host + Codes + Actor + Run + Media and is not OpenVibe.Sites | Proposed 2026-10-05 (plan T19), for the owner's review |