# Specification

*Authors: BlockScience and SDF, September 2023*

> ### Introduction

A notebook containing an end-to-end example implementation for this document can be found on the [gov-modules-demos GitHub repository](https://github.com/BlockScience/gov-modules-demos/blob/main/demos/tier_based_role_certifier.ipynb).

### Definitions

* Attestation: Retrievable boolean assertion. Can contain or be assigned an expiry timestamp. Can be sourced exogenously (eg. by retrieving stamps through oracles) or endogenously (eg. by attestating instantaneously through procedural rules).
* Role: Computable boolean assertion on which the result depends on the aggregation of pre-defined prior attestations.
* Qualifier: Attestation source that can be used for attesting for a given role.
* Disqualifier: Qualifier that will automatically deny a given role attestation.
* Autoqualifier: Qualifier that will automatically approve a given role attestation, provided that it wasn't disqualified beforehand.
* Conditional Qualifier: Qualifier on which the result will add to the accumulated approval criteria for a given role attestation.
* Attestation Weight: Transforms a boolean assertion into a real number. Used for controlling the relative importance for each conditional qualifier.
* Qualifier Aggregator: Transforms a list of real numbers into a single real number. Used for transforming the conditional qualifier weights into a single number that can be compared against.
* Conditional Threshold: Comparison number that is used for transforming a single real number into a boolean assertion. Used for determining whatever the aggregated conditional qualifiers do approve a positive result for the role attestation or not.

### Example Implementation in Python

#### Definitions

```python
AttestationUUID = str
AttestationWeight = float
Days = int


class AttestationProbabilityArgs(NamedTuple):
    attest_probability: float
    invoke_probability: float
    issuance_mean_expiry_in_days: Days
    issuance_std_expiry_in_days: Days


@dataclass
class Attestation():
    attestation_uuid: AttestationUUID
    result: bool  # Between 0.0 and 1.0
    issuance: Days
    expiry: Days


@dataclass
class Agent():
    attestations: dict[AttestationUUID, Attestation]

    def valid_attestations(self, reference_time) -> list[Attestation]:
        return [a
                for a in self.attestations.values()
                if a.expiry > reference_time
                and a.issuance <= reference_time]

    def sucessful_attestations(self, reference_time) -> list[Attestation]:
        return [a
                for a in self.valid_attestations(reference_time)
                if a.result == True]



@dataclass
class RoleCertifier():
    attestation_uuid: AttestationUUID
    issuance_validity: Days

    conditional_qualifiers: dict[AttestationUUID, AttestationWeight]
    auto_qualifers: set[AttestationUUID]
    disqualifiers: set[AttestationUUID]

    qualifier_aggregator: Callable[[dict], float]
    conditional_threshold: float

    def generate_attestation(self, result: bool, time: Days) -> Attestation:
        return Attestation(self.attestation_uuid,
                           result,
                           time,
                           time + self.issuance_validity)

    def agent_certification_result(self,
                                   agent: PassportDemoAgent,
                                   time: Days) -> bool:
        agent_sucessful_attestations = set(a.attestation_uuid
                                           for a in agent.valid_attestations(time)
                                           if a.result == True)

        if len(agent_sucessful_attestations & self.disqualifiers) > 0:
            return False
        elif len(agent_sucessful_attestations & self.auto_qualifers) > 0:
            return True
        else:
            conditional_attestations = set(self.conditional_qualifiers.keys())
            agent_relevant_attestations = agent_sucessful_attestations & conditional_attestations

            agent_conditional_values = {a: self.conditional_qualifiers[a]
                                        for a
                                        in agent_relevant_attestations}
            agent_conditional_value = self.qualifier_aggregator(
                agent_conditional_values)
            if agent_conditional_value >= self.conditional_threshold:
                return True
            else:
                return False

    def attestate_agent(self, agent: PassportDemoAgent, time: Days):
        return self.generate_attestation(self.agent_certification_result(agent, time), time)
```

#### Example instantiation of TRC

```python
def compound_product(lst): 
    if len(lst) > 1:
        return reduce(lambda x, y: (1 + x) * (1 + y) - 1, list(lst))
    elif len(lst) == 1:
        return lst[0]
    else:
        return 0.0


role_A = RoleCertifier(attestation_uuid='role_A_attestation',
                          issuance_validity=90,
                          conditional_qualifiers={
                              'att_1': 1, 'att_2': 2, 'att_3': 3},
                          auto_qualifers=set(),
                          disqualifiers={'att_ban'},
                          qualifier_aggregator=lambda atts: sum(atts.values()),
                          conditional_threshold=2)

role_B = RoleCertifier(attestation_uuid='role_B_attestation',
                          issuance_validity=30,
                          conditional_qualifiers={
                              'att_3': 0.3, 'att_4': 0.25, 'att_5': 0.2},
                          auto_qualifers={'role_A_attestation'},
                          disqualifiers={'att_ban'},
                          qualifier_aggregator=lambda atts: compound_product(
                              tuple(atts.values())),
                          conditional_threshold=0.25)

EXAMPLE_ATTESTATION_PROVIDERS = {
    'att_1': AttestationProbabilityArgs(0.2, 0.1, 7, 10),
    'att_2': AttestationProbabilityArgs(0.2, 0.1, 14, 10),
    'att_3': AttestationProbabilityArgs(0.05, 0.1, 30, 10),
    'att_4': AttestationProbabilityArgs(0.4, 0.05, 15, 3),
    'att_5': AttestationProbabilityArgs(0.6, 0.5, 40, 10),
    'att_ban': AttestationProbabilityArgs(0.02, 0.05, 120, 5)
}

EXAMPLE_ROLE_CERTIFIERS = [role_A, role_B]
```
