> ## Documentation Index
> Fetch the complete documentation index at: https://docs.substrate.42.pe/llms.txt
> Use this file to discover all available pages before exploring further.

# Fields and field_schema: Types, Validation, Human-Only Gates

> Define custom fields per board using field_schema, use gate fields as exit criteria, and mark human_only fields that only a human can set via the CLI.

Every board declares its own custom fields in `field_schema`, split into a `task` section and a `comments` section. A task's values for those fields live in `custom_data`. Fields are the vocabulary your policies speak — a `transition_guard` checks field values to decide whether a move is allowed.

## Defining a field

Declare fields directly in the board's `field_schema.task` object. Here is an example covering every available type:

```json theme={null}
"field_schema": {
  "task": {
    "tests_passing":   { "type": "boolean" },
    "priority":        { "type": "enum", "values": ["low", "med", "high"] },
    "spec_doc":        { "type": "string" },
    "acceptance":      { "type": "markdown" },
    "labels":          { "type": "string_list" },
    "diego_approved":  { "type": "boolean", "human_only": true }
  },
  "comments": {}
}
```

### Field properties

<ParamField path="type" type="string" required>
  The data type for this field. One of: `string`, `number`, `boolean`, `enum`, `markdown`, `string_list`.
</ParamField>

<ParamField path="values" type="string[]">
  The allowed values when `type` is `enum`. Required for enum fields — a board that declares an enum without `values` fails validation.
</ParamField>

<ParamField path="required" type="boolean">
  Marks the field as required. Enforcement is lazy: existing tasks are not retroactively rejected, but `list_tasks` accepts a `missing_required_fields` filter to surface tasks a tightened schema left incomplete.
</ParamField>

<ParamField path="format" type="string">
  A free-form format hint. Informational only — Substrate does not enforce it.
</ParamField>

<ParamField path="human_only" type="boolean">
  When `true`, agent MCP write tools refuse to set this field. Only `substrate approve` can write it. See [Human-only fields](#human-only-fields) below.
</ParamField>

## Validation is lazy

Substrate validates `custom_data` at write time, not retroactively. Changing a board's schema never rejects data already stored — add, tighten, or remove a field without running a migration. Use the `missing_required_fields` filter on `list_tasks` to find tasks that a schema change left incomplete.

## Changing a field

`update_board` with a full `field_schema` replaces the entire schema at once. To change a single field without touching the rest, use `field_schema_patch` — it merges the fields you supply, and setting a field to `null` deletes it:

```json theme={null}
{
  "field_schema_patch": {
    "task": {
      "old_field": null,
      "new_field": { "type": "boolean" }
    }
  }
}
```

## Gate fields

A gate field is a boolean you agree to treat as an exit criterion — `spec_approved`, `tests_passing`, `review_cleared`. An agent flips it via `update_task` as each criterion is met, and a `transition_guard` policy requires it to be `true` before allowing a group move. Reference a gate field from a policy condition as `task.<name>`.

A `"*" → done` guard that requires `tests_passing: true` is the idiomatic definition-of-done gate.

## Human-only fields

<Warning>
  A gate that an autonomous agent can set is a gate it can self-clear. If your workflow requires a genuine human decision, mark the gating field `"human_only": true`.
</Warning>

When a field carries `"human_only": true`:

* `create_task` and `update_task` refuse to set it — the write returns `forbidden`.
* `update_board` refuses to downgrade or delete the flag — an agent cannot strip its own restriction.
* The only write channel is the CLI: `substrate approve <task_id> <field>`. The change is stamped `human:<os-user>` in the event log.
* `substrate unapprove` revokes the approval by deleting the key, so both an `exists` check and an `eq true` check re-block correctly.

Two honest limits to understand:

<CardGroup cols={2}>
  <Card title="Comments fields" icon="message-circle">
    `human_only` applies to task fields only. On `comments` fields it is currently inert.
  </Card>

  <Card title="Policy is still data" icon="file-code">
    The guard policy is substrate-as-code. An agent could rewrite or `archive_policy` the guard — that's a git-visible, event-logged act, but not structurally impossible.
  </Card>
</CardGroup>
