System Properties

Every document carries a few values the platform manages rather than the writer: its id, its owner, and, on some types, its revision, its creator, the times it was created, updated and transferred, and who last moderated it and when. Their names start with $. A document type does not declare them in properties. It names them where it wants to use them: in required, to have a timestamp recorded, in indices, to query by them, and in the keywords that accept one, such as a reference's propertyAgreement.

PropertyHoldsRecorded
$idthe document's idalways
$ownerIdthe identity that owns the document nowalways
$revisionhow many times the document has changed, plus oneon types whose documents can be replaced, transferred or sold, or that keep fields for their moderators
$createdAt, $updatedAt, $transferredAtblock times of the creation, last update and last transferwhen listed in required
$createdAtBlockHeight and the other heightsPlatform and Core block heights of the same eventswhen listed in required
$creatorIdthe identity that created the documenton types whose documents can be transferred or sold
$moderatedAt, $moderatedBythe block time and the moderator of the last write of the fields only moderators writeon types that keep such fields, once a moderator writes them

Example

"listing": {
  "type": "object",
  "documentsMutable": true,
  "transferable": 1,
  "tradeMode": 1,
  "indices": [
    { "name": "byCreator", "properties": [{ "$creatorId": "asc" }, { "$createdAt": "asc" }] },
    { "name": "byOwner", "properties": [{ "$ownerId": "asc" }, { "$updatedAt": "asc" }] }
  ],
  "properties": {
    "title": { "type": "string", "minLength": 1, "maxLength": 100, "position": 0 }
  },
  "required": ["$createdAt", "$updatedAt", "$transferredAt", "title"],
  "additionalProperties": false
}

Every listing records when it was created, last updated and last transferred, because required lists the three times. Listings can be replaced, transferred and sold, so each one also carries a revision and the id of its creator, and the two indexes find them by who made them and by who holds them now.

$id

WhereEvery document
ValueAn identifier: 32 bytes
RecordedAlways
Sinceprotocol version 1
ErrorsInvalidDocumentTransitionIdError (10405): a create whose id is not the one derived for it. SystemPropertyIndexAlreadyPresentError (10208): an index that names $id.

A document's id is derived from the contract id, the owner's id, the document type's name, entropy chosen by the client and, from protocol version 14, the identity contract nonce of the create transition. Consensus derives it again for every create and refuses a transition that carries another. The id never changes. See Document ID Generation.

Documents are already stored by id, so an index may not name $id. A reference's propertyAgreement may name it on the referenced side (see References).

$ownerId

WhereEvery document
ValueAn identifier: the id of an identity
RecordedAlways
Sinceprotocol version 1
ErrorsDocumentOwnerIdMismatchError (40102): a replace, transfer, price update or delete signed by an identity that does not own the document

The owner is the identity that created the document, until a transfer or a purchase hands it to another. Only the owner may replace, transfer, reprice or delete it with a document transition. A document can also leave by other paths, a moderators' deletion or an expired ttl; see Deletion.

$ownerId may be indexed, and several keywords read it, where it usually stands for the writer of the document:

  • distinctFrom: "$ownerId" makes a property differ from the owner.
  • propertyConstraints: a rule may compare an identifier property with $ownerId.
  • References: a propertyAgreement pair, a lookup key and identityProperty may name it, and ownerRefersTo checks the owner itself.
  • encryptedFor: "recipient": "$ownerId" marks a message the writer encrypts to themself.

$revision

WhereDocuments of a type whose documents can be replaced (documentsMutable, true by default), transferred (transferable: 1) or sold (tradeMode: 1), or that keeps fields only its moderators write (moderatorAbilities.changeFields), even when its documents cannot be replaced
ValueAn integer, from 1
RecordedOn those types; absent on every other
Sinceprotocol version 1
ErrorsInvalidDocumentRevisionError (40106): a transition whose revision is not the stored one plus one

A new document has revision 1. Every replace, transfer, price update and purchase raises it by one, and the transition must state the new revision: the stored revision plus one. A transition built against an older copy of the document is refused, rather than silently overwriting a newer one. A moderator's change of the fields a type keeps for its moderators raises it by one too; the moderation transition states no revision, the platform sets it, and an owner's replace built before the change is refused. A type whose documents can never change after creation carries no revision. $revision is not one of the system properties an index may name.

Timestamps

Properties$createdAt, $updatedAt, $transferredAt
ValueA block time, in milliseconds since the Unix epoch
RecordedOnly when listed in the document type's required
Sinceprotocol version 1
On updateThe set is fixed: a contract update may not add one to required or remove one (DataContractInvalidRequiredFieldsUpdateError, 10276)

The platform sets these from the block that processes the transition; the writer never supplies them. A timestamp that is not in required is never recorded, and documents of the type do not have it.

EventSets
Createevery listed timestamp, so $updatedAt and $transferredAt start at the creation time
Replace$updatedAt
Price update$updatedAt
Transfer$transferredAt
Purchase$transferredAt

Timestamps may be indexed. Some keywords need one in required, since they read it:

  • ttl counts from $createdAt.
  • moderatorAbilities.deleteWithin counts from $updatedAt, or from $createdAt on a type that does not record $updatedAt (see Deletion).
  • A time-range index needs the timestamp it buckets.

Block heights

Properties$createdAtBlockHeight, $updatedAtBlockHeight, $transferredAtBlockHeight, $createdAtCoreBlockHeight, $updatedAtCoreBlockHeight, $transferredAtCoreBlockHeight
ValueThe BlockHeight forms: the Platform block height. The CoreBlockHeight forms: the Core chain height recorded with that block.
RecordedOnly when listed in the document type's required
Sinceprotocol version 1
On updateThe set is fixed, as for the timestamps (10276)

The same three events as the timestamps, measured in blocks instead of time. Each is set on the same events as the timestamp of its name, and may be indexed.

$creatorId

WhereDocuments of a type that sets transferable: 1 or tradeMode: 1, in a contract of format 1 whose config is version 1 or later
ValueAn identifier: the id of the identity that created the document
RecordedOn those types, from protocol version 10
Sinceprotocol version 10
ErrorsUndefinedIndexPropertyError (10209): an index naming $creatorId on a type that does not record it

On a type whose documents can change hands, $ownerId follows the document while $creatorId stays with the identity that created it: a transfer or a purchase never changes it. On a type whose documents never change hands the creator is always the owner, and no $creatorId is recorded. Neither is it on a contract of format 0 or with a config of version 0, whatever its types allow.

$creatorId is not written in required or properties. It may be named:

  • in an index;
  • on the referenced side of a reference's propertyAgreement, and as a key reference's identityProperty;
  • by creatorRefersTo, which checks the creator. A type that does not record creators may not declare it (InvalidContractStructure, 10231).

$moderatedAt and $moderatedBy

WhereDocuments of a type that lists moderatorAbilities.changeFields
Value$moderatedAt: a block time, in milliseconds since the Unix epoch. $moderatedBy: an identifier, the id of the moderator.
RecordedOnce a moderator of the contract writes the fields the type keeps for its moderators; absent until then
Sinceprotocol version 14
ErrorsInvalidContractStructure (10231): an index naming either on a type that lists no changeFields, or in a unique index. UndefinedIndexPropertyError (10209): an index naming either before protocol version 14.

The two record the last time a moderator wrote the fields only moderators write, and who did. They are set together, from the block that processes the write, and never by the writer:

EventSets both
A moderator's field change (changeDocumentFields)to the block's time and the moderator who signed it
A create that sets such a field, by a document owner who moderates the contractto the block's time and the owner
A replace that changes, adds or removes such a field, by an owner who moderatesto the block's time and the owner

Nothing else moves them: a replace that leaves those fields as they were, a transfer, a purchase and a price update keep them, and a restore puts them back as they were. They do not change $updatedAt, which stays the owner's. A document no moderator has written carries neither, and a type that keeps no fields for its moderators never records them.

Both may be indexed, on a type that lists changeFields, so an application can find what its moderators handled, by whom and in what order:

"indices": [
  { "name": "byModerator", "properties": [{ "$moderatedBy": "asc" }, { "$moderatedAt": "asc" }] }
]

A unique index may not name them: a moderator's change would be refused because another document holds the same stamp. They are not written in required or properties, and no other keyword reads them.

See also