Current Design
We have a structure called a policy. Policies look like this (if you happen to be an ML programmer):
type policy = { id: branch_id;
killed: bool;
defined_users: (string, user) map;
meta: (string, string) map;
delegation: delegation; }
and delegation = [ [ SimpleDelegate ] ] of branch_policy
| [ [ FullDelegate ] ] of { content_branch: branch_policy option;
subpolicy_branches: (string, branch_policy) map; }
and branch_policy = { id: branch_id;
permitted_users: string set;
status: branch_status;
meta: (string,string) map; }
and branch_status = Active | Dormant
and branch_id = Root
| [ [ ChildOf ] ](branch_id, nonce) // hashed to compress to constant size
and user = { pk: key_id;
meta: (string,string) map; }
Policies are stored in branches, and all branches in monotone become fully hierarchical:
- branches are a proper tree
- branches cannot move between parents
- branches 'can' be renamed within a parent
- every branch has a single parent
- the "id" (stable identity) of a branch incorporates its parent branch via a "hash(parent)+nonce" trick
A policy branch manages both the namespace and the permissions of its
sub-branches, inlcuding an optional unique content branch that has the
same human-friendly name as the policy branch: for example, foo.bar
may name both a particular content branch 'and' contain policy
decisions that name and constrain the sub-branches foo.bar.baz
and
foo.bar.quux
.
Aspects of policy accumulate "down" the tree, from the root policy
towards content trees. So a branch like foo.bar.baz
is judged by
the policy of foo
, plus the policy of foo.bar
, plus the policy of
foo.bar.baz
.
At the root of the policy-branch tree there is a branch that all
projects everywhere refer to as their parent, called Root
. Users
construct their own content for the Root
branch. Monotone makes a
branch called Root
exist if you haven't created one. Root
is not
associated with a particular project. Rather, users 'sign' revisions
into the root branch: revisions in the root branch are policy that
bind top-level project names into the UI. You can think of the Root
branch as a user's personal list of project-policy "mount points". This
'might' be shared between a couple of the user's personal machines, but
the contents of a user's Root branch is generally private.
Why does monotone trust the user's key when signing policy in the Root
policy branch? Because there is an external, non-versioned list of
keys that each user keeps (typically a 1-entry list) that defines
which keys to trust for the Root
branch.
The important design point is that the decision for "which policy node
represents the head of a policy branch" is made by evaluating 'certs' on
that policy branch, and the validity of those certs is determined by
looking in the policy you have in the 'parent' branch. So branch foo
says which keys are legal for manipulating policy on branch foo.bar
(and its sub-branches), and foo.bar
introduces the sub-branch
foo.bar.baz
more keys which are legal for manipulating policy on
branch foo.bar.baz
.
If there are multiple policy heads, one is chosen at random and used; if the user wants they can force the choice between heads. This is harmless conservative behavior because policy cannot refer back to itself, so "bad policy" cannot use "racing with good policy" to bootstrap itself into more permission: bad policy can be reverted by anyone else who has write access to the policy branch containing the bad policy, and anyone adding truly 'malicious' policy can be overridden by more-authorized admins in the parent branch by de-authorizing the writer who produced the malicious policy from writing to the policy branch. The bad certs then go dead for that branch, meaning that the bad revs fall out of the branch (though if the malicious policy was merged with good policy in the meantime, someone will have to commit a revert edge as well to finish cleaning up).
There is also a per-policy-branch "kill switch" that is sticky; once the kill-switch on a branch is set it can never be un-set, so it represents a way for an admin to permanently retire a problematic branch, for example if the admin's key is compromised.
We also include a form of delegation that exists purely for inserting extra levels of authorization and kill-switches, without introducing new branch name components. This is a simple delegate.