diff --git a/docs/docs/Post Platform Guide/signing-for-a-company.md b/docs/docs/Post Platform Guide/signing-for-a-company.md index 098a0ef9e..a97873253 100644 --- a/docs/docs/Post Platform Guide/signing-for-a-company.md +++ b/docs/docs/Post Platform Guide/signing-for-a-company.md @@ -157,7 +157,7 @@ Rules your tool should follow (verifiers ignore records that break them): - **Role:** signed by a director, and `createdBy` must be that director. - **Delegation from a role:** signed by a director, with `grantedBy` set to that director and scopes inside the role's. - **Re-delegation:** signed by the parent's delegate, the parent must allow re-delegation, and scopes must be inside the parent's. -- **Revoking:** write a new signed version with `status: "revoked"` and `revokedBy` set to the signer (a director or the grantor). Revoking a parent fails everything below it without touching those records, but you may also write signed revocations for them so the data reads clearly. +- **Revoking:** write a new signed version with `status: "revoked"` and `revokedBy` set to the signer (a director, or a grantor who hasn't been revoked). By default this stops only that person: what they granted before stays valid. Set `revocationCascade: true` to also revoke everything handed on from them. Narrowing a delegation or role always narrows everything below it. - **Never delete** these records. Deletion doesn't revoke anything, because verifiers read history. ## Logins diff --git a/docs/docs/W3DS Protocol/Company-Delegation.md b/docs/docs/W3DS Protocol/Company-Delegation.md index 026f61ac3..265ca96f2 100644 --- a/docs/docs/W3DS Protocol/Company-Delegation.md +++ b/docs/docs/W3DS Protocol/Company-Delegation.md @@ -15,7 +15,8 @@ Bob signs an NDA "for Acme" with **his own** wallet key. The signature names Acm - Bob's delegation was signed by someone entitled to grant it, - that grant goes back, hop by hop, to a role created by one of Acme's directors, - the directors are the ones Acme's Company record was born with, or later changed to by a sitting director, -- nothing on the way has been revoked or widened. +- nothing on the way was revoked before granting what is below it (or revoked with cascade), +- what was signed is within every link's **current** scopes. **The eVault enforces none of this.** It stores whatever it is given, and all authority is decided by the verifier from signatures and the eVault's version history. @@ -63,7 +64,7 @@ Exactly one of `roleId` or `parentDelegationId` is set. - **From a role:** granted (signed) by a director. `scopes` must be a subset of the role's. - **From a parent delegation:** granted by the parent's delegate, only if the parent has `mayRedelegate: true`. `scopes` must be a subset of the parent's, and the validity window must lie within the parent's. - `grantedBy` is the signer. Immutable after creation: `companyEName`, `delegateEName`, `roleId`, `parentDelegationId`, `grantedBy`. -- **Revoking:** a director or the grantor writes a signed version with `status: "revoked"` and `revokedBy` set to themselves. Revocation is final. +- **Revoking:** a director, or a grantor whose own authority hasn't been revoked, writes a signed version with `status: "revoked"` and `revokedBy` set to themselves. Revocation is final. See [Firing and cascading](#firing-and-cascading). ## Scopes @@ -130,29 +131,50 @@ Every payload in this model starts with `w3ds-`. Login verifiers (`verifyLoginSi - is validly signed and bound to its id, - is not dated before the previous valid version, and not after the eVault stored it (5 minutes of clock skew allowed), - keeps its immutable fields, - - was signed by someone entitled **when it was stored**: a director for roles and role grants, the parent's delegate for re-delegations, a director or the grantor for revocations. + - was signed by someone entitled **when it was stored**: + - a director, for roles and for grants from a role that wasn't yet revoked, + - the delegate of a parent that wasn't yet revoked, for re-delegations, + - a director, or a grantor whose own authority wasn't yet revoked, for revocations. Unsigned, forged, copied or written-back versions are skipped as if absent. Grants made by a director remain valid after that director leaves the board. -5. Walk the chain to the role (at most 16 links). Every link must be: - - active and within its validity window, - - for the same company, - - narrowing its parent, - - granted by the parent's delegate, - - from a parent that allows re-delegation. -6. Check the payload against the chain: + + Each resolved record also carries the eVault's storage times of its first valid grant and of its revocation. Those times decide firing, not dates the signer wrote. +5. Walk the chain to the role (at most 16 links): + - The signer's own delegation must be active and within its validity window. + - Every link above must be for the same company, granted by its parent's delegate, and from a parent that allows re-delegation. + - A revoked link above still counts **if it was revoked after granting what is below it, without cascading** (see below). +6. Effective scopes are the **intersection of every link's current scopes**, role included. +7. Check the payload against the chain: - the right company, signer and delegation, - - the scope is in the chain's scopes, + - the scope is in the effective scopes, - the scope is not core. -**Revocation cascades implicitly.** Revoking or narrowing a role or delegation fails the chain of everything handed on from it at the next verification. Nothing else needs to be written. +### Firing and cascading + +- **Revoking a person stops only them.** If Bob is fired, Bob can't sign any more. But a delegation Bob granted to Dave **before** he was fired stays valid, and so does everything Dave handed on. "Before" means the eVault stored Dave's grant before it stored Bob's revocation. +- **Revoking a role** works the same way. Holders granted before the revocation keep their delegations, and no new grants can be made from the role. +- **Cascading is explicit.** A revocation with `revocationCascade: true` also revokes everything handed on from that record. One signed record does it; the children are not touched. +- **Anything signed after the revocation doesn't count.** A fired Bob can't grant, update or revoke anything. +- **Holders below a fired link can keep handing on.** Dave can still re-delegate, if his own record allows it. +- **A grant can never be wider than its source was when it was made.** If Bob holds only NDAs when he grants Dave NDAs and invoices, that grant never counts, even if Bob is widened later. +- **Narrowing always follows the parent now.** If Bob's delegation (or the role) loses invoices, everyone below Bob loses invoices at the next verification, whatever they were originally given. + +```mermaid +flowchart LR + A{"parent revoked?"} -- no --> OK["counts"] + A -- yes --> B{"revocationCascade?"} + B -- yes --> X1["✗ REVOKED"] + B -- no --> C{"child granted before
the revocation?"} + C -- yes --> OK2["counts"] + C -- no --> X2["✗ REVOKED"] +``` ### Why a trace fails | Code | Meaning | |---|---| | `NOT_FOUND` | No valid version: never granted by anyone entitled, or copied from another record | -| `REVOKED` | This link, or one above it, was revoked | -| `NOT_A_SUBSET` | Scopes wider than the parent's or role's | +| `REVOKED` | The signer was revoked, or a link above was revoked before granting what is below it, or revoked with cascade | | `REDELEGATION_NOT_ALLOWED` | The parent (or role) did not allow passing it on | | `WRONG_GRANTOR` | Not granted by the parent's delegate | | `EXPIRED` / `NOT_YET_VALID` | Outside its validity window | diff --git a/examples/company-delegation-demo/README.md b/examples/company-delegation-demo/README.md index 1b555c244..e5c42958c 100644 --- a/examples/company-delegation-demo/README.md +++ b/examples/company-delegation-demo/README.md @@ -22,9 +22,10 @@ Dana founds Acme and makes Bob Head of Finance; Bob passes it to Dave, and Dave | Tim passes his badge to Mallory | refused at Mallory: Tim couldn't pass it on | | Tim's signature replayed as a login | refused: reserved payload | | Mallory writes herself onto the board | refused: nobody entitled granted it | -| Dana revokes Bob, Tim signs again | refused at Bob: revoked | +| Dana fires Bob, then Bob and Tim sign | Bob refused; Tim valid: Bob was fired after granting | +| Dana revokes Dave and everything he handed on | refused at Dave: revoked with cascade | -**Start fresh** (slide 0) clears Acme and Act 1 provisions a fresh company eVault; on the dashboard, **Start over with a new Acme** does both. Wallets are reused. Revoking Bob is permanent for that Acme, so the acts that need Tim's chain lock until you start fresh. +**Start fresh** (slide 0) clears Acme and Act 1 provisions a fresh company eVault; on the dashboard, **Start over with a new Acme** does both. Wallets are reused. Acts heal themselves: if Acme is missing, or Dave was already revoked with cascade, playing an act founds a fresh Acme first. ## Headless diff --git a/examples/company-delegation-demo/public/dashboard.html b/examples/company-delegation-demo/public/dashboard.html index 17fb8f83a..484802079 100644 --- a/examples/company-delegation-demo/public/dashboard.html +++ b/examples/company-delegation-demo/public/dashboard.html @@ -86,7 +86,7 @@

What happened