diff --git a/forum-network/notes/forum.md b/forum-network/notes/forum.md
index 2a3f76a..8057d1b 100644
--- a/forum-network/notes/forum.md
+++ b/forum-network/notes/forum.md
@@ -10,3 +10,11 @@ Effective power can be considered as a flow rate of posts; (value per post) / (d
Internal energy is similar to Forum total value / DAO total reputation
Total available reputation is similar to thermodynamic free energy
+
+---
+
+Examples to add:
+
+- Incinerator
+
+- Negatively cite a zero-value post -- intent is to show how governance might cite a post as a counter-example
diff --git a/forum-network/notes/matrix.md b/forum-network/notes/matrix.md
new file mode 100644
index 0000000..f5a0f5b
--- /dev/null
+++ b/forum-network/notes/matrix.md
@@ -0,0 +1,9 @@
+Matrix is a communications network.
+It has a client-server, server-server decentralized architecture.
+Rooms are synced (eventually consistent) among all servers with clients participating in the room.
+
+Matrix supports "Application Services", which are limited to funcion in a passive mode, meaning they only piggyback on top of the existing protocols.
+
+Synapse, a Matrix server implementation, supports "Modules"
+
+The Matrix devs recognize the need for a robust reputation system and are in pursuit of funding and development for that purpose.
diff --git a/forum-network/src/classes/dao/forum.js b/forum-network/src/classes/dao/forum.js
index 6a6db0f..7826cca 100644
--- a/forum-network/src/classes/dao/forum.js
+++ b/forum-network/src/classes/dao/forum.js
@@ -3,7 +3,7 @@ import { Action } from '../display/action.js';
import { Actor } from '../display/actor.js';
import params from '../../params.js';
import { ReputationHolder } from '../reputation/reputation-holder.js';
-import { displayNumber, EPSILON } from '../../util.js';
+import { displayNumber, EPSILON, INCINERATOR_ADDRESS } from '../../util.js';
const CITATION = 'citation';
const BALANCE = 'balance';
@@ -27,11 +27,11 @@ class Post extends Actor {
.reduce((total, { weight }) => total += weight, 0);
if (leachingTotal > params.revaluationLimit) {
throw new Error('Post leaching total exceeds revaluation limit '
- + `(${leachingTotal} > ${params.revaluationLimit})`);
+ + `(${leachingTotal} > ${params.revaluationLimit})`);
}
if (donationTotal > params.revaluationLimit) {
throw new Error('Post donation total exceeds revaluation limit '
- + `(${donationTotal} > ${params.revaluationLimit})`);
+ + `(${donationTotal} > ${params.revaluationLimit})`);
}
if (this.citations.some(({ weight }) => Math.abs(weight) > params.revaluationLimit)) {
throw new Error(`Each citation magnitude must not exceed revaluation limit ${params.revaluationLimit}`);
@@ -70,9 +70,14 @@ export class Forum extends ReputationHolder {
}
async addPost(authorId, postContent) {
+ console.log('addPost', { authorId, postContent });
const post = new Post(this, authorId, postContent);
this.posts.addVertex(post.id, post, post.getLabel());
for (const { postId: citedPostId, weight } of post.citations) {
+ // Special case: Incinerator
+ if (citedPostId === INCINERATOR_ADDRESS && !this.posts.getVertex(INCINERATOR_ADDRESS)) {
+ this.posts.addVertex(INCINERATOR_ADDRESS, { name: 'Incinerator' }, 'Incinerator');
+ }
this.posts.addEdge(CITATION, post.id, citedPostId, weight);
}
return post;
@@ -171,22 +176,44 @@ export class Forum extends ReputationHolder {
for (const citationEdge of citationEdges) {
const { weight } = citationEdge;
let outboundAmount = weight * increment;
- const balanceToOutbound = this.posts.getEdgeWeight(BALANCE, citationEdge.from, citationEdge.to) ?? 0;
- // We need to ensure that we at most undo the prior effects of this post
- if (initialNegative) {
- outboundAmount = outboundAmount < 0
- ? Math.max(outboundAmount, -balanceToOutbound)
- : Math.min(outboundAmount, -balanceToOutbound);
- }
if (Math.abs(outboundAmount) > EPSILON) {
- const refundFromOutbound = await this.propagateValue(citationEdge, {
- rewardsAccumulator,
- increment: outboundAmount,
- depth: depth + 1,
- initialNegative: initialNegative || (depth === 0 && outboundAmount < 0),
- });
+ const balanceToOutbound = this.posts.getEdgeWeight(BALANCE, citationEdge.from, citationEdge.to) ?? 0;
+ let refundFromOutbound = 0;
- outboundAmount -= refundFromOutbound;
+ // Special case: Incineration.
+ if (citationEdge.to.id === INCINERATOR_ADDRESS) {
+ // Only a positive amount may be incinerated! Otherwise the sink could be used as a source.
+ if (outboundAmount < 0) {
+ this.scene?.flowchart?.log(`style ${citationEdge.from.id} fill:#620000`);
+ throw new Error('Incinerator can only receive positive citations!');
+ }
+ // Reputation sent to the incinerator is burned! This means it is deducted from the sender,
+ // without increasing the value of any other token.
+ this.actions.propagate.log(citationEdge.from.data, { name: 'Incinerator' }, `(${increment})`);
+ } else {
+ // We need to ensure that we at most undo the prior effects of this post
+ if (initialNegative) {
+ outboundAmount = outboundAmount < 0
+ ? Math.max(outboundAmount, -balanceToOutbound)
+ : Math.min(outboundAmount, -balanceToOutbound);
+ }
+
+ // Recursively propagate reputation effects
+ refundFromOutbound = await this.propagateValue(citationEdge, {
+ rewardsAccumulator,
+ increment: outboundAmount,
+ depth: depth + 1,
+ initialNegative: initialNegative || (depth === 0 && outboundAmount < 0),
+ });
+
+ // Any excess (negative) amount that could not be propagated,
+ // i.e. because a cited post has been reduced to zero value,
+ // is retained by the citing post.
+ outboundAmount -= refundFromOutbound;
+ }
+
+ // Keep a record of the effect of the reputation transferred along this edge in the graph,
+ // so that later, negative citations can be constrained to at most undo these effects.
this.posts.setEdgeWeight(BALANCE, citationEdge.from, citationEdge.to, balanceToOutbound + outboundAmount);
totalOutboundAmount += outboundAmount;
diff --git a/forum-network/src/classes/dao/validation-pool.js b/forum-network/src/classes/dao/validation-pool.js
index 4781608..d288b29 100644
--- a/forum-network/src/classes/dao/validation-pool.js
+++ b/forum-network/src/classes/dao/validation-pool.js
@@ -54,8 +54,7 @@ export class ValidationPool extends ReputationHolder {
|| [null, undefined].includes(duration)
) {
throw new Error(
- `Duration must be in the range [${params.voteDuration.min}, ${
- params.voteDuration.max ?? 'Inf'
+ `Duration must be in the range [${params.voteDuration.min}, ${params.voteDuration.max ?? 'Inf'
}]; got ${duration}`,
);
}
diff --git a/forum-network/src/index.html b/forum-network/src/index.html
index d587684..03916ad 100644
--- a/forum-network/src/index.html
+++ b/forum-network/src/index.html
@@ -21,6 +21,9 @@