Programmer’s Guide

Dependencies

Apache2

This is the web server. Other web servers could be substituted. I chose Apache because it is the premier free and open source web server. Also, because it has mod_wsgi, it is simpler to setup for deploying Django apps.

Apache2 mod_wsgi

For interacting with Django (using the Python WSGI standard directly instead of CGI)

django

Web framework: provides user login and registration; generates the client-side HTML and javascript pages to serve in response to user actions. (It also has its own basic webserver, which could be used to try out the Allsembly™ program without installing Apache.)

graphviz

graphviz-dev

The Python library “PyGraphviz” relies on these. Graphviz is used to draw the graphs.

Problog

A probabilistic programming language. This is used to compute the probabilities.

ZODB

An object database. This is used for persistence. It stores all of the persistent objects. The default backend is file storage, which is currently being used. Other storage backends could be used in the future for better scalability, see Design for scalability.

PyGraphviz

Easy to use Python bindings to the Graphviz library.

RPyC

Python to Python RPC used to communicate between the Django services and the Allsembly™ server. It could be useful later for implementing communication with a registration server. (See Design for confidentiality.)

persistent

This works with ZODB to automatically save and restore objects that subclass “Persistent”.

transaction

This provides a transaction manager that is used with ZODB.

atomic

This provides an atomically updatable integer class.

readerwriterlock

This provides thread synchronization using separate locks for readers and writers. There can be multiple readers but only one writer.

python-daemon

This provides an easy API for making a Python program run as a Unix daemon.

d3.js

This is a graphics library for web clients. I am currently only using it for pan and zoom of scalable vector graphics (SVG) that is the format of the argument graph. It could be used to draw alternative layouts of the argument graph on the client-side if desired, especially when accompanied by d3-graphviz.

dialog-polyfill.js

This provides the HTML DIALOG tag and its functionality for browsers that don’t (fully) support it, yet. The DIALOG tag is used for creating the modal dialogs.

jquery-3.6.0.js

Used to simplify AJAX calls (asynchronous loading of web content using JavaScript).

Future possible dependencies

GPGME

for encrypting the stream encryption keys with the public key(s) of the admins. (The stream encryption key will also be encrypted with the user’s hashed password.) See more details about this in the section Design for confidentiality, below).

PyNaCl

for stream encryption or salsa20 to encrypt a database field containing users’ anonymized ids (see more details about this in the section Design for confidentiality, below).

Pyrsistent

to avoid mistaken call by reference-like behaviors (and, therefore, subtle bugs).

returns

to replace exceptions for error handling.

Files

requirements.txt
List of required dependencies and their version numbers

setup.cfg
Configuration options for mypy and configuration options for Pylint

allsembly/
All of the modules of the Python package

django_app/
App for communicating with users to log them in and to generate
the html and JavaScript of the site. It communicates with the
AllsemblyServer to provide the services.

django_site/
Configuration information for the Django site, which uses the
Django app, the code for which is in the django_app directory.

docs/

_build/
The Sphinx-generated HTML documentation, as a git submodule

conf.py
The Sphinx configuration file.

fdl-1.3.txt
The GNU Free Documentation License

index.rst
The main file of the documentation

how_it_works.md
This file explains how human participants contribute to making the probabilistic estimates of the truth or correctness of conclusions possible, including a contrived example.

make.bat

Makefile
Files for building the documentation. Sphinx is required.

notes.md
This file contains an overview of the dialogue procedure and why it is expected to be effective, including notes about a few salient design issues.

LICENSE.third-party/
Licenses for third-party software distributed with Allsembly™

misc/
Documents containing additional explanation about what Allsembly™ is about and the theory behind it–these files are not part of the documentation, and might be edited for publication elsewhere at some point.

prospectus.pdf
This file contains a scholarly presentation of the initial development of the theory and concepts that are behind this software prototype and includes some of the motivation as well.

scripts/

allsembly-server.py*
Use this script to start the Allsembly™ server. Run it with the --help option to get usage information. Also, see, the section Installation and Testing for some instructions.
allsembly.service
A systemd service script example. Modify the line “ExecStart”; then use this file to make the allsembly-server.py persistent as a daemon (i.e., make it available on system startup).

test/
The tests.

web/
scripts/
The javascript libraries

Specification of requirements (draft)

More is to be added to this section later.

Make all of the argumentation and discussion publicly accessible. This will support a secondary function of having a kind of encyclopedia of arguments pro and con issues for repeated reference. Users should be able to obtain an independent URL to any part of an argument or discussion so as to refer to any specific aspect of that argument or discussion. The data (only completely anonymous or anonymized data) should also be available in a format like Argument Interchange Format (XML-RDF) or CSV (comma-separated values), for computer assisted processing and exploration. (Public accessibility is not currently implemented.)

Future requirement: provide for third-party clients; maintain a stable client interface.

Design overview

@startuml
page 1x2
caption Figure 1

package allsembly.py {
class AllsemblyServer {
-userauth_dbfilename: str
-user_dbfilename: str
-arg_dbfilename: str
-order_queue: deque[Tuple]
-graph_arg_queue: deque[Tuple]
-graph_pos_queue: deque[Tuple]
+process_all_issues_from_queue()
+process_all_orders_from_queue()
+process_all_positions_from_queue()
+process_all_items_from_all_queues()
+process_all_items_of_one_request()
+cleanup()
+server_main_loop()
}

class ServerControl {
- _should_exit: int
+ should_exit()
+ set_should_exit()
+ reset_should_exit()
}
}

ServerControl o.. AllsemblyServer


package rpyc_server.py {
class IssueQueue {
- queue: deque
+ add_issue()
+ delete_issue()
}

class GraphRequest {
+ draw()
+ get_position_details()
}

class LedgerRequest {
{field} (unused)
}


class "AllsemblyServices(rpyc.Service)" as AllsemblyServices {
- order_queue: deque[Tuple]
- graph_arg_queue: deque[Tuple]
- graph_pos_queue: deque[Tuple]
+ on_connect()
+ on_disconnect()
+ exposed_get_user_services()
+ exposed_authenticate_user()
- _add_issue()
}

class UserServices {
+ check_commitments_for_consistency() (unused)
+ delete_issue()
+ argue()
+ propose()
+ get_arg_graph()
+ get_position_details()
}
}

package argument_graph.py {
class Issues(Persistent) {
+ graphs: OOBTree
+ next_issue_id: AtomicLong
}

class ArgumentGraph(Persistent) {
- {static} _v_my_rwlock: RWLockWrite
- _v_my_wlock
- _v_my_rlock
+ issue_name: str
- arg_node_index: PersistentMapping
- pos_node_index: PersistentMapping
- next_arg_id: AtomicLong
- next_pos_id: AtomicLong
- read_buffer_index: int
- write_buffer_index: int
- /_v_gv_graph: AGraph
- /my_problog_prog: str
- /_v_my_g_svg: list[str]
- _add_position_to_gv_graph()
- _add_argument_to_gv_graph()
- _update_gv_graph_nodes()
- _build_initial_gv_graph()
- _add_clause_to_problog_program()
- _add_virtual_evidence_to_problog_program()
- _add_term_and_query_to_problog_program()
- _build_problog_program()
- _problog_calculate()
+ get_problog_program_string()
+ add_argument()
+ get_position_copy()
+ add_position()
+ hide_arguement() (unused)
- _prepare_graph()
+ draw_graph()
}


class PositionNode(Persistent) {
{field} ... (dataclass)
{static} build_PositionNode()
}

class ArgumentNode(Persistent) {
{field} ... (dataclass)
{static} build_ArgumentNode()
}
}

"ArgumentGraph(Persistent)" "1..*" *-- "1" "Issues(Persistent)"
"PositionNode(Persistent)" "0..*" *-- "1" "ArgumentGraph(Persistent)"
"ArgumentNode(Persistent)" "0..*" *-- "1" "ArgumentGraph(Persistent)"

"Issues(Persistent)" *-- AllsemblyServer: <<create>>
IssueQueue *-- AllsemblyServer: <<create>>
"Issues(Persistent)" o-- IssueQueue


"Issues(Persistent)" o-- GraphRequest


GraphRequest o-- AllsemblyServices
LedgerRequest o-- AllsemblyServices

UserServices <--+ AllsemblyServices

package prob_logic.py {
class ProblogModel(Persistent) {
- {static} _v_my_rwlock: RWLockWrite
- _v_my_wlock
- _v_my_rlock
- _read_buffer_index: int
- _write_buffer_index: int
- _problog_queuy_results
- __setstate__()
+ calculate_marginals()
+ add_terms() (unused)
+ update_term_weights() (unused)
}
}

package speech_act.py {
class IndependentBid(orAsk) {
{field} ... (future fields)
}
}

package betting_exchange.py {
class BettingExchange(Persistent) {
}

class BettingMarket(Persistent) {
+ last_support_price: float
+ {field} market_id: int (same as arg_id)
}

class OrderBook(Persistent) {
- support_bids_index
- oppose_bids_index
- support_asks_index
- oppose_asks_index
+ ... () (future methods)
}

class Ledger(Persistent) {
+ index: OOBTree
==
I think there should
be only one Ledger
object (not one per
exchange)
}

class BettingContract(Persistent) {
+ {field} user1_anonid: bytes
+ {field} user2_anonid: bytes
+ purchase_price: float
+ position_locator
...
}
}

"ArgumentGraph(Persistent)" --* "ProblogModel(Persistent)"
"BettingMarket(Persistent)" "1..*" *-- "1" "BettingExchange(Persistent)"
"OrderBook(Persistent)" *-- "BettingMarket(Persistent)"

"IndependentBid(orAsk)" "0..*" *-- "OrderBook(Persistent)": support_bids (or _asks)
"IndependentBid(orAsk)" "0..*" *-- "OrderBook(Persistent)": oppose_bids (or _asks)

"Ledger(Persistent)" "0..*" --* "BettingContract(Persistent)"
"Ledger(Persistent)" "1" o-- "BettingMarket(Persistent)": support_ledger_ref
"Ledger(Persistent)" "1" o-- "BettingMarket(Persistent)": oppose_ledger_ref

"ArgumentGraph(Persistent)" --* "BettingExchange(Persistent)"
@enduml

@startuml
caption Figure 2
UserAgent -> "Django app": Authentication request

alt sucessful authentication

    UserAgent -> "Django app": service request with logged in session
    "Django app" -> AllsemblyServices: service request
    AllsemblyServices -> "Django app": response (for some services)
    "Django app" -> UserAgent: response

else authentication failed
    "Django app" -> UserAgent: Redirect to login page with error message

end
@enduml

The client-side code is generated by the Django templates in django_app/templates according to the code in django_app/views.py.

It communicates with the Allsembly™ server using RPC (provided by the RPyC library). The code on the Allsembly™ server side is in the rpyc_server.py module, especially the AllsemblyServices class.

The interaction proceeds as shown in Figure 2, above. Currently, since there is no interaction between users–each user is in a kind of sandbox of its own–which graph to display is dependent on which user is logged it (i.e., the userid in the encrypted login cookie).

The UserServices object is used to provide each of the specific services that require login, and it is provided with a login username by the django_app, which must have logged in the user. In most cases, the request is just added to a queue. The AllsemblyServices RPyC server object is running its event loop in a separate thread from the server main loop.

The server main loop, allsembly.allsembly.AllsemblyServer.server_main_loop(), is started by the script, “scripts/allsembly-server.py”, and it, in turn, starts the RPyC service in a separate thread. In the main loop, each queue is checked and the requests there are processed. Most of the processing involves calling functions in an ArgumentGraph object. There is a separate ArgumentGraph object for each graph and it is stored in a mapping that is persisted in the database. The ArgumentGraph object draws a new graph and calculates new probabilities with each client request that changes the graph. Currently, after the client makes a request that changes the graph, it immediately after that requests a new drawn graph. The intention is that in a future version, the client would be subscribed to a server sent events or websockets channel or be waiting on a long poll, to learn when a new graph is ready to be loaded.

Class detail

For now this section just contains the docstrings from the modules.

allsembly module

allsembly.py Module for building the Allsembly™ server

The server will be a script that reads in the configuration and then calls the server main loop function in this module. (See scripts/allsembly-server.py)

Overview: Allow users to contribute to an unfolding policy deliberation and obtain updates of the changes made by others.

Users contribute arguments, which are sets of statements that support or rebut other statements added earlier.

Comprehending subjective beliefs in statements as evidence of their truth, the software estimates the truth of statements using a Bayesian network that is constructed implicitly using the Problog module (a probabilistic logic programming language).

In order to obtain the prior probabilities, the software needs to aggregate the judgements of the users, and it does that using betting markets. A betting market elicits something close to the true belief in the truth value of the statement being bet on because, if the user is rational, they adjust their risk to their beliefs. (Although, of course, different users may have different risk tolerances.) NOTE: The betting market code is not fully implemented, and it is not used by the current server. Instead the server serves a client in a single user (“sandboxed”) mode and accepts whatever their bid is as the price. So the user can manually simulate what would happen if the price were thus and so.

The software will implement a betting exchange containing one market for every set of statements that is separately contestable. The markets use limit order books implemented as pairs of priority queues. More details will be in the external documentation, and some details can be found in the docstrings of the betting_exchange module.

This implements a server that provides the following services: User’s may connect to the server and: Contribute new statements and policy proposals Attempt to buy (bid on) betting contracts Get updates on the current state of the deliberation,

including new contributions, current prices, and estimates of the truth value of sets of statements.

class allsembly.allsembly.AllsemblyServer(user_dbfilename: str, arg_dbfilename: str)

server_main_loop function starts the server I put it in a class so that its data can be saved to be inspected after the server exits. It could also be inspected while the server is running if the caller is in a separate thread and uses locks to synchronize access

classmethod check_timeout(timeout_msec: Optional[int], start_time_msec: int, end_time_msec: int)bool

Convenience function: Return true if the difference between end_time_msec and start_time_msec is greater than or equal to timeout_msec indicating that the timeout has expired; Otherwise, return false.

server_main_loop(update_nofication_fileobj: TextIO, server_control: Optional[allsembly.allsembly.ServerControl], listen_port: int, listen_address: str = '::1', ipv6: bool = True)int

Call this function to start the server. Starts an RPyC (Python to Python RPC) service. Loops, processing requests, until calling process

signals it to stop by setting server_control._should_exit.

class allsembly.allsembly.ServerControl

Use this to tell the server_main_loop to exit, by setting “should_exit” by calling set_should_exit(). If you run server_main_loop from a separate thread, use ServerControlThreadSafe instead. If you re-run a server after it exits, you may need to reset “should_exit” first by calling reset_should_exit(). That is not currently necessary in AllsemblyServer.server_main_loop(…). A server should call should_exit() periodically, such as in a main while loop, to check whether it should exit.

allsembly.allsembly.process_one_argument_from_queue(issues: allsembly.argument_graph.Issues, graph_arg_queue: allsembly.rpyc_server.GraphUpdateArgQueue, order_queue: allsembly.rpyc_server.OrderQueue)bool

Return true if graph_arg_queue is not empty after processing one item from the queue; False otherwise

allsembly.allsembly.process_one_issue_from_queue(issues: allsembly.argument_graph.Issues, issue_queue: allsembly.rpyc_server.IssueQueue)bool

Return true if issue_queue is not empty after processing one item from the queue; False otherwise

allsembly.allsembly.process_one_order_from_queue(issues: allsembly.argument_graph.Issues, order_queue: allsembly.rpyc_server.OrderQueue)bool

Return true if order_queue is not empty after processing one item from the queue; False otherwise

allsembly.allsembly.process_one_position_from_queue(issues: allsembly.argument_graph.Issues, graph_pos_queue: allsembly.rpyc_server.GraphUpdatePosQueue)bool

Return true if graph_pos_queue is not empty after processing one item from the queue; False otherwise

rpyc_server module

Provides the Allsembly services via RPC. The AllsemblyServices class is an RPyC (RPC server) which can be

started to provide the services.

And it needs to be provided with queues onto which to place

the requests that will be handled elsewhere. (See the module “allsembly”.)

The caller maintains its own reference to the queues and reads

off each request to handle it.

Some requests directly get information; These are mediated through

GraphRequest and LedgerRequest instances and IssuesQueue, which should perhaps be renamed to IssuesRequest. Only GraphRequest and IssuesQueue are currently implemented. A GraphRequest object can provide a drawn graph and the full text of a position. In addition to adding an issue to the queue, the IssueQueue can provide the id of the next issue that will be used to identify it in the database and for any other requests referencing it.

The RPyC server is threaded, so multiple threads might try to access

information at the same time. That is the reasond for mediation of the requests through thread safe queues and request objects.

class allsembly.rpyc_server.AllsemblyServices(order_queue: allsembly.rpyc_server.OrderQueue, graph_arg_queue: allsembly.rpyc_server.GraphUpdateArgQueue, graph_pos_queue: allsembly.rpyc_server.GraphUpdatePosQueue, issue_queue: allsembly.rpyc_server.IssueQueue, graph_req: allsembly.rpyc_server.GraphRequest, ledger_req: allsembly.rpyc_server.LedgerRequest)

Provides a user with the services through remote procedure calls (RPC)

exception NotAuthenticated
class UserServices(services: allsembly.rpyc_server.AllsemblyServices, userid: bytes)

Provide services requiring user authentication. An instance can only be created by providing authentic credentials. Otherwise, the exception “AllsemblyServices.NotAuthenticated” is raised.

argue(issue: int, subuser: str, argument: bytes)bool

Always returns True. Just puts the request on the queue.

check_commitments_for_consistency()bool
delete_issue(issue: int)None

attempt to delete a presumably existing issue. No feedback given regarding failure or success. This function just puts the request on the queue.

get_arg_graph(issue: int)str

Returns the argument graph as a string. Currently that is a Graphviz produced graph in SVG format. See ArgumentGraph.draw_graph(…) in the argument_graph module, called via GraphRequest.

get_next_arg_graph(issue: int, last_graph_revision_number: int)Union[Tuple[str, int], int]

Returns the argument graph (svg string) and the graph revision number on success or an error code on failure: 1, if the call to get the graph returned early with no graph 2, if the call timed out. TODO: use an enumeration instead of an int for the error codes.

get_position_details(issue: int, pos_id: int)str

Returns the full text of a position.

propose(issue: int, subuser: str, proposal: bytes)bool

Always returns True. Just puts the request on the queue.

on_connect(conn: Any)None

called when the connection is established

on_disconnect(conn: Any)None

called when the connection had already terminated for cleanup (must not perform any IO on the connection)

class allsembly.rpyc_server.AuthCredentials(userid, password)
property password

Alias for field number 1

property userid

Alias for field number 0

class allsembly.rpyc_server.AuthCredentialsStr(userid, password)
property password

Alias for field number 1

property userid

Alias for field number 0

class allsembly.rpyc_server.GraphRequest(issues_from_db: allsembly.argument_graph.IssuesDBAccessor, issues_obj_not_safe_to_write: allsembly.argument_graph.Issues)

Provides a thread-safe way to get data from the argument graph. In particular, use this to draw the SVG representation of the graph or get the pre-drawn SVG.

class ErrCodes(value)

An enumeration.

class Error(code: ‘GraphRequest.ErrCodes’)
class allsembly.rpyc_server.GraphUpdateArgQueue(event_obj: Optional[threading.Event] = None)

wraps a deque allowing us to pass an optional Event object to notify another thread when items have been added and are, therefore, ready for processing.

class allsembly.rpyc_server.GraphUpdatePosQueue(event_obj: Optional[threading.Event] = None)

wraps a deque allowing us to pass an optional Event object to notify another thread when items have been added and are, therefore, ready for processing.

class allsembly.rpyc_server.LedgerRequest

Provides a thread-safe way to get ledger data from the betting exchange. The ledger contains all previous bets and bids. Use this to get a user’s commitment store (bets and bids). Gets a read lock on the ledger data.

class allsembly.rpyc_server.OrderQueue(event_obj: Optional[threading.Event] = None)

wraps a deque allowing us to pass an optional Event object to notify another thread when items have been added and are, therefore, ready for processing.

class allsembly.rpyc_server.UserInfoAndCookie(user_settings, encrypted_cookie)

Alias for field number 1

property user_settings

Alias for field number 0

demo module

user module

Classes containing user data, currently unused.

class allsembly.user.UserClientSettings

Future place for storing a user’s web client settings.

argument_graph module

Contains classes and functions for building arguments and for representing them in a graph, and also for representing a graph visually (currently by using GraphViz to produce an scalable vector graphics string). These classes and functions are used by the allsembly module. The functions to build arguments and insert them into the graph are

called in the allsembly module in response to requests made by users through the RPC module, “rpyc_server”, which in turn come through the WSGI server in the demo module, which in turn come through the web server initiated by the client code in the web directory of this package, “web/allsembly_demo.xsl”.

class allsembly.argument_graph.ArgumentGraph(issue_name: str)

Stores all of the arguments for an issue and generates the visual representation of the graph using PyGraphViz and the probabilistic logic program representation using Problog.

add_argument(argument: allsembly.argument_graph.ArgumentNode)Optional[int]

Enter a new argument into the arg_node_index And return its id number

add_position(position: allsembly.argument_graph.PositionNode)Optional[int]

Enter a new position into the pos_node_index And return its id number. For the convenience of producing the drawn argument graph/diagram as a tree, the positions that are the same are duplicated in the graph. However, just one should be referred to in the BettingExchange and in the Problog model. So, for each position node, a list is kept of other position nodes that represent the same position. The one with the smallest pos_id will be used by the BettingExchange and the Problog model.

class allsembly.argument_graph.ArgumentNode

An argument which has as its conclusion either that its parent is true (when self.supports_conclusion is true) or that its parent is false

class allsembly.argument_graph.Issues

Stores argument graphs for all issues.

class allsembly.argument_graph.IssuesDBAccessor(db: ZODB.DB.DB, read_only: bool = False)

Instantiate one of these with a ZODB database object and pass it to another thread or threads. In the other thread(s) obtain an issues object from the database using the context manager by calling get_context() as part of a ‘with’ statement, e.g.:

… # set backend db = ZODB.DB(my_backend) issue_access = IssuesDBAccessor(db) … # pass issue_access into thread class and start thread # in new thread (‘my_iss_access’ is a local variable corresponding to # ‘issue_access’) with my_iss_access.get_context() as issues:

diagram_svg: str = issues.graph[issue_id].get_drawn_graph()

class allsembly.argument_graph.PositionNode

A premise or a conclusion of an argument

prob_logic module

Interface to the probabilistic logic code (currently using the Problog python package).

class allsembly.prob_logic.ProblogModel

Problog terms and rules that model the argument graph and allow Problog to do the inference.

Note that the current Problog models (constructed in ArgumentGraph methods, _add_clause_to_problog_program(), _add_virtual_evidence_to_problog_program(), and _add_term_and_query_to_problog_program()) are not necessarily the best for all kinds of arguments. The For example, the inference in causal arguments might only go in one direction. Dealing most appropriately with all major types is a longer-term goal.

add_terms(new_terms: str)None

updates the model from string

calculate_marginals()None

calculates the posterior probabilities

update_term_weights(updated_weights: Dict[str, float])None

updates model weights from dict

betting_exchange module

Classes representing the betting exchanges. Not fully implemented. These are not used in the current version. There will be one exchange per ‘issue’ containing one

market per position.

An order book (see https://en.wikipedia.org/wiki/Order_book and

https://www.5minutefinance.org/concepts/the-limit-order-book) is used to keep track of bids. It works like an ‘auction market’ (see https://www.investopedia.com/terms/a/auctionmarket.asp). Since this is a betting market, both sides of a sale make bids, but one side bids to place a bet on success and the other side bids to place a bet on failure. A betting contract can be made between bettors agreeing on price (i.e. price “p” for success and price “1-p” for failure). Ordinarily, this means: if I am bidding on success, I will pay you “1” if there is failure and you will pay me “1” if there is success.

These markets are different. There can be partial success and failure.

The contracts mean the following. For success (i.e., the final estimation of the probability of truth of a position): I am bidding on success, which means a final estimate that is greater than or equal to my bid; I will pay you the amount of the shortfall. You are bidding on failure, which means a final estimate that is less than or equal to your bid. You will pay me the amount by which the final estimate exceeds your bid. For example, if I bet 70 “cents” to your 30 “cents”, and the final estimate is 50 “cents”, I pay you 20 “cents”. If the final estimate is 80 “cents”, you pay me 10 “cents”. You stand to lose at most 30 “cents”, and I stand to lose at most 70 “cents”, but if the market price was 70 “cents” that indicates a high probability of success; the odds are in my favor and my low risk is mitigated by a high price and your high risk is mitigated by a low price.

There are special conditions needed for price to correspond

with probability of success. Participants cannot be withholding information that, if known, would swing the estimate. That is, if I know a decisive reason why a position that currently has a high price is actually false, no one else knows it, and I wait until the near the end of the exercise to share it, then all along, the price was very wrong as an indicator of the probability of success. However, the market is supposed to give participants an incentive to share their best evidence early. They cannot know that no one else knows it, and they know that others have an incentive to investigate and find it out, in any case. So, their best chance of getting the benefit (in the form of ‘profit’) from their knowledge is to share it early. If that happens, then the price might swing a lot early in the trading, but later stabilize. This does not mean that the position is probably true or that no decisive reason that it is false exists. But it is probably true to the best of the knowledge of the group and of their ability to learn relevant pro and con evidence in the time allotted. The “Efficient Market Hypothesis” and related empirical results seeming to confirm it says some of what I wrote above in a different way. I also think I see a reasonable analogy with the way that scientists get credit for their work by publishing it. They have an incentive to publish before others working on similar projects (or so I understand); they cannot change the estimate, accepted within the community, of the probability of the truth of theories in any other way than by offering evidence (in an ideal characterization); and that evidence is presented publicly and peer reviewed.

A “ledger” keeps track of the bets. There will also be a secondary market, integrated with the primary market.

Participants may change their minds. In that case they would want to sell their contracts. That market has both buyers and sellers, not only buyers, just like the securities exchange markets. A seller will issue an “ask” and it can be matched with any bid. In other words, as a buyer I might buy a new contract made with another buyer who is buying the opposite position from me (e.g., “failure” to my purchase of “success”), or I might buy out someone else’s obligations in an existing contract (e.g. they’d bet “success” previously and now want to sell, and I can take over their bet of “success” at a new price, possibly lower than what they paid). In that case, it is useful to look at it as though they have ‘ante’d up’ their betting contract price. If they paid 70 cents, and I buy their contract for 60 cents, their 10 cents remains with the contract. The contract will still pay up to 70 cents to the other bettor on the contract, but 10 cents comes from the previous contract owner before any money is taken from me. So I break even at 60 cents which is the same as if I’d purchased a new contract for 60 cents. Suppose, again, that the original bettor paid 70 cents, but this time I buy their contract for 80 cents. I still only have an obligation to pay the other bettor on the contract up to 70 cents, and I stand to win up to 30 cents, but I have already paid 10 cents to the original bettor (and they received ‘back’, in essence, their ante’d sum of 70 cents, which I replaced). So I only break even at 80 cents. And I stand to gain only up to 20 cents on net, which is the same as if I’d purchased a new contract for 80 cents. In terms of accounting, though, I don’t think I would want to modify the ids of the parties on the ‘contract’ written in the ledger. Instead I think there should be a line indicating that it was sold and to whom and for how much. To find out the current owner, we would work our way forward through the chain of sales, or to start from a current owner and find out which contract they are obligated under: first find the sale item with the presumed current owner’s id; check that there is no later sale, then find the mentioned contract id number.

Purchasing or selling a betting contract depends on there being a

counterpart ready to sell/buy such a contract. In the meantime, prospective buyers/sellers have to wait to complete their purchase/sale until their bid or ask is accepted by a counterpart seller/buyer. In a large enough market, that condition might normally, or even constantly, be met.In markets, in general, there are often _market-makers_, and in betting situations, there are often _book-makers_ who contract for bets using a guess as to their value plus an integrated small transaction cost. The book-maker takes the risk of getting stuck with a contract not worth its value and having to pay out in order to broker sales and make money from the transaction markup. The book-maker or market-maker benefits the market by adding liquidity. There will generally always be someone to buy from or sell to at reasonable terms even if that someone is the book-/market- maker rather than another person intrinsically interested in the betting outcome. Using “Hanson’s market scoring rule”, a market-maker for betting markets could be automated, and that is described in a paper: Berg, Henry and Todd A. Proebsting (2009), “Hanson’s Automated Market Maker”, _Journal of Prediction Markets_, Vol. 3, Iss. 1, pp. 45-59. It is not an essential feature but could be added at some point as an enhancement.

class allsembly.betting_exchange.BettingExchange

A container for all of the markets. There is only one per issue.

class allsembly.betting_exchange.OrderBook

Order book matching algorithm matches highest bids on each side (support or oppose) for pareto optimal matching. See https://blogs.cornell.edu/info4220/2016/03/17/nyse-automated-matching-algorithm/ For that purpose, each side’s orders are stored in priority queues. (Lists will be made into priority queues using the algorithm from the heapq module of the Python standard library.) Re-sale of old betting contracts is accommodated by having

separate seller queues (self.support_asks and self.oppose_asks).

Since sellers cannot sell to other sellers, the order book needs

to keep track of sellers separately from buyers. There will be four queues: bids for support contracts, bids for oppose contracts, offers for support contracts, offers for oppose contracts. Bids for support contracts can be matched with bids for oppose contracts to enter the parties into a new contract or with support offers to transfer an old contract, and correspondingly with bids for oppose contracts and their counterparts.

A user also may not buy from or sell to self.

speech_act module

Classes for representing the kinds of actions that a participant can take in a dialogue. These are used in user requests to the Allsembly server. They will correspond to the formal dialogue specification, which is not yet included with the source package. The formal specification currently exists only as an early draft.

class allsembly.speech_act.ArgueSpeechAct(argument: allsembly.speech_act.Argument)

Request for adding an argument into the argument graph.

class allsembly.speech_act.Argument(pro_or_con: allsembly.speech_act.ProOrCon, first_premise: allsembly.speech_act.Premise, target_position: allsembly.speech_act.UnconcededPosition, bid_on_target: Optional[allsembly.speech_act.Bid], argument_about_first_premise: Optional[allsembly.speech_act.ChainArgument], remaining_premises: List[Tuple[allsembly.speech_act.Premise, Optional[allsembly.speech_act.ChainArgument]]])

Required elements of an argument for an ArgueSpeechAct

class allsembly.speech_act.Ask

Represents an offer to sell an existing bidding contract held by the user-owner. Not implemented yet.

class allsembly.speech_act.Bid(max_price: float, min_price: float, amount: int)

Represents an offer to buy a bidding contract. Bid prices should be between 0 and 1 in certain increments (e.g., .001). Which position it is for and whether it is pro or con the position is determined by context. If it is a bid on the conclusion of an argument, it will have the same pro/con value as the argument. If it is a bid on a premise, it will be a pro bid.

class allsembly.speech_act.ChainArgument(pro_or_con: allsembly.speech_act.ProOrCon, first_premise: allsembly.speech_act.Premise, argument_about_first_premise: Optional[allsembly.speech_act.ChainArgument], remaining_premises: List[Tuple[allsembly.speech_act.Premise, Optional[allsembly.speech_act.ChainArgument]]])

An chain of arguments to be paired with a position. Since it doesn’t give its target. The target can be implicit: whatever it is paired with. So the chain of arguments can be constructed recursively. A user might want to support their asserts with complex chains of arguments. That way they can ensure that the value of their assertions from the start reflects the chain of support that they know about.

class allsembly.speech_act.ExistingPosition(position_id: int)

A position already entered into the graph.

class allsembly.speech_act.HypotheticalPremise

Not implemented yet. A normal bid of support has to be greater than 50 ‘cents’ of a ‘dollar’, indicating support and not indifference (i.e., > 50% confidence). Confidence in a HypotheticalPremise has to be greater than that in any of its mutually exclusive alternatives, expressing a belief that it is the best candidate for truth, so far, among the alternatives. Therefore, asserting a HypotheticalPremise entails stating what the mutually exclusive alternatives are and disclosing one’s level of confidence for all but one of them. It must be consistent with all of them possibly summing to one (when each has a value between zero and one), and the asserted premise must have the largest confidence value among the alternatives.

class allsembly.speech_act.IndependentBid(max_price: float, min_price: float, market_locator: allsembly.speech_act.MarketLocator, pro_or_con: allsembly.speech_act.ProOrCon)

Represents an offer to buy a bidding contract specifically pro or con a specific position. Bid prices should be between 0 and 1 in certain increments (e.g., .001).

class allsembly.speech_act.InitialPosition(conclusion: str, premises: List[Tuple[allsembly.speech_act.Premise, Optional[allsembly.speech_act.ChainArgument]]] = [])

In an inquiry dialogue, these will be the same as arguments, but they will have an explicit conclusion rather than a target, and premises are optional. In a deliberation, these will be either action proposals or evaluation criteria. For now, no bid is required on an initial position. If it is in an inquiry dialogue, there will probably be only one initial position.

class allsembly.speech_act.MarketLocator(issue_id: int, position_id: int)

Issue id and position id, needed to locate a market within the betting exchange

class allsembly.speech_act.Premise(statement: str, bid_on_statement: allsembly.speech_act.Bid)

An ordinary premise. A commitment in the form of a bid is required to assert an ordinary premises. That expresses that the user has confidence in the premise.

class allsembly.speech_act.ProOrCon(value)

An enumeration.

class allsembly.speech_act.ProposeSpeechAct(initial_position: allsembly.speech_act.InitialPosition)

Request for adding an InitialPosition to the argument graph

class allsembly.speech_act.UnconcededPosition(position_id: int)

A position that is already in the graph. When it is used as a premise, no bid is required. Participants may use other participants’ premises in their arguments even when they do not believe them to be true. This is necessary for participants to make reductio style arguments and draw out contradictions in others’ positions.

Future design

Design for confidentiality

This section and the next two sections, “Design for integrity” and “Design for scalability” describe features that are not expected to be added to the prototype. They would be added later, in a stage of development beyond the prototype. But they contain important ideas to keep in mind while developing the prototype.

_images/anonymity_preserving_registration.svg

The figure above shows a design with a separate server for login and a separate server for registration, which could be hosted by different organizations. The organization hosting the Registration server verifies the new user’s identity but never sees the user’s userid or passwords; those are encrypted on the client side using the public key of the organization hosting the login server.

The basic idea is that records containing the userid and records containing the personally identifying information (PII) are not connected by any common fields, and cannot be connected without the private key of the ID guardian. (One can imagine the ID guardian as a law firm with it’s private key stored offline in a vault or something or escrow service that is somehow contractually bound not decrypt the userid field in a database record provided to it from the registration database by the registering organization without, say, a court issued warrant.)

The idea does not necessarily require multiple organizations or servers and separate DBMSes, though. It can be accomplished in a single database managed by one DBMS. A record in the login table contains, e.g., userid and password. A record in the registration database contains, e.g., real name, email address, phone number, address, etc., and encrypted userid (that was encrypted by the client’s device before it reached the server and that can only be decrypted using the private key held offline).

The flow is as follows:

  1. User connects to the registration server and provides personally identifying information that will be used to verify their identity and provides a desired userid that is encrypted with a public key of the login server (the Allsembly™ server). The corresponding private key is stored unencrypted on the login server.

  2. The registration server sends only the encrypted userid and password to the login server.

  3. The login server informs the registration server whether the userid is unique. If it is not, then the registration server informs the user to select a new userid.

  4. If the userid is unique, the login server stores the userid and password in its login database (table) with a flag indicating that it is not yet activated (because the user has not yet been verified).

  5. The registration server discards the encrypted password and further encrypts the already encrypted userid with the public key of the ID guardian. It stores this encrypted userid with the PII in the registration database (table). (The encrypted userid should not be able to be compared with a the same userid encrypted with the same key at a different time. Therefore, this saved userid might have been separatedly encrypted from the one sent to the login server, with a different random padding.)

  6. The next step is not fully worked out. It involves passing a token that proves that the user has been verified. It possibly involves cryptographic signatures and is done in such a way that neither the login server nor the registration server may use its counterpart to send arbitrary encrypted messages to the user. (Imagine that there is limited trust between them.)

  7. After the user has been verified, the user logs in to the login server.

Subsequently, the user uses the login server to change their password. (Or the client might log in to both servers to change passwords, separately on both, in such a way that it only requires one step for the user.)

If the user needs to change their PII, they log in to the registration server using their email adress. (They could potentially use the same password hashed and salted differently.)

To enable recovery of a forgotten userid, userid could be stored by the registration server (or in the registration database) encrypted with the user’s password. The password is not stored bythe registration server, except possibly in hashed form, so the userid is encrypted using the password hashed with a different algorithm and/or parameters. In that case, the user logs into the registration server using email address and password; then, the registration server sends the encrypted userid to the user who decrypts it client-side.

There seems to be no way to recover a forgotten password without temporarily breaching anonymity for that user. Although the user has both email address and userid, the user cannot authenticate with the login server using an email address without connecting the identities, so password reset via email would only get a user access to their profile on the registration server.

If the ID guardian is a separate person/organization from the organization hosting the login server, then the representatives of both organizations are needed to revoke anonymity.

NOTE: THIS IS NOT A PROOF. THERE COULD BE IMPORTANT FLAWS IN THIS APPROACH.

However, I believe the approach has the following desirable properties:

  1. Attacker cannot learn identities of users by listening to traffic between client and server, without other information.

  2. Attacker cannot learn identities of users by obtaining either or both databases (tables).

  3. Attacker cannot learn the identities of existing users by controlling either or both servers. (Such an attacker can only learn identities of new registrants andd users resetting their passwords.)

An additional issue for confidentiality is the connection of bets on positions to userids. To prevent a whole profile of a user’s argumentative commitments being built up by an attacker, ids used to match users with betting contracts can be anonymized. The list of anonymized ids that correspond to a userid can be maintained as an encrypted field in the database table containing user information. The encryption key can be the user’s password itself hashed in a different way and with a different salt than the one stored for login. So, when a user logs in they send both hashes. The server decrypts the anonymized ids field and caches it for a while to provide the services to the user. It remains encrypted at-rest and anytime the user logs out or the cache expires.

Design for integrity

This section describes features that are not expected to be added to the prototype. They would be added later, in a stage of development beyond the prototype. But it contains important ideas to keep in mind while developing the prototype.

To provide accountability that the data stored on the server matches the users’ activities, the server should send cryptographically signed receipts for bids, asks, and betting contract purchases and sales.

The client can store them in local memory and can also store pending transactions to re-send in case it does not get a confirmation receipt.

Likewise, users should not be able to repudiate their orders or betting contracts. So, those should be cryptographically signed by users as well.

Design for scalability

This section describes features that are not expected to be added to the prototype. They would be added later, in a stage of development beyond the prototype. But it contains important ideas to keep in mind while developing the prototype.

The current design was not made for massive scalability. This section is just to add information about what can be done to scale the existing design without extensive changes.

First of all different database backends can be dropped in as replacements. The ZODB API supports ZEO, which is a multi-process available, networkable DBMS. It also supports a “Relstorage” backend, which allows for storage in SQL databasaes such as MySQL, PostgreSQL, and commercial enterprise-scale SQL DBMSes. However, it still stores the objects pickled. An alternative would be to use an SQL DBMS through an object-relational mapper (ORM) to possibly eek out some better concurrency. It might not be necessary to do that, though.

I assume that I will eventually have to replace Problog. It is slow because of the knowledge compilation step. It is intended to improve processing when many queries are made against the same graph, which is not quite my use case. A promising alternative possibility may be to use iterative join-graph probagation (IGJP), which is an “anytime” algorithm for evaluating Bayesian Networks. At each iteration, it gets closer to the exact solution (but it is only exact inference when the “join graph” is a “join tree”). So, the graph could be updated with the approximate values computed so far. There is an existing open source implementation in a software package called “merlin”. It could be integrated into the Allsembly server. It is single threaded. However, I would like to investigate parallelizing the algorithm. There is a dependency of a node on the node from which it receives a message in IJGP (and in other message passing belief propagation algorithms). But it seems like there would be many graphs that would have multiple semi-independent paths parts of which could have their calculations independently computed by separate worker threads. I also intend to investigate algorithms for dynamically updating graphs without losing all of the previous inference or knowledge compilation work.

Regarding exact versus approximate inference, since the primary purpose of evaluating the positions in the dialogue after every move and not only at the end of the dialogue or at other key points, such as when considering extending the time-limit for the dialogue, is to guide participants in choosing the most efficient next moves, a good enough approximation should be adequate to the purpose. Exact inference could be used at the end and, possibly also, at other key points in the dialogue. The approximation needs to be good enough that it does not or only rarely does lead participants to expend their efforts attacking or supporting positions that are not in need of it or doing so when there would be much better targets for attack or support given their beliefs about the evidence and the correctness of various positions.

The evaluated estimate of the confidence that the group of participants as a whole has in aggregate in the weight of evidence supporting a position functions in a similar way to a burden of proof. When the value is less than 50%, it indicates that advocates of that position will ‘lose’ (that is, not have their preferred position be considered justified) if nothing changes before the end of the dialogue. Therefore, as long as they continue to believe that there is evidence to decisively support the position, it is incumbent on them to provide it (subject to their priorities–some other position might be more important to them to focus their efforts on). On the other hand, when the position assessed at greater than 50%, those opposed have the burden of producing evidence while they continue to believe that the position (in and of itself or because of what it supports) is important and that there is decisive evidence opposed to it. So long as the appoximation guides, with high frequency, the participants to make the same strategic assessments as the exact result would, the system will be functioning well (efficiently).

Other than that, I would propose parallelising the Allsembly™ server using separate processes, with each process handling one or more complete issues (whole argument graphs). The separate processes would have to coordinate with regard to users needing to participate in issues–a user may participate on multiple issues overlapping in time–being handled in another process or being handled in multiple processes.

Such changes, which are not too extensive with regard to modifications to the existing design, might enable a lot of scalability and be sufficient for the Allsembly™ project beyond the prototype. Massive scalabily could be part of a separate (we could hope funded) project if it, at some point, becomes a widely used piece of software.

Localization

UPDATE: Part of this can be handled using Django. More information to be added. I would still like to use XML in order to give clients more control over the presentation, but XML containing the translated strings can be generated by Django. The default XSL can also be generated by Django, but in advance, rather than on-the-fly.

My current intention is to implement localization by putting the strings in XML files and using XSLT transforms to select the appropriate string and to combine it with any dynamic data.

The Accept-Language header or the user’s language selection (stored in a cookie or embodied in a special path, such as /en/file.xml) can be used to select the appropriate string.

Directives in XML tags can encode the correct way to transform dynamic data or other words in the text that are dependent on the dynamic data.

For example, if the dynamic data is a number, another word might have to be sometimes singular and sometimes plural:

“You have x token(s) remaining.”

Sometimes there might be special words when the number is 2 or 3.

Sometimes words might need to be marked with affixes or suffixes to do with grammatical role.

These things could be accomodated with regular expressions and XSLT regular expression functions. The same or similar methods can be used to accommodate differences in date, time, and number formats or separators between items in sequences. Alternatively, some format preferences could be handled using special code in the server. For example, the server could be given the user’s preference for probabilities as decimal values or percentages and preference for dot or comma separating the whole and fractional parts of a number, etc., and the server would thenceforth produce probabilities that way, or it could be handled with XSLT and (possibly complex) regular expressions or conditionals.

Benefits of this approach could be:

  • XML and XSLT processing are built into web browsers.

  • Javascript isn’t required (but could be used).

  • XML schemas could validate the documents using readily available tools

  • XML is a commonly used standard. The developer might already know it; otherwise, it may be useful to learn and reuseable knowledge.

Project development roadmap

Accessibility basics

  • Add text description to SVG diagram of the dialogue graph that describes the graph structure.

  • Add keyboard controls for zooming and panning the SVG image.

  • Maintain the page layout reasonably when the page is scaled with ctrl-+/-.

Core features

  • Enable a user to reuse existing positions to support or oppose other arguments.

  • Enable a user to mark two or more positions as the same or as mutually exclusive (the “Relate” feature).

  • Enable a user to add posts to a per-position informal discussion (the “Discuss” feature).

  • Implement the betting markets, including the order book, the matchin algorithm, and the ledger of bids and betting contracts.

  • Enable the (re-)selling of existing betting contracts.

  • Implement detection of inconsistent bids and bets and prevent users from making such.

  • Implement detection of inconsistencies in other commitments (as the result of reductio arguments and some “Relate” actions) and enable users to resolve inconsistencies by selling betting contracts.

  • Enable topic proposal.

  • Enable users to interact on the same dialogues (not only single user sanboxes and simulated multi-user activity).

  • Add policy proposals as the initial position type and add a policy evaluation positon type and the other policy decision features.

  • (For possible inclusion in the prototype:) Add encryption of anonymous IDs that connect a user to their bids and bets in the ledger.

  • (For possible inclusion in the prototype:) Add asymmetric encryption of userids in a registration record to prevent its connection with user login records, as described under Design for confidentiality. If this is added in the prototype, it will be an optional feature and only use a single public key, not two.

Demo features

  • Enable deletion of positions.

  • Add a guest login option.

  • Allow a user to simulate multiple users by selecting the current user from a dropdown menu.

More to be added to this section, including categorizing items as short-, medium-, or long-term and possibly including beyond-the-prototype items, later.

Coding standards

NOTE: This project is uses typed Python. The mypy settings are in the file setup.cfg. It uses all of the settings from mypy --strict (as of mypy version 0.812) except --disallow-subclassing-any, which is not used (especially) since some classes subclass “Persistent” in order to take advantage of the ZODB database.

The modules may be type-checked using the following command from the command line from within the main directory of the project: mypy allsembly/*.py. It should report no errors.

For more information about mypy or about optional type-checking in Python, see http://mypy-lang.org.

Mypy may be installed with: python3.7 -m pip install mypy.

Settings for Pylint (see https://pylint.org) are also in the setup.cfg file. With Pylint installed, the modules may be linted by running Pylint from the command line as follows: pylint --rcfile=setup.cfg allsembly.

The Pylint settings in setup.cfg correspond to: pylint --disable=all -enable F,E,W --disable=python3. This will report many warnings, mostly about unused variables and two errors about imports. The errors are false positives as far as I can tell. The module is successfully using the imported class (OOBTree). I plan to attend to the warnings. I also look at the output of the refactoring module even though it is disabled in setup.cfg, and I plan to implement some of its refactoring recommendations.

Guidelines

Python:

  • In the server modules, don’t use exceptions and don’t allow exceptions to escape your functions (it is okay in scripts). Unhandled exceptions could be the source of unplanned server termination that could affect data integrity or worsen user experience even though the server can be restarted. I will be reviewing the “returns” Python library to help with this in the future.

  • Avoid the use of variables when possible and reasonable and annotate variables as “Final” whenever they do not need to be reused. Reuse few variables.

  • Avoid unnecessary references. I am considering the “Pysistent” Python library to help with this in the future.

  • Make use of the context manager (“with …:” blocks) for resource managemant.

  • Mark private data and functions with a leading underscore (Pylint will catch unintended uses).

  • Use all caps for constants (Pylint will catch unintended uses).

  • Use docstrings to document all modules, classes, and nontrivial functions.

JavaScript:

  • Use only as much JavaScript as necessary.

  • Prefer declarative code to imperative whenever imperative code is not needed to provide a significant advantage.

  • Use comments to document all files and nontrivial classes and functions.

Other languages:

Adopt similar guidelines.

Secure coding:

See the “OWASP Secure Coding Practices Quick Reference Guide” at: https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/migrated_content

Choosing library dependencies:

  • Choose the most widely uses and respected libraries for encryption.

  • For other purposes, choose more or less production ready libraries.

How it works

For now, see docs/how_it_works.md in the source code repository.

Other uses

For example, the software could be adapted to provide a way for writers to distribute bounties to people who help to improve their writing by finding shortcomings in the reasoning. Aside from adapting the code, if one wants to use it in such a way, one needs to be cognizant of any laws in one’s jusrisdiction restricting contests, including ones that contain an element of chance.

Individuals or small groups could provide bounties for a recommended resolution of some disagreement.

Private groups could use it without bounties to deliberate among themselves. If any money is exchanged, for example, grafting a parimutuel betting scheme onto the betting market aspect of the software, then one needs to be cognizant of any laws in one’s jusrisdiction restricting gambling, and possibly also laws in the jurisdictions of each of the participants if those are different.

While Allsembly™ as a community is intended to be independent in the sense of participants choosing their own issues to dialogue about, the software could also be adapted for public consultation in which issues and possibly some other parameters for the dialogue are decided by the organization seeking the policy recommendation to be generated through the dialogue.

It could be used for opinion research in conjunction with opinion polls, e.g., by comparing opinion before and after deliberation (as with Deliberative Polling®) or by comparing the recommendation yielded by deliberation (if any) with an opinion poll result.

It could be used for argumentation or AI research by mining (only completely anonymous) data, such as for training an artifical agent how to argue or deliberate based on the activity of the human participants.