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 = <span class="createlink"><a href="http://monotone.ca/cgi-bin/ikiwiki.cgi?page=simpledelegate&from=wiki%2FVersionedPolicy%2FGraydon&do=create" rel="nofollow">?</a>SimpleDelegate</span> of branch_policy
| <span class="createlink"><a href="http://monotone.ca/cgi-bin/ikiwiki.cgi?page=fulldelegate&from=wiki%2FVersionedPolicy%2FGraydon&do=create" rel="nofollow">?</a>FullDelegate</span> 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
| <span class="createlink"><a href="http://monotone.ca/cgi-bin/ikiwiki.cgi?page=childof&from=wiki%2FVersionedPolicy%2FGraydon&do=create" rel="nofollow">?</a>ChildOf</span>(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.