openvibers/openvibe.network Commits
Refresh the T2 design docs to fbd6312 and decide app on_behalf_of confirmation (#51)
Changes
@@@ -1,6 +1,7 @@{"id": "network.confirmation.manage","version": "1.0",++ "_note": "shipped in openvibe-contracts v0.89.0","owner": "network","status": "planned","visibility": "first-party",@@@ -1,6 +1,7 @@{"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/identity/service-token-claims.v1.json",++ "_note": "shipped in openvibe-contracts v0.89.0","title": "ServiceTokenClaims","description": "Claims of a short-lived RS256 client-credentials token issued by OpenVibe.Network to a service, app, mod, node or agent principal. Replaces X-Internal-Key. App tokens (actor_type app) also carry project_id and env; receivers refuse env=sandbox unless they opted in. Agent tokens (actor_type agent; roadmap WS-Z2, docs/t2-projects-and-grants.md) are minted for an agent by its host and also carry project_id, env and on_behalf_of (the owner), and act (the host).","type": "object",@@@ -43,7 +43,7 @@ A new project is `sandbox` only. Staff switch it to `sandbox+production`, which## Grants, the allowance and the capability catalog- The only grants that exist are capabilities in the openvibe-contracts catalog, at the version Network has installed.--- **The grantability rule.** The contracts `visibility` enum is `public | partner | first-party | internal` (`partner` since the v0.83.0 release; Network pins v0.90.0). Only `active` capabilities with visibility `public` or `partner` may be granted to apps. Staff may put a `partner` capability in one project's allowance by hand, but it never comes from the default or the sandbox allowance. `first-party` and `internal` capabilities are never grantable: staff cannot add them to an allowance, members cannot request them, and token issuance filters them out again.++- **The grantability rule.** The contracts `visibility` enum is `public | partner | first-party | internal` (`partner` since the v0.83.0 release; Network's pin moves to v0.97.0). Only `active` capabilities with visibility `public` or `partner` may be granted to apps. Staff may put a `partner` capability in one project's allowance by hand, but it never comes from the default or the sandbox allowance. `first-party` and `internal` capabilities are never grantable: staff cannot add them to an allowance, members cannot request them, and token issuance filters them out again.- **The allowance** is the set of grantable capabilities one project's apps may hold. Only staff set it. `DEV_DEFAULT_ALLOWANCE` seeds new projects, with public capabilities only. It is empty by default, so staff decide each project's **production** apps.- **The sandbox allowance** is added to the allowance for **sandbox apps only**, in every project, without a staff decision. It comes from `DEV_SANDBOX_ALLOWANCE`. When that is unset, the code default is `media.object.upload`, `media.object.read`, `media.object.list`, `media.object.delete`, `events.app.publish`, `events.app.read`, `events.app.subscribe`, `tools.job.create`, `tools.job.read` and `tools.job.cancel`. Only `public` + `active` capabilities of the installed openvibe-contracts catalog count. `partner`, `first-party`, `internal` and unknown ids are dropped. The three `events.app.*` ids are defined from openvibe-contracts v0.28.0 (Network pins v0.30.1); with an older release they would be left out. `media.object.list` and `media.object.delete` (Media's list and delete verbs, WS-G task 2) are left out the same way until the pinned release defines them; until then a sandbox app's upload and read grants cover them at Media. The project view and `GET /catalog` show it as `sandbox_allowance`. Production apps never use it.- **A grant never exceeds the allowance** (for a sandbox app: allowance ∪ sandbox allowance). It is checked in three places. Approval refuses with `403 grant.beyond_allowance`. Shrinking the allowance revokes approved grants, and denies requested ones, that fall outside it. A sandbox app's grant that is still inside the sandbox allowance is kept. Token issuance intersects approved grants with the current allowance and with the current grantability.@@@ -307,7 +307,7 @@ Until the relay is turned on in an environment, nobody else receives these event## Known gaps (next steps)--- **Consent screen.** The account chooser shows the app's name, not the capabilities it asks for. Codes and Network need a consent step before third-party production apps go live.++- **Consent screen.** The account chooser shows the app's name, not the capabilities it asks for. Codes and Network need a consent step before third-party production apps go live, and it must list the capability ids the app asks for, marking the ones `capability@1` marks `sensitive`; a request without a `scope` must not consent to capabilities the screen did not name. This screen is the app path's compensation for not using the agent confirmation flow — an authorization-code app acts for a person who is present and authorizes it capability by capability, so it carries no `cap_confirm` (decided 2026-10-04, `docs/t2-projects-and-grants.md` section 9).- **Service-side project reads.** The route is built, but no service calls it yet. Host holds the grant. Contracts still has to make `network.project.read` `active`.- **PowerChat as the first project** (ADR-014 migration). This is not done. PowerChat's existing OAuth client is unchanged.- **Receivers.** Every default sandbox audience must refuse `env: sandbox` except on routes that keep sandbox data apart, and must key tenancy by `project_id`. Media and Events do this in Wave 20. Tools does not yet (see [Sandbox tokens](#sandbox-tokens)).@@@ -1,11 +1,11 @@# T2 lane B — cells, the node principal and registry slice 5 (design)--Status: **N1–N7 merged** on `origin/main` `011355a` (registry slice 5, the node principal, node tokens and the registry++Status: **N1–N7 merged** on `origin/main` `fbd6312` (registry slice 5, the node principal, node tokens and the registryoperator writers; each slice's state is in §8). The cells layer of `docs/t2-resource-registry.md` §9 was already on`origin/main` (`d6ed8c4`, PR #5: `migrations/0008_cells_and_node_principals.sql`, `server/registry/cells.js`,`test/cells-registry.test.js`); this document designed the rest of lane B on top of it and re-planned registry slice 5.--Pinned: Network `openvibe-contracts` **v0.90.0** (`package.json:33`, `origin/main` `fb34f33`; the latest Contracts tag,--v0.94.0, is additive and not pinned yet). v0.85.0 closed every contract gap in §2 and the §9.1 T1 brief (the doc was++Pinned: Network `openvibe-contracts` **v0.97.0** (`package.json:33`, `origin/main` `fbd6312`; the latest Contracts++release, to which Network's pin moves in a separate PR). v0.85.0 closed every contract gap in §2 and the §9.1 T1 brief (the doc waswritten against v0.83.0, checked then against v0.84.0). OpenVibe.Bot`origin/main` `17ce069` (pins Contracts v0.79.0, `package.json:29`), OpenVibe.Node `origin/main` `4d2b8e1`. Bot is notdeployed (`ov access run openvibe-ovh releases bot` → `unknown service "bot"`), so no production device exists.@@@ -5,16 +5,17 @@ merge `4ec36e1`) and **lane B step 3 built** (`GET /internal/projects/:project_ionly: `server/internal/routes.js:490`; PR #28, merge `e422342`) and **slice 3 built** (delegated grants and modes:`migrations/0017_agent_grants.sql`, the ceiling in `server/developer/agents.js`), **slices 4–5 built** (confirmations,owner side, and budgets: `0018`, `0019`) and **slices 6–7 built** (`GET /internal/agents/:agent` and the four--`/internal/confirmations` routes, `server/internal/routes.js`, with the pin at `openvibe-contracts` v0.90.0) and **slices++`/internal/confirmations` routes, `server/internal/routes.js`, with the pin at `openvibe-contracts` v0.97.0) and **slices8–9 built** (agent tokens, `server/developer/agent-tokens.js`; `network.confirmation.changed@1` decision events from--`server/developer/confirmations.js`); the owner notification of slice 9 still waits on an openvibe-shared type (§8).++`server/developer/confirmations.js`; and the owner notification, `confirmations.notifyOwner` sending `openvibe-shared`++`TYPES.CONFIRMATION_REQUESTED`, PR #47, merge `fbd6312`).Plan T2, "Projects and grants: projects as the ownership boundary for every resource;`agt_` principals, delegated grants with modes, sensitive capabilities, confirmation requests, budgets (WS-Z2)".--Pinned version: `openvibe-contracts` **v0.90.0** (`package.json:33`, since slice 7; still the pin at `origin/main` `fb34f33`,--the latest Contracts tag v0.94.0 is additive and not pinned yet); every contract claim below was++Pinned version: `openvibe-contracts` **v0.97.0** (`package.json:33`; the latest Contracts release, to which Network's pin++moves in a separate PR; `origin/main` `fbd6312`); every contract claim below wasre-checked against the `v0.85.0` tag of OpenVibe.Contracts when slices 1–2 were built (it was written against v0.83.0).v0.89.0 published `network.confirmation.manage` (`planned`, `implementedBy` the four `/internal/confirmations` routes),--the `agent` actor in `identity.service-token-claims@1` and `network.confirmation.changed@1`; v0.86–v0.90 are additive.++the `agent` actor in `identity.service-token-claims@1` and `network.confirmation.changed@1`; v0.86–v0.97 are additive.Scope: an **agent** is a principal (`agt_<ULID>`) that acts for one person, inside one developer project, run by onehost (a developer app or a first-party service such as OpenVibe.Actor). Its owner **delegates** part of the host's@@@ -58,7 +59,7 @@ allowances changes.("`partner` is not in the enum yet") and the comment at `policy.js:8` predate that; slice 1 corrects both. The ruleitself does not change.--**Gaps — not in v0.85.0, so the slices that need them are blocked (section 8):**++**Gaps — not in v0.85.0, so the slices that needed them were blocked at that tag (section 8):**1. `identity.service-token-claims@1`: `sub` allows only `svc:` / `app:` / `mod:` / `node:` and `actor_type` only`service | app | mod | node`. An agent token cannot validate. (v0.85.0 closed the same gap for the node actor of@@@ -516,7 +517,8 @@ the capability's audience. Owning services read budgets from `GET /internal/agen- **The grantability rule and the allowances.** Not reimplemented: the ceiling *is* `effectiveGrants` for app hosts. Theone new rule (service-hosted agents never get `internal`) only narrows.- **Sensitive app grants.** `media.object.delete` and `space.post.write` keep working for apps exactly as today:-- `capability@1` defines confirmation for **agents**. Apps acting with `on_behalf_of` are listed as unresolved.++ `capability@1` defines confirmation for **agents**, and apps acting with `on_behalf_of` are decided not to use it++ (section 9, Decided): per-capability consent, the consent screen and revocation cover the app path.- **Mod principals, node principals, export tokens.** Untouched.- **Account deletion (`account-data.js:319` `erase`)** revokes the subject's agents (`revoked_by = 'account_deleted'`,then `'project_archived'` for the rest of a project it archives; built, slice 2), cancels their pending and approved-unused@@@ -603,7 +605,7 @@ and agree).6. **Internal agent read** (built). `GET /internal/agents/:agent` under `network.project.read` (already in the catalog),`internalAgentView` in `server/developer/agents.js`, the route in `server/internal/routes.js`;`test/internal-agents.test.js`. Checks: `npm test`, `npm run test:pg`.--7. **Internal confirmations** (built). The pin moved to `openvibe-contracts` v0.90.0 (v0.89.0 published++7. **Internal confirmations** (built). The pin moved to the Contracts release then current (v0.89.0 published`network.confirmation.manage`), the four `/internal/confirmations` routes (`internalRouter`, `read` and `cancel` in`server/developer/confirmations.js`, mounted under the guard in `server/internal/routes.js`), no `DEFAULT_GRANTS`row yet (none until a receiver ships), internal cases in `test/confirmations.test.js`. Checks: `npm test`,@@@ -612,20 +614,43 @@ and agree).`server/developer/tokens.js` and `principals.issueToken`, `cap`/`cap_confirm` split, `test/agent-tokens.test.js`.Network's own guard refuses agent tokens on its routes until a route needs them. No migration. Checks: `npm test`,`npm run test:pg`.--9. **Decision events** (built) **and the owner notification** (*still blocked on an openvibe-shared notification-- type*). Audit rows carry `network.confirmation.changed@1` (`store.audit` takes the envelope's `actor` and-- `on_behalf_of`), `test/confirmation-events.test.js`. Still to do: `notification-service.js` notifies the owner on-- create (payload rules of `docs/notification-digest.md`: never the summary). No migration. Checks: `npm test`,-- `npm run test:pg`.----Slices 2–9 are built, in this order (2–5 each added the next migration number; 6–9 need none); only the slice-9 owner--notification waits on a release outside Network.----## 9. Unresolved++9. **Decision events and the owner notification** (built). Audit rows carry `network.confirmation.changed@1`++ (`store.audit` takes the envelope's `actor` and `on_behalf_of`), `test/confirmation-events.test.js`; on create++ `confirmations.notifyOwner` notifies the owner through `notification-service.js` with `openvibe-shared`++ `TYPES.CONFIRMATION_REQUESTED` (payload rules of `docs/notification-digest.md`: never the summary), wired in++ `server/index.js:621`; PR #47, merge `fbd6312`. No migration. Checks: `npm test`, `npm run test:pg`.++++Slices 2–9 are built, in this order (2–5 each added the next migration number; 6–9 need none); the slice-9 owner++notification shipped with `openvibe-shared` v2.7.0 (`TYPES.CONFIRMATION_REQUESTED`, PR #47).++++## 9. Decided and unresolved++++### Decided++++- **Apps acting `on_behalf_of` a person do not use the confirmation path** (decided 2026-10-04 by the build plan,++ revisable by the owner). An authorization-code token is issued to a person who is at the consent screen and, in the++ interactive flow, driving the app when the action happens; an agent token is minted by its host (section 4, "Agent++ tokens") and used unattended, which is why `capability@1`'s `sensitive` rule — "an agent needs its owner's++ confirmation unless a standing rule covers it" — stays specific to agents. What an app may do is already agreed++ capability by capability: `/oauth/authorize` takes `scope` (capability ids), `tokens.issueCode` keeps the grantable++ subset, and `tokens.mint` intersects it with the app's grants again and can only narrow it, never widen it. Sensitive++ capabilities are almost all `first-party` or `internal` and never grantable to apps; today exactly two are++ app-grantable (public, active), `media.object.delete` and `space.post.write`, and they are not carved out as a named++ subset either: the person authorizes each at consent, and the residual risk is bounded by revocation (grant revoke,++ app revoke, `setAllowance` shrink, project archive), the 300-second token, and a receiver's refusal of `env: sandbox`.++ The agent path defends an autonomous, prompt-injectable principal whose future actions the owner cannot enumerate when++ delegating; an app runs the developer's own code and is authorized per capability. The compensating control on the app++ side is therefore the consent screen, not an inbox: it must list the capability ids the app asks for and mark the++ sensitive ones, and a request without a `scope` must not consent to capabilities the screen did not name++ (`docs/developer-projects.md`, "Consent screen"). An app holding a refresh token (`/oauth/token`++ `grant_type=refresh_token`) can still act after the person has left it; that is the case the per-capability consent++ and revocation exist for, and if it proves too loose for the two app-grantable sensitive capabilities the narrower fix++ is to leave sensitive capabilities out of refreshed tokens (the person consents again), not the agent inbox. No++ contract delta: `cap_confirm` and the confirmation flow stay++ agent-only in `identity.service-token-claims@1`, and Network builds nothing for apps here.++++### Unresolved--- **Apps acting `on_behalf_of` a person** (authorization-code tokens) are not agents, and `capability@1` asks confirmation-- of agents. Whether a third-party app's sensitive actions should also be confirmed is a separate decision (the consent-- screen gap in `docs/developer-projects.md`).- **Per-agent usage display.** Owners cannot see an agent's `used` until `common.usage-recorded@1` gains an agentdimension; whether an agent id may appear in a rollup is a privacy decision for Contracts.- **Money budgets across services** belong to Billing (T5); Network records nothing for them yet.@@@ -3,7 +3,7 @@Status: **resource registry and N6 built** (`migrations/0007_resource_registry.sql`,`migrations/0009_resource_offer_kinds.sql`, `server/registry/offers.js`); **cells and node principals built**(section 9, `migrations/0008`). The original slice plan in §7 is historical.--Current pin: `openvibe-contracts` **v0.90.0** (`package.json` at `origin/main` `fb34f33`; the latest tag, v0.94.0, is additive and not pinned yet; the kinds below are as of v0.85.0); the v0.83.0 observations below describe the original design.++Current pin: `openvibe-contracts` **v0.97.0** (`package.json` at `origin/main` `fbd6312`; the latest release, to which Network's pin moves in a separate PR; the kinds below are as of v0.85.0); the v0.83.0 observations below describe the original design.Scope: one table and one module that store **what the network can sell or place on** — node, provider, storage, delivery,runtime, agent and harness offers — reported by the components that own them, listed publicly so that `openvibe-sdk/placement` can plan against
@@ -1,6 +1,7 @@
@@ -1,6 +1,7 @@
{
{
"id": "network.confirmation.manage",
"id": "network.confirmation.manage",
"version": "1.0",
"version": "1.0",
+ "_note": "shipped in openvibe-contracts v0.89.0",
"owner": "network",
"owner": "network",
"status": "planned",
"status": "planned",
"visibility": "first-party",
"visibility": "first-party",
@@ -1,6 +1,7 @@
@@ -1,6 +1,7 @@
{
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/identity/service-token-claims.v1.json",
"$id": "https://openvibe.network/contracts/identity/service-token-claims.v1.json",
+ "_note": "shipped in openvibe-contracts v0.89.0",
"title": "ServiceTokenClaims",
"title": "ServiceTokenClaims",
"description": "Claims of a short-lived RS256 client-credentials token issued by OpenVibe.Network to a service, app, mod, node or agent principal. Replaces X-Internal-Key. App tokens (actor_type app) also carry project_id and env; receivers refuse env=sandbox unless they opted in. Agent tokens (actor_type agent; roadmap WS-Z2, docs/t2-projects-and-grants.md) are minted for an agent by its host and also carry project_id, env and on_behalf_of (the owner), and act (the host).",
"description": "Claims of a short-lived RS256 client-credentials token issued by OpenVibe.Network to a service, app, mod, node or agent principal. Replaces X-Internal-Key. App tokens (actor_type app) also carry project_id and env; receivers refuse env=sandbox unless they opted in. Agent tokens (actor_type agent; roadmap WS-Z2, docs/t2-projects-and-grants.md) are minted for an agent by its host and also carry project_id, env and on_behalf_of (the owner), and act (the host).",
"type": "object",
"type": "object",
@@ -43,7 +43,7 @@ A new project is `sandbox` only. Staff switch it to `sandbox+production`, which
@@ -43,7 +43,7 @@ A new project is `sandbox` only. Staff switch it to `sandbox+production`, which
## Grants, the allowance and the capability catalog
## Grants, the allowance and the capability catalog
- The only grants that exist are capabilities in the openvibe-contracts catalog, at the version Network has installed.
- The only grants that exist are capabilities in the openvibe-contracts catalog, at the version Network has installed.
-- **The grantability rule.** The contracts `visibility` enum is `public | partner | first-party | internal` (`partner` since the v0.83.0 release; Network pins v0.90.0). Only `active` capabilities with visibility `public` or `partner` may be granted to apps. Staff may put a `partner` capability in one project's allowance by hand, but it never comes from the default or the sandbox allowance. `first-party` and `internal` capabilities are never grantable: staff cannot add them to an allowance, members cannot request them, and token issuance filters them out again.
+- **The grantability rule.** The contracts `visibility` enum is `public | partner | first-party | internal` (`partner` since the v0.83.0 release; Network's pin moves to v0.97.0). Only `active` capabilities with visibility `public` or `partner` may be granted to apps. Staff may put a `partner` capability in one project's allowance by hand, but it never comes from the default or the sandbox allowance. `first-party` and `internal` capabilities are never grantable: staff cannot add them to an allowance, members cannot request them, and token issuance filters them out again.
- **The allowance** is the set of grantable capabilities one project's apps may hold. Only staff set it. `DEV_DEFAULT_ALLOWANCE` seeds new projects, with public capabilities only. It is empty by default, so staff decide each project's **production** apps.
- **The allowance** is the set of grantable capabilities one project's apps may hold. Only staff set it. `DEV_DEFAULT_ALLOWANCE` seeds new projects, with public capabilities only. It is empty by default, so staff decide each project's **production** apps.
- **The sandbox allowance** is added to the allowance for **sandbox apps only**, in every project, without a staff decision. It comes from `DEV_SANDBOX_ALLOWANCE`. When that is unset, the code default is `media.object.upload`, `media.object.read`, `media.object.list`, `media.object.delete`, `events.app.publish`, `events.app.read`, `events.app.subscribe`, `tools.job.create`, `tools.job.read` and `tools.job.cancel`. Only `public` + `active` capabilities of the installed openvibe-contracts catalog count. `partner`, `first-party`, `internal` and unknown ids are dropped. The three `events.app.*` ids are defined from openvibe-contracts v0.28.0 (Network pins v0.30.1); with an older release they would be left out. `media.object.list` and `media.object.delete` (Media's list and delete verbs, WS-G task 2) are left out the same way until the pinned release defines them; until then a sandbox app's upload and read grants cover them at Media. The project view and `GET /catalog` show it as `sandbox_allowance`. Production apps never use it.
- **The sandbox allowance** is added to the allowance for **sandbox apps only**, in every project, without a staff decision. It comes from `DEV_SANDBOX_ALLOWANCE`. When that is unset, the code default is `media.object.upload`, `media.object.read`, `media.object.list`, `media.object.delete`, `events.app.publish`, `events.app.read`, `events.app.subscribe`, `tools.job.create`, `tools.job.read` and `tools.job.cancel`. Only `public` + `active` capabilities of the installed openvibe-contracts catalog count. `partner`, `first-party`, `internal` and unknown ids are dropped. The three `events.app.*` ids are defined from openvibe-contracts v0.28.0 (Network pins v0.30.1); with an older release they would be left out. `media.object.list` and `media.object.delete` (Media's list and delete verbs, WS-G task 2) are left out the same way until the pinned release defines them; until then a sandbox app's upload and read grants cover them at Media. The project view and `GET /catalog` show it as `sandbox_allowance`. Production apps never use it.
- **A grant never exceeds the allowance** (for a sandbox app: allowance ∪ sandbox allowance). It is checked in three places. Approval refuses with `403 grant.beyond_allowance`. Shrinking the allowance revokes approved grants, and denies requested ones, that fall outside it. A sandbox app's grant that is still inside the sandbox allowance is kept. Token issuance intersects approved grants with the current allowance and with the current grantability.
- **A grant never exceeds the allowance** (for a sandbox app: allowance ∪ sandbox allowance). It is checked in three places. Approval refuses with `403 grant.beyond_allowance`. Shrinking the allowance revokes approved grants, and denies requested ones, that fall outside it. A sandbox app's grant that is still inside the sandbox allowance is kept. Token issuance intersects approved grants with the current allowance and with the current grantability.
@@ -307,7 +307,7 @@ Until the relay is turned on in an environment, nobody else receives these event
@@ -307,7 +307,7 @@ Until the relay is turned on in an environment, nobody else receives these event
## Known gaps (next steps)
## Known gaps (next steps)
-- **Consent screen.** The account chooser shows the app's name, not the capabilities it asks for. Codes and Network need a consent step before third-party production apps go live.
+- **Consent screen.** The account chooser shows the app's name, not the capabilities it asks for. Codes and Network need a consent step before third-party production apps go live, and it must list the capability ids the app asks for, marking the ones `capability@1` marks `sensitive`; a request without a `scope` must not consent to capabilities the screen did not name. This screen is the app path's compensation for not using the agent confirmation flow — an authorization-code app acts for a person who is present and authorizes it capability by capability, so it carries no `cap_confirm` (decided 2026-10-04, `docs/t2-projects-and-grants.md` section 9).
- **Service-side project reads.** The route is built, but no service calls it yet. Host holds the grant. Contracts still has to make `network.project.read` `active`.
- **Service-side project reads.** The route is built, but no service calls it yet. Host holds the grant. Contracts still has to make `network.project.read` `active`.
- **PowerChat as the first project** (ADR-014 migration). This is not done. PowerChat's existing OAuth client is unchanged.
- **PowerChat as the first project** (ADR-014 migration). This is not done. PowerChat's existing OAuth client is unchanged.
- **Receivers.** Every default sandbox audience must refuse `env: sandbox` except on routes that keep sandbox data apart, and must key tenancy by `project_id`. Media and Events do this in Wave 20. Tools does not yet (see [Sandbox tokens](#sandbox-tokens)).
- **Receivers.** Every default sandbox audience must refuse `env: sandbox` except on routes that keep sandbox data apart, and must key tenancy by `project_id`. Media and Events do this in Wave 20. Tools does not yet (see [Sandbox tokens](#sandbox-tokens)).
@@ -1,11 +1,11 @@
@@ -1,11 +1,11 @@
# T2 lane B — cells, the node principal and registry slice 5 (design)
# T2 lane B — cells, the node principal and registry slice 5 (design)
-Status: **N1–N7 merged** on `origin/main` `011355a` (registry slice 5, the node principal, node tokens and the registry
+Status: **N1–N7 merged** on `origin/main` `fbd6312` (registry slice 5, the node principal, node tokens and the registry
operator writers; each slice's state is in §8). The cells layer of `docs/t2-resource-registry.md` §9 was already on
operator writers; each slice's state is in §8). The cells layer of `docs/t2-resource-registry.md` §9 was already on
`origin/main` (`d6ed8c4`, PR #5: `migrations/0008_cells_and_node_principals.sql`, `server/registry/cells.js`,
`origin/main` (`d6ed8c4`, PR #5: `migrations/0008_cells_and_node_principals.sql`, `server/registry/cells.js`,
`test/cells-registry.test.js`); this document designed the rest of lane B on top of it and re-planned registry slice 5.
`test/cells-registry.test.js`); this document designed the rest of lane B on top of it and re-planned registry slice 5.
-Pinned: Network `openvibe-contracts` **v0.90.0** (`package.json:33`, `origin/main` `fb34f33`; the latest Contracts tag,
-v0.94.0, is additive and not pinned yet). v0.85.0 closed every contract gap in §2 and the §9.1 T1 brief (the doc was
+Pinned: Network `openvibe-contracts` **v0.97.0** (`package.json:33`, `origin/main` `fbd6312`; the latest Contracts
+release, to which Network's pin moves in a separate PR). v0.85.0 closed every contract gap in §2 and the §9.1 T1 brief (the doc was
written against v0.83.0, checked then against v0.84.0). OpenVibe.Bot
written against v0.83.0, checked then against v0.84.0). OpenVibe.Bot
`origin/main` `17ce069` (pins Contracts v0.79.0, `package.json:29`), OpenVibe.Node `origin/main` `4d2b8e1`. Bot is not
`origin/main` `17ce069` (pins Contracts v0.79.0, `package.json:29`), OpenVibe.Node `origin/main` `4d2b8e1`. Bot is not
deployed (`ov access run openvibe-ovh releases bot` → `unknown service "bot"`), so no production device exists.
deployed (`ov access run openvibe-ovh releases bot` → `unknown service "bot"`), so no production device exists.
@@ -5,16 +5,17 @@ merge `4ec36e1`) and **lane B step 3 built** (`GET /internal/projects/:project_i
@@ -5,16 +5,17 @@ merge `4ec36e1`) and **lane B step 3 built** (`GET /internal/projects/:project_i
only: `server/internal/routes.js:490`; PR #28, merge `e422342`) and **slice 3 built** (delegated grants and modes:
only: `server/internal/routes.js:490`; PR #28, merge `e422342`) and **slice 3 built** (delegated grants and modes:
`migrations/0017_agent_grants.sql`, the ceiling in `server/developer/agents.js`), **slices 4–5 built** (confirmations,
`migrations/0017_agent_grants.sql`, the ceiling in `server/developer/agents.js`), **slices 4–5 built** (confirmations,
owner side, and budgets: `0018`, `0019`) and **slices 6–7 built** (`GET /internal/agents/:agent` and the four
owner side, and budgets: `0018`, `0019`) and **slices 6–7 built** (`GET /internal/agents/:agent` and the four
-`/internal/confirmations` routes, `server/internal/routes.js`, with the pin at `openvibe-contracts` v0.90.0) and **slices
+`/internal/confirmations` routes, `server/internal/routes.js`, with the pin at `openvibe-contracts` v0.97.0) and **slices
8–9 built** (agent tokens, `server/developer/agent-tokens.js`; `network.confirmation.changed@1` decision events from
8–9 built** (agent tokens, `server/developer/agent-tokens.js`; `network.confirmation.changed@1` decision events from
-`server/developer/confirmations.js`); the owner notification of slice 9 still waits on an openvibe-shared type (§8).
+`server/developer/confirmations.js`; and the owner notification, `confirmations.notifyOwner` sending `openvibe-shared`
+`TYPES.CONFIRMATION_REQUESTED`, PR #47, merge `fbd6312`).
Plan T2, "Projects and grants: projects as the ownership boundary for every resource;
Plan T2, "Projects and grants: projects as the ownership boundary for every resource;
`agt_` principals, delegated grants with modes, sensitive capabilities, confirmation requests, budgets (WS-Z2)".
`agt_` principals, delegated grants with modes, sensitive capabilities, confirmation requests, budgets (WS-Z2)".
-Pinned version: `openvibe-contracts` **v0.90.0** (`package.json:33`, since slice 7; still the pin at `origin/main` `fb34f33`,
-the latest Contracts tag v0.94.0 is additive and not pinned yet); every contract claim below was
+Pinned version: `openvibe-contracts` **v0.97.0** (`package.json:33`; the latest Contracts release, to which Network's pin
+moves in a separate PR; `origin/main` `fbd6312`); every contract claim below was
re-checked against the `v0.85.0` tag of OpenVibe.Contracts when slices 1–2 were built (it was written against v0.83.0).
re-checked against the `v0.85.0` tag of OpenVibe.Contracts when slices 1–2 were built (it was written against v0.83.0).
v0.89.0 published `network.confirmation.manage` (`planned`, `implementedBy` the four `/internal/confirmations` routes),
v0.89.0 published `network.confirmation.manage` (`planned`, `implementedBy` the four `/internal/confirmations` routes),
-the `agent` actor in `identity.service-token-claims@1` and `network.confirmation.changed@1`; v0.86–v0.90 are additive.
+the `agent` actor in `identity.service-token-claims@1` and `network.confirmation.changed@1`; v0.86–v0.97 are additive.
Scope: an **agent** is a principal (`agt_<ULID>`) that acts for one person, inside one developer project, run by one
Scope: an **agent** is a principal (`agt_<ULID>`) that acts for one person, inside one developer project, run by one
host (a developer app or a first-party service such as OpenVibe.Actor). Its owner **delegates** part of the host's
host (a developer app or a first-party service such as OpenVibe.Actor). Its owner **delegates** part of the host's
@@ -58,7 +59,7 @@ allowances changes.
@@ -58,7 +59,7 @@ allowances changes.
("`partner` is not in the enum yet") and the comment at `policy.js:8` predate that; slice 1 corrects both. The rule
("`partner` is not in the enum yet") and the comment at `policy.js:8` predate that; slice 1 corrects both. The rule
itself does not change.
itself does not change.
-**Gaps — not in v0.85.0, so the slices that need them are blocked (section 8):**
+**Gaps — not in v0.85.0, so the slices that needed them were blocked at that tag (section 8):**
1. `identity.service-token-claims@1`: `sub` allows only `svc:` / `app:` / `mod:` / `node:` and `actor_type` only
1. `identity.service-token-claims@1`: `sub` allows only `svc:` / `app:` / `mod:` / `node:` and `actor_type` only
`service | app | mod | node`. An agent token cannot validate. (v0.85.0 closed the same gap for the node actor of
`service | app | mod | node`. An agent token cannot validate. (v0.85.0 closed the same gap for the node actor of
@@ -516,7 +517,8 @@ the capability's audience. Owning services read budgets from `GET /internal/agen
@@ -516,7 +517,8 @@ the capability's audience. Owning services read budgets from `GET /internal/agen
- **The grantability rule and the allowances.** Not reimplemented: the ceiling *is* `effectiveGrants` for app hosts. The
- **The grantability rule and the allowances.** Not reimplemented: the ceiling *is* `effectiveGrants` for app hosts. The
one new rule (service-hosted agents never get `internal`) only narrows.
one new rule (service-hosted agents never get `internal`) only narrows.
- **Sensitive app grants.** `media.object.delete` and `space.post.write` keep working for apps exactly as today:
- **Sensitive app grants.** `media.object.delete` and `space.post.write` keep working for apps exactly as today:
- `capability@1` defines confirmation for **agents**. Apps acting with `on_behalf_of` are listed as unresolved.
+ `capability@1` defines confirmation for **agents**, and apps acting with `on_behalf_of` are decided not to use it
+ (section 9, Decided): per-capability consent, the consent screen and revocation cover the app path.
- **Mod principals, node principals, export tokens.** Untouched.
- **Mod principals, node principals, export tokens.** Untouched.
- **Account deletion (`account-data.js:319` `erase`)** revokes the subject's agents (`revoked_by = 'account_deleted'`,
- **Account deletion (`account-data.js:319` `erase`)** revokes the subject's agents (`revoked_by = 'account_deleted'`,
then `'project_archived'` for the rest of a project it archives; built, slice 2), cancels their pending and approved-unused
then `'project_archived'` for the rest of a project it archives; built, slice 2), cancels their pending and approved-unused
@@ -603,7 +605,7 @@ and agree).
@@ -603,7 +605,7 @@ and agree).
6. **Internal agent read** (built). `GET /internal/agents/:agent` under `network.project.read` (already in the catalog),
6. **Internal agent read** (built). `GET /internal/agents/:agent` under `network.project.read` (already in the catalog),
`internalAgentView` in `server/developer/agents.js`, the route in `server/internal/routes.js`;
`internalAgentView` in `server/developer/agents.js`, the route in `server/internal/routes.js`;
`test/internal-agents.test.js`. Checks: `npm test`, `npm run test:pg`.
`test/internal-agents.test.js`. Checks: `npm test`, `npm run test:pg`.
-7. **Internal confirmations** (built). The pin moved to `openvibe-contracts` v0.90.0 (v0.89.0 published
+7. **Internal confirmations** (built). The pin moved to the Contracts release then current (v0.89.0 published
`network.confirmation.manage`), the four `/internal/confirmations` routes (`internalRouter`, `read` and `cancel` in
`network.confirmation.manage`), the four `/internal/confirmations` routes (`internalRouter`, `read` and `cancel` in
`server/developer/confirmations.js`, mounted under the guard in `server/internal/routes.js`), no `DEFAULT_GRANTS`
`server/developer/confirmations.js`, mounted under the guard in `server/internal/routes.js`), no `DEFAULT_GRANTS`
row yet (none until a receiver ships), internal cases in `test/confirmations.test.js`. Checks: `npm test`,
row yet (none until a receiver ships), internal cases in `test/confirmations.test.js`. Checks: `npm test`,
@@ -612,20 +614,43 @@ and agree).
@@ -612,20 +614,43 @@ and agree).
`server/developer/tokens.js` and `principals.issueToken`, `cap`/`cap_confirm` split, `test/agent-tokens.test.js`.
`server/developer/tokens.js` and `principals.issueToken`, `cap`/`cap_confirm` split, `test/agent-tokens.test.js`.
Network's own guard refuses agent tokens on its routes until a route needs them. No migration. Checks: `npm test`,
Network's own guard refuses agent tokens on its routes until a route needs them. No migration. Checks: `npm test`,
`npm run test:pg`.
`npm run test:pg`.
-9. **Decision events** (built) **and the owner notification** (*still blocked on an openvibe-shared notification
- type*). Audit rows carry `network.confirmation.changed@1` (`store.audit` takes the envelope's `actor` and
- `on_behalf_of`), `test/confirmation-events.test.js`. Still to do: `notification-service.js` notifies the owner on
- create (payload rules of `docs/notification-digest.md`: never the summary). No migration. Checks: `npm test`,
- `npm run test:pg`.
-
-Slices 2–9 are built, in this order (2–5 each added the next migration number; 6–9 need none); only the slice-9 owner
-notification waits on a release outside Network.
-
-## 9. Unresolved
+9. **Decision events and the owner notification** (built). Audit rows carry `network.confirmation.changed@1`
+ (`store.audit` takes the envelope's `actor` and `on_behalf_of`), `test/confirmation-events.test.js`; on create
+ `confirmations.notifyOwner` notifies the owner through `notification-service.js` with `openvibe-shared`
+ `TYPES.CONFIRMATION_REQUESTED` (payload rules of `docs/notification-digest.md`: never the summary), wired in
+ `server/index.js:621`; PR #47, merge `fbd6312`. No migration. Checks: `npm test`, `npm run test:pg`.
+
+Slices 2–9 are built, in this order (2–5 each added the next migration number; 6–9 need none); the slice-9 owner
+notification shipped with `openvibe-shared` v2.7.0 (`TYPES.CONFIRMATION_REQUESTED`, PR #47).
+
+## 9. Decided and unresolved
+
+### Decided
+
+- **Apps acting `on_behalf_of` a person do not use the confirmation path** (decided 2026-10-04 by the build plan,
+ revisable by the owner). An authorization-code token is issued to a person who is at the consent screen and, in the
+ interactive flow, driving the app when the action happens; an agent token is minted by its host (section 4, "Agent
+ tokens") and used unattended, which is why `capability@1`'s `sensitive` rule — "an agent needs its owner's
+ confirmation unless a standing rule covers it" — stays specific to agents. What an app may do is already agreed
+ capability by capability: `/oauth/authorize` takes `scope` (capability ids), `tokens.issueCode` keeps the grantable
+ subset, and `tokens.mint` intersects it with the app's grants again and can only narrow it, never widen it. Sensitive
+ capabilities are almost all `first-party` or `internal` and never grantable to apps; today exactly two are
+ app-grantable (public, active), `media.object.delete` and `space.post.write`, and they are not carved out as a named
+ subset either: the person authorizes each at consent, and the residual risk is bounded by revocation (grant revoke,
+ app revoke, `setAllowance` shrink, project archive), the 300-second token, and a receiver's refusal of `env: sandbox`.
+ The agent path defends an autonomous, prompt-injectable principal whose future actions the owner cannot enumerate when
+ delegating; an app runs the developer's own code and is authorized per capability. The compensating control on the app
+ side is therefore the consent screen, not an inbox: it must list the capability ids the app asks for and mark the
+ sensitive ones, and a request without a `scope` must not consent to capabilities the screen did not name
+ (`docs/developer-projects.md`, "Consent screen"). An app holding a refresh token (`/oauth/token`
+ `grant_type=refresh_token`) can still act after the person has left it; that is the case the per-capability consent
+ and revocation exist for, and if it proves too loose for the two app-grantable sensitive capabilities the narrower fix
+ is to leave sensitive capabilities out of refreshed tokens (the person consents again), not the agent inbox. No
+ contract delta: `cap_confirm` and the confirmation flow stay
+ agent-only in `identity.service-token-claims@1`, and Network builds nothing for apps here.
+
+### Unresolved
-- **Apps acting `on_behalf_of` a person** (authorization-code tokens) are not agents, and `capability@1` asks confirmation
- of agents. Whether a third-party app's sensitive actions should also be confirmed is a separate decision (the consent
- screen gap in `docs/developer-projects.md`).
- **Per-agent usage display.** Owners cannot see an agent's `used` until `common.usage-recorded@1` gains an agent
- **Per-agent usage display.** Owners cannot see an agent's `used` until `common.usage-recorded@1` gains an agent
dimension; whether an agent id may appear in a rollup is a privacy decision for Contracts.
dimension; whether an agent id may appear in a rollup is a privacy decision for Contracts.
- **Money budgets across services** belong to Billing (T5); Network records nothing for them yet.
- **Money budgets across services** belong to Billing (T5); Network records nothing for them yet.
@@ -3,7 +3,7 @@
@@ -3,7 +3,7 @@
Status: **resource registry and N6 built** (`migrations/0007_resource_registry.sql`,
Status: **resource registry and N6 built** (`migrations/0007_resource_registry.sql`,
`migrations/0009_resource_offer_kinds.sql`, `server/registry/offers.js`); **cells and node principals built**
`migrations/0009_resource_offer_kinds.sql`, `server/registry/offers.js`); **cells and node principals built**
(section 9, `migrations/0008`). The original slice plan in §7 is historical.
(section 9, `migrations/0008`). The original slice plan in §7 is historical.
-Current pin: `openvibe-contracts` **v0.90.0** (`package.json` at `origin/main` `fb34f33`; the latest tag, v0.94.0, is additive and not pinned yet; the kinds below are as of v0.85.0); the v0.83.0 observations below describe the original design.
+Current pin: `openvibe-contracts` **v0.97.0** (`package.json` at `origin/main` `fbd6312`; the latest release, to which Network's pin moves in a separate PR; the kinds below are as of v0.85.0); the v0.83.0 observations below describe the original design.
Scope: one table and one module that store **what the network can sell or place on** — node, provider, storage, delivery,
Scope: one table and one module that store **what the network can sell or place on** — node, provider, storage, delivery,
runtime, agent and harness offers — reported by the components that own them, listed publicly so that `openvibe-sdk/placement` can plan against
runtime, agent and harness offers — reported by the components that own them, listed publicly so that `openvibe-sdk/placement` can plan against