The below psuedocode blocks describe the structure of valid json objects within an open standard schema. Open standard json schemas must conform to these specifications.
- An example json file illustrating the different object types can be found here: schemas/example.json
Notes for psuedocode interpretation:
- Capitalized value types indicate object types or enumeration types that are defined by this specification (e.g.
parties: [Party]
indicates that theparties
array can only contain objects of typeParty
). ?
at the end of an object key indicates that the key is optional.?
at the end of a value type indicates that the value is nullable.?*
indicates optional/nullable as above, but only under certain conditions.|
can be read as "or", and is used to indicate that an attribute, array, or json object can contain a more than one type of object or value.//
indicates an inline code comment.- The
reference()
syntax indicates that the attribute must be a global reference to an entity of the object type specified within the brackets. An example of a global reference to anObjectPromise
entity (denoted asreference(ObjectPromise)
) is"object_promise:2"
, whereobject_promise
is the reference type (snake case) and2
is theid
of the referencedObjectPromise
. The referenceable unique identifiers for each object type can be found at the end of each object type description below. - The
reference_path()
syntax is the same as thereference()
syntax, except it indicates that a dot-separated path from the referenced object can be specified as part of the reference. For example,"object_promise:2.my_attribute"
would reference the value ofmy_attribute
on the promised object instance, provided that theObjectType
that is referenced by theObjectPromise
defines an attribute calledmy_attribute
.
Root json object:
- Henceforth denoted as
root
. standard
is the name of the open standard.parties
is the list of parties relevant to the open standard.actions
andcheckpoints
are lists containing theAction
andCheckpoint
objects that comprise the standard's dependency chart.
{
standard: string,
imports: [SchemaImport],
terms: [Term],
parties: [Party],
object_types: [ObjectType],
object_promises: [ObjectPromise],
actions: [Action],
checkpoints: [Checkpoint],
thread_groups?: [ThreadGroup],
pipelines: [Pipeline]
}
ObjectType:
ObjectType
s can be thought of as classes, withObjectType.name
being the class name andObjectType.attributes
defining the names and types of properties that exist on the class.attributes
on anObjectType
can be specified by attributename
as part of reference paths.- For
attributes
,object_type
is optional whentype
is set to anyFieldType
enum value. Iftype
is set to"EDGE"
or"EDGE_COLLECTION"
thenobject_type
is required to specify theObjectType
of object instance(s) that the edge or edge collection can reference. - Referenceable:
id
,name
type ObjectType {
id: integer,
name: string,
description?: string,
attributes: [
{
name: string,
description?: string,
type: FieldType | "EDGE" | "EDGE_COLLECTION",
object_type?*: reference(ObjectType)
}
]
}
ObjectPromise:
- An
ObjectPromise
promises an instance of anObjectType
. The promise is fulfilled at runtime when anAction
that references theObjectPromise
is performed, thereby instantiating an object to which data can be written. Only the firstAction
to reference theObjectPromise
creates a new instance; every subsequentAction
which references the sameObjectPromise
edits the same object instance. object_type
determines theObjectType
of the promised object instance.- The
context
of anObjectPromise
must match the context of theAction
that fulfills the promise. If the fulfillerAction
does not specify acontext
, then theObjectPromise
must likewise omitcontext
. Furthermore,Action
s that edit anObjectPromise
must have the same context as theObjectPromise
they edit. - Referenceable:
id
,name
type ObjectPromise {
id: integer,
name: string,
description?: string,
object_type: reference(ObjectType),
context?*: reference(ThreadGroup)
}
Action:
id
must be unique withinroot.actions
.name
must be unique withinroot.actions
.operation
allows specifying which attributes can be set when completing the action.object_promise
specifies the promised object instance on which theoperation
will act.context
determines whether theAction
is to be completed as part of a thread within aThreadGroup
.depends_on
references theCheckpoint
that must be satisfied before the action can be taken.- An
Action
may specify 0 or moreMilestone
values, but a givenMilestone
value may not appear onAction
objects more than once per schema. - Referenceable:
id
,name
type Action {
id: integer,
name: string,
description: string,
steps?: {
title: string,
description: string
},
party: reference(Party),
object_promise: reference(ObjectPromise),
operation: Operation,
context?: reference(ThreadGroup),
depends_on?: reference(Checkpoint),
milestones?: [Milestone],
supporting_info?: [string]
}
Operation:
include
is for specifying the fields that can be set on the object instance that is promised by the parentAction
'sobject_promise
. Specifyingnull
for this attribute indicates that no fields are included, or in other words, that all fields are excluded.exclude
is for specifying the fields that cannot be set on the promised object instance. Specifyingnull
for this attribute indicates that no fields are excluded, or in other words, that all fields are included.include
andexclude
are mutually exclusive.- An
Action
may specify 0 or moreMilestone
values, but a givenMilestone
value may not appear onAction
objects more than once per schema. - If the parent
Action
fulfills the object promise (is the first action in the state map to reference that specificObjectPromise
, and will therefore trigger the creation of the object instance), thendefault_values
anddefault_edges
can optionally be specified. Defaults are not supported for edge collections. appends_objects_to
can be used to reference an edge collection to which new instances of the object promise will be automatically appended. This mechanism is useful for manually spawning threads within aThreadGroup
that is set to spawn a new thread for each item in the referenced edge collection. Specifying this property is only allowed if the parentAction
fulfills the object promise and is never referenced by a checkpoint dependency (thereby enabling the action to be performed repeatedly).- If the parent
Action
DOES NOT fulfill the object promise (an ancestorAction
within the state map fulfills the promise), itsoperation
is inferred to be editing an already-instantiated object. In that case,default_fields
anddefault_edges
shall not be specified by theoperation
.
type Operation {
// Mutually exclusive:
include: [<attribute>]?,
exclude: [<attribute>]?,
default_values?: {
<attribute>: scalar
},
default_edges?: {
<attribute>: reference(ObjectPromise)
},
appends_objects_to?*: reference_path(ObjectPromise)
}
Checkpoint:
id
must be unique withinroot.checkpoints
alias
should a human-readable name, and must also be unique withinroot.checkpoints
.- The
description
should provide more information about the significance of theCheckpoint
. - The
dependencies
array can includeDependency
objects and/orCheckpointReference
objects. - To prevent redundancies, a lone
CheckpointReference
cannot be the only item in thedependencies
array. gate_type
shall not be specified when the number of items in thedependencies
array is 1.context
determines theThreadGroup
context in which theCheckpoint
is allowed to be referenced.- Referenceable:
id
,alias
type Checkpoint {
id: integer,
alias: string,
description: string,
abbreviated_description?: string,
supporting_info?: [string],
gate_type?*: GateType,
dependencies: [Dependency | CheckpointReference],
context?: reference(ThreadGroup)
}
CheckpointReference:
- The
checkpoint
attribute references aCheckpoint
object from theroot
object'scheckpoints
array. CheckpointReference
s can be used to achieve complex logic gate combinations. A basic example would bedependency_1
AND (dependency_2
ORdependency_3
).
type CheckpointReference {
checkpoint: reference(Checkpoint)
}
Dependency:
- Represents a dependency of the parent
Checkpoint
object. left
andright
are the operands to be compared using theoperator
. At least one of the operands must be aReferencedOperand
.description
can be used to provide more detail about theDependency
.
type Dependency {
compare: {
left: ReferencedOperand | LiteralOperand,
right: ReferencedOperand | LiteralOperand,
operator: ComparisonOperator,
description?: string
}
}
ReferencedOperand:
- A reference to an attribute on the applicable
ObjectType
, or a reference to a path that begins with athread_variable
. - If
ref
is areference_path(Action)
, the applicableObjectType
is inferred from theAction
that is referenced by the first segment of theReferencedOperand
'sref
path. ThatAction
'sobject_promise
attribute points to anObjectPromise
, which in turn has anobject_type
attribute that points to the applicableObjectType
. - A
ReferencedOperand
evaluates to the value of the referenced attribute orthread_variable
. - The
field_type
of the reference is evaluated and checked against the other-side operand of the parentDependency
and theDependency.compare.operator
to determine whether the comparison is valid.
type ReferencedOperand {
ref: reference_path(Action | thread_variable)
}
LiteralOperand:
- A literal value of any type to be used as an operand in
Dependency.compare
. LiteralOperand
types are checked against the other-side operand of the parentDependency
and theDependency.compare.operator
to determine whether the comparison is valid.
type LiteralOperand {
value: scalar
}
ThreadGroup:
- Some sequences of
Action
s andCheckpoint
s need to be completed for each member of a party, for each item in a list, or for each object in an edge collection.ThreadGroups
accomplish this by enabling thecontext
attribute of anAction
orCheckpoint
to indicate that it is part of a threaded sequence and will be executed in parallel with the other threads in theThreadGroup
. id
must be unique withinroot.thread_groups
.spawn
defines the source from which to spawn threadsforeach
item in a collection. It also defines the name of thethread_variable
(as
) to which the value of the item will be assigned for a given thread in the thread group. If anObjectPromise
is referenced byspawn.foreach
, it must be an ancestor of theThreadGroup
.- If
context
is specified, theThreadGroup
is nested within anotherThreadGroup
. NestedThreadGroup
s can be spawned usingreference_path
s as normal, or from paths from existingthread_variable
s that are available in theThreadGroup.context
. Pipeline
s are a mechanism for specifying how data should be aggregated during the execution of a state map instance.- Referenceable:
id
,name
type ThreadGroup {
id: integer,
name: string,
description?: string,
context?: reference(ThreadGroup),
depends_on?: reference(Checkpoint),
spawn: {
foreach: reference_path(ObjectPromise | thread_variable),
as: string
}
}
Term:
- Defines a term that is used in the schema.
attributes
can be used flexibly to aid in the clarity of thedescription
.- Referenceable:
name
type Term {
name: string,
description: string,
attributes?: [string]
}
Party:
- Defines a relevant party for the schema.
- Example party
name
s: "Project Developer", "Carbon Auditor", "Government Representatives" id
andname
must be unique withinroot.parties
.hex_code
sets the color of the applicable Miro shapes in state map visualizations (see the Schema Visualization) section below. Ifhex_code
is not specified, the default#ffffff
is used.- Referenceable:
id
,name
type Party {
id: integer,
name: string,
hex_code?: string
}
SchemaImport:
file_name
is the name of the schema file to be imported, without the .json file extension, from open-impact-standards/schemas.connections
defines the ways the native schema connects to the imported schema. Note that connections from the imported schema to the native schema can be defined as normal withinCheckpoint.dependencies
orAction.depends_on
.- Referenceable objects from imported schemas are namespaced and can be referenced by prepending
schema:{file_name}.
to a global reference, for example:schema:{my_imported_schema}.action:0
would reference theAction
withid
=0
from theroot.actions
array ofmy_imported_schema.json
. - Referenceable:
file_name
type SchemaImport {
file_name: string,
connections: [ImportConnection]
}
ImportConnection
to_ref
specifies an object from the imported schema to which a dependency will be added. Since imported schemas cannot be modified, such dependencies must be specified as part ofSchemaImport.connections
.add_dependency
specifies theCheckpoint
on which theto_ref
object will depend. If the object referenced byto_ref
already has one or more dependencies, the existing dependencies will be automatically combined with the added dependency via aCheckpoint
withgate_type
"AND"
.
type ImportConnection {
to_ref: reference(Action | Checkpoint),
add_dependency: reference(Checkpoint)
}
- A pipeline's
object_promise
references the promised object instance to which thePipeline.output
s will write. The pipeline inherits its threaded (or non-threaded) context from the referencedObjectPromise
. When a pipeline inherits a threaded context, in-scopethread_variable
s can be referenced within the pipeline where indicated by this specification. - The runtime execution order of a pipeline is as follows: initialize
variables
, thentraverse
, thenapply
, thenoutput
. The execution order is the same withinPipelineTraversal
s (minusoutput
which is not part of traversals). Nested traversals are always executed before theapply
step. Pipeline:
type Pipeline {
id: integer,
name: string,
object_promise: reference(ObjectPromise),
variables: [PipelineVariable],
traverse?: [PipelineTraversal],
apply?: [PipelineApplication],
output: [PipelineOutput]
}
PipelineVariable:
- Declaration of a variable to be used within a pipeline. This specification indicates the
pipeline_variable
value type wherever aPipelineVariable.name
can be referenced.reference_path(pipeline_variable)
indicates that the value can be a path beginning with aPipelineVariable.name
, provided that thePipelineVariable.type
is"OBJECT"
(or in some special cases,"OBJECT_LIST"
). - An
initial
value must be provided for aPipelineVariable
, and it must match the specifiedtype
. List types cannot be initialized tonull
, but an empty array ([]
) is valid.
type PipelineVariable {
name: string,
type: FieldType | "OBJECT" | "OBJECT_LIST",
initial: scalar? | [scalar]
}
PipelineTraversal:
- A
PipelineTraversal
loops over the value of theref
and executes the specified pipeline operationsforeach
item in the referenced list (ref
must evaluate to a list type). - A traversal loop variable is declared by
PipelineTraversal.foreach.as
. This variable can be referenced within the traversal and any nested traversals. PipelineApplications
withinPipelineTraversal
s can applyto
pipeline_variable
s that were defined in parent scopes.
type PipelineTraversal {
ref: reference_path(ObjectPromise | pipeline_variable | thread_variable),
foreach: {
as: string,
variables: [PipelineVariable],
traverse: [Pipelinetraversal],
apply: [PipelineApplication]
}
}
PipelineApplication:
- The
from
value can be aggregated using one ofaggregate
,filter
,sort
, orselect
, and the resulting value is appliedto
the referencedpipeline_variable
. Thefrom
value must evaluate to a list type ifaggregate
,filter
, orsort
are specified. Note that aPipelineApplication
could omit the aforementioned 4 properties altogether and simply applyfrom
a referenceto
a variable. - It must be valid to use the
method
to apply the aggregated value to theto
type. PipelineTraversal
loop variables (defined byPipelineTraversal.foreach.as
) cannot be referenced byPipelineApplication.to
.
type PipelineApplication {
from: reference_path(ObjectPromise | pipeline_variable | thread_variable),
// Begin mutually exclusive properties
aggregate?: {
field: string,
operator: AggregationOperator
},
filter?: {
where: [FilterComparison | NestedFilter],
gate_type?*: GateType
},
sort?: [
{
field: string,
order: "ASC" | "DESC"
}
],
select?: string,
// End mutually exclusive properties
method: ApplicationMethod,
to: pipeline_variable
}
FilterComparison:
- Since a
PipelineApplication.filter
is applied to each item in a list,filter_variable
refers to the individual item being compared. Thefilter_variable
source isPipelineApplication.from
. - Either
left
,right
, or both must befilter_variable
references.
type FilterComparison {
left: reference_path(filter_variable | ObjectPromise | pipeline_variable | thread_variable) | scalar,
operator: ComparisonOperator,
right: reference_path(filter_variable | ObjectPromise | pipeline_variable | thread_variable) | scalar
}
NestedFilter:
- Facilitates complex logic gate combinations within
PipelineApplication.filter
s.
type NestedFilter {
where: [FilterComparison | NestedFilter],
gate_type?*: GateType
}
PipelineOutput:
- Output values must come
from
apipeline_variable
defined by the samePipeline
. - Pipelines output values
to
attributes on the referencedPipeline.object_promise
.
type PipelineOutput {
from: pipeline_variable,
to: string
}
FieldType enumeration:
enum FieldType {
"STRING",
"NUMERIC",
"BOOLEAN",
"STRING_LIST",
"NUMERIC_LIST",
"BOOLEAN_LIST
}
Milestone enumeration:
enum Milestone {
"REAL",
"CLEAR_OWNERSHIP",
"PERMANENT",
"ADDITIONAL",
"VERIFIABLE"
}
GateType enumeration:
- Logic gate types through which groups of dependencies (
Checkpoint
objects) can be evaluated. XNOR
has been intentionally omitted as it reduces state map clarity.
enum GateType {
"AND",
"OR",
"XOR",
"NAND",
"NOR"
}
ComparisonOperator enumeration:
enum ComparisonOperator {
"EQUALS",
"DOES_NOT_EQUAL",
"GREATER_THAN",
"LESS_THAN",
"GREATER_THAN_OR_EQUAL_TO",
"LESS_THAN_OR_EQUAL_TO",
"ONE_OF",
"NONE_OF",
"CONTAINS",
"DOES_NOT_CONTAIN",
"CONTAINS_ANY_OF",
"CONTAINS_NONE_OF"
"IS_SUBSET_OF",
"IS_SUPERSET_OF"
}
ApplicationMethod enumeration:
enum ApplicationMethod {
"ADD",
"SUBTRACT",
"MULTIPLY",
"DIVIDE",
"APPEND",
"PREPEND",
"CONCAT",
"SELECT",
"SET",
"AND",
"OR"
}
AggregationOperator enumeration:
enum AggregationOperator {
"AVERAGE",
"COUNT",
"MAX",
"MIN",
"SUM",
"FIRST",
"LAST",
"AND",
"OR"
}
Running Tests:
- Validation tests can be found in tests/test_schema_validation.py.
- The
test_validate_schema
test runs validation on the schema at the specifiedjson_file_path
. Simply run the test and check stdout for validation errors (refer to the test's docstring for more details). - The
test_get_next_action_id
andtest_get_all_action_ids
tests are validation utilities. Check the docstring on each test for usage instructions.
General Usage:
from validation.schema_validator import SchemaValidator
# Specify which JSON file to validate.
json_file_path = "path/to/my_schema_file.json"
validator = SchemaValidator()
errors = validator.validate(json_file_path=json_file_path)
if errors:
validator.print_errors()
Prerequisites:
- Must create a Miro app and generate an OAuth token. To do this from the Miro dashboard navigate to Team profile > Profile settings > Your apps > Create new app. Once the app has been created scroll down to All Plans, then select
boards:read
andboards:write
permissions, then click "Install app and get OAuth token". - Must include the OAuth token in a file called
tokens.json
in the root folder of the repository, like so:
{
"access_token": "4U7H021Z3_M3"
}
Running Tests:
- Visualization tests can be found in tests/test_dependency_graph.py.
- The
test_generate_miro_board
test attempts to generate a Miro board representation of the schema at the specifiedjson_schema_file_path
. See the test's docstring for usage details.
General Usage:
from visualization.dependency_graph import DependencyGraph
# Specify which JSON file to visualize.
json_schema_file_path = "schemas/my_schema_file.json"
# Convert json schema to graph and calculate node coordinates.
graph = DependencyGraph(json_schema_file_path=json_schema_file_path)
# Generate a new Miro board on the connected Miro app.
graph.generate_miro_board(board_name="New Board Name")
Important Note: The dependency_chart
layout algorithm has not been adequately tested for dependency charts that have more than one exit node, and such schemas may yield unexpected results. Multiple entry nodes (nodes with 0 dependencies) are supported.