Merge branch 'policy_table' into 'master'
Policy table See merge request medlabboulder/modpol!21
This commit is contained in:
@ -6,10 +6,14 @@ local localdir = modpol.topdir
|
||||
dofile (localdir .. "/users/users.lua")
|
||||
|
||||
--orgs
|
||||
dofile (localdir .. "/orgs/orgs.lua")
|
||||
dofile (localdir .. "/orgs/base.lua")
|
||||
dofile (localdir .. "/orgs/requests.lua")
|
||||
|
||||
--interactions
|
||||
dofile (localdir .. "/interactions/interactions.lua")
|
||||
|
||||
-- messaging functions
|
||||
dofile (localdir .. "/processes/processes.lua")
|
||||
dofile (localdir .. "/processes/processes.lua")
|
||||
|
||||
--modules
|
||||
dofile (localdir .. "/modules/consent.lua")
|
@ -71,9 +71,17 @@ dofile (topdir .. "/api.lua")
|
||||
|
||||
-- ===================================================================
|
||||
-- Final checks
|
||||
for id, org in ipairs(modpol.orgs.array) do
|
||||
if type(org) == 'table' then
|
||||
setmetatable(org, modpol.orgs)
|
||||
|
||||
-- sets org metatable on load
|
||||
if (modpol.orgs.array) then
|
||||
for id, org in ipairs(modpol.orgs.array) do
|
||||
if type(org) == 'table' then
|
||||
setmetatable(org, modpol.orgs)
|
||||
-- sets process metatable on load
|
||||
for id, process in ipairs(org.processes) do
|
||||
setmetatable(process, modpol.modules[process.type])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
72
modpol/modules/consent.lua
Normal file
72
modpol/modules/consent.lua
Normal file
@ -0,0 +1,72 @@
|
||||
modpol.modules = modpol.modules or {}
|
||||
|
||||
modpol.modules.consent = {
|
||||
type = "consent",
|
||||
}
|
||||
|
||||
-- sets consent to its own callback
|
||||
modpol.modules.consent.__index = modpol.modules.consent
|
||||
|
||||
function temp_consent_process()
|
||||
return {
|
||||
org_id = nil,
|
||||
request_id = nil,
|
||||
total_votes = 0,
|
||||
majority_to_pass = 0.51,
|
||||
votes_yes = {},
|
||||
votes_no = {}
|
||||
}
|
||||
end
|
||||
|
||||
-- ===============================================
|
||||
-- function to create a new consent process to resolve a pending process
|
||||
function modpol.modules.consent:new_process(request_id, org_id)
|
||||
local process = temp_consent_process()
|
||||
process.request_id = request_id
|
||||
process.org_id = org_id
|
||||
|
||||
setmetatable(process, modpol.modules.consent)
|
||||
modpol.ocutil.log('Created new process for request id #' .. request_id)
|
||||
|
||||
return process
|
||||
end
|
||||
|
||||
-- ======================================================
|
||||
-- function for users to vote on a pending request
|
||||
function modpol.modules.consent:approve(user, decision)
|
||||
if not modpol.orgs.get_org(self.org_id):has_member(user) then
|
||||
modpol.ocutil.log('Error in consent:approve -> user not a member of the org')
|
||||
return
|
||||
end
|
||||
|
||||
if decision then
|
||||
table.insert(self.votes_yes, user)
|
||||
modpol.ocutil.log('User ' .. user .. ' voted yes on request #' .. self.request_id)
|
||||
else
|
||||
table.insert(self.votes_no, user)
|
||||
modpol.ocutil.log('User ' .. user .. ' voted no on request #' .. self.request_id)
|
||||
end
|
||||
|
||||
self.total_votes = self.total_votes + 1
|
||||
|
||||
self:update_status()
|
||||
end
|
||||
|
||||
-- ===================================================
|
||||
-- determines whether process has finished and resolves request if it has (unfinished)
|
||||
function modpol.modules.consent:update_status()
|
||||
local process_org = modpol.orgs.get_org(self.org_id)
|
||||
local eligible_voters = process_org:get_member_count()
|
||||
local votes_needed = math.ceil(self.majority_to_pass * eligible_voters)
|
||||
|
||||
if #self.votes_yes >= votes_needed then
|
||||
modpol.ocutil.log('Request #' .. self.request_id .. ' passes')
|
||||
process_org:resolve_request(self.request_id, true)
|
||||
elseif #self.votes_no >= votes_needed then
|
||||
modpol.ocutil.log('Request #' .. self.request_id .. ' fails to pass')
|
||||
process_org:resolve_request(self.request_id, false)
|
||||
else
|
||||
modpol.ocutil.log('Waiting for more votes...')
|
||||
end
|
||||
end
|
||||
|
0
modpol/modules/defer_to.lua
Normal file
0
modpol/modules/defer_to.lua
Normal file
@ -14,20 +14,12 @@ function temp_org()
|
||||
policies = {},
|
||||
processes = {},
|
||||
requests = {},
|
||||
request_count = 0,
|
||||
members = {},
|
||||
parent = nil,
|
||||
children = {}
|
||||
}
|
||||
end
|
||||
|
||||
modpol.orgs.request_params = {
|
||||
add_org = 1,
|
||||
delete = 0,
|
||||
add_member = 1,
|
||||
remove_member = 1
|
||||
}
|
||||
|
||||
-- ==================================================
|
||||
-- returns org when given its id or name
|
||||
function modpol.orgs.get_org(arg)
|
||||
@ -79,10 +71,11 @@ end
|
||||
function modpol.orgs.reset()
|
||||
for id, org in ipairs(modpol.orgs.array) do
|
||||
if id > 1 then
|
||||
modpol.orgs.array[id] = nil
|
||||
modpol.orgs.array[id] = "removed"
|
||||
end
|
||||
end
|
||||
|
||||
modpol.ocutil.log('Reset all orgs')
|
||||
modpol.orgs:record('Resetting all orgs', 'org_reset')
|
||||
end
|
||||
|
||||
@ -92,7 +85,7 @@ end
|
||||
function modpol.orgs.init_instance()
|
||||
local error_msg
|
||||
if modpol.orgs.array[1] then
|
||||
modpol.ocutil.log('Error: instance has already been initialized')
|
||||
modpol.ocutil.log('Error in orgs.init_instance -> instance has already been initialized')
|
||||
return false
|
||||
end
|
||||
|
||||
@ -105,6 +98,7 @@ function modpol.orgs.init_instance()
|
||||
-- adding instance to org list
|
||||
modpol.orgs.array[1] = instance
|
||||
|
||||
modpol.ocutil.log('Initialized the instance org')
|
||||
modpol.orgs:record('Initialized the instance org', 'create_instance')
|
||||
|
||||
return instance
|
||||
@ -127,15 +121,15 @@ function modpol.orgs:record(msg, entry_type)
|
||||
if type(msg) == 'string' and not(modpol.ocutil.str_empty(msg)) then
|
||||
entry.action_msg = msg
|
||||
else
|
||||
modpol.ocutil.log('Error: msg must be a non empty string')
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':record -> msg must be a non empty string')
|
||||
return false
|
||||
end
|
||||
|
||||
if type(entry_type) == 'string' and not(modpol.ocutil.str_empty(entry_type)) then
|
||||
entry.entry_type = entry_type
|
||||
else
|
||||
modpol.ocutil.log('Error: entry_type must be a non empty string')
|
||||
print(msg, entry_type)
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':record -> entry_type must be a non empty string')
|
||||
modpol.ocutil.log(msg, entry_type)
|
||||
return false
|
||||
end
|
||||
|
||||
@ -152,17 +146,17 @@ end
|
||||
-- ex: instance:add_org('town hall')
|
||||
function modpol.orgs:add_org(name)
|
||||
if self.id == nil then
|
||||
modpol.ocutil.log('Error: add_org can only be called by another org')
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':add_org -> add_org can only be called by another org')
|
||||
return false
|
||||
end
|
||||
|
||||
if modpol.ocutil.str_empty(name) then
|
||||
modpol.ocutil.log('Error: org name is required')
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':add_org -> org name is required')
|
||||
return false
|
||||
end
|
||||
|
||||
if modpol.orgs.get_org(name) then
|
||||
modpol.ocutil.log('Error: org name is already being used')
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':add_org -> org name is already being used')
|
||||
return false
|
||||
end
|
||||
|
||||
@ -182,7 +176,7 @@ function modpol.orgs:add_org(name)
|
||||
modpol.orgs.array[child_org.id] = child_org
|
||||
|
||||
self:record('created sub org ' .. name, 'add_org')
|
||||
modpol.ocutil.log('Created sub org ' .. name)
|
||||
modpol.ocutil.log('Created ' .. name .. ' (suborg of ' .. self.name .. ')')
|
||||
|
||||
return child_org
|
||||
end
|
||||
@ -193,20 +187,20 @@ end
|
||||
-- note: "reason" param was removed, can be added back
|
||||
function modpol.orgs:delete()
|
||||
if self.id == 1 then
|
||||
modpol.ocutil.log('Error: cannot delete instance')
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':delete -> cannot delete instance')
|
||||
return false
|
||||
end
|
||||
|
||||
if #self.children > 0 then
|
||||
for i, child_id in pairs(self.children) do
|
||||
local child = modpol.orgs.get_org(child_id)
|
||||
print(child_id, child)
|
||||
modpol.ocutil.log(child_id, child)
|
||||
child:delete()
|
||||
end
|
||||
end
|
||||
|
||||
modpol.orgs.array[self.id] = 'removed'
|
||||
modpol.ocutil.log('Removed ' .. self.name .. ': ' .. self.id)
|
||||
modpol.ocutil.log('Deleted org ' .. self.name .. ': ' .. self.id)
|
||||
|
||||
self:record('Deleted ' .. self.name .. ' and all child orgs', 'del_org')
|
||||
|
||||
@ -226,6 +220,12 @@ end
|
||||
-- ===========================================
|
||||
-- adds a user to an org
|
||||
function modpol.orgs:add_member(user)
|
||||
for id, name in ipairs(self.members) do
|
||||
if user == name then
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':add_member -> user already in org')
|
||||
return false
|
||||
end
|
||||
end
|
||||
-- trys to fill in empty spots first
|
||||
local empty_index = self:get_member_index('')
|
||||
if empty_index then
|
||||
@ -234,6 +234,8 @@ function modpol.orgs:add_member(user)
|
||||
-- adds to end if no empty spots
|
||||
table.insert(self.members, user)
|
||||
end
|
||||
|
||||
modpol.ocutil.log('Added member ' .. user .. ' to ' .. self.name)
|
||||
self:record('Added member ' .. user, 'add_member')
|
||||
|
||||
end
|
||||
@ -246,7 +248,10 @@ function modpol.orgs:remove_member(user)
|
||||
user_index = self:get_member_index(user)
|
||||
if user_index then
|
||||
self.members[user_index] = ''
|
||||
else
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':remove_member -> user not in org')
|
||||
end
|
||||
modpol.ocutil.log('Removed member ' .. user .. ' from ' .. self.name)
|
||||
self:record('Removed member ' .. user, 'del_member')
|
||||
end
|
||||
|
||||
@ -266,7 +271,8 @@ end
|
||||
function modpol.orgs:list_member()
|
||||
local str
|
||||
for k, v in ipairs(self.members) do
|
||||
if str then
|
||||
-- checking to see if member name is valid
|
||||
if str and str ~= '' then
|
||||
str = str .. '\n' .. v
|
||||
else
|
||||
str = v
|
||||
@ -275,56 +281,28 @@ function modpol.orgs:list_member()
|
||||
return str
|
||||
end
|
||||
|
||||
-- ===========================
|
||||
-- compares to requests to see if they are identical
|
||||
function modpol.orgs.comp_req(req1, req2)
|
||||
-- compares request type
|
||||
if req1.type ~= req2.type then
|
||||
return false
|
||||
else
|
||||
-- comparing parameters
|
||||
-- we can assume the number of params is the same as this is checked in the make_request func
|
||||
for k, v in ipairs(req1.params) do
|
||||
if v ~= req2.params[k] then
|
||||
return false
|
||||
end
|
||||
-- ==============================
|
||||
-- because member list uses lazy deletion, using #org.members will not show an accurate number
|
||||
function modpol.orgs:get_member_count()
|
||||
local count = 0
|
||||
for k, v in ipairs(self.members) do
|
||||
-- the empty string represents a deleted member in the members list
|
||||
if v ~= '' then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return true
|
||||
return count
|
||||
end
|
||||
-- ====================================
|
||||
-- adds a new policy to the policy table
|
||||
-- must define the policy type, process associated with it, and whether the request must be made by an org member
|
||||
function modpol.orgs:set_policy(policy_type, process_type, must_be_member)
|
||||
local new_policy = {
|
||||
process_type = process_type,
|
||||
must_be_member = must_be_member
|
||||
}
|
||||
self.policies[policy_type] = new_policy
|
||||
modpol.ocutil.log('Added policy for ' .. policy_type .. ' in ' .. self.name)
|
||||
end
|
||||
|
||||
-- ================================
|
||||
-- tries to make a request to the org
|
||||
function modpol.orgs:make_request(request)
|
||||
-- makes sure the request has the valid number of parameters
|
||||
local num_params = modpol.orgs.request_params[request.type]
|
||||
|
||||
if num_params == nil then
|
||||
modpol.ocutil.log("Error: request type is invalid")
|
||||
return false
|
||||
end
|
||||
|
||||
for k, v in ipairs(request.params) do
|
||||
num_params = num_params - 1
|
||||
end
|
||||
|
||||
if num_params ~= 0 then
|
||||
modpol.ocutil.log("Error: request has invalid number of parameters")
|
||||
return false
|
||||
end
|
||||
|
||||
-- checking to see if identical request already exists
|
||||
for k, v in ipairs(self.requests) do
|
||||
if self.comp_req(request, v) == true then
|
||||
modpol.ocutil.log("Error: request has already been made")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- use lazy deletion here, not very clean
|
||||
-- table.insert(self.requests, request)
|
||||
self.request_count = self.request_count + 1
|
||||
self.requests[self.request_count] = request
|
||||
return self.request_count
|
||||
|
||||
end
|
179
modpol/orgs/requests.lua
Normal file
179
modpol/orgs/requests.lua
Normal file
@ -0,0 +1,179 @@
|
||||
modpol.orgs.request_params = {
|
||||
add_org = 1,
|
||||
delete = 0,
|
||||
add_member = 1,
|
||||
remove_member = 1
|
||||
}
|
||||
|
||||
-- ================================
|
||||
-- creates a new process linked to a request id
|
||||
function modpol.orgs:create_process(process_type, request_id)
|
||||
if not modpol.modules[process_type] then
|
||||
modpol.ocutil.log('Process type "' .. process_type .. '" does not exist')
|
||||
return
|
||||
end
|
||||
|
||||
-- retrieving requested module
|
||||
local module = modpol.modules[process_type]
|
||||
local new_process = module:new_process(request_id, self.id)
|
||||
|
||||
-- linear search for empty process slots (lazy deletion)
|
||||
for k, v in ipairs(self.processes) do
|
||||
if v == 'deleted' then
|
||||
local empty_index = k
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- attempts to fill empty spots in list, otherwise appends to end
|
||||
if empty_index then
|
||||
self.processes[empty_index] = new_process
|
||||
return empty_index
|
||||
else
|
||||
table.insert(self.processes, new_process)
|
||||
return #self.processes
|
||||
end
|
||||
end
|
||||
|
||||
-- ===========================
|
||||
-- compares to requests to see if they are identical
|
||||
function modpol.orgs.comp_req(req1, req2)
|
||||
-- compares request type
|
||||
if req1.type ~= req2.type then
|
||||
return false
|
||||
else
|
||||
-- comparing parameters
|
||||
-- we can assume the number of params is the same as this is checked in the make_request func
|
||||
for k, v in ipairs(req1.params) do
|
||||
if v ~= req2.params[k] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- ===============================
|
||||
-- returns string of all active requests
|
||||
function modpol.orgs:list_request()
|
||||
local str
|
||||
for id, req in ipairs(self.requests) do
|
||||
if req ~= "deleted" then
|
||||
if str then
|
||||
str = str .. '\n' .. req.type .. ' (' .. req.user .. ') '
|
||||
else
|
||||
str = req.type .. ' (' .. req.user .. ') '
|
||||
end
|
||||
end
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
-- ===============================
|
||||
-- if the request was approved, the associated function is called, otherwise it is deleted
|
||||
function modpol.orgs:resolve_request(request_id, approve)
|
||||
if approve then
|
||||
local request = self.requests[request_id]
|
||||
local p = request.params
|
||||
|
||||
-- there's probably a way to clean this up, the issue is the varying number of commands
|
||||
-- ex: self['add_member'](self, 'member_name')
|
||||
-- not sure if this is safe, more testing to do
|
||||
self[request.type](self, p[1], p[2], p[3])
|
||||
|
||||
-- if request.type == "add_org" then
|
||||
-- self:add_org(p[1])
|
||||
-- elseif request.type == "delete" then
|
||||
-- self:delete()
|
||||
-- elseif request.type == "add_member" then
|
||||
-- self:add_member(p[1])
|
||||
-- elseif request.type == "remove_member" then
|
||||
-- self:remove_member(p[1])
|
||||
-- end
|
||||
|
||||
end
|
||||
|
||||
self.requests[request_id] = "deleted"
|
||||
end
|
||||
|
||||
-- ================================
|
||||
-- tries to make a request to the org
|
||||
function modpol.orgs:make_request(request)
|
||||
-- makes sure the request has the valid number of parameters
|
||||
local num_params = modpol.orgs.request_params[request.type]
|
||||
|
||||
if num_params == nil then
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> request type is invalid')
|
||||
return false
|
||||
end
|
||||
|
||||
-- num_params should equal zero at the end if request.params matches the num of params for that type
|
||||
for k, v in ipairs(request.params) do
|
||||
num_params = num_params - 1
|
||||
end
|
||||
|
||||
if num_params ~= 0 then
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> request has invalid number of parameters')
|
||||
return false
|
||||
end
|
||||
|
||||
-- checking to see if identical request already exists
|
||||
for k, v in ipairs(self.requests) do
|
||||
if self.comp_req(request, v) == true then
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> request has already been made')
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- checking to see if user is able to make request
|
||||
local requested_policy = self.policies[request.type]
|
||||
local parent_policy = modpol.orgs.get_org(self.parent).policies[request.type]
|
||||
|
||||
-- tries to use org's policy table, defers to parent otherwise
|
||||
if not requested_policy then
|
||||
modpol.ocutil.log(request.type .. ' policy not found, deferring to parent org')
|
||||
requested_policy = parent_policy
|
||||
|
||||
if not parent_policy then
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> parent policy undefined')
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- make sure user is allowed to make request
|
||||
if requested_policy.must_be_member and not self:has_member(request.user) then
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> user must be org member to make this request')
|
||||
return false
|
||||
end
|
||||
|
||||
-- linear search for empty process slots (lazy deletion)
|
||||
for k, v in ipairs(self.requests) do
|
||||
if v == 'deleted' then
|
||||
local empty_index = k
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- attempts to fill empty spots in list, otherwise appends to end
|
||||
local request_id = nil
|
||||
if empty_index then
|
||||
self.requests[empty_index] = request
|
||||
request_id = empty_index
|
||||
else
|
||||
table.insert(self.requests, request)
|
||||
-- finds end of list to return current request's id
|
||||
local count = 0
|
||||
for k, v in ipairs(self.requests) do
|
||||
count = count + 1
|
||||
end
|
||||
request_id = count
|
||||
end
|
||||
modpol.ocutil.log("Request made by " .. request.user .. " to " .. request.type)
|
||||
|
||||
-- launching process tied to this request
|
||||
local process_id = self:create_process(requested_policy.process_type, request_id)
|
||||
|
||||
-- returns process id of processes launched by this request
|
||||
return process_id
|
||||
end
|
||||
|
23
modpol/tests/org_basic_test.lua
Normal file
23
modpol/tests/org_basic_test.lua
Normal file
@ -0,0 +1,23 @@
|
||||
dofile("../modpol.lua")
|
||||
|
||||
print('\nRemoving existing orgs')
|
||||
modpol.orgs.reset()
|
||||
|
||||
print('\nCreating an org called "test_org"')
|
||||
test_org = modpol.instance:add_org('test_org')
|
||||
|
||||
print('\nTrying to create an org with the same name')
|
||||
duplicate = modpol.instance:add_org('test_org')
|
||||
|
||||
print('\nAdding user "luke" to test_org')
|
||||
test_org:add_member('luke')
|
||||
|
||||
print('\nTrying to add duplicate user to test_org')
|
||||
test_org:add_member('luke')
|
||||
|
||||
print('\nRemoving user "luke" from test_org')
|
||||
test_org:remove_member('luke')
|
||||
|
||||
print('\nTrying to remove user "luke" from empty member list')
|
||||
test_org:remove_member('luke')
|
||||
|
26
modpol/tests/org_req_test.lua
Normal file
26
modpol/tests/org_req_test.lua
Normal file
@ -0,0 +1,26 @@
|
||||
dofile('../modpol.lua');
|
||||
|
||||
modpol.orgs.reset()
|
||||
|
||||
test_org = modpol.instance:add_org('test_org')
|
||||
test_org:add_member('luke')
|
||||
test_org:add_member('nathan')
|
||||
|
||||
test_org:set_policy("add_member", "consent", false);
|
||||
|
||||
new_request = {
|
||||
user = "josh",
|
||||
type = "add_member",
|
||||
params = {"josh"}
|
||||
}
|
||||
|
||||
request_id = test_org:make_request(new_request)
|
||||
|
||||
-- process_id = test_org:create_process("consent", request_id)
|
||||
for id, process in ipairs(test_org.processes) do
|
||||
process:approve('luke', true)
|
||||
process:approve('nathan', true)
|
||||
end
|
||||
-- process = test_org.processes[process_id]
|
||||
-- process:approve("luke", true)
|
||||
-- process:approve("nathan", true)
|
Reference in New Issue
Block a user