syntax = "proto3";
package provenance.metadata.v1;

option go_package = "github.com/provenance-io/provenance/x/metadata/types";

option java_package        = "io.provenance.metadata.v1";
option java_multiple_files = true;

import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/descriptor.proto";
import "provenance/metadata/v1/specification.proto";

/**

Overview

The structures in this file are used to efficiently store the data for the metadata module.  The structure the core
concept of the  Metadata module is a Scope that contains a list of Records/Facts.  Updates to scopes are performed
in a session with details persisted in a Session.  Each session contains a specification that details
constraints on parties that must sign, inputs, and outputs that can be recorded.  Each Scope may contain a list of
allowed specifications that may be used.


Data Structures

  Metadata Scope

  A metadata scope contains a collection of records of data, each made up of a hash, data type, and ownership
  information that establishes provenance.

  Sessions

  Typically multiple records will be established within a scope at the same time (see: Contract Memorialization).  A
  Session contains context for the recording of these records linking a set of records into a common process/
  execution associated with a specification indicating allowed and required values.

  Record

  A single record collects information about a process that generated it, a collection of inputs, and one or more
  outputs into a structure identified by a name that is unique within a scope.


KVStore State Data

Iterators
- Get All Scopes (<scope prefix>) []Scope
- Get All Sessions (<session prefix>:<scope_id>) []Session
  * This method would be used to list all current session/transaction contexts with Records set on the scope
- Get All Scope Records (<records prefix>:<scope_id>) : []Record


Direct Access
- Get Scope (<scope prefix>:<scope_id>) Scope
- Get Session (<session prefix>:<scope_id>:<session_id>) : Session
- Get Record (<record prefix>:<scope_id>:<record_name_hash>) : Record


Indexes (special kvstore records for efficient cross reference/queries)
- Get Session Records - use filtered iterator over all scope records
- Get all scopes for a party/address?
- Get all scopes of a specific type (scope spec)?
- Get all sessions of a specific specification type?
- Get all Records of a type?


*/

// Scope defines a root reference for a collection of records owned by one or more parties.
message Scope {
  option (gogoproto.goproto_stringer) = false;
  option (gogoproto.stringer)         = true;

  // Unique ID for this scope.  Implements sdk.Address interface for use where addresses are required in Cosmos
  bytes scope_id = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "MetadataAddress"];
  // the scope specification that contains the specifications for data elements allowed within this scope
  bytes specification_id = 2 [(gogoproto.nullable) = false, (gogoproto.customtype) = "MetadataAddress"];
  // These parties represent top level owners of the records within.  These parties must sign any requests that modify
  // the data within the scope.  These addresses are in union with parties listed on the sessions.
  repeated Party owners = 3 [(gogoproto.nullable) = false];
  // Addresses in this list are authorized to receive off-chain data associated with this scope.
  repeated string data_access = 4;
  // The address that controls the value associated with this scope.
  //
  // The value owner is actually tracked by the bank module using a coin with the denom "nft/<scope_id>".
  // The value owner can be changed using WriteScope or anything that transfers funds, e.g. MsgSend.
  //
  // During WriteScope:
  //  - If this field is empty, it indicates that there should not be a change to the value owner.
  //    I.e. Once a scope has a value owner, it will always have one (until it's deleted).
  //  - If this field has a value, the existing value owner will be looked up, and
  //    - If there's already an existing value owner, they must be a signer,
  //      and the coin will be transferred to the new value owner.
  //    - If there isn't yet a value owner, the coin will be minted and sent to the new value owner.
  //      If the scope already exists, the owners must be signers (just like changing other fields).
  //      If it's a new scope, there's no special signer limitations related to the value owner.
  string value_owner_address = 5;
  // Whether all parties in this scope and its sessions must be present in this scope's owners field.
  // This also enables use of optional=true scope owners and session parties.
  bool require_party_rollup = 6;
}

// Session defines an execution context against a specific specification instance.
// The context will have a specification and set of parties involved.
//
// NOTE: When there are no more Records within a Scope that reference a Session, the Session is removed.
message Session {
  option (gogoproto.goproto_stringer) = false;
  option (gogoproto.stringer)         = true;

  bytes session_id = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "MetadataAddress"];
  // unique id of the contract specification that was used to create this session.
  bytes specification_id = 2 [(gogoproto.nullable) = false, (gogoproto.customtype) = "MetadataAddress"];
  // parties is the set of identities that signed this contract
  repeated Party parties = 3 [(gogoproto.nullable) = false];
  // name to associate with this session execution context, typically classname
  string name = 4;
  // context is a field for storing client specific data associated with a session.
  bytes context = 5;
  // Created by, updated by, timestamps, version number, and related info.
  AuditFields audit = 99;
}

// A record (of fact) is attached to a session or each consideration output from a contract
message Record {
  option (gogoproto.goproto_stringer) = false;
  option (gogoproto.stringer)         = true;

  // name/identifier for this record.  Value must be unique within the scope.  Also known as a Fact name
  string name = 1;
  // id of the session context that was used to create this record (use with filtered kvprefix iterator)
  bytes session_id = 2 [(gogoproto.nullable) = false, (gogoproto.customtype) = "MetadataAddress"];
  // process contain information used to uniquely identify an execution on or off chain that generated this record
  Process process = 3 [(gogoproto.nullable) = false];
  // inputs used with the process to achieve the output on this record
  repeated RecordInput inputs = 4 [(gogoproto.nullable) = false];
  // output(s) is the results of executing the process on the given process indicated in this record
  repeated RecordOutput outputs = 5 [(gogoproto.nullable) = false];
  // specification_id is the id of the record specification that was used to create this record.
  bytes specification_id = 6 [(gogoproto.nullable) = false, (gogoproto.customtype) = "MetadataAddress"];
}

// Process contains information used to uniquely identify what was used to generate this record
message Process {
  option (gogoproto.goproto_stringer) = false;
  // unique identifier for this process
  oneof process_id {
    // the address of a smart contract used for this process
    string address = 1;
    // the hash of an off-chain process used
    string hash = 2;
  }
  // a name associated with the process (type_name, classname or smart contract common name)
  string name = 3;
  // method is a name or reference to a specific operation (method) within a class/contract that was invoked
  string method = 4;
}

// Tracks the inputs used to establish this record
message RecordInput {
  option (gogoproto.goproto_stringer) = false;
  // Name value included to link back to the definition spec.
  string name = 1;
  // data source
  oneof source {
    // the address of a record on chain (For Established Records)
    bytes record_id = 2 [(gogoproto.customtype) = "MetadataAddress"];
    // the hash of an off-chain piece of information (For Proposed Records)
    string hash = 3;
  }
  // from proposed fact structure to unmarshal
  string type_name = 4;
  // Indicates if this input was a recorded fact on chain or just a given hashed input
  RecordInputStatus status = 5;
}

// A set of types for inputs on a record (of fact)
enum RecordInputStatus {
  // RECORD_INPUT_STATUS_UNSPECIFIED indicates an invalid/unknown input type
  RECORD_INPUT_STATUS_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "Unknown"];
  // RECORD_INPUT_STATUS_PROPOSED indicates this input was an arbitrary piece of data that was hashed
  RECORD_INPUT_STATUS_PROPOSED = 1 [(gogoproto.enumvalue_customname) = "Proposed"];
  // RECORD_INPUT_STATUS_RECORD indicates this input is a reference to a previously recorded fact on blockchain
  RECORD_INPUT_STATUS_RECORD = 2 [(gogoproto.enumvalue_customname) = "Record"];
}

// RecordOutput encapsulates the output of a process recorded on chain
message RecordOutput {
  option (gogoproto.goproto_stringer) = false;

  // Hash of the data output that was output/generated for this record
  string hash = 1;
  // Status of the process execution associated with this output indicating success,failure, or pending
  ResultStatus status = 2;
}

// ResultStatus indicates the various states of execution of a record
enum ResultStatus {
  // RESULT_STATUS_UNSPECIFIED indicates an unset condition
  RESULT_STATUS_UNSPECIFIED = 0;
  // RESULT_STATUS_PASS indicates the execution was successful
  RESULT_STATUS_PASS = 1;
  // RESULT_STATUS_SKIP indicates condition/consideration was skipped due to missing inputs or delayed execution
  RESULT_STATUS_SKIP = 2;
  // RESULT_STATUS_FAIL indicates the execution of the condition/consideration failed.
  RESULT_STATUS_FAIL = 3;
}

// A Party is an address with/in a given role associated with a contract
message Party {
  option (gogoproto.goproto_stringer) = false;

  // address of the account (on chain)
  string address = 1;
  // a role for this account within the context of the processes used
  PartyType role = 2;
  // whether this party's signature is optional
  bool optional = 3;
}

// AuditFields capture information about the last account to make modifications and when they were made
message AuditFields {
  // the date/time when this entry was created
  google.protobuf.Timestamp created_date = 1 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
  // the address of the account that created this record
  string created_by = 2;
  // the date/time when this entry was last updated
  google.protobuf.Timestamp updated_date = 3 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
  // the address of the account that modified this record
  string updated_by = 4;
  // an optional version number that is incremented with each update
  uint32 version = 5;
  // an optional message associated with the creation/update event
  string message = 6;
}

// NetAssetValue defines a scope's net asset value
message NetAssetValue {
  // price is the complete value of the asset's volume
  cosmos.base.v1beta1.Coin price = 1 [(gogoproto.nullable) = false];
  // updated_block_height is the block height of last update
  uint64 updated_block_height = 2;
  // volume is the number of scope instances that were purchased for the price
  // Typically this will be null (equivalent to one) or one.  The only reason this would be more than
  // one is for cases where the precision of the price denom is insufficient to represent the actual price
  uint64 volume = 3;
}
