Bedeho Mender 5 лет назад
Родитель
Сommit
26caa4c541

+ 343 - 8
testnets/rome/specification/README.md

@@ -1,12 +1,347 @@
-Rome Testnet Specification
-==========================
+# Rome Testnet Specification
+
+## Table Of Contents
 
-Table of Contents
-=================
-<!-- TOC START min:1 max:3 link:true asterisk:false update:true -->
 - [Document](#document)
-<!-- TOC END -->
+- [Caveat](#caveat)
+- [Glossary](#glossary)
+- [Overview](#overview)
+- [Substrate Runtime](#substrate-runtime)
+  - [Runtime Version](#runtime-version)
+  - [Native Version](#native-version)
+  - [Substrate Version](#substrate-version)
+  - [Modules](#modules)
+    - [TokenMint](#tokenmint)
+    - [RecurringReward](#recurringreward)
+    - [Staking](#staking)
+    - [Hiring](#hiring)
+    - [VersionedStore](#versionedstore)
+    - [VersionedStorePermissions](#versionedstorepermissions)
+    - [ContentDirectoryWorkingGroup](#contentdirectoryworkinggroup)
+    - [Forum](#forum)
+    - [Proposals](#proposals)
+    - [CouncilElection](#councilelection)
+    - [Council](#council)
+    - [Memo](#memo)
+    - [Membership](#membership)
+    - [Migration](#migration)
+    - [Actors](#actors)
+    - [DataObjectTypeRegistry](#dataobjecttyperegistry)
+    - [DataDirectory](#datadirectory)
+    - [DataObjectStorageRegistry](#dataobjectstorageregistry)
+    - [DownloadSessions](#downloadsessions)
+  - [SRML Modules](#srml-modules)
+  - [Runtime API](#runtime-apis)
+<!-- - [Communication Protocols](#communication-protocols) -->
+
+## Document
+
+The purpose of this document is to serve as a starting point for the implementation phase of the given testnet. It is only a starting point in the sense that the implementation effort will always depart from the up front specification to some, possibly substantial, extent, but no effort is made to synchronise the specification at any time. Instead, the any such synchronisation would be done in the specification of a future network. The value of such a specification is primarily to establish a relatively precise shared understanding among all contributors to a testnet release.
+
+## Caveat
+
+Parts of the specification are not well harmonised with the rest, in particular the storage system, due to time constraints. Read graciously, this will be resolved later. Also, a substantial number of modules are not yet specced, as already implemented modules from prior testnets are being reused. Full specs will eventually be available as future networks are released where changes are made to these modules. Lastly, a number off communication protocols are not captured in this spec due to time constraints, they will be introduced for later networks.
+
+## Glossary
+
+- **Substrate:** A blockchain SDK, used for this testnet, that abstracts away concerns about consensus and p2p communication.
+
+- **Runtime:** Application specific consensus code written for the Substrate SDK. Includes state and transaction rules specific to the application, but excludes consensus algorithm and p2p networking.
+
+- **Substrate Module:** A substrate runtime is partitioned into functionality integrated units, called modules, which have their own local state, transaction types and events.
+
+- **SRML:** Substrate standard runtime library (SRML), is a set of highly reusable Substrate modules.
+
+- **AURA:** Component of consensus responsible for block authoring.
+
+- **GRANDPA:** Component of consensus responsible for block finality.
+
+<!--
+## Format
+
+[Read here to understand the format of this specification.](specification-format.md)
+
+-->
+
+## Overview
+
+The Acropolis testnet features the following roles
+
+- **Validator**: Block producer and validator.
+- **Council Member:** Council participant, currently only involved in voting on runtime upgrades.
+- **Storage Provider**: Does storage and distribution of content, peer provider synchronisation, as well as upload liaison.
+- **Forum Moderator**: A single actor role for managing the new on-chain forum.
+
+This release also sees the introduction of a new storage infrastructure based on IPFS for distribution and synchronisation, and IPNS for host resolution. A generalised host resolution module is also introduced as part of making the latter functionality possible, and it will be reused for host resolution across the platform.
+
+A full featured hierarchical topic based, and single moderator, membership forum is also introduced.
+
+## Substrate Runtime
+
+### Runtime Version
+
+**TODO: Update**
+
+- **spec_name:** `joystream-node`
+- **impl_name:** `joystream-node`
+- **authoring_version:** `5`
+- **spec_version:** `4`
+- **impl_version:** `0`
+- **apis:** `RUNTIME_API_VERSIONS`
+
+### Native Version
+
+- **runtime_version:** [Runtime Version](#runtime-version)
+<!-- - **can_author_with:** Default::default() -->
+
+### Substrate Version
+
+`2.0`
+
+### Modules
+
+These are the Joystream specific modules, for each module, there is either a link to a module specification document, or no link, for already implemented modules (see [caveat](#caveat)). Standard configurations, for example based on values from the System module, are omitted.
+
+An integrated explanation of the modules constituting the storage system is found [here](runtime/storage-modules.md).
+
+#### TokenMint
+
+- **Description**: Transferrable capacity constrained token minting.
+- **Specification**: [**READ HERE**](runtime/token-mint-module.md)
+- **Status:** New.
+- **Configuration:**
+  - _TokenMintId_: `u64`
+
+#### RecurringReward
+
+- **Description**: Recurring periodic minting of rewards for recipients.
+- **Specification**: [**READ HERE**](runtime/recurring-reward-module.md)
+- **Status:** New.
+- **Configuration:**
+  - _RecipientId_: `u64`
+  - _RewardRelationshipId_: `u64`
+  - _PayoutStatusHandler_: `()`
+
+#### Staking
+
+- **Description**: Managed staking, unstaking and slashing.
+- **Specification**: [**READ HERE**](runtime/staking-module.md)
+- **Status:** New.
+- **Configuration:**
+  - _StakeId_: `u64`
+  - _SlashId_: `u64`
+
+#### Hiring
+
+- **Description**: Hiring lifecycle management.
+- **Specification**: [**READ HERE**](runtime/hiring-module.md)
+- **Status:** New.
+- **Configuration:**
+  - _OpeningId_: `u64`
+  - _ApplicationId_: `u64`
+
+#### VersionedStore
+
+- **Description**: A versioned linked data store.
+- **Specification**: [**READ HERE**](runtime/versioned-store.md)
+- **Status:** New.
+- **Configuration:**
+  - _ClassId_: `u64`
+  - _EntityId_: `u64`
+
+#### VersionedStorePermissions
+
+- **Description**: A flexible permission system for writing to the versioned store.
+- **Specification**: [**READ HERE**](runtime/versioned-store-permissions.md)
+- **Status:** New.
+- **Configuration:**
+  - _GroupMembershipChecker_: Implemented by `ContentDirectoryWorkingGroup`
+
+#### ContentDirectoryWorkingGroup
+
+- **Description**: A working group for the content directory.
+- **Specification**: [**READ HERE**](runtime/content-directory-working-group.md)
+- **Status:** New.
+- **Configuration:**
+  - _PermissionGroupId_: `u64`
+
+#### Forum
+
+- **Description**: An on-chain discussion forum for members.
+- **Specification**: [**READ HERE**](runtime/forum-module.md)
+- **Status:** Unchanged from Acropolis.
+- **Configuration:**
+  - _ForumUserRegistry_: Proxy call to `get_profile` on [Membership](#membership) module.
+
+#### Proposals
+
+- **Description**: Proposal system, currently only supports runtime upgrades.
+- **Specification**: [**NA**](#)
+- **Status:** Unchanged from Acropolis.
+- **Configuration:**
+  - _Members_ : [Membership](#membership) module.
+
+#### CouncilElection
+
+- **Description**: Council election manager for council system.
+- **Specification**: [**NA**](#)
+- **Status:** Unchanged from Acropolis.
+- **Configuration:**
+  - _CouncilElected_: [Council](#council) module
+  - _Members_: [Membership](#membership) module.
+
+#### Council
+
+- **Description**: Council system.
+- **Specification**: [**NA**](#)
+- **Status:** Unchanged from Acropolis.
+- **Configuration:**
+  - _CouncilTermEnded_: [CouncilElection](#councilelection) module.
+
+#### Memo
+
+- **Description**: Account based public message field.
+- **Specification**: [**NA**](#)
+- **Status:** Unchanged from Acropolis.
+- **Configuration:** Standard only.
+
+#### Membership
+
+- **Description**: Membership registry.
+- **Specification**: [**NA**](#)
+- **Status:** Updated
+- **Configuration:**
+  - _RoleActorId_: `u64`
+  - _MemberId_: `u64`
+  - _PaidTermId_: `u64`
+  - _SubscriptionId_: `u64`
+
+#### Discovery
+
+- **Description**: Host resolution system for public keys.
+- **Specification**: [**READ-HERE**](runtime/discovery-module.md)
+- **Status:** Unchanged from Acropolis.
+- **Configuration:**
+  - _Roles_: Shim trait which enables account lookup and by proxying to [Actors](#actors) module. Keeping this brief, since it will change.
+
+#### Migration
+
+- **Description**: Runs migration & initialisation routines after on-chain upgrade from past testnet (Acropolis).
+- **Specification**: [**NA**](#)
+- **Status:** Missing.
+- **Configuration:** Standard only.
+
+#### Actors
+
+- **Description**: Staking manager for storage system.
+- **Specification**: [**READ HERE**](runtime/actors-module.md)
+- **Status:** Unchanged from Acropolis.
+- **Configuration:**
+  - _Members_: [Membership](#membership) module.
+  - _OnActorRemoved_: Proxy call to `remove_account_info` on [Discovery](#discovery).
+
+#### DataObjectTypeRegistry
+
+- **Description**:
+- **Specification**: [**READ HERE**](runtime/data-object-type-registry-module.md)
+- **Status:** Unchanged from Acropolis.
+- **Configuration:**
+  - _DataObjectTypeId_: `u64`
+
+#### DataDirectory
+
+- **Description**:
+- **Specification**: [**READ HERE**](runtime/data-directory-module.md)
+- **Status:** Unchanged from Acropolis.
+- **Configuration:**
+  - _ContentId_: `primitives::H256`
+  - _SchemaId_: `u64`
+  - _Members_: [Membership](#membership) module.
+  - _Roles_: same as `Roles` configuration of [Discovery](#discovery) module.
+  - _IsActiveDataObjectType_: [DataObjectTypeRegistry](#dataobjecttyperegistry)
+
+#### DataObjectStorageRegistry
+
+- **Description**:
+- **Specification**: [**READ HERE**](runtime/data-object-storage-registry-module.md)
+- **Status:** Unchanged from Acropolis.
+- **Configuration:**
+  - _DataObjectStorageRelationshipId_: `u64`
+  - _Members_: [Membership](#membership) module.
+  - _Roles_: same as `Roles` configuration of [Discovery](#discovery) module.
+  - _ContentIdExists_: [DataDirectory](#datadirectory)
+
+#### DownloadSessions
+
+- **Description**: Manages download sessions in storage system.
+- **Specification**: [**NA**](#)
+- **Status:** Not used.
+- **Configuration:**
+  - _DownloadSessionId_: `u64`
+  - _ContentHasStorage_: [DataObjectStorageRegistry](#dataobjectstorageregistry)
+
+### SRML Modules
+
+These modules are part of the runtime, but are already implemented part of the SRML.
+
+- **System**: The system module provides low-level access to core types and cross-cutting utilities.
+- **Timestamp**: The Timestamp module provides functionality to get and set the on-chain time.
+- **Consensus**: The consensus module manages the authority set for the native code.
+- **Aura**: The Aura module extends Aura consensus by managing offline reporting.
+- **Indices**: An index is a short form of an address. This module handles allocation of indices for a newly created accounts.
+- **Balances**: The balances module provides functionality for handling accounts and balances.
+- **Session**: Is told the validators and allows them to manage their session keys for the consensus module.
+- **Staking**: The staking module is the means by which a set of network maintainers (known as _authorities_ in some contexts and _validators_ in others) are chosen based upon those who voluntarily place funds under deposit.
+- **Sudo**: The sudo module allows for a single account (called the "sudo key") to execute dispatchable functions that require a `Root` call or designate a new account to replace them as the sudo key.
+- **Grandpa**: This manages the GRANDPA authority set ready for the native code.
+
+### Runtime APIs
+
+The runtime implements the following set of APIs.
+
+#### [Core](https://crates.parity.io/substrate_client/runtime_api/trait.Core.html)
+
+- **version**: Return [Runtime Version](#runtime-version)
+- **execute_block**: Executive module `execute_block` routine.
+- **initialize_block**: Executive module `initialize_block` routine.
+- **authorities**: Executive module `apply_extrinsic` routine.
+
+#### [Metadata](https://crates.parity.io/substrate_client/runtime_api/trait.Metadata.html)
+
+- **metadata**: Runtime `metadata` routine.
+
+#### [BlockBuilder](https://crates.parity.io/substrate_client/block_builder/api/trait.BlockBuilder.html)
+
+- **apply_extrinsic**: Executive module `apply_extrinsic` routine.
+- **finalize_block**: Executive module `finalize_block` routine.
+- **inherent_extrinsics**: `create_extrinsics` routine on inherent data.
+- **check_inherents**: Inherent `check_extrinsics` routine on block.
+- **random_seed**: System module `random_seed` routine.
+
+#### [TaggedTransactionQueue](https://crates.parity.io/substrate_client/runtime_api/trait.TaggedTransactionQueue.html)
+
+- **validate_transaction**: Executive module `validate_transaction` routine.
+
+#### [OffchainWorkerApi](https://crates.parity.io/substrate_offchain/trait.OffchainWorkerApi.html)
+
+- **offchain_worker**: Executive module `offchain_worker` routine.
+
+#### [GrandpaApi](https://crates.parity.io/substrate_finality_grandpa_primitives/trait.GrandpaApi.html)
+
+- **grandpa_pending_change**, **grandpa_forced_change**, **grandpa_authorities**: Default.
+
+#### [AuraApi](https://crates.parity.io/substrate_consensus_aura/trait.AuraApi.html)
+
+- **slot_duration**: Aura module `slot_duration` routine.
+
+#### [AuthoritiesApi](#)
+
+- **authorities**: Consensus module `authorities` routine.
+
+<!--
+
+## Communication Protocols
 
-# Document
+**TBD.**
 
-The purpose of this document is to serve as a starting point for the implementation phase of the given testnet. It is only a starting point in the sense that the implementation effort will always depart from the up front specification to some, possibly substantial, extent, but no effort is made to synchronize the specification at any time. Instead, the any such synchronization would be done in the specification of a future network. The value of such a specification is primarily to establish a relatively precise shared understanding among all contributors to a testnet release.
+-->

BIN
testnets/rome/specification/runtime/.DS_Store


+ 294 - 0
testnets/rome/specification/runtime/actors-module.md

@@ -0,0 +1,294 @@
+# Storage Staking Module
+
+## Table Of Contents
+
+- [Name](#name)
+- [Dependencies](#dependencies)
+- [Design](#design)
+- [Concepts](#concepts)
+- [State](#state)
+- [Events](#events)
+- [Dispatchable Methods](#dispatchable-methods)
+  - [create_tranche](#create_tranche)
+  - [update_tranche_parameters](#update_tranche_parameters)
+  - [request_entry](#request_entry)
+  - [stake](#stake)
+  - [unstake](#unstake)
+  - [eject_provider](#eject_provider)
+
+## Name
+
+`Actors`
+
+## Dependencies
+
+- `srml::system`
+- `LockableCurrency`
+- `Membership`
+
+## Design
+
+### Motivation
+The storage staking module is the gatekeeper for entry and orderly exit of platform members into and out of the `Storage Provider` role.
+Entry is achieved through staking funds. Storage providers are rewarded periodically with new funds as incentive to maintain operational status. Storage providers can choose to stop offering their services by un-staking. System sudo account can forcefully remove storage providers from active participation, and optionally punish provider by slashing staked funds.
+
+### Tranches
+The module is used to configure the storage tranches available in the storage system, and for updating operational parameters associated with each tranche, such as minimum stake and storage capacity.
+Tranches must always aim to maintain a minimum number of providers `MinSlots`.
+The module will prevent un-staking when it would result in `MinSlots` no longer being filled. Once tranches are created they cannot be destroyed, however their operational parameters can be adjusted. If at anytime the maximum number of slots `MaxSlots` for a tranche is update to be less than the number of active providers `N` in the tranche, no new providers can be allowed to join until the number drops below `MaxSlots` again.
+
+### Role Account
+Members will utilize a separate account, referred to as the role account, which will be associated with their membership, to hold the staked funds. This corresponding key, is referred to as the role key.
+The role key is also used to sign a extrinsics sent by the storage provider software when interacting with the chain.
+A role account may only only be associated with a single member at any given time.
+A role account can only be used to stake into a single tranche at any given time.
+
+### Locking Funds
+Staking is achieved by locking funds in the role account. The amount locked will be equal to the `StakeAmount` parameter for the tranche the storage provider is participating in. The storage provider may transfer funds out of the role account only that are in excess of the locked amount.
+For convenience, the locked funds can still be used to pay for transactions fees. This means the balance may fall below the locked amount.
+
+### Entering Role
+Entering a tranche is a multi-step process:
+  1. A member must generate a new keypair and deposit at least the StakeAmount funds into the corresponding account, plus the request entry fee for the tranche they plus some extra to cover transaction fees.
+  1. Request entry into a specific tranche using the role account, associating it with their membership account.
+  1. Using their member account, approve the request.
+
+There must be an available slot in a tranche to enter. All providers stake the same amount of funds, this means it's not possible for a new storage provider to displace an existing provider.
+
+### Rewards
+At regular intervals defined by tranche parameters, a fixed award is distributed to distributors. The reward amount is a configurable parameter.
+The reward will go to the member account, unless the balance in the role account is below the `StakeAmount`, in which case it
+will go to the role account. This forces storage providers to gradually add funds to their role account in order to maintain their commitment of staking a certain amount.
+
+### Leaving Role
+When a storage provider decides to leave a tranche, they can initiate the process by un-staking. If the platform allows it, the provider goes immediately out of service.
+An un-bonding period follows after which the funds in the role account become transferable.
+
+
+## Concepts
+  - `StorageProviderInfo`: Represents when the provider joined a tranche, the member identifier they are associated with, and their role account identifier.
+  - `EntryRequest`: Represents intent to join a specific tranche, with a certain role account and member identity. Requests have a lifetime `RequestLifeTime`. If not approved before expiry they are cleared from state.
+
+## State
+
+- `TrancheCount`: Number of tranches created.
+- `RoleAccountIds`: List of currently staked role accounts.
+- `StorageProviderInfoByRoleAccount`: Map of RoleAccount to StorageProviderInfo
+- `RoleAccountsByTrancheId`: Map of tranche identifier to list of role accounts
+- `RoleAccountIdsByMemberId`: Map of member identifier to list of role accounts
+- `EntryRequests`: List of pending requests to enter storage provider role
+- `RequestLifeTime`: Duration of time before a role entry request expires
+- `TrancheIdsByDataObjectTypeId`: Mapping of tranches available for a `DataObjectType`
+
+Tranche Operational Parameters
+
+- `StakeAmount`: Map of tranche identifier to Optional Stake Amount
+- `MinSlots`: Map of tranche identifier to Optional Minimum Number of slots
+- `MaxSlots`: Map of tranche identifier to Optional Maximum Number of slots
+- `FixedReward`: Map of tranche identifier to Optional Reward Amount
+- `FixedRewardPeriod`: Map of tranche identifier to Optional BlockNumber
+- `UnbondingPeriod`: Map of tranche identifier to Optional BlockNumber
+- `EntryRequestFee`: Map of tranche identifier to Optional Fee Amount
+- `DataObjectTypeId`: The `DataObjectType` for which this tranche is created.
+
+Tranche identifier starts at 0. So if TrancheCount = 4, the identifiers of the created tranches are 0, 1, 2, and 3.
+
+**Note:** Currently, tranches are not limited in size. When good tooling for
+monitoring and creating tranches is available, the intend is to limit tranches
+in size, and add new ones before storage space runs out.
+
+## Events
+Each event has payload as sublist
+
+  - `TrancheCreated`: A new tranche was created
+    - Tranche identifier
+  - `TrancheUpdated`: Some tranche parameters were changed
+    - Tranche identifier
+  - `EntryRequested`: A new role key
+    - Role account identifier
+    - Member identifier
+    - Tranche Identifier
+  - `ProviderJoined`: A new storage provider joined a tranche
+    - Tranche identifier
+    - Role account identifier
+  - `ProviderLeft`: A storage provider un-staked
+    - Tranche identifier
+    - Role account identifier
+  - `ProviderEjected`: A storage provider was forcefully removed
+    - Tranche Identifier
+    - Role account
+
+
+## Dispatchable Methods
+
+### create_tranche
+
+#### Payload
+- `origin`: call origin
+- `parameters`: List of operational parameters
+  - `stake_amount`
+  - `min_providers`
+  - `max_providers`
+  - `fixed_reward`
+  - `fixed_reward_period`
+  - `unbonding_period`
+  - `entry_request_fee`
+  - `data_object_type_id`
+
+
+#### Description
+Creates a new tranche with provided parameters.
+
+#### Errors
+  - Bad signature
+  - origin is not Root
+  - StakeAmount == 0
+  - MaxSlots == 0
+  - MinSlots == 0 unless we want [temporary tranches](#temporary tranches)
+  - MinSlots > MaxSlots
+  - No matching `DataObjectType`.
+
+#### Side effects
+  - `TrancheCount` increased by one
+  - Tranche operational parameter values set in each corresponding parameter map
+#### Event(s)
+  - TrancheCreated
+    - TrancheCount - 1
+
+### update_tranche_parameters
+#### Payload
+  - `origin`
+  - Tranche Identifier
+  - List of tranche operational parameters
+    - `stake_amount`
+    - `min_providers`
+    - `max_providers`
+    - `fixed_reward`
+    - `fixed_reward_period`
+    - `unbonding_period`
+    - `entry_request_fee`
+
+#### Description
+Change tranche operational parameters
+
+#### Errors
+  - Bad signature
+  - origin is not Root
+  - Tranche Identifier < TrancheCount
+  - StakeAmount == 0
+  - MaxProviders == 0
+  - MinSlots == 0 unless we want [temporary tranches](#temporary tranches)
+  - MinSlots > MaxSlots
+
+#### Side effects
+  - Operational parameter values updated
+
+#### Event(s)
+  - `TrancheUpdated`
+
+### request_entry
+#### Payload
+ - `origin`: role account
+ - Tranche identifier
+ - membership identifier
+
+#### Description
+Member creating request to join a tranche
+
+#### Errors
+  - Bad signature
+  - origin is a member account
+  - origin already in a tranche
+  - origin already made a request
+  - tranche identifier >= TrancheCount (invalid tranche)
+  - not enough balance in role account to pay entry request fee
+  - membership identifier invalid
+  - no available slots in tranche
+  - origin still un-bonding
+
+#### Side effects
+  - Entry request fee burned from role account balance
+  - If no errors
+    - `EntryRequests` List has new request
+
+#### Event(s)
+  - `EntryRequested`
+
+### stake
+#### Payload
+  - origin: member account
+  - tranche identifier
+  - role account
+
+#### Description
+Member approves a pending request to stake and join a tranche using the role account.
+
+#### Errors
+  - Bad signature
+  - No matching request found
+  - no available slot in tranche (availability may have changed since request was made)
+  - not enough balance in role account to stake
+
+#### Side effects
+  - Stake amount locked in role account
+  - Role account added to `RoleAccountIds`
+  - `StorageProviderInfoByRoleAccount` map
+  - Role account added to list for tranche in `RoleAccountsByTrancheId`
+  - Role account added to member mapping in `RoleAccountIdsByMemberId`
+  - Request removed from `EntryRequests`
+
+#### Events
+  - `ProviderJoined`
+    - role account
+
+### unstake
+#### Payload
+  - origin
+  - role account
+
+#### Description
+Member chooses to un-stake and stop providing service.
+
+#### Errors
+  - Bad signature
+  - origin is not a member associated with role account
+  - role account is not staked provider account
+  - too few slots filled if un-staking allowed
+
+#### Side effects
+  - Balance lock updated on role account, so funds become liquid after UnbondingPeriod
+  - Role account removed from `RoleAccountsByTrancheId`, `RoleAccounts`, `RoleAccountIdsByMemberId`, `StorageProviderInfoByRoleAccount`
+
+### eject_provider
+
+#### Payload
+  - origin: call Origin
+  - role_account
+  - punish: bool
+
+#### Description
+Forcefully remove a storage provider from service. And optionally slash staked balance.
+
+#### Errors
+  - Bad signature
+  - origin is not Root
+  - role_account is not an active storage provider
+
+### Side effects
+  - Balance lock updated on role account, so funds become liquid after UnbondingPeriod
+  - Remove role account from `RoleAccountsByTrancheId`, `RoleAccounts`, `RoleAccountIdsByMemberId`, `StorageProviderInfoByRoleAccount`
+  - If `punish` is true, slash stake amount from role account
+
+## On Initialize
+Clear expired entry requests.
+
+## On Finalize
+Make reward payouts.
+
+#### Temporary Tranches
+A tranche that has MinSlots = 0, where content is not guaranteed to persist. Suitable for short lived data?
+
+#### Idea for improvements:
+- The actor account private key is used on a live system, and is considered a 'hot wallet'. We should look at supporting cold wallet similar to the stash/controller model in validator staking module.
+- Limit destination of transfer of funds from actor account to only the member account. This can limit loss of funds in the event of a key compromise.
+- Have a List of banned member id. Members can be optionally banned when ejected.

+ 411 - 0
testnets/rome/specification/runtime/content-directory-working-group.md

@@ -0,0 +1,411 @@
+# Content Directory Working Group Module
+
+## Table of Contents
+
+- [Name](#name)
+- [Design](#design)
+- [Dependencies](#dependencies)
+- [Concepts](#concepts)
+- [State](#state)
+- [Events](#events)
+- [Dispatchable Methods](#dispatchable-methods)
+  - [update_lead_role_account](#update_lead_role_account)
+  - [update_lead_reward_account](#update_lead_reward_account)
+  - [add_permission_group](#add_permission_group)
+  - [update_permission_group](#update_permission_group)
+  - [add_curator_opening](#add_curator_opening)
+  - [accept_curator_applications](#accept_curator_applications)
+  - [begin_curator_applicant_review](#begin_curator_applicant_review)
+  - [fill_curator_opening](#fill_curator_opening)
+  - [update_curator_reward](#update_curator_reward)
+  - [slash_curator](#slash_curator)
+  - [terminate_curator](#terminate_curator)
+  - [apply_on_curator_opening](#apply_on_curator_opening)
+  - [update_curator_role_account](#update_curator_role_account)
+  - [update_curator_reward_account](#update_curator_reward_account)
+  - [exit_curator_role](#exit_curator_role)  
+- [Non-dispatchable Methods](#non-dispatchable-methods)
+  - [set_lead](#set_lead)
+  - [unset_lead](#unset_lead)
+  - [set_opening_policy](#set_opening_policy)
+  - [update_lead_reward](#update_lead_reward)
+  - [account_is_in_group](#account_is_in_group)
+
+## Name
+
+`ContentDirectoryWorkingGroup`
+
+## Motivation
+
+The working groups solves the problems of how to administrate the write access permissions to the content directory state.
+
+## Design
+
+The working group has two types of participants. A _lead_, which is inducted by root. The lead is involved in two activities. First, hiring and managing the second participant type, namely _curators_. Second, creating and managing the actor groups, called _permission groups_, for the content directory. These groups are defined by some criteria for what accounts belong to them at any given time, and in particular they may defined to include the account(s) of one of the following
+
+- the led,
+- a specific curator
+- a specific member
+- a specific publisher
+- any curator
+- any member
+- any publisher.
+
+These permission groups are presumed to be used with the [permission module](#) and the [version store module](#). Both leads and curators are possibly staked and rewarded. The staking is managed by the [staking module](#), and the rewards are managed by the [recurring rewards module](#). All rewards in the group flow from a single group mint.
+
+## Usage
+
+I used in concert with a range of other modules described in the next section.
+
+## Dependencies
+
+ - `RecurringReward` module
+ - `Hiring` module
+ - `Membership` module
+ - `VersionedStorePermissions` module
+
+## Concepts
+
+```Rust
+
+trait Trait : RecurringReward::Trait + Hiring::Trait + Membership::Trait VersionedStorePermissions::Trait {
+
+  // Type of identifier for permissions groups.
+  type PermissionGroupId : INTEGER_TRAIT_CONSTRAINTS;
+
+}
+
+// Type of identifier for lead.
+type LeadId = Trait::RoleActorId;
+
+// Type of identifier for curators.
+type CuratorId = Trait::RoleActorId;
+
+// Type of identifier for publishers.
+type PublisherId = Trait::MemberId;
+
+enum LeadRoleState {
+  Active,
+  Exited {
+    initiated_at_block_number: T::BlockNumber,
+  }
+}
+
+// Working group lead: curator lead
+// For now this role is not staked or inducted through an structured process, like the hiring module,
+// hence information about this is missing. Recurring rewards is included, somewhat arbitrarily!
+struct Lead<T: Trait> {
+
+  // Account used to authenticate in this role,
+  role_account: T::AccountId,
+
+  // Whether the role has recurring reward, and if so an identifier for this.
+  reward_relationship: Option<T::RewardRelationshipId>,
+
+  // When was inducted
+  // TODO: Add richer information about circumstances of induction
+  inducted: T::BlockNumber,
+
+  //
+  stage: LeadRoleState
+
+}
+
+enum CuratorExitInitiationOrigin {
+  Lead,
+  Curator
+}
+
+enum CuratorRoleStage<T: Trait> {
+
+  Active,
+
+  Exited {
+
+    origin: CuratorExitInitiationOrigin,
+
+    initiated_at_block_number: T::BlockNumber,
+
+    rationale_text: Vec<u8>
+
+  }
+
+}
+
+struct CuratorInduction<T: Trait> {
+
+  // Lead responsible
+  lead: LeadId,
+
+  // Application through which curator was inducted
+  application: T::ApplicationId,
+
+  // When induction occurred
+  at_block: T::BlockNumber
+}
+
+// Working group participant: curator
+// This role can be staked, have reward and be inducted through the hiring module.
+struct Curator<T: Trait> {
+
+  // Account used to authenticate in this role,
+  role_account: T::AccountId,
+
+  // Whether the role has recurring reward, and if so an identifier for this.
+  reward_relationship: Option<T::RewardRelationshipId>,
+
+  // Whether participant is staked, and if so, the identifier for this staking in the staking module.
+  stake: Option<T::StakeId>,
+
+  //
+  stage: CuratorRoleStage<T>,
+
+  //
+  induction: CuratorInduction<T>
+}
+
+// The type of permission groups supported
+enum PermissionGroupType<T: Trait> {
+
+  CurrentLead,
+
+  Curator(T::CuratorId),
+
+  Member(T::MemberId),
+
+  Publisher(T::PublisherId),
+
+  AnyCurator,
+
+  AnyMember,
+
+  AnyPublisher
+}
+
+// Represents a group as understood by the VersionedStorePermissions module
+struct PermissionGroup<T: Trait> {
+
+  // ..
+  type: PermissionGroupType<T>,
+
+  // ..
+  is_active: bool,
+
+  // ..
+  created: T::BlockNumber,
+
+  // ..
+  description: Vec<u8>
+}
+
+// Policy governing any curator opening which can be made by lead.
+struct OpeningPolicy<T: Trait> {
+
+ /**
+  * // Whether there should be a number of active curators which would block the
+  * // creation of new openings, and if so what value.
+  * active_curator_count_blocking_new_openings: Option<u16>,
+  */
+
+  // Maximum length of review period of applications
+  max_review_period_length: BlockNumber,
+
+  // Staking policy for application
+  application_staking_policy: Option<T::StakingPolicy>,
+
+  // Staking policy for role itself
+  role_staking_policy: Option<T::StakingPolicy>
+}
+
+```
+
+## State
+
+- `mint: T::TokenMintId`: The mint currently funding the rewards for this module.
+- `current_lead: Option<T::LeadId>`: The current lead.
+- `leadById: linked_map T::LeadId => Lead<T>`: Maps identifier to corresponding lead.
+- `nextLeadId: T::LeadId`: Next identifier for new current lead.
+- `openings_policy: Option<OpeningPolicy<T>>`: The constraints lead must respect when creating a new curator opening. Lack of policy is interpreted as blocking any new openings at all.
+- `openings: linked_map T::OpeningId => ()`: set of identifiers for all openings originated from this group.
+- `curatorById: linked_map T::CuratorId => Curator<T>`: Maps identifier to corresponding curator.
+- `nextCuratorId: T::CuratorId`: Next identifier for new curator.
+- `permissionGroupById: linked_map T::PermissionGroupId => PermissionGroup<T>`: Maps identifier to corresponding permission group.
+- `nextPermissionGroupId: T::PermissionGroupId`: Next identifier for new permission group.
+- `maxPermissionGroupDescriptionLength: u16`: Upper bound for character length of `description` field of any new or updated `PermissionGroup`
+- `maxCuratorExitRationaleTextLength: u16`: Upper bound for character length of the `rationale_text` field of any new `CuratorRoleStage`.
+
+## Events
+
+- `LeadSet`
+- `LeadUnset`
+- `OpeningPolicySet`
+- `LeadRewardUpdated`
+- `LeadRoleAccountUpdated`
+- `LeadRewardAccountUpdated`
+- `PermissionGroupAdded`
+- `PermissionGroupUpdated`
+- `CuratorOpeningAdded`
+- `AcceptedCuratorApplications`
+- `BeganCuratorApplicationReview`
+- `CuratorOpeningFilled`
+- `CuratorSlashed`
+- `TerminatedCurator`
+- `AppliedOnCuratorOpening`
+- `CuratorRewardUpdated`
+- `CuratorRoleAccountUpdated`
+- `CuratorRewardAccountUpdated`
+- `CuratorExited`
+
+
+## Implemented Traits
+
+Module implements `VersionedStorePermissions::GroupMembershipChecker`, and sets `GroupMembershipChecker::GroupdId` to `T::PermissionGroupId`.
+
+## Dispatchables
+
+### `update_lead_role_account`
+
+Can only be called by **Signed** origin, and caller must pass test on membership module of `key_can_sign_for_role` for the `current_lead`.
+
+Updates `role_account` of `current_lead` to given new value.
+
+Emits `LeadRoleAccountUpdated`.
+
+### `update_lead_reward_account`
+
+Can only be called by **Signed** origin, and caller must match `role_account` of set `current_lead`. Also, `reward_relationship` must be set on `current_lead`.
+
+Updates `account` on `reward_relationship` to given new value.
+
+Emits `LeadRewardAccountUpdated`.
+
+### `add_permission_group`
+
+Can only be called by **Signed** origin, and caller must match `role_account` of set `current_lead`.
+
+Creates and adds new permission group.
+
+Emits `PermissionGroupAdded`.
+
+### `update_permission_group`
+
+Can only be called by **Signed** origin, and caller must match `role_account` of set `current_lead`.
+
+Updates a given permission group, based on its identifier. Field `created` is not mutable.
+
+Emits `PermissionGroupUpdated`.
+
+### `add_curator_opening`
+
+Can only be called by **Signed** origin, and caller must match `role_account` of set `current_lead`.
+Also requires that `openings_policy` is set, and if so, a new application is added to the `Hiring` module using relevant parameter values from this policy. The new opening identifier is added to `openings`.
+
+Emits `CuratorOpeningAdded`.
+
+### `accept_curator_applications`
+
+Can only be called by **Signed** origin, and caller must match `role_account` of set `current_lead`.
+Given opening identifier must be in `openings`. If so, `begin_accepting_applications` is called on the corresponding opening the `Opening` module.
+
+Emits `AcceptedCuratorApplications`
+
+### `begin_curator_applicant_review`
+
+Can only be called by **Signed** origin, and caller must match `role_account` of set `current_lead`. Given opening identifier must be in `openings`. If so, `begin_review` is called on the corresponding opening the `Opening` module.
+
+Emits `BeganCuratorApplicationReview`
+
+### `fill_curator_opening`
+
+Can only be called by **Signed** origin, and caller must match `role_account` of set `current_lead`. Given opening identifier must be in `openings`. Checks with the `Membership` module that all hires still can step into the role as curator at this time. If so, `fill_opening` is called on the corresponding opening the `Opening` module.
+All hired curators are turned into new `Curator` instances, with identifiers based on `nextCuratorId`, and added to `curatorsById`.
+
+Emits `CuratorOpeningFilled` and `CuratorAdded` for each curator hired.
+
+### `update_curator_reward`
+
+Can only be called by **Signed** origin, and caller must match `role_account` of set `current_lead`. Updates `reward_relationship` on given curator.
+
+Emits `CuratorRewardUpdated`.
+
+### `slash_curator`
+
+Can only be called by **Signed** origin, and caller must match `role_account` of set `current_lead`.
+Also requires that `stake` is set on given curator, if so, `initiate_slashing` is called on the `Stake` module on the stake for this curator.
+
+Emits `CuratorSlashed`.
+
+### `terminate_curator`
+
+Can only be called by **Signed** origin, and caller must match `role_account` of set `current_lead`.
+Also requires that given curator is found in `curatorsById` in the `Active` stage. If so, the stage is updated `Exited`, with suitable other parameters, and any `reward_relationship` set is disabled. Lastly, unstaking is initiated if present.
+
+Emits `TerminatedCurator`.
+
+### `apply_on_curator_opening`
+
+Can only be called by **Signed** origin, and caller must pass test on `Membership` module of whether they can occupy a curator role at this time. Next, provided parameters must match staking and other requirements of opening. If so, `add_application` is on `Hiring` module. Return value is returned.
+
+Emits `AppliedOnCuratorOpening`.
+
+### `update_curator_role_account`
+
+Can only be called by **Signed** origin, and account must match `role_account` of provided curator identifier. Also requires that given curator is found in `curatorsById` in the `Active` stage.
+
+Updates `role_account` on given curator.
+
+Emits `CuratorRoleAccountUpdated`.
+
+### `update_curator_reward_account`
+
+Can only be called by **Signed** origin, and account must match `role_account` of provided curator identifier. Also requires that given curator is found in `curatorsById` in the `Active` stage. Also, given curator must also have `reward_relationship` set.
+
+Updates `account` on given curator `reward_relationship`.
+
+Emits `CuratorRewardAccountUpdated`.
+
+### `exit_curator_role`
+
+Can only be called by **Signed** origin, and account must match `role_account` of provided curator identifier. Also requires that given curator is found in `curatorsById` in the `Active` stage. If so, the stage is updated `Exited`, with suitable other parameters, and any `reward_relationship` set is disabled. Lastly, unstaking is initiated if present.
+
+Emits `CuratorExited`.
+
+## Non-dispatchable Methods
+
+### `set_lead`
+
+Can only be called by **Root** origin, and requires that `current_lead` is not set. Membership module is consulted as to whether the given signing account can be setup as the curator lead with id `nextLeadId`, if no, then method terminates. Otherwise continues as follows.
+
+A new recipient and reward relationship is added to the reward module, and a new `current_lead` instance is created with corresponding values and id `nextLeadId`, added to `leadById` undert this id, and `current_lead` i also set to this key. Lastly, `nextLeadId` is incremented.
+
+Emits event `LeadSet`. Returns nothing.
+
+### `unset_lead`
+
+Can only be called by **Root** origin, and requires that `current_lead` is set. Membership module is informed to unregister the lead as playing the role of a curator lead. If `reward_relationship` is set on lead, then disable it to any further rewards. Lastly set `exited` to the current block number.
+
+Emits event `LeadUnset`. Returns nothing.
+
+### `set_opening_policy`
+
+Can only be called by **Root** origin. Updates `opening_policy`.
+
+Emits event `OpeningPolicySet`. Returns nothing.
+
+### `update_lead_reward`
+
+Can only be called by **Root** origin, and requires that `current_lead` is set. The lead must also have `reward_relationship` set. If so, then `amount_per_payout`, `next_payment_in_block` and `payout_interval` of the reward relationship can be updated.
+
+Emits event `LeadRewardUpdated`. Returns nothing.
+
+### `account_is_in_group`
+
+Takes group id and looks up `permissionGroupById`, lack of match results in `false` being returned. A match is then used to evaluate the given account as follows
+
+- `CurrentLead`: Account matches `role_account` of `current_lead` which is set.
+- `Curator(id)`: ..
+- `Member(id)`: ..
+- `Publisher(id)`: .
+- `AnyCurator`: ..
+- `AnyMember`: ..
+- `AnyPublisher`: ..

+ 212 - 0
testnets/rome/specification/runtime/content-directory.md

@@ -0,0 +1,212 @@
+# Content Directory
+
+## Table Of Contents
+
+- [Name](#name)
+- [Dependencies](#dependencies)
+- [Design](#design)
+- [Future TODOs](#future-todos)
+- [State](#state)
+- [Events](#events)
+- [Dispatchable Methods](#dispatchable-methods)
+  - [add_metadata](#add_metadata)
+  - [update_metadata](#update_metadata)
+
+## Name
+
+`ContentDirectory`
+
+## Dependencies
+
+- The [Storage System](storage-system.md).
+
+## Design
+
+### Motivation
+
+Once data is stored on the network, it must be made discoverable. Part of this
+is to assign meaning to any stored data. For these purposes, the Content
+Directory is introduced.
+
+The Content Directory is a list of `ContentMetadata` entries on the runtime.
+Each `ContentMetadata` entry can:
+
+- Contain a JSON payload conforming to a schema identified by a `SchemaId`.
+- Contain a list of `ContentId` as children of the entry.
+
+As `ContentMetadata` is itself identified by a `ContentId`, it is inextricably
+linked to a `DataObject` with the same identifier, and intended to describe
+this object.
+
+Therefore the runtime enforces that for any `ContentMetadata`:
+
+1. A `DataObject` with the same `ContentId` exists.
+1. An active `StorageRelationship` for the `ContentId` exists.
+
+Additionally, `ContentMetadata` may exist in a published or unpublished state.
+Of course, nothing is entirely private on the runtime, but only published
+`ContentMetadata` is intended for inclusion in content listing, searches or
+other user interfaces for the *Content Directory*. Unpublished entries can
+e.g. be used as drafts during the creation process.
+
+#### Hierarchy
+
+`ContentMetadata` may contain child content IDs, which are used to indicate
+a hierarchy of content of sorts. Each child must itself have a meaning;
+therefore, for each child content ID, an appropriate `ContentMetadata` entry
+must also exist, with the above constraints enforced.
+
+This means that when creating a hierarchy of `ContentMetadata`, it must be
+done in a leaf-to-root order.
+
+The purpose of creating such a hierarchy depends on the `SchemaId` used
+in the root entry. Possible uses are:
+
+* For structuring podcast/video episodes in a series.
+* For providing multiple  language tracks, subtitle tracks, commentary,
+  etc. for video content.
+* etc.
+
+**Hierarchy and Publishing**
+
+Note in particular that in hierarchically structured content, the published
+state may be interpreted differently depending on whether the metadata
+describes a root, branch or leaf element, and on the metadata schema.
+
+Largely, it is the published state of *root* entries that determines whether
+content is discoverable at all. For branch or leaf elements, the flag may
+be ignored - but such a decision is outside the scope of this document, and
+belongs in a description of schemata to be used.
+
+For example, in episodic content like a podcast, the root level element may
+describe the podcast series, and contain:
+
+1. Image `DataObjects`, for describing the podcast. These may be considered
+   to be published if the root element itself is published.
+1. Episode `DataObjects`, each of which may stay in a draft state until
+   it is finalized. Here, the published flag should be interpreted.
+
+Using schema identifiers in `ContentMetadata` permits easy decision making
+in rendering or indexing apps as to how the hierarchy should be interpreted.
+
+#### Content Creation
+
+The content creation protocol is as follows:
+
+1. Create one (or several related) `DataObjects` as described in the
+  [Data Directory](./storage-module/data-directory.md) section of the
+  [Storage Module](./storage-module.md).
+1. Create appropriate `ContentMetadata` entries in a leaf-to-root order
+  in the `ContentDirectory`.
+1. Publish the root `ContentMetadata` (see the [Hierarchy](#hierarchy)
+  section) to make content discoverable.
+
+## State
+
+- `MetadataByContentId`: a map of `ContentId` to matching `ContentMetadata`
+  Objects.
+
+- `PublishedRootContent`: a vector of published `ContentIds` that describe
+  root content in a `ContentMetadata` hierarchy - in other words, the
+  discoverable `ContentIds`.
+
+  **Note:** This list should be moved off-chain once an indexing node provides
+  content discovery services; it exists solely for not having to scan the entire
+  `MetadataByContentId` map in the pioneer app.
+
+## Events
+
+- `MetadataAdded`: `ContentMetadata` has been added to `MetadataByContentId`. The
+  event payload is the `ContentId` of the matching metadata.
+
+- `MetadataUpdated`: `ContentMetadata` has been updated. The
+  event payload is the `ContentId` of the matching metadata.
+
+- `RootContentUpdated`: a `ContentMetadata` that represents root content in a
+  metadata hierarchy has been updated. The payload is the `ContentId`, and a
+  flag indicating whether or not this content is published.
+
+  **Note:** This event exists to trigger updates in either pioneer's rendering
+  of the content directory, or an indexing node's internal database. It's
+  a special case of `MetadataUpdated` that can be listened to explicitly.
+
+## Dispatchable Methods
+
+### `add_metadata`
+
+#### Payload
+
+- The publisher origin.
+- `content_id`: the `ContentId` for which the `ContentMetadata` applies.
+- `data`: a `ContentMetadata` struct.
+- `is_root`: a boolean flag indicating whether the metadata represents the
+  root of a metadata hierarchy.
+
+#### Description
+
+Create a new `ContentMetadata` object for the given `ContentId`. All but
+optional fields must be supplied. It is possible to set the published field
+here immediately, but for hierarchical metadata that is discouraged.
+
+#### Errors
+
+- The origin is not an active member.
+- There already exists `ContentMetadata` for this `ContentId`.
+- For the given `ContentId`, the [Storage Module](./storage-module.md) does
+  not have any currently active `StorageRelationship` entries, i.e. the
+  content does not exist on the network.
+  **Note:** it would be possible to limit this error only to situations
+  in which published flag is to be set.
+
+#### Side effect(s)
+
+- The `ContentMetadata` struct is added under the given `ContentId` to the
+  `MetadataByContentId` map.
+- If the `ContentMetadata`'s published flag is set, and `is_root` is set,
+  the `ContentId` is also added to `PublishedRootContent`.
+
+#### Event(s)
+
+- `MetadataAdded`
+- If the `ContentMetadata`'s published flag is set, and `is_root` is set,
+  also `RootContentUpdated` is emitted.
+
+### `update_metadata`
+
+#### Payload
+
+- The publisher origin.
+- `content_id`: the `ContentId` for which the `ContentMetadata` applies.
+- `data`: a `ContentMetadataUpdate` struct.
+- `is_root`: a boolean flag indicating whether the metadata represents the
+  root of a metadata hierarchy.
+
+#### Description
+
+Modify an existing `ContentMetadata` for the given `ContentId`. With the
+`ContentMetadataUpdate` struct, only fields to be updated must be supplied.
+
+#### Errors
+
+- The origin is not an active member.
+- The origin is not the original creator of the `ContentMetadata`.
+- There exists no `ContentMetadata` for this `ContentId`.
+- For the given `ContentId`, the [Storage Module](./storage-module.md) does
+  not have any currently active `StorageRelationship` entries, i.e. the
+  content does not exist on the network.
+  **Note:** it would be possible to limit this error only to situations
+  in which published flag is to be set afterwards (aka explicitly set now,
+  already set).
+
+#### Side effect(s)
+
+- The `ContentMetadata` struct is modified under the given `ContentId` to the
+  `MetadataByContentId` map.
+- If the `ContentMetadataUpdate`'s published flag is set, and `is_root` is set,
+  the `ContentId` may also added to `PublishedRootContent`.
+
+#### Event(s)
+
+- `MetadataUpdated`
+- If the `ContentMetadataUpdate`'s published flag is set, and `is_root` is set,
+  also `RootContentUpdated` is emitted.

+ 211 - 0
testnets/rome/specification/runtime/data-directory-module.md

@@ -0,0 +1,211 @@
+# Data Directory Module
+
+## Table Of Contents
+
+- [Name](#name)
+- [Dependencies](#dependencies)
+- [Design](#design)
+- [State](#state)
+- [Events](#events)
+- [Dispatchable Methods](#dispatchable-methods)
+  - [add_data_object](#add_data_object)
+  - [accept_data_object](#accept_data_object)
+  - [reject_data_object](#reject_data_object)
+
+## Name
+
+`DataDirectory`
+
+## Dependencies
+
+- [Data Object Type Registry](./data-object-type-registry.md): A storage
+  sub-module which holds constraint information on data.
+
+## Design
+
+### Motivation
+
+The Data Directory is used to manage what data exists on the network. As such,
+it contains a complete list of all accessible content; each file uploaded to
+any storage node requires an entry in the data directory, in the form of a
+`DataObject`. These `DataObjects` are identified via a unique `DataObjectId`.
+
+It also links to the [Data Object Type Registry](./data-object-type-registry.md),
+permitting all participating apps (consuming apps or storage node) to negotiate
+whether content may be uploaded.
+
+#### Uploading
+
+The simplified workflow for uploading content is as follows:
+
+1. Choose a `ContentId`. The `ContentId` is a secure random Byte string,
+   and will usually be represented in a [Base58](https://en.wikipedia.org/wiki/Base58)
+   encoded form.
+1. Select an appropriate `DataObjectType` from the *Data Object Type Registry*.
+1. Verify that the file to be uploaded passes the constraints on the
+   `DataObjectType`.
+1. Create a `DataObject` linking to the selected `DataObjectType`. The
+  `DataObject` is not initially active.
+1. The runtime will select a *Liaison*, a storage node that must handle
+   the upload.
+1. Contact the *Liaison* for upload. The *Liaison* will accept or reject
+   the upload depending on whether the uploaded data fulfils the constraints
+   placed on the `DataObjectType`. The *Liaison* will update the `DataObject`
+   according to its decision.
+1. If the upload was accepted, the *Liaison* will also create an entry in
+   the [Data Object Storage Registry](./data-object-storage-registry.md),
+   indicating that the data is stored.
+1. Other storage nodes may replicate the data object, and create their own
+   entries in the *Data Object Storage Registry*.
+
+#### Downloading
+
+Any storage provider listed in the *Data Object Storage Registry* as holding
+a given file can be contacted for downloads.
+
+#### Uploading/Downloading Protocol
+
+While the above describes the abstract protocol for up- and downloads, the
+specific wire protocol are HTTP requests conforming to the most-current
+[OpenAPI](https://swagger.io/specification/) specification of the storage node.
+
+It is generated from code documentation, so not easily linked here. The API
+version is currently not stable, so a `v0` namespace is specified. [The most
+current API specs](https://storage-node-1.joystream.org/swagger.json) live
+on Joystream's first storage node.
+
+#### Storage Backend Metadata
+
+Note that `DataObject` contains a `storage_metadata` and corresponding
+`storage_metadata_version` object. The version determines how to interpret the
+metadata field. They are maintained by the *Liaison* (and potentially other
+storage providers) for managing storage backend specific metadata.
+
+In version `1` of this metadata, it is expected that:
+
+1. It is serialized JSON with an Object at the top level.
+1. It contains an `ipfs_content_id` field specifying a content ID to map
+   to when using [IPFS](https://ipfs.io/) as the backend.
+
+#### Liaison Selection
+
+The runtime is responsible for selecting a *Liaison* for a `DataObject`. The
+*Liaison* is selected from the currently staked pool of storage providers from
+any of the *tranches* configured in the [Staking](./staking.md) part of this
+module, for matching `DataObjectType`.
+
+The implication of this is that between creating a `DataObject` and uploading
+data to the *Liaison*, not too much time should pass. If there is a reason for
+the *Liaison* to un-stake before re-distributing the content, then the
+`DataObject` cannot be uploaded, and a new `DataObject` needs to be created.
+
+
+## State
+
+- `DataObjects` - a map of `ContentId` to `DataObject`.
+
+## Events
+
+- `DataObjectAdded` - a new `DataObject` has been created, with `Pending`
+   liaison judgement. See above for a high-level description of how
+   a *Liaison* will judge whether a `DataObject` is acceptable.
+
+   The event payload consists of the `ContentId` and the uploader's
+   `AccountId`.
+
+- `DataObjectAccepted` - the chosen *Liaison* accepted the data object.
+
+   The event payload consists of the `ContentId` and the *Liaison*
+   `AccountId`.
+
+- `DataObjectRejected` - the chosen *Liaison* rejected the data object.
+
+   The event payload consists of the `ContentId` and the *Liaison*
+   `AccountId`.
+
+## Dispatchable Methods
+
+### `add_data_object`
+
+#### Payload
+
+- The uploader origin.
+- `content_id`: The `ContentId` chosen by the uploader.
+- `type_id`: The `DataObjectTypeId` chosen by the uploader.
+- `size`: The file size, in Bytes.
+
+#### Description
+
+As one of the first steps of the upload process, the uploader will call
+this function to register their intent to upload data. This also registers
+the `ContentId`, making it unavailable for further upload attempts.
+
+As part of this function, the runtime will chose an appropriate *Liaison*,
+and record it in the `DataObject`.
+
+#### Errors
+
+- The uploader is not an active member.
+- The chosen `ContentId` is already in use.
+- The chosen `DataObjectType` is inactive.
+
+#### Side effect(s)
+
+- A `DataObject` with the given size and type is created, and registered
+  under the `ContentId` in `DataObjects`.
+
+#### Event(s)
+
+- `DataObjectAdded`
+
+### `accept_data_object`
+
+#### Payload
+
+- The *Liaison* origin.
+- `content_id`: The `ContentId` of the `DataObject` to accept.
+
+#### Description
+
+The *Liaison* calls this function when its checks indicate that the `DataObject`
+should be accepted.
+
+#### Errors
+
+- The provided `ContentId` was not found.
+- The signing caller is not the designated *Liaison* for the `DataObject`.
+
+#### Side effect(s)
+
+- The `DataObject` identified by `ContentId` has their `liaison_judgement` field
+  set to Accepted.
+
+#### Event(s)
+
+- `DataObjectAccepted`
+
+### `reject_data_object`
+
+#### Payload
+
+- The *Liaison* origin.
+- `content_id`: The `ContentId` of the `DataObject` to reject.
+
+#### Description
+
+The *Liaison* calls this function when its checks indicate that the `DataObject`
+should be rejected.
+
+#### Errors
+
+- The provided `ContentId` was not found.
+- The signing caller is not the designated *Liaison* for the `DataObject`.
+
+#### Side effect(s)
+
+- The `DataObject` identified by `ContentId` has their `liaison_judgement` field
+  set to Rejected.
+
+#### Event(s)
+
+- `DataObjectRejected`

+ 133 - 0
testnets/rome/specification/runtime/data-object-storage-registry-module.md

@@ -0,0 +1,133 @@
+# Data Object Storage Registry Module
+
+## Table Of Contents
+
+- [Name](#name)
+- [Dependencies](#dependencies)
+- [Design](#design)
+- [Future TODOs](#future-todos)
+- [State](#state)
+- [Events](#events)
+- [Dispatchable Methods](#dispatchable-methods)
+
+## Name
+
+`DataObjectStorageRegistry`
+
+## Dependencies
+
+- [Data Directory](./data-directory.md): A storage sub-module which lists
+  all `DataObjects` on the network.
+
+## Design
+
+### Motivation
+
+The Data Object Storage Registry is where storage providers commit to storing
+any given `DataObject`.
+
+When any storage provider has accepted and stored content - whether it is the
+*Liaison* or a mirror - it will create a `StorageRelationship` entry
+in the registry to indicate that it can be contacted to serve the content.
+
+For an *available* `DataObject`, there is thus at least one, possibly many
+`StorageRelationship` entries in the runtime:
+
+- `DataObject` describes content that *should* exist on the network, and
+  links to a *Liaison* which must have it available.
+- `StorageRelationships` indicate that content exists in a
+  particular location.
+
+`StorageRelationship` contains an `available` flag, which shows
+whether the relationship is currently fulfillable by the storage provider.
+This permits indicating an intent to fulfil on the runtime by providing
+an inactive relationship, then synchronizing content, and finally updating
+the relationship when content can be served.
+
+## State
+
+- `StorageRelationships`: a map of `ContentId` to a vector of
+  `StorageRelationship`. An empty vector means there is no
+  relationship, i.e. the content is not stored.
+
+  However, there still need to be `available` relationships in the vector. Merely
+  finding *a* relationship is not sufficient to determine whether the
+  content is stored somewhere on the network.
+
+## Events
+
+- `StorageRelationshipAdded`: a storage provider has committed to serve
+  a particular content.
+
+  The event payload consists of the `ContentId` of the content added, and
+  the `AccountId` of the storage provider adding it. Note that merely
+  adding a storage relationship does not mean that the content is available
+  to be served.
+
+- `StorageRelationshipAvailabilityChanged`: the given storage relationship's
+  availability changed.
+
+  The event payload consists of the `ContentId`, the storage provider's
+  `AccountId`, and the storage relationship's `available` flag value.
+
+## Dispatchable Methods
+
+### `add_storage_relationship`
+
+#### Payload
+
+- The storage provider origin.
+- `content_id`: The `ContentId` to add a storage relationship for.
+
+#### Description
+
+When a storage provider intends to store content, it calls this function
+to register this intent on the chain. The `StorageRelationship` created
+by this function is set to not be `available` initially.
+
+#### Errors
+
+- The origin is not a storage provider.
+- The `ContentId` does not exist in the [Data Directory](./data-directory.md).
+- There is already a `StorageRelationship` for this `ContentId` and storage
+  provider `AccountId`.
+
+#### Side effect(s)
+
+A new, not-`available` `StorageRelationship` for the `ContentId` and storage
+provider `AccountId` is added to the `StorageRelationships` state.
+
+#### Event(s)
+
+- `StorageRelationshipAdded`
+
+### `set_storage_relationship_availability`
+
+#### Payload
+
+- The storage provider origin.
+- `content_id`: The `ContentId` to modify the `StorageRelationship` for.
+- `available`: a boolean flag indicating the new availability.
+
+#### Description
+
+When a storage provider has finished synchronizing content identified
+by the `ContentId`, it can register itself as having the content available for
+download. The function only modifies state if new value of the `available`
+flag differs from the stored value.
+
+#### Errors
+
+- The origin is not a storage provider.
+- The `ContentId` does not exist in the [Data Directory](./data-directory.md).
+- There is no `StorageRelationship` for this `ContentId` and storage
+  provider `AccountId`.
+
+#### Side effect(s)
+
+The `StorageRelationship` identified by the storage provider and `ContentId`
+has its `available` flag set to the passed value.
+
+#### Event(s)
+
+- `StorageRelationshipAvailabilityChanged`

+ 206 - 0
testnets/rome/specification/runtime/data-object-type-registry-module.md

@@ -0,0 +1,206 @@
+# Data Object Type Registry Module
+
+## Table Of Contents
+
+- [Name](#name)
+- [Dependencies](#dependencies)
+- [Design](#design)
+- [Concepts](#concepts)
+- [State](#state)
+- [Events](#events)
+- [Dispatchable Methods](#dispatchable-methods)
+  - [register_data_object_type](#register_data_object_type)
+  - [update_data_object_type](#update_data_object_type)
+  - [activate_data_object_type](#activate_data_object_type)
+  - [deactivate_data_object_type](#deactivate_data_object_type)
+
+## Name
+
+`DataObjectTypeRegistry`
+
+## Dependencies
+
+None.
+
+## Design
+
+### Motivation
+
+All stored `DataObjects` are associated with a type. This type does not describe
+a file or media type, but rather how `DataObjects` of this type are to be
+handled by the storage network. This information is encapsulated in the
+`DataObjectType` structure, identified by a `DataObjectTypeId`.
+
+Of course, storage nodes should still impose some constraints on how what
+uploads to accept. These constraints are described by the `constraints` field
+of the `DataObjectType`. There is a corresponding `constraints_version` field,
+which indicates the version of the constraints specification, i.e. how to
+interpret the `constraints` field.
+
+## Concepts
+
+See [the module documentation](../storage-module.md#concepts) for an overview.
+
+Each `DataObjectType` contains a `constraints` field to be queried by
+uploading apps, and to be enforced by storage nodes. The current version of
+the `constraints_version` field is 1, and the corresponding `constraints`
+field is expected to be valid JSON with the following structure:
+
+1. The top-level entry of the JSON is an Object, not an Array.
+1. Each property key corresponds to to a [IANA Media Type](https://www.iana.org/assignments/media-types/media-types.xhtml).
+   Wildcard keys such as `image/*` are allowed. The wildcard `*/*` matches any
+   file, and therefore becomes the default entry (see below).
+1. Each property value is itself a JSON Object, specifying further constraints
+   for the matching media type:
+   1. A `maximum_file_size` specifies the maximum size of permitted files, in
+      Bytes.
+
+### Content Creation
+
+For apps attempting to upload data, the choice of `DataObjectType` is a
+hardcoded property. `DataObjectType` represents a *purpose*, i.e. a media cover
+image or some such. The uploading app knows exactly the purpose of each file
+to upload, and chooses the appropriate `DataObjectType` accordingly.
+
+1. Read and parse the `constraints` filed of the `DataObjectType`.
+1. If the file to be uploaded matches a full media type, e.g. `image/jpeg`,
+   apply constraints associated with it.
+1. If the above was not true, and the file to be uploaded matches a partial
+   media type, e.g. `image/*`, apply constraints associated with it.
+1. If the above was not true, and the file to be uploaded matches the wildcard
+   media type `*/*`, apply constraints associated with it.
+1. If none of the above held, the file is not permitted, and upload should not
+   commence.
+
+Applying constraints means verifying that the file to be uploaded passes the
+constraints - if it would not pass, the content is rejected immediately, and none
+of the following steps are executed. This effectively implements OR-chaining
+of conditions from most specific to least specific matches.
+
+Once the app has verified that data may be uploaded, it follows the steps
+outlined in the [Data Directory](./data-directory.md#uploading) section of this
+specification.
+
+### Content Liaison Approval
+
+For storage nodes acting as liaison for a data object, the same process applies.
+An upload is rejected if a constraint is not passed, or no constraints for the
+media type could be found.
+
+## State
+
+- `FirstDataObjectTypeId`/`NextDataObjectTypeId` - handling incrementing numeric
+  `DataObjectTypeIds`.
+
+- `DataObjectTypes` - map of `DataObjectTypeId` to `DataObjectType`.
+
+## Events
+
+Each event has the affected `DataObjectTypeId` as its payload.
+
+- `DataObjectTypeRegistered` - a new `DataObjectType` was added to the registry.
+
+- `DataObjectTypeUpdated` - a `DataObjectType` was modified in the registry.
+
+## Dispatchable Methods
+
+### `register_data_object_type`
+
+#### Payload
+
+- implied root origin
+- `data_object_type`: the `DataObjectType` to write.
+
+#### Description
+
+Add a new `DataObjectType`.
+
+#### Errors
+
+- Origin isn't root.
+
+#### Side effect(s)
+
+- `NextDataObjectTypeId` incremented.
+- `DataObjectTypes` contains the new `DataObjectType` under the previous
+  value of `NextDataObjectTypeId`.
+
+#### Event(s)
+
+- `DataObjectTypeRegistered`
+
+### `update_data_object_type`
+
+#### Payload
+
+- implied root origin
+- `id`: the `DataObjectTypeId` to update.
+- `data_object_type`: the `DataObjectType` to write.
+
+#### Description
+
+Update a `DataObjectType`.
+
+#### Errors
+
+- Origin isn't root.
+- No `DataObjectType` with the given ID is currently registered.
+
+#### Side effect(s)
+
+- `DataObjectTypes` contains the updated `DataObjectType` under the given
+  ID.
+
+#### Event(s)
+
+- `DataObjectTypeUpdated`
+
+### `activate_data_object_type`
+
+#### Payload
+
+- implied root origin
+- `id`: the `DataObjectTypeId` to update.
+
+#### Description
+
+Activate a `DataObjectType`. Only active `DataObjectTypes` can be used for
+uploading new `DataObjects`.
+
+#### Errors
+
+- Origin isn't root.
+- No `DataObjectType` with the given ID is currently registered.
+
+#### Side effect(s)
+
+- The `DataObjectType` for the given ID is activated.
+
+#### Event(s)
+
+- `DataObjectTypeUpdated`
+
+### `deactivate_data_object_type`
+
+#### Payload
+
+- implied root origin
+- `id`: the `DataObjectTypeId` to update.
+
+#### Description
+
+Deactivate a `DataObjectType`. Only active `DataObjectTypes` can be used for
+uploading new `DataObjects`.
+
+#### Errors
+
+- Origin isn't root.
+- No `DataObjectType` with the given ID is currently registered.
+
+#### Side effect(s)
+
+- The `DataObjectType` for the given ID is deactivated.
+
+#### Event(s)
+
+- `DataObjectTypeUpdated`

+ 189 - 0
testnets/rome/specification/runtime/discovery-module.md

@@ -0,0 +1,189 @@
+# Discovery Module
+
+## Table of Contents
+
+- [Design](#design)
+- [Dependencies](#dependencies)
+- [Name](#name)
+- [Concepts](#concepts)
+- [State](#state)
+- [Events](#events)
+- [Dispatchable Methods](#dispatchable-methods)
+  - [set_account_info_lifetime](#set_account_info_lifetime)
+  - [set_boostrap_endpoints](#set_bootstrap_endpoints)
+  - [set_ipns_peer_id](#set_ipns_peer_id)
+  - [unset_ipns_peer_id](#unset_ipns_peer_id)
+- [Other](#other)
+
+## Design
+
+### Motivation
+
+Accessing most services requires resolving a host corresponding the public key of some service provider. This module describes the on-chain component of making this possible.
+
+### Basic Functionality
+
+The Actors module provides a way for member accounts to stake for becoming an
+actor in the Joystream network, generating a new actor key along the way. This
+actor key then is used to fulfil the role on behalf of the member.
+
+Due to this structure, discovering a service really means discovering which
+service-specific endpoint information is associated with an actor key.
+
+The *discovery workflow*, then, is to retrieve some structured data by providing
+an actor key, and being able to verify the data has been created by the actor
+with the help of a cryptographic signature.
+
+As such, the *discovery workflow* is and should be largely independent on
+how discovery is performed. The *discovery workflow* is almost entirely off-chain,
+but bootstraps from on-chain information.
+
+The *publishing workflow* does require actors to understand how discovery is
+performed. This current version uses [IPNS](https://docs.ipfs.io/guides/concepts/ipns/)
+as a building block for discovery. The *publishing workflow* includes modifying
+on-chain data, albeit relatively rarely.
+
+<!-- This document is concerned only with *on-chain* operations. For the full,
+high-level documentation see [TODO](#TODO). -->
+
+## Name
+
+`Discovery`
+
+## Dependencies
+
+None.
+
+## Concepts
+
+- `BootstrapEndpoints`: Identifies a URL prefix for querying the discovery system.
+
+- `IPNSPeerId`: Identifies a peer on the IPFS network, of which IPNS is part.
+
+## State
+
+- `bootstrapEndpoints`: A vector of bootstrap endpoints maintained by sudo.
+
+- `PeerIdByAccount`: A map of Account IDs to `IPNSPeerId`. The map value also
+  contains lifetime parameters in order to gracefully remove outdated entries.
+
+- `AccountInfoLifetime`: A time-to-live for entries in the `PeerIdByAccount` table.
+  Configured from the genesis block.
+
+## Events
+
+- `AccountInfoUpdated`: A mapping from Account ID to `IPNSPeerId` was created or
+  updated. Receiving this event allows client implementations to discard all data
+  currently cached for the associated Account ID.
+
+## Dispatchable Methods
+
+### `set_account_info_lifetime`
+
+#### Payload
+
+- implied root origin
+- `lifetime`: a new lifetime value.
+
+#### Description
+
+Allow root to set the lifetime for entries in the `PeerIdByAccount` mapping.
+Note that setting this value does not modify
+the lifetime of already existing entries, and is only applied to newly
+created or updated entries.
+
+#### Errors
+
+- Not root origin
+- Zero or negative lifetime.
+
+#### Side effect(s)
+
+`AccountInfoLifetime` is updated.
+
+#### Event(s)
+
+None.
+
+### `set_bootstrap_endpoints`
+
+#### Payload
+
+- implied root origin
+- `bootstrapEndpoints`: a vector of URL prefixes
+
+#### Description
+
+Allow root to set the current bootstrap endpoints. Note that the number of endpoints
+is never expected to grow large, as new nodes are discovered from this set of
+bootstrap endpoints - therefore always setting the entire vector is sufficient.
+
+#### Errors
+
+- Not root origin
+- Empty bootstrap endpoint vector
+- Empty vector entries
+
+#### Side effect(s)
+
+`bootstrapEndpoints` is updated.
+
+#### Event(s)
+
+None.
+
+### `set_ipns_peer_id`
+
+#### Payload
+
+- `origin`: Actor key
+- `peerId`: The IPNS peer ID.
+
+#### Description
+
+Allows actors to set their associated IPNS peer ID for discovery.
+
+#### Errors
+
+- Not actor origin
+- Empty peer ID
+
+#### Side effect(s)
+
+`peerIdByAccount` is updated to contain the actor key as the key, and the peer
+ID as the value.
+
+#### Event(s)
+
+- `AccountInfoUpdated`
+
+### `unset_ipns_peer_id`
+
+#### Payload
+
+- `origin`: Actor key
+
+#### Description
+
+Allows well-behaving actors to clear their IPNS peer ID, e.g. when shutting down.
+
+#### Errors
+
+- Not actor origin
+- No error is raised if the actor is not registered; the function simply does
+  nothing.
+
+#### Side effect(s)
+
+`peerIdByAccount` is updated to remove any value associated with the actor key.
+
+#### Event(s)
+
+- `AccountInfoUpdated`
+
+## Other
+
+<!--  bad format, underspecified , change later to use on_initlize -->
+
+Every block, outdated entries are removed and an `AccountInfoUpdated` event is
+raised for each removed entry.

+ 373 - 0
testnets/rome/specification/runtime/forum-module.md

@@ -0,0 +1,373 @@
+# Forum Module
+
+## Table Of Contents
+
+- [Design](#design)
+- [Dependencies](#dependencies)
+- [Name](#name)
+- [Concepts](#concepts)
+- [State](#state)
+- [Events](#events)
+- [Dispatchable Methods](#dispatchable-methods)
+  - [create_category](#create_category)
+  - [update_category](#update_category)
+  - [create_thread](#create_thread)
+  - [moderate_thread](#moderate_thread)
+  - [add_post](#add_post)
+  - [edit_post_text](#edit_post_text)
+  - [moderate_post](#moderate_post)
+  - [set_forum_sudo](#set_forum_sudo)
+- [Non-dispatchable Methods](#non-dispatchable-methods)
+
+## Design
+
+### Motivation
+
+This module holds the basic content and structure of a hierarchical topic based forum with trivial sudo moderation. It allows a blockchain to have _direct_ assertible custody of the forum governance and function. Systems which depend critical on reliable and fair asynchronous public discourse will benefit from this functionality.
+
+### Structure
+
+The structure of the forum is a collection of category trees. A category tree has of two types of nodes, a category or a thread. A category node represents a topic category, with a name and associated intended scope of discussion topics. A thread node represents an actual thread of one or more posts.
+
+### Posts and threads
+
+A thread is a sequence of posts, in a given category, which has some initial post from the original author, and title. A post exists in the context of a thread, and has some position in thread post sequence, as well as a body text. Both have a corresponding author and creation date. The text in a post can be edited by any time by the original author, however the history of all texts are available in the state.
+
+### Users
+
+Forum users can create threads in categories, and post to existing threads. This module does not maintain its own set of forum users, but rather depends on some external module for this. The rationale for this is to allow reuse of the module with a diversity of user management systems, without requiring that runtime developer must keep user set synchronised, or waste state space.
+
+### Forum sudo
+
+There will be a single account, called the _forum sudo_ account. This account is set by the Sudo of the runtime, and can
+
+- **Create a category**: Can either be a new root category, or if parent category is referenced, it would be a subcategory.
+
+- **Archive|Delete (Unarchive|Undelete) a category**: Results in category being marked as archived or deleted, while it and all corresponding threads, posts and subcategories remain in the state. It is however no longer possible to delete or mutate anything in the category in any way, such as adding posts, creating threads or subcategories, etc. Well-behaved UIs will not render deleted categories. In what follows a category is said to be _directly_ archived or deleted, if its applying directly to that category, and _indirectly_ if it applies to some ancestor category. The only distinction between archiving and deletion in the runtime is that a directly deleted category cannot be unarchived.
+
+- **Moderate a post in a thread**: Results in post being marked as moderated, with a corresponding rationale for the moderation added, but it remains in the system state. It is not longer possible to edit the post text, and well-behaved UIs will not render such posts.
+
+- **Moderate a thread**: Results in thread being marked as moderated, with a corresponding rationale for the moderation added, but it remains in the system state. It is not longer possible to moderate posts, edit post texts or add posts to the thread. Well-behaved UIs will not render such threads.
+
+### Limits
+
+There is a maximum depth to a category tree. This is because doing any mutation will require traversing the category tree to the root to check for whether there has been any deletion or archiving along the path to the root, and there needs to be a bound on this, herein called `MAX_CATEGORY_DEPTH`.
+
+## Name
+
+`Forum`
+
+## Dependencies
+
+- `ForumUserRegistry`: An external module which holds actual user state, allowing it to be queried based on a corresponding account, and recovering some representation of a user.
+
+## Concepts
+
+- `ForumUser`: Represents an actual forum user, which is provided by `ForumUserRegistry` dependency.
+
+- `ForumSudoId`: Identifies a forum sudo authority.
+
+- `ModerationAction`: Represents a moderation outcome applied to a post or a thread. Includes a moderation date, a text rationale and the `ForumSudoId` of moderator.
+
+- `Post`: Represents a thread post, and includes initial text, identifier for the corresponding `Thread`, a position, an optional `ModerationAction`, a vector of identifiers for `PostTextEdit` instances ordered chronologically by edit time, creation date and identifier of `ForumUser` creator. Is identified with an integer which is unique across all instances in all categories.
+
+- `PostTextEdit`: Represents a revision of the text of a `Post`, includes new text and revision date.
+
+- `Thread`: Represents a thread, and includes a title, identifier for the corresponding `Category`, a position, an optional `ModerationAction`, number of unmoderated posts, number of moderated posts, creation date and identifier of `ForumUser` creator. Is identified with an integer which is unique across all instances in all categories.
+
+- `Category`: Represents a forum category, and includes a title, short topic description text, creation date, deletion status, archival status, number of subcategories, number of unmoderated threads, number of moderated threads, optional `Category` identifier for parent category and `ForumSudoId` of creator. Is identified with an integer which is unique across all instances in all categories.
+
+## State
+
+- `categoryById`: Map `Category` identifier to corresponding instance.
+
+- `nextCategoryId`: Identifier value to be used for the next `Category` created.
+
+- `threadById`: Map `Thread` identifier to corresponding instance.
+
+- `nextThreadId`: Identifier value to be used for next `Thread` in `threadById`
+
+- `postById`: Map `Post` identifier to corresponding instance.
+
+- `nextPostId`: Identifier value to be used for for next `Post` created.
+
+- `forumSudo`: Optional `ForumSudoId` of forum sudo.
+
+## Events
+
+Each event has payload as sublist
+
+- `CategoryCreated`: A category was introduced
+  - category identifier
+
+- `CategoryUpdated`: A category had its direct archival and/or deletion status updated to a new value.
+  - category identifier
+  - whether deletion status was changed, if so to what
+  - whether archival status was changed, if so to what
+
+- `ThreadCreated`: A thread was created with.
+  - thread identifier
+
+- `ThreadModerated`: A thread was moderated.
+  - thread identifier
+
+- `PostAdded`: A post was introduced.
+  - post identifier
+
+- `PostModerated`: A post was moderated.
+  - post identifier
+
+- `PostTextUpdated`: A post had the post text edited.
+  - post identifier
+  - edit number of new text
+
+- `ForumSudoSet`: A new forum sudo was set by root.
+  - optional account for old forum sudo
+  - optional account of new forum sudo
+
+## Dispatchable Methods
+
+### `create_category`
+
+#### Payload
+
+- `origin`: call origin
+- `parent`: not set, or category identifier of parent
+- `title`: text title
+- `description`: description text
+
+#### Description
+
+Add a new category.
+
+#### Errors
+
+- Bad signature
+- `forumSudo` does not match signature
+- `parent` is set, but does not exist
+- `parent` is set, but is (directly or indirectly) archived or deleted category
+- category depth exceeded, see `MAX_CATEGORY_DEPTH`.
+- `title` invalid
+- `description` invalid
+
+#### Side effect(s)
+
+- `categoryById` extended with new `Category` under old value of `nextCategoryId` as identifier
+- `nextCategoryId` incremented
+- if `parent` is not root, then subcategory count
+
+#### Event(s)
+
+- `CategoryCreated`
+
+### `update_category`
+
+#### Payload
+
+- `origin`: call origin
+- `categoryId`: id of category to update
+- `archive`: whether to archive
+- `deleted`: whether it is deleted
+
+#### Description
+
+Update a category.
+
+#### Errors
+
+- Bad signature
+- `forumSudo` does not match signature
+- `categoryId` does not match any category
+- category with `categoryId` is directly deleted, cannot be unarchived
+- category with `categoryId` is indirectly archived or deleted, cannot be updated in any way
+
+_Note: We don't mind directly archived/deleted categories from being re-archived/deleted respectively, we just ignore_
+
+#### Side effect(s)
+
+- category in `categoryById` under key `categoryId` has archival and deletion status equal to `archive` and `delete`, respectively, and if parent is set, then it will have number of subcategories decremented if `delete` is true, but category was perviously not.
+
+#### Event(s)
+
+- `CategoryUpdated` with new status values, as they apply
+
+### `create_thread`
+
+#### Payload
+
+- `origin`: call origin
+- `categoryId`: identifier of category where thread should be created
+- `title`: thread title text
+- `text`: text of initial post
+
+#### Description
+
+Create new thread in category.
+
+#### Errors
+
+- Bad signature
+- Signer is not forum user
+- `categoryId` not a valid category
+- `categoryId` is (directly or indirectly) archived
+- `categoryId` is (directly or indirectly) deleted
+- `title` not valid
+- `text` not valid
+
+#### Side effect(s)
+
+- `threadById` extended with new `Thread` instance under old value of `nextThreadId` as identifier
+- increment unmoderated thread count of category with identifier `categoryId`
+- `nextThreadId` incremented
+
+#### Event(s)
+
+- `ThreadCreated`
+
+### `moderate_thread`
+
+#### Payload
+
+- `origin`: call origin
+- `threadId`: identifier of `Thread` to delete
+- `rationale`:  text rationale
+
+#### Description
+
+Moderate thread.
+
+#### Errors
+
+- Bad signature
+- `forumSudo` does not match signature
+- `threadId` does not match any thread
+- `rationale` invalid
+- thread already moderated
+- thread in (directly or indirectly) archived category
+- thread in (directly or indirectly) deleted category
+
+#### Side effect(s)
+
+- corresponding `Thread` instance in `threadById` has `ModerationAction` set
+- update moderated and unmoderated thread count of corresponding category
+
+#### Event(s)
+
+- `ThreadModerated`
+
+### `add_post`
+
+#### Payload
+
+- `origin`: call origin
+- `threadId`: thread in which to add post
+- `text`: text of post
+
+#### Description
+
+Adding post to thread
+
+#### Errors
+
+- Bad signature
+- Signer is not forum user
+- thread with identifier value `threadId` does not exist
+- thread with identifier value `threadId` is moderated
+- category of thread is (directly or indirectly) archived
+- category is (directly or indirectly) deleted
+
+#### Side effect(s)
+
+- `postById` extended with new `Post` instance with under old value of `nextPostId` as identifier
+- `nextPostId` updated
+- unmoderated posts updated in corresponding thread
+
+#### Event(s)
+
+- `PostAdded`
+
+### `edit_post_text`
+
+#### Payload
+
+- `origin`: call origin
+- `postId`: post to be edited
+- `new_text`: new text
+
+#### Description
+
+Edit post text
+
+#### Errors
+
+- Bad signature
+- `postId` does not correspond to a post
+- Signer does not match creator of post with identifier `postId`
+- post with identifier `postId` is moderated
+- category is (directly or indirectly) archived
+- category is (directly or indirectly) deleted
+
+#### Side effect(s)
+
+- Post with identifier `postId` has its edit vector with new `PostTextEdit` instance at front
+
+#### Event(s)
+
+- `PostTextUpdated`
+
+### `moderate_post`
+
+#### Payload
+
+- `origin`: call origin
+- `postId`: post to be edited
+- `rationale`:  text rationale
+
+#### Description
+
+Moderate post
+
+#### Errors
+
+- Bad signature
+- `forumSudo` does not match signature
+- `postId` does not match any post
+- `rationale` invalid
+- post already moderated
+- thread already moderated
+- thread in (directly or indirectly) archived category
+- thread in (directly or indirectly) deleted category
+
+#### Side effect(s)
+
+- corresponding `Post` instance in `postById` has `ModerationAction` set
+- update moderated and unmoderated post count of corresponding thread
+
+#### Event(s)
+
+- `PostModerated`
+
+### `set_forum_sudo`
+
+Note: I am not sure how to do this one, I am not familiar with Substrate **Sudo** functionality.
+
+#### Payload
+
+- `newForumSudo`: optional account of new proposed forum sudo
+
+#### Description
+
+Set forum sudo.
+
+#### Errors
+
+- Bad signature
+- Not root origin
+
+#### Side effect(s)
+
+- `forumSudo` equals `newForumSudo`
+
+#### Event(s)
+
+- `ForumSudoSet`

+ 377 - 0
testnets/rome/specification/runtime/hiring-module.md

@@ -0,0 +1,377 @@
+# Hiring Module
+
+## Table of Contents
+
+- [Name](#name)
+- [Design](#design)
+- [Dependencies](#dependencies)
+- [Concepts](#concepts)
+- [State](#state)
+- [Events](#events)
+- [Dispatchables](#dispatchables)
+- [Non-dispatchables](#non-dispatchables)
+  - [add_opening](#add_opening)
+  - [begin_accepting_applications](#begin_accepting_applications)
+  - [fill_opening](#fill_opening)
+  - [cancel_opening](#cancel_opening)
+  - [remove_opening](#remove_opening)
+  - [add_application](#add_application)
+  - [deactivate_application](#deactivate_application)
+  - [remove_application](#remove_application)
+  - [unstaked](#unstaked)
+  - [on_finalize](#on_finalize)
+
+## Name
+
+`Hiring`
+
+## Motivation
+
+A variety of roles will require a set of candidates to express, possibly staked, interest in joining, to some authority, whom is then tasked with selecting a subset of winners to enter the given role. While this sort of interaction could be facilitated off chain, the benefit of avoiding possibly faulty authority nodes and a more robust evidentiary trail. It also makes it easier to organise more complex hiring business logic, such as the outcome of some group decision making process where stakeholders may act asynchronously.
+
+## Design
+
+There are two core concepts in this module, _an opening_ and an _application_.
+
+An opening represents the process of hiring one or more new actors into some available role, and the core states and state transitions are sketched in the following figure.
+
+![hiring_opening](https://user-images.githubusercontent.com/437292/63675509-4296d780-c7e9-11e9-8512-05b71a8646b9.png)
+
+An application represents the bid of some actor to occupy the position opened for in a given opening , and the core states and state transitions are sketched in the following figure.
+
+![hiring_application](https://user-images.githubusercontent.com/437292/63675514-46c2f500-c7e9-11e9-9c46-7a89e6a2645f.png)
+
+As part of hiring, applicants may have to stake both for the application and for the role. This module presumes that some external module exists to manage staking, slashing and unstaking. It expects to be able to invoke these actions, and also to be notified about the completion of unstaking actions which have h had an unstaking period.
+
+## Usage
+
+In many contexts, stepping into a role may have extraneous informational or economic constraints that are not embodied in this module. In this case those should be modelled on top of this module.
+
+## Dependencies
+
+- `Staking`: The staking module
+
+## Concepts
+
+```Rust
+
+trait Trait {
+ type ApplicationId: .. ,
+ type OpeningId: ...,
+
+ /* OnApplicationDeactivated(id, cause) : ... */
+
+}
+
+// Possible causes
+enum ApplicationDeactivationCause {
+  External, // Add ID here for simplicity?
+  Hired,
+  NotHired
+  CrowdedOut,
+  OpeningCancelled,
+  ReviewPeriodExpired,
+  OpeningFilled,
+}
+
+// Possible status of an application
+enum ApplicationStage {
+
+  // Normal active state
+  Active,
+
+  //  Waiting for one or more unstakings, with a non-zero unstaking period, to complete.
+  Unstaking {
+
+    // When deactivation was initiated.
+    deactivation_initiated: BlockNumber,
+
+    // The cause of the deactivation.
+    cause: ApplicationDeactivationCause
+  },
+
+  // No longer active, can't do anything fun now.
+  Inactive {
+
+    // When deactivation was initiated.
+    deactivation_initiated: BlockNumber,
+
+    // When deactivation was completed, and the inactive state was established.
+    deactivated: BlockNumber,
+
+    // The cause of the deactivation.
+    cause: ApplicationDeactivationCause
+
+  }
+}
+
+// An application for an actor to occupy an opening.
+struct Application {
+
+  // Application identifier
+  id: ApplicationId,
+
+  // Identifier for opening for which this application is for.
+  opening_id: OpeningId,
+
+  // Index of arrival across all applications for given opening.
+  applicant_arrival_index: u32,
+
+  // Block at which this application was submitted.
+  activated: BlockNumber,
+
+  // Identifier for stake that may possibly be established for role
+  // May possibly no longer be valid, depending on stage.
+  initial_role_staking_id: Option<StakeId>,
+
+  // Identifier for stake that may possibly be established for application
+  // May possibly no longer be valid, depending on stage.
+  initial_application_staking_id: Option<StakeId>,
+
+  // Status of this application
+  stage: ApplicationStage,
+
+ //
+ human_readable_text: Vec<u8>
+}
+
+// How one may automatically rank different outstanding applications. This is require to enforce a limited application pool.
+enum ApplicationRankingPolicy {
+
+  // Rank on arrival time of application
+  RankOnArrivalTime {
+
+    // Wether to rank arrival times in ascending or descending order
+    ascending: bool
+  },
+
+  // Rank on staked quantity in application
+  RankOnApplicationStake {
+
+    // Whether to do secondary tie break on the quantity staked for the role itself
+    tie_break_on_role_stake: bool,
+  },
+
+  // Rank on staked quantity for role itself
+  RankOnRoleStake {
+
+    // Whether to do secondary tie break on the quantity staked in for the application itself
+    tie_break_on_application_stake: bool
+  }
+}
+
+// How to limit the number of eligible applicants
+struct ApplicationRationingPolicy {
+
+  // The maximum number of applications that can be on the list at any time.
+  max_active_applicants: u32,
+
+  // How applicants will be ranked, in order to respect the maximum simultaneous application limit
+  applicant_ranking: ApplicationRankingPolicy
+
+}
+
+enum OpeningDeactivationCause {
+  CancelledBeforeActivation,
+  CancelledAcceptingApplications,
+  CancelledInReviewPeriod,
+  ReviewPeriodExpired,
+  Filled
+}
+
+enum ActiveOpeningStage {
+
+    AcceptingApplications {
+
+      //
+      started_accepting_applicants_at_block: BlockNumber,
+    }
+
+    //
+    ReviewPeriod {
+
+      started_accepting_applicants_at_block: BlockNumber,
+
+      started_review_period_at_block: BlockNumber,
+    }
+
+    //
+    Deactivated {
+
+      cause: OpeningDeactivationCause,
+
+      deactivated_at_block: BlockNumber,
+
+      started_accepting_applicants_at_block: BlockNumber,
+
+      started_review_period_at_block: BlockNumber,
+
+    }
+}
+
+// The stage at which an `Opening` may be at.
+enum OpeningStage {
+
+  // ..
+  WaitingToBegin {
+    begins_at_block: BlockNumber
+  },
+
+  // ..
+  Active {
+
+    // Active stage
+    stage: ActiveOpeningStage,
+
+    // Identifiers for all applications which have been added, but not removed, for this opening.
+    applicants: Vec<ApplicationId>,
+
+    // Identifiers for applications that are still Began. These are the only applications which
+    // still in the running for getting hired while the opening is still no deactivated.-.
+    active_applicants: Vec<ApplicationId>,
+
+    // Counters over all possible application states. Are very useful
+    // for light clients.
+    // NB: The sum of all of the below is the total number of applications ever activated.
+    active_application_count: u64,
+    unstaking_application_count: u64,
+    deactivated_application_count: u64, // for any reason.
+    removed_application_count: u64
+  }
+
+}
+
+// Constraints around staking amount
+enum StakingAmountMode {
+  AtLeast,
+  Exact
+}
+
+type UnstakingPeriodLength: Option<BlockNumber>;
+
+// Policy for staking
+struct StakingPolicy {
+
+  // Staking amount
+  amount: Balance,
+
+  // How to interpret the amount requirement
+  amount_mode: StakingAmountLimitMode,
+
+  // The unstaking period length, if any, deactivation causes that are autonomous,
+  // that is they are triggered internally to this module.
+  crowded_out_unstaking_period_length: UnstakingPeriodLength,
+  review_period_expired_unstaking_period_length: UnstakingPeriodLength,
+}
+
+// An opening
+struct Opening {
+
+  // Identifier for opening
+  id: OpeningId,
+
+  // Block at which opening was added
+  created: BlockNumber,
+
+  // Current stage for this opening
+  stage: OpeningStage,
+
+  // Maximum length of the review stage.
+  max_review_period_length: BlockNumber,
+
+  // Whether, and if so how, to limit the number of active applicants....
+  application_rationing_policy: Option<ApplicationRationingPolicy>,
+
+  // Whether any staking is required just to apply, and if so, how that stake is managed.
+  application_staking_policy: Option<StakingPolicy>,
+
+  // Whether any staking is required for the role, and if so, how that stake is managed.
+  role_staking_policy: Option<StakingPolicy>,
+
+  // Description of opening
+  human_readable_text: Vec<u8>
+}
+
+enum StakeType {
+  Role,
+  Application
+}
+
+// The purpose of some staked funds.
+struct StakePurpose {
+
+  // Application to which the stake corresponds
+  application_id: ApplicationId,
+
+  // Type of
+  type: StakeType
+}
+
+```
+
+## State
+
+- `openingsById: Map OpeningID => Opening`: Openings.
+- `nextOpeningId: OpeningId`: Identifier for next opening to be added.
+- `applicationsById: Map ApplicationId => Application`: Applications.
+- `nextApplicationId: ApplicationId`: Identifier for next application to be added.
+- `stakePurposeByStakingId: StakeId => StakePurpose`: Internal purpose of given stake, i.e. fro what application, and whether for the role or for the application.
+
+## Events
+
+**None**
+
+## Dispatchables
+
+**None**
+
+## Non-dispatchables
+
+**Explain how an application is deactivated... callback `OnApplicationDeactivated`, synch vs. async.**
+
+### `add_opening`
+
+Add new opening based on given inputs policies. The new `Opening` instance has stage `WaitingToBegin`, and is added to `openingsById`, and has identifier equal to `nextOpeningId`. The latter is incremented. The used identifier is returned.
+
+### `begin_accepting_applications`
+
+Applies when given opening is in `WaitingToBegin` stage. The stage is updated to `Began.AcceptingApplications` stage. Returns nothing.
+
+### `fill_opening`
+
+Applies when given opening is in `ReviewPeriod` stage. Given list of applications are deactivated to under the `Hired`, all other active applicants are `NotHired`. Separately for each group, unstaking periods for any applicable application and/or role stake must be provided. Returns nothing.
+
+### `begin_review`
+
+Applies when given opening is in `Began.AcceptingApplications` stage. The stage is updated to `Began.ReviewPeriod`. Returns nothing.
+
+### `cancel_opening`
+
+Applies when given opening is in stage `Began.AcceptingApplications` or `Began.ReviewPeriod`. The stage is updated to `Began.Inactive` in all cases. There may be active applications, in which case deactivation of all is initiated. The number of applications which had deactivation initiated is returned.
+
+### `remove_opening`
+
+Applies when a given opening is in stage `WaitingToBegin` or `Began.Inactive`. In the latter it is also required that all applications are inactive. Opening, and all associated applications, are removed from `openingsById` and `applicationsById`. The number of applications removed is returned.
+
+### `add_application`
+
+Applies when a given opening is in stage `Began.AcceptingApplications`. If the `application_rationing_policy` of the opening has a restrictive policy, and space has ran out, then possibly deactivate another application which may get bumped out. If the new application does not make it, then return `false`. Otherwise move forward. Create a new `Application` instance, with identifier value equal to `nextApplicationId`, which is incremented, and adds to `applicationsById`. Do possible initial staking based on policy of opening and given inputs, and update `stakePurposeByStakingId` correspondingly.
+
+### `deactivate_application`
+
+Applies when a given opening is in stage `Began.AcceptingApplications`, and the application is in stage `Active`. If the application has any staking associated with it, unstaking is initiated, and the application is set to stage `Unstaking`.  Otherwise, the stage is set directly to `Inactive`. The `cause` is set to `External`. An invocation of `OnApplicationDeactivated` is lastly made. Returns the resulting stage of the application during the call.
+
+### `remove_application`
+
+Applies when an application is in the stage `Inactive`. Results in removing instance from `applicationsById`, and from the corresponding opening. Returns nothing.
+
+### `unstaked`
+
+Checks whether staking identifier is key in `stakePurposeByStakingId`, if so, removes mapping and recovers the relevant application and triggers `unstaked` on it. Returns a boolean indicating whether there was a match.
+
+### `on_finalize`
+
+Manages the following autonomous events across all applications:
+
+- beginning to accept applications
+- review period expiry

+ 202 - 0
testnets/rome/specification/runtime/membership-module.md

@@ -0,0 +1,202 @@
+# Membership Module
+
+## Table Of Contents
+
+- [Design](#design)
+- [Dependencies](#dependencies)
+- [Name](#name)
+- [Concepts](#concepts)
+- [State](#state)
+- [Events](#events)
+- [Dispatchable Methods](#dispatchable-methods)
+  - [buy_membership](#`buy_membership`)
+  - [change_member_about_text](#`change_member_about_text`)
+  - [change_member_avatar](#`change_member_avatar`)
+  - [change_member_handle](#`change_member_handle`)
+  - [update_profile](#`update_profile`)
+  - [add_screened_member](#`add_screened_member`)
+  - [set_screening_authority](#`set_screening_authority`)
+- [Non-dispatchable Methods](#non-dispatchable-methods)
+  - [is_active_member](#`is_active_member`)
+  - [primary_account_by_member_id](#`primary_account_by_member_id`)
+  - [can_register_role_on_member](#`can_register_role_on_member`)
+  - [can_unregister_role_on_member](#`can_unregister_role_on_member`)
+  - [register_role_on_member](#`register_role_on_member`)
+  - [unregister_role_on_member](#`unregister_role_on_member`)  
+
+## Name
+
+`Membership`
+
+## Motivation
+
+The membership module is supposed to represent the current set of members on the platform, and manage corresponding rich profiles and what roles they occupy, as well as the entry of new members.
+
+## Design
+
+### Membership
+
+Each membership corresponds to an account, but not all accounts will have a membership. An account may only be associated with a single membership. A membership has a corresponding rich profile, including practical user facing information, such as a unique immutable handle, an avatar URI and a short descriptive text.
+
+A membership can also be active or inactive.
+
+### Terms
+
+A membership is established in the context of membership terms, which describe the price of admission and human readable conditions being agreed to. At any given time there may be a range of different terms available, and the set may change over time.
+
+### Adding members
+
+A member can be introduced in two ways. Either way depends on the module either being open or closed for accepting new members.
+
+The first way is for a member to simply pay for getting entry, with a reference to a given set of terms. In this case, the membership will have a reference to the terms under which it was established.
+
+The second is for a screening authority, which is a designated account, to simply add the new member. In this case, the membership will have a reference to the screening authority.
+
+## Usage
+
+Used as a simple membership and role registry.
+
+## Dependencies
+
+- `Currency`
+
+## Concepts
+
+```Rust
+
+trait Trait {
+  type RoleActorId : INTEGER_TRAIT_CONSTRAINTS;
+  type MemberId : INTEGER_TRAIT_CONSTRAINTS;
+  type PaidTermId :  INTEGER_TRAIT_CONSTRAINTS;
+  type SubscriptionId : INTEGER_TRAIT_CONSTRAINTS;
+}
+
+pub struct PaidMembershipTerms<T: Trait> {
+    /// Quantity of native tokens which must be provably burned
+    pub fee: BalanceOf<T>,
+    /// String of capped length describing human readable conditions which are being agreed upon
+    pub text: Vec<u8>,
+}
+
+enum RoleType {
+ ForumUser,
+ Curator,
+ CurationLead,
+...
+}
+
+struct ActorInRole {
+ role_type: RoleType,
+ role_id: RoleActorId
+}
+
+struct Profile<T: Trait> {
+
+   pub handle: Vec<u8>,
+   pub avatar_uri: Vec<u8>,
+   pub about: Vec<u8>,
+   pub registered_at_block: T::BlockNumber,
+   pub registered_at_time: T::Moment,
+   pub entry: EntryMethod<T>,
+   pub suspended: bool,
+   pub subscription: Option<T::SubscriptionId>,
+   pub controller_account: T::AccountId,
+   pub roles: BTreeMap<RoleId, Vec<ActorId>>,
+
+}
+
+pub struct UserInfo {
+    pub handle: Option<Vec<u8>>,
+    pub avatar_uri: Option<Vec<u8>>,
+    pub about: Option<Vec<u8>>,
+}
+```
+
+## State
+
+- `membershipByActorInRole: Map ActorInRole => MemberId`
+
+- `NextMemberId`: Unique identifier for next member, should equal total number of members ever created.
+
+- `AccountIdByMemberId`: Maps member id to an account id.
+
+- `MemberIdByAccountId`: Maps account id to optional member id.
+
+- `MemberProfile`: Maps member id to `Profile` of member.
+
+- `Handles`: Maps handle to corresponding member id.
+
+- `NextPaidMembershipTermsId`: Next paid membership terms id.
+
+- `PaidMembershipTermsById`: Maps paid terms id to actual terms.
+
+- `ActivePaidMembershipTerms`: Set of active paid term ids.
+
+- `NewMembershipsAllowed`: Whether new memberships can currently be established.
+
+- `ScreeningAuthority`: Optional account of screener.
+
+- `MinHandleLength`, `MaxHandleLength`, `MaxAvatarUriLength`, `MaxAboutTextLength`: Mutable constraint variables
+
+## Events
+
+- `MemberRegistered`: A member was registered with a given id and account.
+- `MemberUpdatedAboutText`: A member, with given id, had text updated.
+- `MemberUpdatedAvatar`: A member, with given id, had avatar URI updated.
+- `MemberUpdatedHandle`: A member, with given id, had handle updated.
+
+## Dispatchable Methods
+
+### `buy_membership`
+
+Establish new membership through payment.
+
+### `change_member_about_text`
+
+Change about text on membership.
+
+### `change_member_avatar`
+
+Change the avatar of a member.
+
+### `change_member_handle`
+
+Change the unqiue handle of a member.
+
+### `update_profile`
+
+Update the profile of a member.
+
+### `add_screened_member`
+
+Introduce a new screend member.
+
+### `set_screening_authority`
+
+Update the screening authority.
+
+## Non-dispatchable Methods
+
+### `is_active_member`
+
+Check whether member is active.
+
+### `primary_account_by_member_id`
+
+Returns primary account of given member by id.
+
+### `can_register_role_on_member`
+
+Whether a given member can step into a given role at this time.
+
+### `can_unregister_role_on_member`
+
+Whether a given member can step out of a role at this time.
+
+### `register_role_on_member`
+
+Register a member in a role.
+
+### `unregister_role_on_member`
+
+Unregister a member in a role.

+ 86 - 0
testnets/rome/specification/runtime/migration-module.md

@@ -0,0 +1,86 @@
+# Migration Module
+
+## Table of Contents
+
+- [Design](#design)
+- [Name](#name)
+- [Dependencies](#dependencies)
+- [Concepts](#concepts)
+- [State](#state)
+- [Events](#events)
+- [on_initialize](#on_initialize)
+
+## Design
+
+### Motivation
+
+The purpose of this module is to run migration routines required in the upgrading from Athens to Acropolis network through an on-chain runtime upgrade.
+
+### Function
+
+Routines run when the `on_initialize` dispatchable is invoked. In the upgrade to Acropolis there are no other modules which have similar code in their own handler of this callback which depend on the migration having being executed. This means it is immaterial in what order the migration module is invoked, however in the future care must be made to guarantee the migration module running first.
+
+## Name
+
+`Migration`
+
+## Dependencies
+
+ - [Data Directory Module](./substrate-runtime/data-directory-module.md)
+
+ - [Data Object Storage Registry Module](./substrate-runtime/data-object-storage-registry.md)
+
+ - [Sudo SRML Module](#)
+
+## Concepts
+
+Standard.
+
+## State
+
+- `SpecVersion`: If set, what runtime spec version the store was initialised, otherwise not set.
+
+## Events
+
+- `Migrated`: Runtime migration code was executed
+  - block number of migration
+  - spec version
+
+## on_initialize
+
+### Description
+
+Pre-block execution code which actually performs migration and initialization under suitable conditions, namely
+
+- `SpecVersion` is not set, which would possibly be the case from genesis
+- `SpecVersion` is less than the runtime [spec version](../README.md#runtime-version)
+
+### Event(s)
+
+- `Migrated`
+
+### Side effect(s)
+
+- `SpecVersion` is set to [spec version](../README.md#runtime-version).
+
+#### Forum
+
+- `NextCategoryId` is `1`
+- `NextThreadId` is `1`
+- `NextPostId` is `1`
+- `ForumSudo` is `key` in `Sudo` module.
+- `CategoryTitleConstraint` is `(10,90)`
+- `CategoryDescriptionConstraint` is `(10, 490)`
+- `ThreadTitleConstraint` is `(10, 90)`
+- `PostTextConstraint` is `(10, 990)`
+- `ThreadModerationRationaleConstraint` is `(10, 290)`
+- `PostModerationRationaleConstraint` is `(10, 290)`
+
+#### DataDirectory
+
+- `PrimaryLiaisonAccountId` is gone.
+- `KnownContentIds`, `DataObjectByContentId` and `MetadataByContentId` are all empty.
+
+#### DataObjectStorageRegirstry
+
+- `RelationshipsByContentId` is empty.

+ 150 - 0
testnets/rome/specification/runtime/recurring-reward-module.md

@@ -0,0 +1,150 @@
+# Recurring Reward Module
+
+## Table of Contents
+
+- [Name](#name)
+- [Design](#design)
+- [Dependencies](#dependencies)
+- [Concepts](#concepts)
+- [State](#state)
+- [Events](#events)
+- [Dispatchable Methods](#dispatchable-methods)
+- [Non-dispatchable Methods](#non-dispatchable-methods)
+  - [add_recipient](#add_recipient)
+  - [remove_recipient](#remove_recipient)
+  - [add_reward_relationship](#add_reward_relationship)
+  - [remove_reward_relationship](#remove_reward_relationship)
+  - [set_reward_relationship](#set_reward_relationship)
+  - [on_finalize](#on_finalize)
+
+## Name
+
+`RecurringReward`
+
+## Design
+
+### Motivation
+
+Some actors will be in a long term role with a corresponding pre-determined recurring reward amounts and payout frequencies, being financed out of resource budget with a purpose encompassing their role.
+
+### Design
+
+A _recipient_ designates an actor who can receive rewards through being registered as a recipient, through a _receives from source_ relationship, from a _reward source_. Each relationship has its own amount and payout frequency, which can be in a repeated or one-off payment mode.
+
+### Usage
+
+The module is meant to be a low level component of a fuller reward environment, where more context specific modules will layer contextual permissions and requirements on top of this module. This is why there are no dispatchables or events.
+
+## Dependencies
+
+- `TokenMint` module
+
+## Concepts
+
+```Rust
+
+trait Trait : TokenMint::Trait + System::Trait {
+
+ // Type of identifier for recipients.
+ type RecipientId: INTEGER_TRAIT_CONSTRAINTS,
+
+ // Type for identifier for relationship representing that a recipient recieves recurring reward from a token mint.
+ type RewardRelationshipId: INTEGER_TRAIT_CONSTRAINTS,
+
+ // Handle for aftermath of a payout attempt
+ type PayoutStatusHandler: fn(Self::RewardRelationshipId: id, bool: status, destination_account: T::AccountId, amount: T::Balance);
+}
+
+// A recipient of recurring rewards
+struct Recipient<T: Trait> {
+
+  /// stats
+
+  // Total payout received by this recipient
+  total_reward_received: T::Balance,
+
+  // Total payout missed for this recipient
+  total_reward_missed: T::Balance
+}
+
+struct RewardRelationship<T: Trait> {
+
+  // Identifier for receiver
+  recipient: T::RecipientId,
+
+  // Identifier for reward source
+  mint_id: T::TokenMintId,
+
+  // Destination account for reward
+  account: T::AccountId,
+
+  // Paid out for
+  amount_per_payout: T::Balance,
+
+  // When set, identifies block when next payout should be processed,
+  // otherwise there is no pending payout
+  next_payment_in_block: Option<T::Blocknumber>,
+
+  // When set, will be the basis for automatically setting next payment,
+  // otherwise any upcoming payout will be a one off.
+  payout_interval: Option<T::Blocknumber>,
+
+  /// stats
+
+  // Total payout received in this relationship
+  total_reward_received: T::Balance,
+
+  // Total payout failed in this relationship
+  total_reward_missed: T::Balance
+}
+
+```
+
+## State
+
+- `recipients: map T::RecipientId => Recipient<T>`
+- `nextRecipientId: T::RecipientId`
+- `rewardRelationships: map T::RewardRelationshipId => RewardRelationship<T>`
+- `nextRewardRelationshipId: T::RewardRelationshipId`
+
+## Events
+
+**None**
+
+## Dispatchables
+
+**None**
+
+## Non-dispatchable Methods
+
+### `add_recipient`
+
+Adds a new `Recipient` recipient to `recipients`, with identifier equal to `nextRecipientId`, which is also incremented, and returns the new recipient identifier.
+
+### `remove_recipient`
+
+Removes a mapping from `reward_recipients` based on the given identifier.
+
+### `add_reward_relationship`
+
+Adds a new `RewardRelationship` to `rewardRelationships`, for a given source, recipient, account, etc., with identifier equal to current `nextRewardRelationshipId`. Also increments `nextRewardRelationshipId`.
+
+### `remove_reward_relationship`
+
+Removes a mapping from `depenencies` based on given identifier.
+
+### `set_reward_relationship`
+
+For `RecievesFromSource` found in `rewardRelationships` with given identifier, new valus for the following can be set
+
+- `account`
+- `amount_per_payout`
+- `next_payment_in_block`
+- `payout_interval`
+
+### `on_finalize`
+
+For all `RecievesFromSource` found in `rewardRelationships` where `next_payment_in_block` is set and matches current block height, a call to `pay_reward` is made for the suitable amount, recipient and source. The `next_payment_in_block` is updated based on `payout_interval`.
+
+If the call succeeds, `total_reward_received` is incremented on both
+recipient and dependency with `amount_per_payout`, and a call to `T::PayoutStatusHandler` is made. Otherwise, analogous steps for failure.

+ 259 - 0
testnets/rome/specification/runtime/staking-module.md

@@ -0,0 +1,259 @@
+#  Staking Module
+
+## Table of Contents
+
+- [Name](#name)
+- [Design](#design)
+- [Dependencies](#dependencies)
+- [Concepts](#concepts)
+- [State](#state)
+- [Events](#events)
+- [Dispatchable Methods](#dispatchable-methods)
+- [Non-dispatchable Methods](#non-dispatchable-methods)
+  - [add_stake](#add_stake)
+  - [remove_stake](#remove_stake)
+  - [stake](#stake)
+  - [increase_stake](#increase_stake)
+  - [decrease_stake](#decrease_stake)
+  - [initiate_slashing](#inititae_slashing)
+  - [pause_slashing](#pause_slashing)
+  - [continue_slashing](#continue_slashing)
+  - [cancel_slashing](#cancel_slashing)
+  - [initiate_unstaking](#initiate_unstaking)
+  - [pause_unstaking](#pause_unstaking)
+  - [continue_unstaking](#continue_unstaking)
+  - [on_finalize](#on_finalize)
+
+## Name
+
+`Stake`
+
+## Design
+
+### Motivation
+
+A module representing the lifecycle of the staking of platform actors. The module is meant to be a low level component of a fuller staking environment, where more context specific modules will layer contextual permissions and requirements on top of this module.
+
+### Implementation
+
+As a consequence of having no dispatchables, there are also no events, client code must layer this on top, possibly decorated with more context. There are autonomous state transitions, triggered at block arrival, and they are signalled through callbacks from corresponding client calls, and module level callback handlers, in this order.
+
+### Notation
+
+This document uses UML state machines, which are hierarchical state machines that have extended state. The event arrows have decorations `([condition])/name(/action)`, where the guts of the `()` are optional, and `condition` evaluates to a boolean and has no side effects on extended state, and the `action` may have side effects.
+
+### Design
+
+This module is organized around the idea of a _stake_, which refers to some amount of funds staked, or bonded, by some actor, for some specific purpose, under some constraints surrounding unstaking. This module only attempts to model the a common lifecycle found in the staking and slashing actions of a broad range of stakes on the platform.
+
+#### Staking
+
+The following image summarizes the key states and transitions concerning the staking.
+
+![role_staking](https://user-images.githubusercontent.com/437292/62929919-b4b7f700-bdbb-11e9-8161-4ab37ede8084.png)
+
+All currently staked funds live in a module owned staking account. No one can sign for spending from this account.
+
+In the staked state, a stake holds a claim on a given amount of value in this account. This claim can rise and fall over time with slashings and explicit changes to the staking amount. The value can only be unlocked by successfully unstaking, and allowing a given amount of time to transpire in the active state.
+
+A key design choice is to allow possibly multiple simultaneous slashing processes to take place simultaneously on the same role. The following  calls are used:
+
+- `begin_new_slashing_stm()`: Create new slashing related state.
+- `slashing_count()`: The number of currently ongoing slashings (see next section), hence no side effects.
+- `unstaking_clock()`: The total number of blocks arrived in the active state, hence no side effects.
+
+The rationale for the paused unstaking state is to allow a possibly extended slashing ordeal to take place during unstaking, without necessarily having to race against the unstaking timer.
+
+#### Slashing
+
+The following image summarizes the key states and transitions concerning a the life cycle of a single slashing.
+
+![role_slashing](https://user-images.githubusercontent.com/437292/62929935-bbdf0500-bdbb-11e9-834e-d60c789bbf53.png)
+
+The `begin_new_slashing_stm` call above involves creating a new instance of such a state machine.
+
+The key design decision here is to not instantly slash, but to have a pausable countdown period. The pausing is ideal for allowing higher order processes around contesting a slashing action, possibly multiple times through a governance hierarchy for example.
+
+A slashing itself involves reducing the claim of a stake on the staking fund. Multiple slashing attempts in flight will possibly compete, in that one brings the claim below the target slashing amount of the other, in which case the maximum feasible amount is slashed.
+
+### Usage
+
+Since all methods are non-dispatchable, the module should be used by one or more use case specific modules that introduce their own set of relevant permissions and extrinsics, as well as interleave the staking and slashing activities with any other use case specific requirements.
+
+## Dependencies
+
+- `Currency`: SRML module
+
+## Concepts
+
+```Rust
+
+// CAN WE REPLACE THIS WITH THE WAY ONE CAN PASS VALUES TO
+// MODULE TRAIT CONFIGURATION?
+static const MODULE_STAKING_FUND_ACCOUNT_ID = 189;
+
+trait StakingEventSink {
+
+  // Type of handler which handles unstaking event.
+  OnUnstakeHandler: fn(id: T::StakeId),
+
+  // Type of handler which handles slashing event.
+  // NB: actually_slashed can be less than amount of the slash itself if the
+  // claim amount on the stake cannot cover it fully.
+  OnSlashedHandler: fn(id: T::StakeId, slash_id: T::SlashId, actually_slashed: T::Balance)
+
+}
+
+trait Trait : StakingEventSink + Currency::Trait {
+
+  // Type of identifier for stake
+  type StakeId: INTEGER_TRAIT_CONSTRAINTS,
+
+  // Type of identifier for a slashing
+  type SlashId: INTEGER_TRAIT_CONSTRAINTS
+}
+
+struct Slash<T: Trait> {
+
+  // The block slashing was initiated.
+  started_at_block: T::BlockNumber,
+
+  // Whether slashing is in active, or conversley paused state
+  // Blocks are only counted towards slashing execution delay when active.
+  is_active: bool,
+
+  // The number blocks which must be finalised while in the active period before the slashing can be executed
+  nr_of_blocks_remaining_in_active_period_for_slashing: T::BlockNumber
+}
+
+enum StakedStatus<T: Trait> {
+
+  // Baseline staking status, nothing is happening.
+  Normal,
+
+  // Unstaking is under way
+  Unstaking {
+
+        // The block where the unstaking was initiated
+        started_in_block: T::BlockNumber,
+
+        // Whether unstaking is in active, or conversely paused state
+        // Blocks are only counted towards unstaking period when active.
+        is_active: bool,
+
+        // The number blocks which must be finalised while in the active period before the unstaking is finished
+        nr_of_blocks_remaining_in_active_period_for_unstaking: T::BlockNumber,
+  }
+}
+
+enum StakingStatus<T: Trait> {
+
+  NotStaked,
+
+  Staked {
+
+    // Total amount of funds at stake
+    staked_amount: T::Balance,
+
+    // All ongoing slashing process.
+    // There may be some issue with BTreeMap for now in Polkadotjs,
+    // consider replacing this with Vec<Slash<T>>, and remove nextSlashId from state, for now in that case,
+    ongoing_slashes: BTreeMap<T::SlashId, Slash<T>>,
+
+    // Status of the staking
+    staked_status: StakedStatus<T>
+  }
+}
+
+struct Stake<T: Trait> {
+
+  // When role was created
+  created: time,
+
+  // Status of any possible ongoing staking
+  staking_status: StakingStatus<T>,
+
+}
+```
+
+## State
+
+- `stakes: linked_map T::StakeId => Stake<T>`: Maps identifiers to a stake.
+- `nextStakeId: T::StakeId`: Identifier value for next stake.
+- `nextSlashId: T::SlashId`: Identifier value for next slashing.
+- `staking_fund_account_id: T::AccountId`: Identifier for account of this module that holds staking funds.
+
+## Events
+
+**None**
+
+## Dispatchable Methods
+
+**None**
+
+## Non-dispatchable Methods
+
+### `add_stake`
+
+- **Parameters:** **None**
+- **Description:** Adds a new stake.
+- **Side-effect(s):** Adds a new stake which is `NotStaked`, created at given block, into `stakes` map with id `nextStakeId`, and increments `nextStakeId`.
+- **Events:** **None**
+- **Returns:** New `StakeId`
+
+### `remove_stake`
+
+- **Parameters:**
+  - `id`: Identifier of stake to be removed
+- **Description:** Removes an unstaked stake.
+- **Side-effect(s):** Given that stake with id `id` exists in `stakes` and is `NotStaked`, remove from `stakes`.
+- **Events:** **None**
+- **Returns:** **None**
+
+### `stake`
+
+Provided the stake exists and is in state `NotStaked` and the given account has sufficient free balance to cover the given staking amount, then the amount is transferred to the `MODULE_STAKING_FUND_ACCOUNT_ID` account, and the corresponding `staked_balance` is set to this amount in the new `Staked` state.
+
+### `increase_stake`
+
+Provided the stake exists and is in state `Staked.Normal`, and the given source account covers the amount, then the amount is transferred to the `MODULE_STAKING_FUND_ACCOUNT_ID` account, and the corresponding `staked_balance` is increased by the amount. New value of `staked_balance` is returned.
+
+### `decrease_stake`
+
+...
+
+### `initiate_slashing`
+
+Initiate a new slashing of a staked stake.
+
+### `pause_slashing`
+
+Pause an ongoing slashing.
+
+### `continue_slashing`
+
+Continue a currently paused ongoing slashing.
+
+### `cancel_slashing`
+
+Cancel an ongoing slashing (regardless of whether its active or paused).
+
+### `initiate_unstaking`
+
+Initiate unstaking of a staked stake.
+
+### `pause_unstaking`
+
+Puase an ongoing unstaking.
+
+### `continue_unstaking`
+
+Continue a currently paused ongoing unstaking.
+
+### `on_finalize`
+
+Handle timers for finalizing unstaking and slashing.
+
+Finalised unstaking results in the `staked_balance` in the given stake to be transferred.
+
+Finalised slashing results in the `staked_balance` in the given stake being correspondingly reduced.

+ 141 - 0
testnets/rome/specification/runtime/storage-modules.md

@@ -0,0 +1,141 @@
+# Storage Modules
+
+## Table Of Contents
+
+- [Design](#design)
+- [Concepts](#concepts)
+- [Architecture](#architecture)
+- [Traits](#traits)
+
+## Design
+
+### Motivation
+
+The storage modules contains all runtime functionality pertaining to managing
+the Joystream storage and distribution network. As such it contains information
+on actors participating in the network, as well as data that should be
+retrievable.
+
+### Structure
+
+There are the following modules, with their own detailed specifications.
+
+1. [Data Object Type Registry](data-object-type-registry-module.md):
+   manages how data may be stored on the network.
+2. [Data Directory](data-directory-module.md):
+   manages *what* data exists on the network.
+3. [Data Object Storage Registry](data-object-storage-registry-module.md):
+   manages  *where* data exists on the network.
+4. [Storage Staking](storage-staking-module.md) manages joining and leaving
+   storage *tranches*.
+
+Also related is the [Content Directory](content-directory.md), which provides
+information for users to discover stored content, but it is not a proper module.
+
+## Concepts
+
+- `DataObjectType`: a structure describing the type of data objects that can be
+  stored. This is not to be confused with file types. Instead, data object
+  types will be used to group files that should follow the same storage
+  patterns. See the [Data Object Type Registry](data-object-type-registry-module.md)
+  for details.
+
+- `ContentId`: a unique identifier for `DataObject` and `ContentMetadata`
+  entries.
+
+- `DataObject`: an entry in the [Data Directory](data-directory-module.md)
+  describing a single piece of content in the network.
+
+- `ContentMetadata`: a structure for describing content metadata in a
+  hierarchical fashion. Refers to one or more `DataObject` entries.
+
+- `SchemaId`: an identifier for a metadata schema. Metadata schemas are used to
+  validate `ContentMetadata` entries.
+
+- `Liaison`: the actor account that is responsible for accepting uploads for
+  a `DataObject`, and making the content available to other storage nodes.
+
+- `StorageRelationship`: an entry in the [Data Object Storage Registry](data-object-storage-registry-module.md),
+  describing which actor has stored a particular `DataObject`.
+
+- A storage provider is an `actor` who has staked for a storage tranche.
+
+#### ContentId, DataObject, ContentMetadata
+
+There is a somewhat strange relationship between these three concepts, as
+`ContentId` identifies both `DataObject` and `ContentMetadata`.
+
+Each `ContentId` can be thought of as a file name in a file system: it
+identifies the file contents on disk (i.e. `DataObject` here), as well as some
+metadata, such as file ownership, permissions, etc.
+
+In our system, we do not manage ownership or permissions in quite this manner,
+but in order to have content discoverable by humans, *do* manage descriptive
+information - aka `ContentMetadata`.
+
+The most often used term for such identifiers is a *content identifier*, hence
+the `ContentId` and corresponding `ContentMetadata` names. They best reflect
+the consumer's point of view, that content has a name and some information.
+
+The `DataObject` on the other hand refers to any generic data BLOB. Rather
+than introducing a `DataObjectId` and creating a 1:1 mapping between them
+and `ContentIds`, the latter is simply re-used.
+
+#### Storage Providers
+
+Storage providers, as indicated above, are actor accounts (public keys) which
+have staked for a storage tranche. The specs will treat these and storage nodes
+interchangeably, which may leave the impression that the runtime stores any
+information on where storage machines are to be contacted, such as IP addresses
+or host names. This is not true.
+
+At the level of abstraction of the storage module, *only* actor account IDs are
+managed. It is the purpose of the [Discovery Module](discovery-module.md) to
+resolve actor account IDs further to currently up-to-date contact information.
+
+### Architecture
+
+The basic unit of storage is a `DataObject`, for which a unique `ContentId` is
+entered into the `DataDirectory`. Each `DataObject` is associated with a
+`DataObjectType`, which describes storage parameters such as maximum permissible
+file sizes, etc.
+
+For each `DataObject`, one storage provider acts as the `Liaison`, accepting and
+validating the actual content upload, and making the content available to other
+storage providers. The `Liaison` and any other storage provider that holds the
+content available enters this fact into the runtime as a
+`StorageRelationship`.
+
+For purposes of content discovery, `ContentMetadata` is added to the runtime.
+Each `ContentMetadata` is identified by a `ContentId`; that is, one `ContentId`
+usually maps to a `DataObject` and a `ContentMetadata` entry. The
+`ContentMetadata` has a JSON payload, and a `SchemaId` indicating to clients how
+are to interpret the payload.
+
+`ContentMetadata` *can* be used hierarchically. Each entry can have any number
+of `ContentId` as children. These child IDs can be used to store `DataObject`
+and/or `ContentMetadata` entries of their own, allowing for organizing
+`DataObject` entries into hierarchical structures, e.g. for:
+
+- Podcast episodes in a Podcast
+- Series episodes in a video series
+- Individual language audio files for translated videos, or subtitle texts.
+- etc.
+
+The runtime imposes no restrictions on how `SchemaId` is to be used; however,
+the intent is to eventually add a schema registry that stores e.g.
+[well documented schemas](https://schema.org), or some Joystream specific
+derivates.
+
+## Traits
+
+<!-- This section must be reworked and renamed, we dont ahve this in standard. -->
+
+Most of the storage module's sub-modules only make use of each other, so
+there is not much need for documenting traits as interfaces between them.
+However, one public trait, to be used by the related [Content Directory](content-directory.md)
+does exist:
+
+- `DataObjectHasActiveStorageRelationships`: implements a method
+  `has_active_storage_relationships(content_id)` that returns true if there
+  exist active `StorageRelationship` entries, and false otherwise.

+ 126 - 0
testnets/rome/specification/runtime/token-mint-module.md

@@ -0,0 +1,126 @@
+# Token Mint Module
+
+## Table of Contents
+
+- [Name](#name)
+- [Design](#design)
+- [Dependencies](#dependencies)
+- [Concepts](#concepts)
+- [State](#state)
+- [Events](#events)
+- [Dispatchable Methods](#dispatchable-methods)
+- [Non-dispatchable Methods](#non-dispatchable-methods)
+  - [add_mint](#add_mint)
+  - [remove_mint](#remove_mint)
+  - [transfer_tokens](#transfer_tokens)
+  - [set_capacity](#set_limit)
+  - [transfer_capacity](#transfer_capacity)
+  - [on_finalize](#on_finalize)
+
+## Name
+
+`TokenMint`
+
+## Motivation
+
+Assume an actor is allocating tokens on behalf of others in the service of some broad goal. First, one may want to have flexibility in the _automatic_ constraints under which the allocation occurs. For example, one may want to
+
+- limit the total quantity within a given time period.
+
+- limit the set of possible recipients.
+
+- limit the set of possible distributions across recipients.
+
+These limits are cannot be enforced by simply crediting a discretionary account controlled by the allocator. Moreover, one may not want to finance the allocation by a large up-front minting, but rather mint tokens as they are required.
+
+Second, there may be multiple such actors organized into some sort of network or hierarchy, where the goals of one actor support the goals of another, and as a result, one may want an actor to be able to finance the allocative budget of another.
+
+## Design
+
+The design centers around the idea of a _token mint_. A token mint has the ability to mint and transfer new tokens to an account. It has a _minting capacity_, which indicates how much can be minted currently, and this capacity is decreased with each minting action. A minting action is only valid if it respects the current capacity. The capacity can be changed at any time, and it can also be automatically adjusted at a given block interval, by either being set to some fixed value, adjusted by some fixed value (up or down). Lastly, a mint can transfer some part of its capacity to another mint.
+
+## Usage
+
+The module is meant to be a low-level component of a fuller reward environment, where more context-specific modules will layer contextual permissions and requirements on top of this module. This is why there are no dispatchables or events.
+
+## Dependencies
+
+- `Currency`: SRML currency module
+
+## Concepts
+
+```Rust
+trait Trait : Curreny::Trait {
+
+ /* Identifier type for a token mint. */
+ type TokenMintId: INTEGER_TRAIT_CONSTRAINTS
+}
+
+enum AdjustCapacityBy {
+ Setting,
+ Adding,
+ Reducing  
+}
+
+struct TokenMint<T: Trait> {
+
+ id: T::TokenMintId,
+
+ capacity: T::Balance,
+
+ adjustment_type: AdjustCapacityBy,
+
+ block_interval: T::BlockNumber,
+
+ // Whether there is an upcoming block where
+ // When this is not set, the mint is effectively paused.
+ // There should be invariant check that Some(next_in_block) > now
+ adjust_capacity_in_block_nr: Option<T::BlockNumber>,
+
+ created: T::BlockNumber,
+
+ /// Stats
+
+ total_minted: T::Balance,
+}
+
+```
+
+## State
+
+- `mints: T::TokenMintId => TokenMint<T>`
+- `nextTokenMintId: T::TokenMintId`
+
+## Events
+
+**None**
+
+## Dispatchables
+
+**None**
+
+## Non-dispatchable Methods
+
+### `add_mint`
+
+Adds a new mint with a given settings to `mints`, and returns new `id`.
+
+### `remove_mint`
+
+Removes a mint with given `id` from `mints`
+
+### `transfer_tokens`
+
+Mints the given amount of tokens out of given mint and credits given account, provided it is within the current capacity, and reduces capacity correspondingly,
+
+### `set_capacity`
+
+Sets capacity of given mint to new given value.
+
+### `transfer_capacity`
+
+Given two mints, deducts from the capacity of one and credits capacity of the other, provided the amount respects the current capacity of the former.
+
+### `on_finalize`
+
+Updates capacity of all mints where the `adjust_capacity_in_block_nr` value match the current block number. For such mints, the value is updated by adding `block_interval`.

+ 144 - 0
testnets/rome/specification/runtime/versioned-store-permissions.md

@@ -0,0 +1,144 @@
+# Versioned Store Permissions Module
+
+## Table of Contents
+
+- [Name](#name)
+- [Design](#design)
+- [Dependencies](#dependencies)
+- [Concepts](#concepts)
+- [State](#state)
+- [Events](#events)
+- [Dispatchables](#dispatchables)
+- [Non-dispatchables](#non-dispatchables)
+  - [create_class](#create_class)
+  - [add_class_property](#add_class_property)
+  - [add_class_schema](#add_class_schema)
+  - [update_entity](#update_entity)
+  - [delete_entity](#delete_entity)
+
+## Name
+
+`VersionedStorePermissions`
+
+## Motivation
+
+Assume one has to use the versioned store in a context where runtime upgrades are not available every time one adds a new schema or a new class or entity. In this case the rules for how to regulate who can update these objects has to already exist in the runtime to begin with. This permissions module attempts to be such a generalised rule system which has sufficient flexibility to model some core constraints required for the currently conceived use case for the versioned store in the Joystream Rome testnet, namely a store of the content directory data.
+
+Notice that the inability to do runtime upgrades need not rule add adding new custom rules per schema, class or entity as they are added, if a smart contract abstraction exists. In this case there is no requirement to commit to a fixed model of updating rules up front.
+
+## Design
+
+Actions on and ownership in the versioned store are done through a two major actor personae types, a _base principal_ and a _entity principal_. These personae are meant to lift the actor model to a level of abstraction that allows for dynamic groups of participants to act in under shared set of privileges, if needed.
+
+A base principal type comes in varieties
+
+- **System:** Root origin in the runtime as an actor
+- **Account:** an account directly behaving as an actor
+- **Group Member:** member of one among a set of externally defined actor groups
+
+An entity principal is either a base principal, or the owner of a given entity relevant to the context at hand.
+
+All actions on the underlying version store require the actor to provide the personae under which they are acting.
+
+The permissions themselves come in three levels. First, the permission to create classes. The ability to do this, and as a consequence define the permissions for the relevant classses created, is a high powered permissions meant for very few actors at any given time.
+
+Second, are _class permissions_ which are specific to a given class, and which define what base personaes have the auhtority to peform the stanard set of actiosn available on a class, as well as update the permissions themselves, which are limited to so called _class admins_. There are also some base integrity constraints for how references can be used with this class.
+
+Third, are _entity permissions_ which refers both to the set of base principal currently designated as the _entity owner_, and to the sets of base entity principals which are allowed to update an entity property value, or delete the entity.
+## Usage
+
+Is meant to sit in front of the versioned store, gating all updates, and to have some complementary governance mechanism to adjudicate who has the top level permissions to create classes, and what accounts belong to what external groups at what time.
+
+## Dependencies
+
+- `VersionedStore`
+
+## Concepts
+
+```Rust
+
+trait GroupMembershipChecker {
+   type GroupId: INTEGER_TRAIT_CONSTRAINTS;
+   fn account_is_in_group(account: AccountId, group: Self::GroupId) -> bool;
+}
+
+//module trait
+trait Trait {
+    type GroupMembershipChecker : GroupMembershipChecker
+}
+
+enum BasePrincipal {
+  System,
+  Account(AccountId),
+  GroupMember(GroupMembershipChecker:GroupId),
+}
+
+enum EntityPrincipal {
+  Base(BasePrincipal)
+  Owner
+}
+
+
+// Represents instance of a Class in the versioned store
+struct ClassPermissions {
+  // concrete permissions
+  entity_permissions: EntityPermissions, // or just Vec<BasePrincipal>
+  entities_can_be_created: bool,
+  add_schemas: Vec<BasePrincipal>,
+  create_entities: Vec<BasePrincipal>,
+  can_only_be_ref_by: Vec<(ClassId, int)>
+
+  // admins can only be set by root origin
+  // admins can update all concrete permissions
+  admins: Vec<BasePrincipal>,
+
+  // Block where permissions were changed (including `admins`)
+  last_permissions_update: BlockNumber
+}
+
+struct EntityPermissions {
+  update: Vec<EntityPrincipal>,
+  delete: Vec<EntitytPrincipal>,
+}
+```
+
+## State
+
+- `create_classes : Vec<BasePrincipal>`: this group of users can be considered the schema admins.
+- `classPermissions: map ClassId => ClassPermissions`: ...
+- `entityOwners: map EntityId => BasePrincipal`: ...
+
+## Events
+
+**TBD**
+
+## Dispatchables
+
+
+### create_class
+
+Creates a new class with given name, description, properties and class schemas.
+
+### add_class_property
+
+Adds a property to a given class.
+
+### add_class_schema
+
+Adds a schema to a given class.
+
+### create_entity
+
+Creates an entity, under a given class, which supports a specific given first schema.
+
+### update_entity
+
+Updates all fields in an entity.
+
+### delete_entity
+
+Deletes a given entity.
+
+## Non-dispatchables
+
+**TBD**

+ 166 - 0
testnets/rome/specification/runtime/versioned-store.md

@@ -0,0 +1,166 @@
+# Versioned Store Module
+
+## Table of Contents
+
+- [Name](#name)
+- [Design](#design)
+- [Dependencies](#dependencies)
+- [Concepts](#concepts)
+- [State](#state)
+- [Events](#events)
+- [Dispatchables](#dispatchables)
+- [Non-dispatchables](#non-dispatchables)
+  - [create_class](#create_class)
+  - [add_class_property](#add_class_property)
+  - [add_class_schema](#add_class_schema)
+  - [update_entity](#update_entity)
+  - [delete_entity](#delete_entity)
+
+## Name
+
+`VersionedStore`
+
+## Motivation
+
+Assume one must model a domain with a large number of entities, where updating representation of an entity must submit to rich dynamic access permissions that live on-chain. Access permissions specifically mean rules about what actor can mutate what representation, and in what way.
+
+Further assume that the representations are structured, in order to allow sufficiently rich and convenient updating, and also linked, to allow semantic depth in the chosen representations. Lastly, they submit to schemas which enforce integrity constraints on the representations.
+
+The problem arises when there is a need to change the schema which currently applies to a large number of entities. This is a computationally expensive operation, which easily could be infeasible within the timeliness constraints of normal blockchain transaction processing.
+
+This can be solved by not requiring all entities to submit to the same schema simultaneously, but instead introducing versioned representations of the same entity through different schemas. This allows the freedom to add newer representations over time, without the need to a large simultaneous migration.
+
+This module embodies such a versioned linked data store.
+
+## Design
+
+The design centers around two key concepts, a _class_ and an _entity_. A class represents a type of entity family, and it may have sequence of _schemas_ associated with it, which defines different ways an entity of the given class may be encoded. A schema can express familiar constraints around what _properties_ an entity must have in order to submit to the schema. A property is defined by some data type requirements, whether it is optional or not, and some metadata. Importantly, one special data type is called the _internal_ property type, which requires an identifier for some entity of a class defined int he store. This is how data is linked. An entity should be understood as some persistent instance of a class that may exist in one or more different versions simultaneously.
+
+### Example
+
+Assume you are modeling two related ideas, the idea of a motion picture and an actor. These two ideas correspond to _classes_.
+
+For an actor, the simplest representation would for example only require one property, their full name. A slightly more advanced representation would be both the name, and a date of birth. These two representations correspond to _schemas_ for the actor class. An actor entity would be a specific actor, like Charlie Chaplin. This entity may live in the data store under only one, or both, schemas mentioned.
+
+For a motion picture, the simplest representation would just be the title name, and a slightly more advanced one could include the lead actor as a property. These two representations are two separate schemas for the motion picture class. A motion picture entity would be a specific motion picture, like The Great Dictator. This entity may live in the data store under only one, or both, schemas mentioned. If being reprsented under the second schema, then the value for the lead actor property would be the identifier of the actor entity Charlie Chaplin.
+
+<img src="versioned_data_store_example.png" />
+
+## Usage
+
+This module has not extrinsics and no concept of permissions or higher level integrity constraints, this must layered on top.
+
+## Dependencies
+
+`None`
+
+## Concepts
+
+```Rust
+
+trait Trait: {
+  type ClassId: INTEGER_TRAIT_CONSTRAINTS,
+  type EntityId: INTEGER_TRAIT_CONSTRAINTS,
+}
+
+struct Class<T: Trait> {
+  name: Name,
+  description: Description,
+
+  // For Person, think "height", "weight", etc.
+  // Unlikely to be more than roughly 20ish, often less.
+  properties: Vec<Property>,
+
+  // All scehmas that are available for this class, think v0.0 Person, v.1.0 Person, etc.
+  class_schemas: Vec<ClassSchema>
+}
+
+struct Entity<T: Trait> {
+  class: T::ClassId, // entity is an instance of this class
+
+  // What schemas under which this entity of a class is available, think
+  // v.2.0 Person schema for John , v3.0 Person schema for John
+  // Unlikely to be more than roughly 20ish, assuming schemas for a given class eventually stableize, or that very old schema are eventually removed.
+  schemas: Vec<u32>, // indexes of schema in corresponding class
+
+  // Values for properties on class that are used by some schema used by this entity!
+  // Length is no more than Class.properties.
+  values: Vec<(u32, PropertyValue)>, // Index is into properties vector of class. Fix anonymous type later.
+  name: Name,
+  deleted: bool
+}
+
+// A schema defines what properties describe an entity
+struct ClassSchema<T: Trait> {
+  version: Version,
+  properties: Vec<uint> // indexes into properties vector for the corresponding class
+}
+
+struct Property {
+  data_type: PropertyDataType,
+  optional: bool,
+  name: Name,
+  description: Description,
+}
+
+enum PropertyDataType {
+  External(ExternalProperty),
+  Internal(ClassId),
+  unsigned_integer_64,
+  signed_integer_64,
+  float64,
+  varchar(u32),
+  boolean
+}
+
+enum PropertyValue {
+  External(ExternalPropertyType),
+  Internal(EntityId),
+  unsinged_integer_64(u64),
+  signed_integer_64(i64),
+  float64(f64)
+  boolean(bool),
+  varchar(Vec<u8>),
+  none
+}
+
+```
+
+## State
+
+- `ClassById : ClassId => Class`: Classes.
+- `EntityById: EntityId => Entity`: Entities.
+
+## Events
+
+**None**
+
+## Dispatchables
+
+**None**
+
+## Non-dispatchables
+
+### create_class
+
+Creates a new class with given name, description, properties and class schemas.
+
+### add_class_property
+
+Adds a property to a given class.
+
+### add_class_schema
+
+Adds a schema to a given class.
+
+### create_entity
+
+Creates an entity, under a given class, which supports a specific given first schema.
+
+### update_entity
+
+Updates all fields in an entity.
+
+### delete_entity
+
+Deletes a given entity.

BIN
testnets/rome/specification/runtime/versioned_data_store_example.png