# Unit contract v1: JSON Schemas

These are the schemas of [contract v1](/units-code/contract), draft 2020-12, copied verbatim from the files the agent embeds and validates against. Each is also served as a file under `/contract/units/v1/`. The `$id` URLs are the canonical identifiers.

### `POST /accepting request body`

A JSON boolean (the literal true or false). An empty body means true. The signals ready, idle, busy and shutdown take no body. The same text is the content of the file signals/accepting.

[Raw file: `accepting-request.schema.json`](/contract/units/v1/accepting-request.schema.json)

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://zaroz.cloud/contract/units/v1/accepting-request.schema.json",
  "title": "POST /accepting request body",
  "description": "A JSON boolean (the literal true or false). An empty body means true. The signals ready, idle, busy and shutdown take no body. The same text is the content of the file signals/accepting.",
  "type": "boolean"
}
```

### `/zaroz/allocation.json`

null while the unit is not allocated. When allocated, user-data.json already holds the allocation's user_data.

[Raw file: `allocation.schema.json`](/contract/units/v1/allocation.schema.json)

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://zaroz.cloud/contract/units/v1/allocation.schema.json",
  "title": "/zaroz/allocation.json",
  "description": "null while the unit is not allocated. When allocated, user-data.json already holds the allocation's user_data.",
  "oneOf": [
    {
      "type": "null"
    },
    {
      "type": "object",
      "required": [
        "id",
        "allocated_at",
        "generation"
      ],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        },
        "allocated_at": {
          "type": "string",
          "format": "date-time"
        },
        "generation": {
          "type": "integer"
        }
      }
    }
  ]
}
```

### `/zaroz/contract.json`

Which contract the files in this directory follow. Read it first.

[Raw file: `contract.schema.json`](/contract/units/v1/contract.schema.json)

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://zaroz.cloud/contract/units/v1/contract.schema.json",
  "title": "/zaroz/contract.json",
  "description": "Which contract the files in this directory follow. Read it first.",
  "type": "object",
  "required": [
    "version",
    "agent_version"
  ],
  "properties": {
    "version": {
      "const": 1,
      "description": "Contract major version. Within a major, changes are additive only."
    },
    "agent_version": {
      "type": "string",
      "minLength": 1
    }
  }
}
```

### `Error response of the signal socket`



[Raw file: `error.schema.json`](/contract/units/v1/error.schema.json)

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://zaroz.cloud/contract/units/v1/error.schema.json",
  "title": "Error response of the signal socket",
  "type": "object",
  "required": [
    "error"
  ],
  "properties": {
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "string",
          "description": "Stable machine-readable code, for example unknown_signal, invalid_body, invalid_wait, not_ready, body_too_large."
        },
        "message": {
          "type": "string"
        }
      }
    }
  }
}
```

### `Unit event envelope`

One event of the units event log, as returned by the events route and delivered to webhooks. Consumers must ignore unknown fields and unknown event types.

[Raw file: `event.schema.json`](/contract/units/v1/event.schema.json)

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://zaroz.cloud/contract/units/v1/event.schema.json",
  "title": "Unit event envelope",
  "description": "One event of the units event log, as returned by the events route and delivered to webhooks. Consumers must ignore unknown fields and unknown event types.",
  "type": "object",
  "required": [
    "seq",
    "id",
    "type",
    "contract_version",
    "occurred_at",
    "service_instance_id",
    "unit",
    "data"
  ],
  "properties": {
    "seq": {
      "type": "integer",
      "minimum": 0,
      "description": "Gapless and strictly increasing per Workload, from 1. Only the delivery-only webhook.ping may carry 0 (the cursor of a webhook that has delivered nothing yet)."
    },
    "id": {
      "type": "string",
      "pattern": "^evt_[0-9a-fA-F-]{32,36}$"
    },
    "type": {
      "type": "string",
      "minLength": 1
    },
    "contract_version": {
      "const": 1
    },
    "occurred_at": {
      "type": "string",
      "format": "date-time"
    },
    "service_instance_id": {
      "type": "string",
      "minLength": 1
    },
    "unit": {
      "oneOf": [
        {
          "type": "null"
        },
        {
          "type": "object",
          "required": [
            "id",
            "key",
            "labels"
          ],
          "properties": {
            "id": {
              "type": "string",
              "minLength": 1
            },
            "key": {
              "type": "string",
              "minLength": 1,
              "maxLength": 40
            },
            "labels": {
              "type": "object",
              "additionalProperties": {
                "type": "string"
              }
            }
          }
        }
      ]
    },
    "data": {
      "type": "object"
    }
  },
  "allOf": [
    {
      "if": {
        "properties": {
          "type": {
            "const": "webhook.ping"
          }
        },
        "required": [
          "type"
        ]
      },
      "else": {
        "properties": {
          "seq": {
            "minimum": 1
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "type": {
            "const": "unit.exited"
          }
        },
        "required": [
          "type"
        ]
      },
      "then": {
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "code",
              "restarted"
            ],
            "properties": {
              "code": {
                "type": "integer"
              },
              "restarted": {
                "type": "boolean"
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "type": {
            "enum": [
              "unit.failed",
              "unit.destroyed"
            ]
          }
        },
        "required": [
          "type"
        ]
      },
      "then": {
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "reason"
            ],
            "properties": {
              "reason": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "type": {
            "const": "units.scaled"
          }
        },
        "required": [
          "type"
        ]
      },
      "then": {
        "properties": {
          "unit": {
            "type": "null"
          },
          "data": {
            "type": "object",
            "required": [
              "from",
              "to",
              "reason"
            ],
            "properties": {
              "from": {
                "type": "integer",
                "minimum": 0
              },
              "to": {
                "type": "integer",
                "minimum": 0
              },
              "reason": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "type": {
            "enum": [
              "unit.ready",
              "unit.allocated"
            ]
          }
        },
        "required": [
          "type"
        ]
      },
      "then": {
        "properties": {
          "unit": {
            "type": "object"
          },
          "data": {
            "type": "object",
            "required": [
              "private_name"
            ],
            "properties": {
              "private_name": {
                "type": "string"
              },
              "public_endpoints": {
                "type": "array"
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "type": {
            "enum": [
              "unit.created",
              "unit.starting",
              "unit.accepting",
              "unit.not_accepting",
              "unit.allocation_delivered",
              "unit.allocation_undelivered",
              "unit.idle",
              "unit.busy",
              "unit.draining",
              "unit.stopped"
            ]
          }
        },
        "required": [
          "type"
        ]
      },
      "then": {
        "properties": {
          "unit": {
            "type": "object"
          }
        }
      }
    }
  ]
}
```

### `Response of POST /<signal>`



[Raw file: `signal-response.schema.json`](/contract/units/v1/signal-response.schema.json)

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://zaroz.cloud/contract/units/v1/signal-response.schema.json",
  "title": "Response of POST /<signal>",
  "type": "object",
  "required": [
    "ok",
    "signal",
    "changed"
  ],
  "properties": {
    "ok": {
      "const": true
    },
    "signal": {
      "enum": [
        "ready",
        "accepting",
        "idle",
        "busy",
        "shutdown"
      ]
    },
    "changed": {
      "type": "boolean",
      "description": "false when the signal repeated the current state and was coalesced."
    }
  }
}
```

### `/zaroz/state.json`

Whether the unit is being drained. A draining unit has already left the load-balanced set; finish up and exit before the pod's termination grace ends.

[Raw file: `state.schema.json`](/contract/units/v1/state.schema.json)

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://zaroz.cloud/contract/units/v1/state.schema.json",
  "title": "/zaroz/state.json",
  "description": "Whether the unit is being drained. A draining unit has already left the load-balanced set; finish up and exit before the pod's termination grace ends.",
  "oneOf": [
    {
      "type": "object",
      "required": [
        "draining"
      ],
      "properties": {
        "draining": {
          "const": false
        }
      }
    },
    {
      "type": "object",
      "required": [
        "draining",
        "reason"
      ],
      "properties": {
        "draining": {
          "const": true
        },
        "reason": {
          "enum": [
            "stop",
            "destroy",
            "replace",
            "scale_down"
          ]
        }
      }
    }
  ]
}
```

### `UnitSignal (units.zaroz.cloud/v1) spec`

Written by the kernel, read by the agent. The agent ignores unknown fields and refuses a spec that fails validation, keeping the previous contract.

[Raw file: `unit-signal-spec.schema.json`](/contract/units/v1/unit-signal-spec.schema.json)

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://zaroz.cloud/contract/units/v1/unit-signal-spec.schema.json",
  "title": "UnitSignal (units.zaroz.cloud/v1) spec",
  "description": "Written by the kernel, read by the agent. The agent ignores unknown fields and refuses a spec that fails validation, keeping the previous contract.",
  "type": "object",
  "required": [
    "unitId",
    "key",
    "contractVersion",
    "generation"
  ],
  "properties": {
    "unitId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "key": {
      "type": "string",
      "minLength": 1,
      "maxLength": 40,
      "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
    },
    "labels": {
      "type": "object",
      "maxProperties": 16,
      "propertyNames": {
        "minLength": 1,
        "maxLength": 32,
        "pattern": "^[a-z0-9]([a-z0-9_.-]*[a-z0-9])?$"
      },
      "additionalProperties": {
        "type": "string",
        "maxLength": 64
      }
    },
    "userData": {
      "description": "Any JSON value, null for none."
    },
    "allocation": {
      "oneOf": [
        {
          "type": "null"
        },
        {
          "type": "object",
          "required": [
            "id",
            "generation"
          ],
          "properties": {
            "id": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            },
            "generation": {
              "type": "integer"
            },
            "allocatedAt": {
              "type": "string",
              "format": "date-time",
              "description": "Optional; the agent stamps the time it first saw the allocation when absent."
            }
          }
        }
      ]
    },
    "draining": {
      "oneOf": [
        {
          "const": false
        },
        {
          "enum": [
            "stop",
            "destroy",
            "replace",
            "scale_down"
          ]
        }
      ],
      "description": "false, or the reason."
    },
    "contractVersion": {
      "const": 1
    },
    "generation": {
      "type": "integer",
      "minimum": 0,
      "description": "Bump on every change; the agent acknowledges it in status.observedGeneration after the files are written."
    },
    "publicEndpoints": {
      "type": "array",
      "description": "Optional override of the pod's ZAROZ_PUBLIC_ENDPOINTS, for an address assigned after pod creation.",
      "items": {
        "type": "object",
        "required": [
          "protocols",
          "internal_port",
          "address"
        ],
        "properties": {
          "protocols": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "internal_port": {
            "type": "integer",
            "minimum": 1,
            "maximum": 65535
          },
          "address": {
            "type": "string"
          }
        }
      }
    }
  }
}
```

### `UnitSignal (units.zaroz.cloud/v1) status`

Written by the agent through the status subresource only, coalesced to at most 5 patches per second. Every field is always present (null, not absent) because the agent patches with a JSON merge patch.

[Raw file: `unit-signal-status.schema.json`](/contract/units/v1/unit-signal-status.schema.json)

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://zaroz.cloud/contract/units/v1/unit-signal-status.schema.json",
  "title": "UnitSignal (units.zaroz.cloud/v1) status",
  "description": "Written by the agent through the status subresource only, coalesced to at most 5 patches per second. Every field is always present (null, not absent) because the agent patches with a JSON merge patch.",
  "type": "object",
  "required": [
    "observedGeneration",
    "agentVersion",
    "signalledReady",
    "accepting",
    "activity",
    "activitySince",
    "shutdownRequestedAt",
    "allocationAck",
    "seq"
  ],
  "properties": {
    "observedGeneration": {
      "type": "integer"
    },
    "agentVersion": {
      "type": "string"
    },
    "signalledReady": {
      "oneOf": [
        {
          "type": "null"
        },
        {
          "type": "string",
          "format": "date-time"
        }
      ]
    },
    "accepting": {
      "type": "boolean"
    },
    "activity": {
      "oneOf": [
        {
          "type": "null"
        },
        {
          "enum": [
            "idle",
            "busy"
          ]
        }
      ]
    },
    "activitySince": {
      "oneOf": [
        {
          "type": "null"
        },
        {
          "type": "string",
          "format": "date-time"
        }
      ]
    },
    "shutdownRequestedAt": {
      "oneOf": [
        {
          "type": "null"
        },
        {
          "type": "string",
          "format": "date-time"
        }
      ]
    },
    "allocationAck": {
      "oneOf": [
        {
          "type": "null"
        },
        {
          "type": "string"
        }
      ],
      "description": "Id of the allocation whose files are written, null when none."
    },
    "seq": {
      "type": "integer",
      "minimum": 0,
      "description": "Monotonic per agent start."
    }
  }
}
```

### `/zaroz/unit.json`

Who this unit is. Labels update live; every other field is fixed for the life of the pod. Ignore fields you do not know.

[Raw file: `unit.schema.json`](/contract/units/v1/unit.schema.json)

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://zaroz.cloud/contract/units/v1/unit.schema.json",
  "title": "/zaroz/unit.json",
  "description": "Who this unit is. Labels update live; every other field is fixed for the life of the pod. Ignore fields you do not know.",
  "type": "object",
  "required": [
    "contract_version",
    "unit_id",
    "key",
    "labels",
    "origin",
    "workload",
    "project_id",
    "region",
    "private_name",
    "public_endpoints",
    "incarnation",
    "started_at"
  ],
  "properties": {
    "contract_version": {
      "const": 1
    },
    "unit_id": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "key": {
      "type": "string",
      "minLength": 1,
      "maxLength": 40,
      "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
    },
    "labels": {
      "type": "object",
      "maxProperties": 16,
      "propertyNames": {
        "minLength": 1,
        "maxLength": 32,
        "pattern": "^[a-z0-9]([a-z0-9_.-]*[a-z0-9])?$"
      },
      "additionalProperties": {
        "type": "string",
        "maxLength": 64
      }
    },
    "origin": {
      "type": "string",
      "minLength": 1,
      "description": "How the unit came to exist (for example policy, code, warm)."
    },
    "workload": {
      "type": "object",
      "required": [
        "id",
        "slug"
      ],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1
        },
        "slug": {
          "type": "string",
          "minLength": 1
        }
      }
    },
    "project_id": {
      "type": "string",
      "minLength": 1
    },
    "region": {
      "type": "string",
      "minLength": 1
    },
    "private_name": {
      "type": "string",
      "minLength": 1,
      "description": "<key>.<workload>, resolvable from the project's other units."
    },
    "public_endpoints": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "protocols",
          "internal_port",
          "address"
        ],
        "properties": {
          "protocols": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "internal_port": {
            "type": "integer",
            "minimum": 1,
            "maximum": 65535
          },
          "address": {
            "type": "string"
          }
        }
      }
    },
    "incarnation": {
      "type": "integer",
      "minimum": 0
    },
    "started_at": {
      "type": "string",
      "format": "date-time"
    }
  }
}
```

### `/zaroz/user-data.json`

The customer's JSON value for this unit, or null. At most 64 KiB serialized compactly and nested at most 32 deep (enforced before it is delivered); any JSON value otherwise.

[Raw file: `user-data.schema.json`](/contract/units/v1/user-data.schema.json)

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://zaroz.cloud/contract/units/v1/user-data.schema.json",
  "title": "/zaroz/user-data.json",
  "description": "The customer's JSON value for this unit, or null. At most 64 KiB serialized compactly and nested at most 32 deep (enforced before it is delivered); any JSON value otherwise."
}
```

