feat(authorship): add publishing permissions #42

Closed
opened 2025-08-07 13:43:45 +00:00 by chartgerink · 2 comments
Owner

When working with co-authors, all authors currently need to approve every minor change. Even one reference update requires approval from all the co-authors again. In my personal experience, this has led to serious publication delays.

In order to make the publishing process more manageable, I would like to introduce publishing permissions for authorships. For example, if we have authors A, B, and C, we can have various scenarios:

  1. All authors need to approve the publication before it gets published (A, B, C).
  2. A delegated approver is assigned (A delegates C, meaning B + C need to approve). This is a one-to-one relation (delegate to one person)
  3. Trust levels are assigned (A and B trust C, so when C approves A + B also approve). This is a one-to-many relation (can trust multiple people)
  4. Authorship formulated approval conditions (A decides to approve when B approves and C has not disapproved).

This issue is in relation to #41 to a certain degree – all this information needs to be stored on an authorship level, not the user level.


The thinking on this is in progress, so please chime in if you have ideas :)

When working with co-authors, all authors currently need to approve every minor change. Even one reference update requires approval from all the co-authors again. In my personal experience, this has led to serious publication delays. In order to make the publishing process more manageable, I would like to introduce publishing permissions for authorships. For example, if we have authors A, B, and C, we can have various scenarios: 1. All authors need to approve the publication before it gets published (A, B, C). 2. A delegated approver is assigned (A delegates C, meaning B + C need to approve). This is a one-to-one relation (delegate to one person) 3. Trust levels are assigned (A and B trust C, so when C approves A + B also approve). This is a one-to-many relation (can trust multiple people) 4. Authorship formulated approval conditions (A decides to approve when B approves and C has not disapproved). This issue is in relation to #41 to a certain degree – all this information needs to be stored on an authorship level, not the user level. --- The thinking on this is in progress, so please chime in if you have ideas :)
Author
Owner

In order to make publishing somewhat smoother than it currently is, all authorships will default to consenting to publish at first, which can be withdrawn at any time, per publication version.

Other authors can be set to be "trusted authors" with which withdrawn consent will be reconciliated. This would mean that if Person A trusts Person B, and has withdrawn their consent to publish at some point, they will consent to publish once Person B consents.

This is reconciled using a trust and consent matrix, respectively:

\begin{pmatrix} 1 & 1\\ 0 & 1  \end{pmatrix}  \cdot  \begin{pmatrix} 0 & 1 \end{pmatrix} = \begin{pmatrix} 1 & 1 \end{pmatrix}

The diagonal on the square matrix is the user themselves. Reading it row by row, gives trust values. Here row 1, column 2, could be seen as Person A's trust in Person B. The row vector indicates the consent values, here A=0 indicating false and B=1 indicating true.

The dot product of these two matrices provides the outcome matrix that can be used to determine whether the publication version is allowed to be published. Trusted authors are tracked by a one to many relationship, indicating one author can trust multiple other authors on the same version.

At this stage, I will implement this in such a way that trust and consent to publish are copied from version to version. In other words, if not updated, it will remain in the same state across versions.

In order to make publishing somewhat smoother than it currently is, all authorships will default to consenting to publish at first, which can be withdrawn at any time, per publication version. Other authors can be set to be "trusted authors" with which withdrawn consent will be reconciliated. This would mean that if Person A trusts Person B, and has withdrawn their consent to publish at some point, they will consent to publish once Person B consents. This is reconciled using a trust and consent matrix, respectively: $$\begin{pmatrix} 1 & 1\\ 0 & 1 \end{pmatrix} \cdot \begin{pmatrix} 0 & 1 \end{pmatrix} = \begin{pmatrix} 1 & 1 \end{pmatrix}$$ The diagonal on the square matrix is the user themselves. Reading it row by row, gives trust values. Here row 1, column 2, could be seen as Person A's trust in Person B. The row vector indicates the consent values, here `A=0` indicating false and `B=1` indicating true. The dot product of these two matrices provides the outcome matrix that can be used to determine whether the publication version is allowed to be published. Trusted authors are tracked by a one to many relationship, indicating one author can trust multiple other authors on the same version. At this stage, I will implement this in such a way that trust and consent to publish are copied from version to version. In other words, if not updated, it will remain in the same state across versions.
Author
Owner

I had an implementation of the comment above, but decided to go a different route that does not require matrix multiplication after all. Sharing the code here for later (potential) reference:

use ndarray::{Array1, Array2};

// trust is a square matrix of # author length
// consent is a row vector (in Array1) of # author length
pub fn permissions(trust: Array2<u8>, consent: Array1<u8>) -> Array1<u8> {
    let result = trust.dot(&consent);
    let clamped = result.mapv(|x| if x > 0 { 1 } else { 0 });

    clamped
}

#[cfg(test)]
mod tests {
    use super::*;
    use ndarray::{arr1, arr2};

    #[test]
    fn test_zero_trust() {
        // A, B, C do not trust or distrust each other
        let zero_trust = arr2(&[[1, 0, 0], [0, 1, 0], [0, 0, 1]]);

        let scenario0 = permissions(zero_trust.clone(), arr1(&[0, 0, 0]));
        assert_eq!(scenario0, arr1(&[0, 0, 0]));

        let scenario1 = permissions(zero_trust.clone(), arr1(&[1, 0, 0]));
        assert_eq!(scenario1, arr1(&[1, 0, 0]));

        let scenario2 = permissions(zero_trust.clone(), arr1(&[1, 1, 0]));
        assert_eq!(scenario2, arr1(&[1, 1, 0]));

        let scenario3 = permissions(zero_trust.clone(), arr1(&[1, 1, 1]));
        assert_eq!(scenario3, arr1(&[1, 1, 1]));
    }

    #[test]
    fn test_trusted_permissions() {
        // A and C trust B
        let trust_1 = arr2(&[[1, 1, 0], [0, 1, 0], [0, 1, 1]]);

        let scenario0 = permissions(trust_1.clone(), arr1(&[0, 0, 0]));
        assert_eq!(scenario0, arr1(&[0, 0, 0]));

        let scenario1 = permissions(trust_1.clone(), arr1(&[0, 1, 0]));
        assert_eq!(scenario1, arr1(&[1, 1, 1]));

        let scenario2 = permissions(trust_1.clone(), arr1(&[1, 0, 1]));
        assert_eq!(scenario2, arr1(&[1, 0, 1]));

        // A trusts B+C, C trusts B
        let trust_2 = arr2(&[[1, 1, 1], [0, 1, 0], [0, 1, 1]]);

        let scenario0 = permissions(trust_2.clone(), arr1(&[0, 0, 0]));
        assert_eq!(scenario0, arr1(&[0, 0, 0]));

        let scenario1 = permissions(trust_2.clone(), arr1(&[0, 1, 0]));
        assert_eq!(scenario1, arr1(&[1, 1, 1]));

        let scenario2 = permissions(trust_2.clone(), arr1(&[0, 0, 1]));
        assert_eq!(scenario2, arr1(&[1, 0, 1]));
    }

    #[test]
    fn test_full_trust() {
        // A trusts B+C, C trusts B
        let trust_1 = arr2(&[[1, 1, 1], [1, 1, 1], [1, 1, 1]]);

        let scenario0 = permissions(trust_1.clone(), arr1(&[0, 0, 0]));
        assert_eq!(scenario0, arr1(&[0, 0, 0]));

        let scenario1 = permissions(trust_1.clone(), arr1(&[1, 0, 0]));
        assert_eq!(scenario1, arr1(&[1, 1, 1]));

        let scenario2 = permissions(trust_1.clone(), arr1(&[0, 1, 0]));
        assert_eq!(scenario2, arr1(&[1, 1, 1]));

        let scenario3 = permissions(trust_1.clone(), arr1(&[0, 0, 1]));
        assert_eq!(scenario3, arr1(&[1, 1, 1]));

        let scenario4 = permissions(trust_1.clone(), arr1(&[1, 1, 1]));
        assert_eq!(scenario4, arr1(&[1, 1, 1]));
    }
}
I had an implementation of the comment above, but decided to go a different route that does not require matrix multiplication after all. Sharing the code here for later (potential) reference: ```rust use ndarray::{Array1, Array2}; // trust is a square matrix of # author length // consent is a row vector (in Array1) of # author length pub fn permissions(trust: Array2<u8>, consent: Array1<u8>) -> Array1<u8> { let result = trust.dot(&consent); let clamped = result.mapv(|x| if x > 0 { 1 } else { 0 }); clamped } #[cfg(test)] mod tests { use super::*; use ndarray::{arr1, arr2}; #[test] fn test_zero_trust() { // A, B, C do not trust or distrust each other let zero_trust = arr2(&[[1, 0, 0], [0, 1, 0], [0, 0, 1]]); let scenario0 = permissions(zero_trust.clone(), arr1(&[0, 0, 0])); assert_eq!(scenario0, arr1(&[0, 0, 0])); let scenario1 = permissions(zero_trust.clone(), arr1(&[1, 0, 0])); assert_eq!(scenario1, arr1(&[1, 0, 0])); let scenario2 = permissions(zero_trust.clone(), arr1(&[1, 1, 0])); assert_eq!(scenario2, arr1(&[1, 1, 0])); let scenario3 = permissions(zero_trust.clone(), arr1(&[1, 1, 1])); assert_eq!(scenario3, arr1(&[1, 1, 1])); } #[test] fn test_trusted_permissions() { // A and C trust B let trust_1 = arr2(&[[1, 1, 0], [0, 1, 0], [0, 1, 1]]); let scenario0 = permissions(trust_1.clone(), arr1(&[0, 0, 0])); assert_eq!(scenario0, arr1(&[0, 0, 0])); let scenario1 = permissions(trust_1.clone(), arr1(&[0, 1, 0])); assert_eq!(scenario1, arr1(&[1, 1, 1])); let scenario2 = permissions(trust_1.clone(), arr1(&[1, 0, 1])); assert_eq!(scenario2, arr1(&[1, 0, 1])); // A trusts B+C, C trusts B let trust_2 = arr2(&[[1, 1, 1], [0, 1, 0], [0, 1, 1]]); let scenario0 = permissions(trust_2.clone(), arr1(&[0, 0, 0])); assert_eq!(scenario0, arr1(&[0, 0, 0])); let scenario1 = permissions(trust_2.clone(), arr1(&[0, 1, 0])); assert_eq!(scenario1, arr1(&[1, 1, 1])); let scenario2 = permissions(trust_2.clone(), arr1(&[0, 0, 1])); assert_eq!(scenario2, arr1(&[1, 0, 1])); } #[test] fn test_full_trust() { // A trusts B+C, C trusts B let trust_1 = arr2(&[[1, 1, 1], [1, 1, 1], [1, 1, 1]]); let scenario0 = permissions(trust_1.clone(), arr1(&[0, 0, 0])); assert_eq!(scenario0, arr1(&[0, 0, 0])); let scenario1 = permissions(trust_1.clone(), arr1(&[1, 0, 0])); assert_eq!(scenario1, arr1(&[1, 1, 1])); let scenario2 = permissions(trust_1.clone(), arr1(&[0, 1, 0])); assert_eq!(scenario2, arr1(&[1, 1, 1])); let scenario3 = permissions(trust_1.clone(), arr1(&[0, 0, 1])); assert_eq!(scenario3, arr1(&[1, 1, 1])); let scenario4 = permissions(trust_1.clone(), arr1(&[1, 1, 1])); assert_eq!(scenario4, arr1(&[1, 1, 1])); } } ```
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: libscie/researchequals-api#42
No description provided.